Tutorial: Burning ESP-AT Firmware into the OSOLINK WiFi/BLE I/O Shield v1.0 using an Arduino UNO
Applies to: OSOLINK WiFi/BLE I/O Shield v1.0 (Espressif ESP32-C3-MINI-1)
Flashing port: 4-pin ESP UART0 header (silkscreen: 3V3 / ESP_RX / ESP_TX / GND)
Tool needed: Arduino UNO (used as a USB-to-Serial adapter) + Espressif Flash Download Tool
The OSOLINK WiFi/BLE I/O Shield v1.0 ships with Espressif ESP-AT firmware already installed, so in normal use you do not need to flash anything — you simply send AT commands from your Arduino over UART.
However, you may want to reflash the ESP-AT firmware if:
AT commands (blank or garbage reply), which can happen if the module was shipped without pre-loaded firmware.This guide shows how to reflash the firmware using only an Arduino UNO as the USB-to-Serial adapter — no dedicated USB-to-TTL module is required. The Arduino UNO’s on-board USB chip is repurposed as a transparent serial bridge between your PC and the shield’s ESP UART0 flashing header.

An Arduino UNO already contains a USB-to-Serial converter that connects to the main microcontroller’s D0 (RX) and D1 (TX) pins. If we disable the main microcontroller, the USB converter becomes a transparent bridge between your PC and whatever you wire to D0/D1 — exactly what we need to talk to the shield.
To disable the main microcontroller, simply connect the RESET pin to a GND pin on the Arduino UNO with a jumper wire, and keep it connected for the whole flashing process:
Arduino UNO RESET → Arduino UNO GND (jumper wire, stays connected).
This holds the main chip in reset so it will not drive the serial lines. The Arduino UNO does not need any sketch uploaded — and you should not stack the shield on top of this Arduino. The UNO is used stand-alone, only as a USB-to-Serial adapter, wired to the shield by jumper wires.
On the shield, locate the 4-pin header labeled ESP UART0 (silkscreen pins: 3V3, ESP_RX, ESP_TX, GND). If this header has no pins soldered, either solder a 2.54 mm header or press a 4-pin test clip onto the pads.
Connect the Arduino UNO to the shield’s ESP UART0 header as follows. Because the main chip is held in reset, the wiring is straight-through (RX → RX, TX → TX):
| Arduino UNO | Shield – ESP UART0 (silkscreen) | |
|---|---|---|
| 3.3V | → | 3V3 |
| D0 (RX0) | → | ESP_RX |
| D1 (TX1) | → | ESP_TX |
| GND | → | GND |
Keep the RESET–GND jumper from Section 3 in place. After all wires are connected, plug the Arduino UNO’s USB cable into the PC. The shield’s power indicator (PWR LED) should light up, powered by the 3.3 V line.
3.3V → 3V3, TXD → ESP_RX, RXD → ESP_TX, GND → GND.ESP32-C3-MINI-1-AT-V3.2.0.0\ESP32-C3-MINI-1-V3.2.0.0\factory\factory_MINI-1.binfactory_MINI-1.bin already contains the bootloader, partition table and AT application combined, so it is flashed to address 0x0 in one step.6.1 Run flash_download_tool.exe. In the first dialog, set:
Click OK.
6.2 In the main window, configure it as shown below:
| Step | Setting | Value / Action |
|---|---|---|
| a | LockSettings | Un-check it first so the fields below become editable. |
| b | SPI Flash Config | Keep the defaults: 40MHz, DIO. |
| c | Firmware row checkbox | Tick the checkbox on the first firmware row to enable it. |
| d | Firmware file | Click ... and select ...\factory\factory_MINI-1.bin from the unzipped AT package. |
| e | Flash address | Enter 0x0 in the address box next to the firmware file. |
| f | LockSettings | Re-check it to lock the settings. |
| g | COM | Select the COM port of your Arduino UNO (check Windows Device Manager if unsure). |
| h | BAUD | Set 115200 for the Arduino-UNO method (reliable). A dedicated USB-to-TTL module can use a higher rate for faster flashing. |
The ESP32-C3 must be put into download (bootloader) mode using the shield’s own BOOT and EN buttons. Follow this sequence:
After it finishes, press the EN button once more (or unplug and replug USB) to reboot the module and run the new AT firmware.
First, disconnect all flashing wires and remove the RESET–GND jumper from Section 3.
8.1 Power-on test. Mount the shield onto an Arduino UNO — align the headers carefully and seat them fully without bending any pins. Connect the UNO to the PC with a USB cable. The shield’s on-board power indicator LED should stay on. If it does, this test passes.
8.2 Upload the test sketch. Use the OSOYOO functional-test sketch (shown below) to have the Arduino talk to the ESP32-C3 over the AT interface (ATRX / ATTX → D5 / D4), run a sequence of AT commands, and scan for nearby Wi-Fi:
Test sketch – copy the whole sketch below into a new Arduino IDE window, then upload it:
/*
* ESP32-C3 WiFi/BLE Shield -- production-line test + factory restore
* (for QC use; the board can be shipped directly after the test)
* Hardware: Arduino UNO + ESP32-C3 Shield (AT firmware, SoftwareSerial D4/D5)
*
* Flow (all done automatically in setup(); press the UNO reset button to test the next board):
* STEP A AT+RESTORE factory restore (covers both 115200 and 9600 as the current baud; module ends at factory 115200)
* STEP B Temporarily drop the baud to 9600 (AT+UART_CUR, not written to flash, this session only) for stable SoftwareSerial comms
* STEP 1~5 Basic AT / firmware version / Station mode / scan config / WiFi scan -- PASS/FAIL per step
* STEP D AT+RESTORE again to wipe the temporary config; module ends at [factory default 115200], ready to ship
*
* Serial Monitor: baud 9600, line ending "Newline (NL)".
*/
#include
SoftwareSerial esp(4, 5); // D4=RX, D5=TX
// Forward declarations
bool testAT(const char* title, String cmd, String expect, int timeout);
String readFor(int timeout);
void scanWiFi();
void printAp(int n, String line);
String ecnToStr(int ecn);
void factoryRestore();
void flushEsp();
int passCount = 0, failCount = 0;
void setup() {
Serial.begin(9600);
delay(300);
Serial.println(F("\n=========================================="));
Serial.println(F(" ESP32-C3 Shield production test + factory restore"));
Serial.println(F("=========================================="));
// ---------- STEP A: factory restore ----------
Serial.println(F("\nSTEP A: Factory restore (AT+RESTORE) ..."));
factoryRestore(); // handles unknown baud; module ends at factory default 115200
Serial.println(F(" Factory restored; module should now be at 115200"));
// ---------- STEP B: temporarily drop to 9600 (not written to flash) ----------
Serial.println(F("\nSTEP B: Temporarily switch to 9600 for testing (AT+UART_CUR) ..."));
esp.begin(115200);
delay(400);
esp.println("AT+UART_CUR=9600,8,1,0,0"); // this session only; lost after power-off / RESTORE
delay(800);
esp.end();
esp.begin(9600);
delay(400);
flushEsp();
// ---------- STEP 1~4 ----------
bool ok1 = testAT("STEP 1: Basic AT communication", "AT", "OK", 1500);
if (!ok1) {
Serial.println(F(" !! No AT response, please check:"));
Serial.println(F(" - Whether the UART Selector jumper is set to D4/D5"));
Serial.println(F(" - Module power / whether it is seated properly"));
Serial.println(F(" - If the STEP B baud command was not received, press UNO reset and retry"));
}
testAT("STEP 2: Read firmware version (AT+GMR)", "AT+GMR", "OK", 2500);
testAT("STEP 3: Set Station mode", "AT+CWMODE=1", "OK", 1500);
testAT("STEP 4: Configure scan output (sort/compact)", "AT+CWLAPOPT=1,23", "OK", 1000);
// ---------- STEP 5: scan WiFi ----------
Serial.println(F("\nSTEP 5: Scan nearby WiFi (AT+CWLAP) ..."));
scanWiFi();
// ---------- summary ----------
Serial.println(F("\n------------------------------------------"));
Serial.print(F("Result: PASS ")); Serial.print(passCount);
Serial.print(F(", FAIL ")); Serial.println(failCount);
if (failCount == 0) Serial.println(F("*** TEST PASSED ***"));
else Serial.println(F("!!! Some items failed, please check !!!"));
Serial.println(F("------------------------------------------"));
// ---------- STEP D: restore factory again after test; keep clean before shipping ----------
Serial.println(F("\nSTEP D: Factory restore for direct shipping ..."));
flushEsp();
esp.println("AT+RESTORE"); // currently 9600; wipe temp config and reboot to factory 115200
delay(3000);
Serial.println(F("* Module restored to factory default (115200, no leftover config)"));
Serial.println(F("* Ready to ship; no re-flash needed."));
Serial.println(F("=========================================="));
Serial.println(F("(Next board: swap in a new Shield and press the UNO reset button)"));
}
void loop() {
// After factory restore the module is back at 115200; this program no longer talks to it, just idles.
}
// ---- factory restore: cover both possible current baud rates 115200 and 9600 ----
void factoryRestore() {
long bauds[2] = {115200, 9600};
for (int i = 0; i < 2; i++) { esp.end(); esp.begin(bauds[i]); delay(300); esp.print("+++"); // exit any passthrough mode (no newline, whitespace around) delay(1000); esp.println("AT+RESTORE"); // restore factory and reboot delay(2500); Serial.print(F(" Sent +++ / AT+RESTORE at ")); Serial.print(bauds[i]); Serial.println(F(" baud")); } // after RESTORE the module returns to factory default 115200 } // ---- send an AT command, check whether the response contains the expected keyword, print PASS/FAIL ---- bool testAT(const char* title, String cmd, String expect, int timeout) { Serial.println(); Serial.println(title); Serial.println(" > " + cmd);
flushEsp();
esp.println(cmd);
String resp = readFor(timeout);
resp.trim();
if (resp.length()) {
Serial.print(" < ");
for (unsigned int i = 0; i < resp.length(); i++) {
char c = resp[i];
Serial.write(c);
if (c == '\n') Serial.print(" ");
}
Serial.println();
} else {
Serial.println(" < (no response)");
}
bool ok = resp.indexOf(expect) != -1;
Serial.println(ok ? " [PASS]" : " [FAIL]");
if (ok) passCount++; else failCount++;
return ok;
}
// ---- collect the module response within timeout (refresh the timeout on new data) ----
String readFor(int timeout) {
String r = "";
unsigned long t = millis();
while (millis() - t < (unsigned long)timeout) { while (esp.available()) { r += (char)esp.read(); t = millis(); if (r.length() > 250) return r; // limited UNO RAM; avoid an oversized String
}
}
return r;
}
// ---- scan WiFi: parse and print each line as it arrives; still counts on timeout ----
void scanWiFi() {
flushEsp();
esp.println("AT+CWLAP");
char buf[90];
byte idx = 0;
int count = 0;
bool done = false;
unsigned long t = millis();
while (millis() - t < 9000 && !done) {
while (esp.available()) {
char c = esp.read();
t = millis();
if (c == '\r') continue;
if (c == '\n') {
buf[idx] = '\0';
String line = String(buf);
idx = 0;
if (line.indexOf("+CWLAP:") != -1) { count++; printAp(count, line); }
else if (line.indexOf("OK") != -1) { done = true; break; }
else if (line.indexOf("ERROR") != -1) { Serial.println(F(" Returned ERROR")); done = true; break; }
} else if (idx < sizeof(buf) - 1) { buf[idx++] = c; } } } Serial.print(F(" Found ")); Serial.print(count); Serial.println(F(" hotspots")); if (count > 0) { Serial.println(F(" [PASS] RF and scan function OK")); passCount++; }
else {
Serial.println(F(" [FAIL] No hotspots found (check mode / wiring / antenna)"));
failCount++;
}
}
// ---- parse and print one +CWLAP line ----
void printAp(int n, String line) {
int lp = line.indexOf('(');
if (lp == -1) return;
int c1 = line.indexOf(',', lp);
int ecn = line.substring(lp + 1, c1).toInt();
int q1 = line.indexOf('"', c1);
int q2 = line.indexOf('"', q1 + 1);
String ssid = (q1 != -1 && q2 != -1) ? line.substring(q1 + 1, q2) : "";
if (ssid.length() == 0) ssid = "(hidden SSID)";
int c2 = line.indexOf(',', q2);
int c3 = line.indexOf(',', c2 + 1);
int rssi = line.substring(c2 + 1, c3).toInt();
int rp = line.lastIndexOf(')');
int lc = line.lastIndexOf(',');
int ch = line.substring(lc + 1, rp).toInt();
Serial.print(F(" ")); Serial.print(n); Serial.print(F(". SSID="));
Serial.print(ssid);
Serial.print(F(" RSSI=")); Serial.print(rssi); Serial.print(F("dBm"));
Serial.print(F(" CH=")); Serial.print(ch);
Serial.print(F(" ENC=")); Serial.println(ecnToStr(ecn));
}
String ecnToStr(int ecn) {
switch (ecn) {
case 0: return "OPEN";
case 1: return "WEP";
case 2: return "WPA-PSK";
case 3: return "WPA2-PSK";
case 4: return "WPA/WPA2-PSK";
case 5: return "WPA2-Enterprise";
case 6: return "WPA3-PSK";
case 7: return "WPA2/WPA3-PSK";
default: return "?";
}
}
void flushEsp() {
while (esp.available()) esp.read();
}
8.3 Expected result. If the firmware is working, the Serial Monitor prints the AT firmware version, the result of each AT step, and a list of nearby Wi-Fi hotspots, then restores the module to factory defaults. A representative output looks like this:
STEP 1: Basic AT communication
> AT
< OK [PASS]
STEP 2: Read firmware version (AT+GMR)
AT version:3.2.0.0 … Bin version:v3.2.0.0(MINI-1)
[PASS]
STEP 3: Set Station mode (AT+CWMODE=1) [PASS]
STEP 4: Configure scan output (AT+CWLAPOPT) [PASS]
STEP 5: Scan nearby Wi-Fi (AT+CWLAP) …
1. SSID=TP-LINK***** RSSI=-40dBm CH=4
2. SSID=NVDA RSSI=-43dBm CH=6
… 15 hotspots found [PASS]
——————————————
Result: PASS 5, FAIL 0 ★★★ TEST PASSED ★★★
★ Module restored to factory defaults (115200, no leftover config)
★ Ready to ship — no re-flash needed.
If you see the firmware version, a list of nearby Wi-Fi SSIDs, and a PASS summary, the ESP-AT firmware is installed and working correctly. To test the next board, swap in a new shield and press the UNO’s reset button.
1. The tool cannot connect / “Failed to connect”.
Re-do the boot sequence: click START first, then hold BOOT, tap EN, release BOOT. Double-check that the RESET–GND jumper on the Arduino UNO is in place (main chip disabled), and that D0→ESP_RX and D1→ESP_TX are not swapped.
2. No COM port shown.
Install the Arduino UNO’s USB-serial driver and confirm the port appears in Windows Device Manager. Try a different USB cable/port — some cables are power-only.
3. Flashing starts but fails partway.
Lower the BAUD to 115200, keep wires short, and make sure the shield is powered only from the Arduino UNO 3.3 V pin. Poor jumper/test-clip contact is the most common cause of mid-flash errors.
4. The functional test returns no AT response.
Remember the AT interface is on ATRX / ATTX (D5 / D4), not the flashing port. Make sure the shield is fully seated on the Arduino UNO, the UART Selector is at its factory-default D5 / D4 position, the Serial Monitor is set to 9600 baud, and no other program is holding the COM port. Press the UNO reset button to re-run the test.
© 2026 OSOYOO | www.osoyoo.com
DownLoad Url osoyoo.com