教程:使用 Arduino UNO 为 OSOLINK WiFi/BLE I/O Shield v1.0 烧录 ESP-AT 固件
适用产品:OSOLINK WiFi/BLE I/O Shield v1.0(Espressif ESP32-C3-MINI-1)
烧录接口:4 针 ESP UART0 排针(丝印:3V3 / ESP_RX / ESP_TX / GND)
所需工具:Arduino UNO(用作 USB 转串口适配器)+ 乐鑫 Flash Download Tool
OSOLINK WiFi/BLE I/O Shield v1.0 出厂时已预烧录乐鑫 ESP-AT 固件,正常使用时无需自行烧录,只需从 Arduino 通过 UART 发送 AT 指令即可。
遇到以下情况时,您可能需要重新烧录 ESP-AT 固件:
AT 指令无响应(无回复或乱码)— 极少数模组出厂时可能未预烧录固件。本教程演示仅使用一块 Arduino UNO 作为 USB 转串口适配器来重新烧录固件的方法 — 无需额外的 USB 转 TTL 模块。我们将 Arduino UNO 板载的 USB 芯片用作电脑与扩展板 ESP UART0 烧录接口之间的透明串口桥。

Arduino UNO 板载已经带有一颗 USB 转串口芯片,它连接到主控芯片的 D0(RX)和 D1(TX)引脚。如果我们让主控芯片停止工作,这颗 USB 转串口芯片就会变成电脑与 D0/D1 之间的透明桥梁 — 这正是我们与扩展板通信所需要的。
只需用一根杜邦线将 Arduino UNO 的 RESET 引脚与 GND 引脚短接,并在整个烧录过程中保持连接:
Arduino UNO 的 RESET → Arduino UNO 的 GND(杜邦线,全程保持连接)。
这样会让主控芯片一直处于复位状态,从而不再驱动串口线路。该 Arduino UNO 无需上传任何程序,也不要把扩展板插在这块 Arduino 上。UNO 单独使用,仅作为 USB 转串口适配器,通过杜邦线与扩展板相连。
在扩展板上找到丝印为 ESP UART0 的 4 针接口(丝印引脚:3V3、ESP_RX、ESP_TX、GND)。若该接口未焊排针,可焊接 2.54 mm 排针,或使用 4P 测试夹夹住焊盘。
按下表将 Arduino UNO 连接到扩展板的 ESP UART0 接口。由于主控芯片已被复位,此时接线为直连(RX → RX,TX → TX):
| Arduino UNO | 扩展板 – ESP UART0(丝印) | |
|---|---|---|
| 3.3V | → | 3V3 |
| D0 (RX0) | → | ESP_RX |
| D1 (TX1) | → | ESP_TX |
| GND | → | GND |
保持第 3 节中 RESET–GND 的短接线不变。全部接好后,将 Arduino UNO 的 USB 线插入电脑。此时扩展板的电源指示灯(PWR LED)应点亮,由 3.3 V 线供电。
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 已将 bootloader、分区表和 AT 应用合并在一起,因此只需一次性烧录到地址 0x0。6.1 运行 flash_download_tool.exe。在弹出的第一个对话框中设置:
然后点击 OK。
6.2 进入主界面后,按下表进行设置:
| 步骤 | 设置项 | 值 / 操作 |
|---|---|---|
| a | LockSettings | 先取消勾选,使下方选项可编辑。 |
| b | SPI Flash Config | 保持默认:40MHz、DIO。 |
| c | 固件行复选框 | 勾选第一行固件前的复选框以启用该行。 |
| d | 固件文件 | 点击 ...,从解压后的 AT 固件包中选择 ...\factory\factory_MINI-1.bin。 |
| e | 烧录地址 | 在固件文件旁的地址框中填入 0x0。 |
| f | LockSettings | 重新勾选,锁定设置。 |
| g | COM | 选择 Arduino UNO 对应的 COM 端口(不确定时可在 Windows 设备管理器查看)。 |
| h | BAUD | Arduino UNO 方法建议设为 115200(更稳定)。使用专用 USB 转 TTL 模块时可选更高波特率以加快烧录。 |
需要使用扩展板自身的 BOOT 和 EN 按键让 ESP32-C3 进入下载(bootloader)模式。按以下顺序操作:
烧录完成后,再按一下 EN 按键(或拔插 USB)让模组重启,即可运行新的 AT 固件。
先断开所有烧录连线,并拆掉第 3 节中 RESET–GND 的短接线。
8.1 上电测试。将扩展板安装到 Arduino UNO 上 — 注意排针对齐并完整插入,不要将排针弄歪。用 USB 线将 UNO 连接到电脑。此时应能看到扩展板板载电源指示灯常亮。若常亮,则该项测试通过。
8.2 上传测试代码。使用 OSOYOO 功能测试代码(如下所示),让 Arduino 通过 AT 接口(ATRX / ATTX → D5 / D4)与 ESP32-C3 通信,执行一系列 AT 指令并扫描附近 WiFi:
测试代码 – 将下方完整代码复制到 Arduino IDE 新建窗口后上传:
/*
* ESP32-C3 WiFi/BLE Shield —— 产线测试 + 出厂恢复(供同事测试,测完可直接出货)
* 硬件:Arduino UNO + ESP32-C3 Shield(AT 固件,SoftwareSerial D4/D5)
*
* 流程(全部在 setup 自动完成,按 UNO 复位键即可测下一片):
* STEP A AT+RESTORE 恢复出厂(覆盖 115200/9600 两种当前波特率,测完模块回到出厂 115200)
* STEP B 临时把波特率降到 9600(AT+UART_CUR,不写 flash,仅本次有效)以便软串口稳定通信
* STEP 1~5 基本AT / 固件版本 / Station模式 / 扫描配置 / 扫描WiFi —— 每步 PASS/FAIL
* STEP D 再次 AT+RESTORE,抹掉临时配置,模块以【出厂默认 115200】结束,可直接出货
*
* 串口监视器:波特率 9600,行结束符「换行 NL」。
*/
#include <SoftwareSerial.h>
SoftwareSerial esp(4, 5); // D4=RX, D5=TX
// 前向声明
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 产线测试 + 出厂恢复"));
Serial.println(F("=========================================="));
// ---------- STEP A: 恢复出厂 ----------
Serial.println(F("\nSTEP A: 恢复出厂状态 (AT+RESTORE) ..."));
factoryRestore(); // 处理未知波特率;结束后模块为出厂默认 115200
Serial.println(F(" 已恢复出厂,模块当前应为 115200"));
// ---------- STEP B: 临时降速到 9600(不写 flash)----------
Serial.println(F("\nSTEP B: 临时切到 9600 用于测试 (AT+UART_CUR) ..."));
esp.begin(115200);
delay(400);
esp.println("AT+UART_CUR=9600,8,1,0,0"); // 仅本次有效,掉电/RESTORE 后失效
delay(800);
esp.end();
esp.begin(9600);
delay(400);
flushEsp();
// ---------- STEP 1~4 ----------
bool ok1 = testAT("STEP 1: 基本 AT 通信", "AT", "OK", 1500);
if (!ok1) {
Serial.println(F(" !! AT 无响应,请检查:"));
Serial.println(F(" - UART 选择器跳线是否在 D4/D5"));
Serial.println(F(" - 模块供电/是否插好"));
Serial.println(F(" - 若 STEP B 的降速指令没送达,按 UNO 复位重试一次"));
}
testAT("STEP 2: 读取固件版本 (AT+GMR)", "AT+GMR", "OK", 2500);
testAT("STEP 3: 设置 Station 模式", "AT+CWMODE=1", "OK", 1500);
testAT("STEP 4: 配置扫描输出(排序/精简)", "AT+CWLAPOPT=1,23", "OK", 1000);
// ---------- STEP 5: 扫描 WiFi ----------
Serial.println(F("\nSTEP 5: 扫描附近 WiFi (AT+CWLAP) ..."));
scanWiFi();
// ---------- 汇总 ----------
Serial.println(F("\n------------------------------------------"));
Serial.print(F("测试结果:PASS ")); Serial.print(passCount);
Serial.print(F(" 项,FAIL ")); Serial.print(failCount);
Serial.println(F(" 项"));
if (failCount == 0) Serial.println(F("★★★ 测试通过 ★★★"));
else Serial.println(F("!!! 有项目未通过,请排查 !!!"));
Serial.println(F("------------------------------------------"));
// ---------- STEP D: 测完再次恢复出厂,出货前保持纯净 ----------
Serial.println(F("\nSTEP D: 恢复出厂状态以便直接出货 ..."));
flushEsp();
esp.println("AT+RESTORE"); // 当前 9600,抹掉临时配置并重启到出厂 115200
delay(3000);
Serial.println(F("★ 模块已恢复出厂默认(115200、无残留配置)"));
Serial.println(F("★ 可直接出货,无需再次烧录。"));
Serial.println(F("=========================================="));
Serial.println(F("(测下一片:换上新 Shield,按一下 UNO 复位键即可)"));
}
void loop() {
// 出厂恢复后模块已回到 115200,本程序不再与其通信,空转即可。
}
// ---- 恢复出厂:覆盖 115200 与 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("+++"); // 退出可能的透传模式(无换行,前后留白)
delay(1000);
esp.println("AT+RESTORE"); // 恢复出厂并重启
delay(2500);
Serial.print(F(" 已在 "));
Serial.print(bauds[i]);
Serial.println(F(" 下发送 +++ / AT+RESTORE"));
}
// RESTORE 后模块统一回到出厂默认 115200
}
// ---- 发送 AT 指令并判断是否包含期望关键字,打印 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(" < (无返回)");
}
bool ok = resp.indexOf(expect) != -1;
Serial.println(ok ? " [PASS]" : " [FAIL]");
if (ok) passCount++; else failCount++;
return ok;
}
// ---- 在 timeout 内收集模块返回(收到数据就刷新超时)----
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; // UNO 内存有限,防止 String 过大
}
}
return r;
}
// ---- 扫描 WiFi:边收边解析并逐条打印,超时也照常统计 ----
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(" 返回 ERROR")); done = true; break; }
} else if (idx < sizeof(buf) - 1) {
buf[idx++] = c;
}
}
}
Serial.print(F(" 共发现 "));
Serial.print(count);
Serial.println(F(" 个热点"));
if (count > 0) { Serial.println(F(" [PASS] 射频与扫描功能正常")); passCount++; }
else {
Serial.println(F(" [FAIL] 未扫到热点(检查模式/接线/天线)"));
failCount++;
}
}
// ---- 解析并打印一条 +CWLAP 行 ----
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 = "(隐藏 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(" 信号=")); Serial.print(rssi); Serial.print(F("dBm"));
Serial.print(F(" 信道=")); Serial.print(ch);
Serial.print(F(" 加密=")); 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 预期结果。若固件正常,串口监视器会打印 AT 固件版本、各 AT 步骤的结果以及附近 WiFi 热点列表,最后将模块恢复出厂默认。典型输出如下:
STEP 1: 基本 AT 通信
> AT
< OK [PASS]STEP 2: 读取固件版本 (AT+GMR)STEP 3: 设置 Station 模式 (AT+CWMODE=1) [PASS]STEP 5: 扫描附近 WiFi (AT+CWLAP) …——————————————
测试结果:PASS 5 项,FAIL 0 项 ★★★ 测试通过 ★★★
★ 模块已恢复出厂默认(115200、无残留配置)
★ 可直接出货,无需再次烧录。
若能看到固件版本、附近 WiFi SSID 列表以及 PASS 汇总,即表示 ESP-AT 固件已正确烧录并正常工作。测下一片时,换上新扩展板,按一下 UNO 的复位键即可。
1. 工具无法连接 / 提示 “Failed to connect”。
重新执行下载时序:先点 START,再长按 BOOT,点一下 EN,松开 BOOT。检查 Arduino UNO 上的 RESET–GND 短接线是否到位(主控芯片是否已被复位),并确认 D0→ESP_RX、D1→ESP_TX 没有接反。
2. 看不到 COM 端口。
安装 Arduino UNO 的 USB 转串口驱动,并确认端口出现在 Windows 设备管理器中。更换 USB 线或 USB 口再试 — 部分数据线仅供电、不传数据。
3. 烧录中途失败。
将 BAUD 降到 115200,杜邦线尽量短,并确保扩展板仅由 Arduino UNO 的 3.3 V 引脚供电。杜邦线或测试夹接触不良是中途报错最常见的原因。
4. 功能测试时 AT 无响应。
请记住 AT 接口在 ATRX / ATTX(D5 / D4),而非烧录口。确认扩展板已完整插入 Arduino UNO、UART 选择器处于出厂默认的 D5 / D4 位置、串口监视器波特率设为 9600,且没有其他程序占用该 COM 端口。按一下 UNO 复位键可重新运行测试。
© 2026 OSOYOO | www.osoyoo.com
DownLoad Url osoyoo.com