← 返回教程库

固件批量烧录:从烧一台到烧上千台

最后更新 2026-06-24
L5 · 产品化 ⏱ 约 17 分钟 🟡 涉接线/强电
你将学到
  • 用 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 的操作,先拿一两片注定要报废的样片练手,确认整条命令和参数都对了,再上量产板。


动手挑战

别只读,这一关你桌上就能亲手走通:

  1. 把你的工程合成单镜像,再用 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,体会一下产线拿到的就是这一个文件、这一条命令。

  2. 读出你这颗 ESP32-S3 的出厂 MAC。esptool.py --chip esp32s3 --port 你的端口 read_mac,记下那串 MAC——这就是你这台设备天生的唯一身份。想想看,如果你要做五百台,这个免费的唯一 ID 能帮你省掉多少事。

💡 提示

卡住了就把你的 flasher_args.json、想合并的 bin 列表、用的端口一起发给 AI,让它对照 esptool 官方文档 帮你拼出 merge_binwrite_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 产品化全景路线图 对一眼。

📄 来源 / 自校链接

本文为公开资料整理,非亲测。关键参数与代码请结合实物与下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为公开资料的学习整理,非亲测。涉接线/花钱/合规的步骤请结合实物与官方最新资料验证,风险自负。见免责声明