固件批量烧录:从烧一台到烧上千台
- 用 esptool.py 命令行把固件刷进板子,看懂 write_flash 的关键参数和波特率
- 用 idf.py merge-bin 把分散的 bin 合并成一个量产镜像,offset 不再手抖
- 理解量产烧录工装和产测固件怎么把"烧录 + 自检"连成产线流水线
- 给每台板子写不同的序列号到 NVS、读出 ESP32-S3 出厂 MAC
你在 L5 固件工程化 那节把一台设备伺候稳了——看门狗、断网重连、配置进 NVS、OTA 口子都留好了。现在工厂打来电话:第一批五百台板子下周到货,固件你来刷。
你愣了一下。你平时刷固件是这样的:插上 USB、idf.py flash、等三十秒、看到 Done。一台。可这次是五百台,下个月还有第二批一千台。你总不能搬张凳子坐在产线边上,插一台、点一下、拔一台,重复一千五百遍——光插拔的手就废了,还保不齐哪台漏烧、哪台烧错版本。
这一节讲的就是这件事:当数量从"一台"变成"上千台",烧录这件小事会从手动操作变成一条要设计的流水线。 这里的好消息是,这一关你能亲手走通——idf.py 背后调的就是 esptool,你现在就能在自己桌上把命令敲一遍、把镜像合一合、把序列号写一写,完全不用等产线。下面给的都是能直接跑的真命令。
单台烧录够用,为什么量产就崩了
先把"一台"和"上千台"的差别摊开看。你自己玩的那套流程,放到量产上每一步都卡:
idf.py flash帮你藏了太多东西:它内部先 build、再算每个 bin 该烧到哪个地址、再调 esptool 烧进去。一台时这种"全自动"很爽,可产线上的烧录工不会装 ESP-IDF、也不该每台都重新 build——他们要的是一个固定的镜像 + 一条固定的命令,谁来按都一样。- 多个 bin 文件容易刷错:一个 ESP-IDF 工程烧进去的不止一个文件——引导程序 bootloader、分区表 partition-table、你的应用 app,各自有各自的烧录地址(offset)。手动一个个
write_flash 0x0 ... 0x8000 ... 0x10000 ...,产线上手一抖把地址敲错,板子直接不启动。 - 没法并行:一台一台串着烧,五百台就是五百个三十秒,纯等。
量产烧录的思路是反过来的:把"算地址、合文件"这些活在产线之前一次性做完,留给产线的只剩"对准、按一下"。 下面一步步来。
用 esptool 命令行烧:产线认的是它
idf.py flash 底下真正干活的是 esptool.py(随 ESP-IDF 一起装好了)。产线上我们直接用它,因为它不依赖工程、不需要 build,给个 bin、给个端口就能烧。
最朴素的一条烧录命令长这样:
esptool.py --chip esp32s3 --port COM5 --baud 921600 \
write_flash 0x0 firmware.bin
拆开看每个参数,产线上每一个都不能含糊:
--chip esp32s3:明确芯片型号。写死它,别让 esptool 去自动探测——产线上探测偶尔会因接触不良误判,直接指定最稳。--port COM5:板子枚举出来的串口。Windows 是COM5这类,Linux/Mac 是/dev/ttyUSB0或/dev/ttyACM0。--baud 921600:波特率,量产时直接拉满。默认 115200 烧一个几 MB 的固件要等很久;921600 能把单台时间压到几分之一。多数 ESP32-S3 板子吃得住 921600,个别线材差的退回 460800。write_flash 0x0 firmware.bin:把firmware.bin烧到 flash 的0x0地址。
几个让产线省心的小开关:加 --before default_reset --after hard_reset,让 esptool 烧前自动复位进下载模式、烧完自动复位运行——烧录工不用去戳 BOOT/RST 键。再加 --erase-all 可以在烧之前把整片 flash 擦干净,避免上一版残留的 NVS 数据(比如旧 WiFi 配置)混进新板子;但要注意它会连出厂写好的校准数据一起擦,擦之前确认你的 app 不依赖那些。
多串口并行是量产提速的关键。一台电脑插一个 USB Hub、接八路烧录夹具,就同时跑八条 esptool 命令、各管各的端口:
# Linux 下八路并行的最朴素写法:后台各跑一条,端口各不同
for port in /dev/ttyUSB{0..7}; do
esptool.py --chip esp32s3 --port "$port" --baud 921600 \
write_flash 0x0 firmware.bin &
done
wait # 等八路全烧完
一轮八台、几十秒,五百台分几十轮,一个人盯着就能跑完。这就是产线的样子。
合并成单镜像:别让产线对付三个 bin
上面那条命令为什么只烧一个 0x0 firmware.bin,而不是 bootloader + 分区表 + app 三个?因为我们提前把它们合并成了一个镜像。这是量产烧录里最值钱的一步。
ESP-IDF 自带这个工具,build 完直接合:
idf.py merge-bin -o firmware.bin
它会读你工程的 flasher_args.json(build 时生成的,里面记着每个 bin 该烧到哪个 offset),把 bootloader、分区表、app 等按正确地址拼进一个文件 firmware.bin,从 0x0 一路填好。产线只需要烧这一个文件到一个地址——offset 算错的可能性直接归零。
如果你不在 ESP-IDF 工程里,只有几个散 bin,也能用 esptool 直接合,地址你自己点明:
esptool.py --chip esp32s3 merge_bin -o firmware.bin \
0x0 bootloader/bootloader.bin \
0x8000 partition_table/partition-table.bin \
0x10000 my_app.bin
(ESP32-S3 上 bootloader 在 0x0,这点和 ESP32 不同——老 ESP32 的 bootloader 在 0x1000,别照搬旧教程的地址。)
合出来的 firmware.bin 就是你交给产线的唯一交付物:一个文件、一条命令、烧到 0x0。版本号(还记得 L5 固件工程化 里强调的开机打印版本号吗)就锁在这个镜像里,产线烧的是哪一版一目了然。
把这个合好的 firmware.bin 连同烧录命令、版本号写成一张烧录作业指导书,文件名带上版本(firmware_v1.0.3.bin)。产线换版本时只换文件、不改命令,从源头杜绝"用了上一版镜像"这种最常见的量产事故。
烧录工装 + 产测固件:烧完立刻自检
数量再大,光靠 USB 线插拔也撑不住。成熟产线用的是烧录工装(夹具):板子往夹具上一放,弹簧顶针(pogo pin)自动压到板子的烧录测试点上,连线、烧录、复位全自动,烧录工只管放板子、取板子。一台夹具配多路,就是上面说的多串口并行。
但"烧进去了"不等于"这板子是好的"。flash 烧成功,可能焊接有虚焊、某个传感器没贴上、晶振没起振——这些烧录本身查不出来。所以产线会再走一步:产测固件(产测程序)。
产测固件是一版专门用来测试的固件,烧进去开机后自动跑一遍硬件自检:
// 产测固件 app_main 的骨架:开机自检,结果打到串口
void app_main(void)
{
bool ok = true;
ok &= test_psram(); // PSRAM 能不能读写
ok &= test_flash_size(); // flash 实际容量对不对
ok &= test_sensor_i2c(); // I2C 传感器在不在线(读它的 ID 寄存器)
ok &= test_wifi_scan(); // 能不能扫到 WiFi(射频通路通不通)
ok &= test_led_gpio(); // 关键 GPIO 通不通
// 把结论打到串口,产线工装据此判 PASS/FAIL
printf(ok ? "TEST_RESULT: PASS\n" : "TEST_RESULT: FAIL\n");
}
产线的流程因此变成一条流水线:放板子 → 烧产测固件 → 自动跑自检 → 工装读串口 TEST_RESULT → PASS 的板子接着烧正式固件,FAIL 的当场挑出来返修。坏板子绝不流到下一站。
这一步正是 L5 测试与老化 那套"故意坑它"思路在产线上的工程化落地——只不过那里是你一台台手动坑,这里是工装自动、几秒一台地坑。
别把产测代码留在正式固件里。新手常犯的错是图省事,把自检逻辑用一个开关塞进出厂固件——结果某个用户偶然触发了那个开关,设备进了产测模式开始疯狂扫 WiFi、刷串口,看起来像中了毒。产测固件和正式固件是两个独立镜像,产线先烧前者测、测过了再烧后者出厂,泾渭分明。
序列号与 MAC:让每台板子有自己的身份
到这里有个问题没解决:你烧的是同一个 firmware.bin,五百台板子里的内容一模一样。可现实里每台设备得能被区分开——OTA 时知道升的是哪台、出问题时知道是哪台、联网时拿什么当唯一标识。
每台写不同序列号到 NVS
正式固件烧完后,再单独给每台写一个独一无二的序列号到 NVS(还记得 L5 固件工程化 里那块断电不丢的 NVS 吗)。esptool 的姊妹工具能把一个 NVS 分区镜像烧进去——产线为每台生成一个带不同序列号的 NVS bin,烧到分区表里 NVS 的地址(比如 0x9000):
# 每台板子烧一个内容不同的 NVS 镜像(序列号已写在里面)
esptool.py --chip esp32s3 --port COM5 --baud 921600 \
write_flash 0x9000 nvs_SN20260624001.bin
那个 nvs_*.bin 怎么来的?ESP-IDF 自带 nvs_partition_gen.py,喂它一个 CSV(里面写 serial_number, data, string, SN20260624001)就能生成。产线脚本每烧一台,序列号自增一个,生成对应的 NVS bin 再烧——每台从此带着自己的"身份证"出厂。
ESP32-S3 出厂自带的 MAC
好消息是,身份这件事 ESP32-S3 给了你一半:每颗芯片出厂时,乐鑫已经在 eFuse 里烧好了一个全球唯一的 MAC 地址(eFuse 是芯片里一次性、烧了不可改的存储)。这个 MAC 你不用自己分配、不用自己写,读出来就能当唯一标识用:
esptool.py --chip esp32s3 --port COM5 read_mac
输出里会有一行 MAC: 7c:df:a1:xx:xx:xx。很多产品干脆就拿这个出厂 MAC 当设备唯一 ID,省掉自己分配序列号的麻烦;序列号则留给"你自己的产品编号体系"(带产线、批次、日期信息),两者各管各用。
防拷贝:eFuse / Secure Boot / Flash 加密(一句话先知道有这事)
最后提一嘴,免得你日后吃亏:你辛苦做的固件烧进 flash,默认是能被别人直接读出来、原样拷到山寨板上的。要防这种抄袭,ESP32-S3 提供了 Flash 加密(flash 里的固件加密存储,读出来是密文)和 Secure Boot(只允许运行你签名过的固件),都靠 eFuse 里烧死的密钥实现。这几样一旦开启不可逆、配错直接变砖,属于"想清楚再动"的高危操作,这里不展开命令——量产真要做防拷贝时,务必照 L3 安全基础 和 乐鑫官方 Security 文档 一步步来,别凭记忆敲。
这几个坑,第一次做量产烧录十有八九中招
| 坑 | 后果 | 怎么避 |
|---|---|---|
| 照搬 ESP32 的 bootloader 地址 | ESP32-S3 bootloader 在 0x0 不是 0x1000,地址错板子不启动 |
用 idf.py merge-bin 让它自动算地址,别手敲 offset |
| 波特率默认 115200 没改 | 单台烧录慢几倍,五百台拖垮产线节拍 | 量产 --baud 921600,线材差退 460800 |
| 产测代码混进正式固件 | 用户偶然触发,设备进产测模式行为异常 | 产测、正式做成两个独立镜像,先测后烧 |
| 五百台烧成同一个 NVS | 每台序列号/配置全一样,没法区分、OTA 乱套 | 每台烧独立 NVS 镜像,或直接用 eFuse 出厂 MAC 当 ID |
| 没记烧的是哪一版 | 出问题不知道该不该升、混了旧镜像 | 文件名带版本号,版本号打进固件开机打印 |
| 手痒先开了 Flash 加密 | 配错直接变砖,且不可逆 | 防拷贝是高危操作,照官方文档来,别凭感觉敲 |
这里面最致命的是地址错和Flash 加密手抖。地址错顶多板子不启动、重烧一遍;Flash 加密 / Secure Boot 配错是不可逆的砖——eFuse 烧了改不回来。所以记住一条铁律:凡是动 eFuse 的操作,先拿一两片注定要报废的样片练手,确认整条命令和参数都对了,再上量产板。
动手挑战
别只读,这一关你桌上就能亲手走通:
把你的工程合成单镜像,再用 esptool 烧。 在工程目录
idf.py build之后跑idf.py merge-bin -o firmware.bin,然后用纯 esptool 命令esptool.py --chip esp32s3 --port 你的端口 --baud 921600 write_flash 0x0 firmware.bin烧进去——不走idf.py flash,体会一下产线拿到的就是这一个文件、这一条命令。读出你这颗 ESP32-S3 的出厂 MAC。 跑
esptool.py --chip esp32s3 --port 你的端口 read_mac,记下那串 MAC——这就是你这台设备天生的唯一身份。想想看,如果你要做五百台,这个免费的唯一 ID 能帮你省掉多少事。
卡住了就把你的 flasher_args.json、想合并的 bin 列表、用的端口一起发给 AI,让它对照 esptool 官方文档 帮你拼出 merge_bin 或 write_flash 命令——但烧之前一定自己核一遍地址(尤其 ESP32-S3 的 0x0),亲手烧一片验证开机正常,再敢往量产上推。
小结 · 下一步
- 你看清了"一台"和"上千台"的分水岭:量产要把算地址、合文件这些活提前做完,留给产线的只剩对准、按一下。
- 你会用
esptool.py write_flash烧固件、用--baud 921600提速、用多串口并行;会用idf.py merge-bin合成一个量产镜像,offset 不再手抖。 - 你理解了烧录工装 + 产测固件怎么把"烧录 + 自检"连成流水线,坏板当场挑出。
- 你知道了怎么给每台写不同序列号到 NVS、ESP32-S3 出厂 MAC 在 eFuse 里读出来就能当唯一 ID;也知道了 Flash 加密 / Secure Boot 防拷贝是把"不可逆"的双刃剑,要照官方文档慎动。
固件刷进去了、每台有了身份,接下来该把这些板子逐台过一遍测试、再连续熬几天,才敢出厂。下一步:看 L5 量产测试与老化,把产测和老化测试的整套做法讲透;想看自己处在产品化路线的哪一站,回 L5 产品化全景 或 路线图 对一眼。