专栏前言
上一篇我们掌握了固件安全与安全启动,实现了固件防篡改、防克隆、防降级的硬件级防护,完成了产品发布前的安全准备。
而从开发原型到规模量产,中间还有关键的量产工程环节:批量烧录一致性、产线校准精度、功能测试覆盖率、不良品管控、全链路追溯,直接决定了产品的良率、成本与售后能力。很多开发者只关注功能开发,忽略量产工程设计,导致量产阶段良率低、效率差、出问题无法定位批次,最终推高交付成本。
ESP32拥有成熟的量产工具链,从底层esptool烧录工具、脱机烧录器支持,到射频校准、产线测试框架,配合标准化的工位设计与追溯体系,可快速搭建工业级量产产线。
无需昂贵的专用设备,基于通用工具即可实现从烧录、校准、测试到分拣追溯的全流程管控,兼顾效率与成本。
本篇一次性讲透ESP-IDF下的量产烧录与产线测试:
量产流程总览与烧录方案选型 → 批量烧录配置与合并固件生成 → 产线校准与自动化功能测试 → 不良品分拣与追溯体系搭建 → 工程最佳实践 → 高频踩坑汇总
全程配可落地流程与代码示例,零基础也能快速搭建工业级量产交付体系,实现从开发到量产的完整闭环。
一、量产流程总览与烧录方案选型
1. 量产全流程四大核心工位
完整的ESP32产品量产线,按顺序分为四个核心工位,每个工位输出明确,不合格不流入下一站:
- 烧录工位:固件烧录、安全配置烧录、唯一SN写入,是量产的起点
- 校准工位:射频、时钟、传感器等硬件参数校准,修正个体差异
- 测试工位:全功能自动化测试,验证功能与性能达标
- 分拣追溯工位:不良品分拣、数据上传、SN与批次追溯,成品包装
2. 主流烧录方案对比
根据批量大小、自动化程度、成本预算,常用四种烧录方案:
| 方案类型 | 典型工具 | 效率(片/小时) | 适用批量 | 核心特点 | 成本水平 |
|---|---|---|---|---|---|
| 手动USB烧录 | esptool+USB转串口 | 20~50 | 百级以内试产 | 简单灵活,无需专用设备,成本最低 | 极低 |
| 脱机一拖N烧录 | 通用脱机烧录器 | 100~300 | 千级中小批量 | 离线操作,无需电脑,一拖四/八,效率中等 | 低 |
| 在线ATE烧录 | 自动化测试夹具+工控机 | 300~800 | 万级大批量 | 自动烧录测试一体,无人值守,效率高 | 中 |
| 原厂预烧录 | 芯片原厂晶圆/芯片级预烧 | 1000+ | 十万级以上大规模 | 出厂自带固件,省掉烧录工位,一致性最好 | 高 |
工程提示:中小批量量产优先选用一拖八脱机烧录器,支持合并固件烧录、校验、eFuse烧录,性价比最高;十万级以上大规模可考虑原厂预烧+产线校准测试的模式。
3. ESP32量产核心工具
- esptool.py:官方底层烧录工具,支持所有芯片所有烧录功能,是所有量产工具的基础
- espsecure.py:安全相关工具,签名、加密、eFuse烧录,量产安全配置必备
- esp_rf_cal:射频校准工具,产线校准WiFi/蓝牙射频参数
- 产线测试固件:定制开发的自动化测试固件,上电自动执行所有测试项

【配图1:量产全流程工位拓扑图】
配图说明:展示从芯片来料→烧录工位→校准工位→功能测试工位→不良品分拣区→合格成品包装的完整产线流向;标注每个工位的核心输入、输出与关键动作,侧边标注配套工具与数据记录,直观呈现量产全流程的工位布局与物流方向。
下面是量产全流程的工位拓扑与物流方向图:
#mermaid-svg-4ZNrjELNfyrEF8QM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4ZNrjELNfyrEF8QM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4ZNrjELNfyrEF8QM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4ZNrjELNfyrEF8QM .error-icon{fill:#552222;}#mermaid-svg-4ZNrjELNfyrEF8QM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4ZNrjELNfyrEF8QM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4ZNrjELNfyrEF8QM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4ZNrjELNfyrEF8QM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4ZNrjELNfyrEF8QM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4ZNrjELNfyrEF8QM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4ZNrjELNfyrEF8QM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4ZNrjELNfyrEF8QM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4ZNrjELNfyrEF8QM .marker.cross{stroke:#333333;}#mermaid-svg-4ZNrjELNfyrEF8QM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4ZNrjELNfyrEF8QM p{margin:0;}#mermaid-svg-4ZNrjELNfyrEF8QM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4ZNrjELNfyrEF8QM .cluster-label text{fill:#333;}#mermaid-svg-4ZNrjELNfyrEF8QM .cluster-label span{color:#333;}#mermaid-svg-4ZNrjELNfyrEF8QM .cluster-label span p{background-color:transparent;}#mermaid-svg-4ZNrjELNfyrEF8QM .label text,#mermaid-svg-4ZNrjELNfyrEF8QM span{fill:#333;color:#333;}#mermaid-svg-4ZNrjELNfyrEF8QM .node rect,#mermaid-svg-4ZNrjELNfyrEF8QM .node circle,#mermaid-svg-4ZNrjELNfyrEF8QM .node ellipse,#mermaid-svg-4ZNrjELNfyrEF8QM .node polygon,#mermaid-svg-4ZNrjELNfyrEF8QM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4ZNrjELNfyrEF8QM .rough-node .label text,#mermaid-svg-4ZNrjELNfyrEF8QM .node .label text,#mermaid-svg-4ZNrjELNfyrEF8QM .image-shape .label,#mermaid-svg-4ZNrjELNfyrEF8QM .icon-shape .label{text-anchor:middle;}#mermaid-svg-4ZNrjELNfyrEF8QM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4ZNrjELNfyrEF8QM .rough-node .label,#mermaid-svg-4ZNrjELNfyrEF8QM .node .label,#mermaid-svg-4ZNrjELNfyrEF8QM .image-shape .label,#mermaid-svg-4ZNrjELNfyrEF8QM .icon-shape .label{text-align:center;}#mermaid-svg-4ZNrjELNfyrEF8QM .node.clickable{cursor:pointer;}#mermaid-svg-4ZNrjELNfyrEF8QM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4ZNrjELNfyrEF8QM .arrowheadPath{fill:#333333;}#mermaid-svg-4ZNrjELNfyrEF8QM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4ZNrjELNfyrEF8QM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4ZNrjELNfyrEF8QM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4ZNrjELNfyrEF8QM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4ZNrjELNfyrEF8QM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4ZNrjELNfyrEF8QM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4ZNrjELNfyrEF8QM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4ZNrjELNfyrEF8QM .cluster text{fill:#333;}#mermaid-svg-4ZNrjELNfyrEF8QM .cluster span{color:#333;}#mermaid-svg-4ZNrjELNfyrEF8QM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4ZNrjELNfyrEF8QM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4ZNrjELNfyrEF8QM rect.text{fill:none;stroke-width:0;}#mermaid-svg-4ZNrjELNfyrEF8QM .icon-shape,#mermaid-svg-4ZNrjELNfyrEF8QM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4ZNrjELNfyrEF8QM .icon-shape p,#mermaid-svg-4ZNrjELNfyrEF8QM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4ZNrjELNfyrEF8QM .icon-shape .label rect,#mermaid-svg-4ZNrjELNfyrEF8QM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4ZNrjELNfyrEF8QM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4ZNrjELNfyrEF8QM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4ZNrjELNfyrEF8QM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} PASS
FAIL
芯片来料
烧录工位
校准工位
功能测试工位
测试结果
不良品分拣区
合格成品包装
A级报废 / B级返工 / C级现场修复
SN与批次追溯上传
二、批量烧录配置与合并固件生成
1. 合并固件的核心价值
开发阶段分开烧录bootloader、partition_table、app、数据分区,量产时多次烧录效率低、易出错。合并为单一整包固件,一次烧录完成所有分区,是量产的标准做法:
- 效率高:一次擦写完成,减少多次操作的时间开销
- 出错少:无需配置多个地址,避免地址偏移错误
- 一致性好:所有设备固件完全一致,版本统一
- 易管理:单个固件文件,版本管理与产线分发简单
2. 实战1:生成量产合并固件与批量烧录脚本
步骤1:编译生成各分区文件
正常编译工程后,build目录下生成三个核心文件:
- bootloader.bin:引导程序
- partition-table.bin:分区表
- app.bin:应用固件
步骤2:合并为单一量产固件
使用esptool合并多个分区文件,指定对应地址,生成整包固件:
c
esptool.py --chip esp32 merge_bin \
-o output_factory_v1.0.0.bin \
0x1000 bootloader.bin \
0x8000 partition-table.bin \
0x10000 app.bin
生成的output_factory_v1.0.0.bin就是量产用整包固件,烧录起始地址0x0,一次写入即可。
步骤3:批量烧录脚本(多串口自动识别)
产线多工位同时烧录时,可用Python脚本遍历串口,自动识别设备并烧录:
c
import os
import serial.tools.list_ports
firmware_path = "output_factory_v1.0.0.bin"
baud = 921600
ports = list(serial.tools.list_ports.comports())
for port in ports:
print(f"发现端口: {port.device}")
cmd = f"esptool.py --chip esp32 -p {port.device} -b {baud} write_flash --verify 0x0 {firmware_path}"
os.system(cmd)
print(f"{port.device} 烧录完成")
下面是合并固件生成与批量烧录的完整流程:
#mermaid-svg-6yc0FXrSSLlIO7Lp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6yc0FXrSSLlIO7Lp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6yc0FXrSSLlIO7Lp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6yc0FXrSSLlIO7Lp .error-icon{fill:#552222;}#mermaid-svg-6yc0FXrSSLlIO7Lp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6yc0FXrSSLlIO7Lp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6yc0FXrSSLlIO7Lp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6yc0FXrSSLlIO7Lp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6yc0FXrSSLlIO7Lp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6yc0FXrSSLlIO7Lp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6yc0FXrSSLlIO7Lp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6yc0FXrSSLlIO7Lp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6yc0FXrSSLlIO7Lp .marker.cross{stroke:#333333;}#mermaid-svg-6yc0FXrSSLlIO7Lp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6yc0FXrSSLlIO7Lp p{margin:0;}#mermaid-svg-6yc0FXrSSLlIO7Lp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6yc0FXrSSLlIO7Lp .cluster-label text{fill:#333;}#mermaid-svg-6yc0FXrSSLlIO7Lp .cluster-label span{color:#333;}#mermaid-svg-6yc0FXrSSLlIO7Lp .cluster-label span p{background-color:transparent;}#mermaid-svg-6yc0FXrSSLlIO7Lp .label text,#mermaid-svg-6yc0FXrSSLlIO7Lp span{fill:#333;color:#333;}#mermaid-svg-6yc0FXrSSLlIO7Lp .node rect,#mermaid-svg-6yc0FXrSSLlIO7Lp .node circle,#mermaid-svg-6yc0FXrSSLlIO7Lp .node ellipse,#mermaid-svg-6yc0FXrSSLlIO7Lp .node polygon,#mermaid-svg-6yc0FXrSSLlIO7Lp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6yc0FXrSSLlIO7Lp .rough-node .label text,#mermaid-svg-6yc0FXrSSLlIO7Lp .node .label text,#mermaid-svg-6yc0FXrSSLlIO7Lp .image-shape .label,#mermaid-svg-6yc0FXrSSLlIO7Lp .icon-shape .label{text-anchor:middle;}#mermaid-svg-6yc0FXrSSLlIO7Lp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6yc0FXrSSLlIO7Lp .rough-node .label,#mermaid-svg-6yc0FXrSSLlIO7Lp .node .label,#mermaid-svg-6yc0FXrSSLlIO7Lp .image-shape .label,#mermaid-svg-6yc0FXrSSLlIO7Lp .icon-shape .label{text-align:center;}#mermaid-svg-6yc0FXrSSLlIO7Lp .node.clickable{cursor:pointer;}#mermaid-svg-6yc0FXrSSLlIO7Lp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6yc0FXrSSLlIO7Lp .arrowheadPath{fill:#333333;}#mermaid-svg-6yc0FXrSSLlIO7Lp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6yc0FXrSSLlIO7Lp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6yc0FXrSSLlIO7Lp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6yc0FXrSSLlIO7Lp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6yc0FXrSSLlIO7Lp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6yc0FXrSSLlIO7Lp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6yc0FXrSSLlIO7Lp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6yc0FXrSSLlIO7Lp .cluster text{fill:#333;}#mermaid-svg-6yc0FXrSSLlIO7Lp .cluster span{color:#333;}#mermaid-svg-6yc0FXrSSLlIO7Lp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6yc0FXrSSLlIO7Lp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6yc0FXrSSLlIO7Lp rect.text{fill:none;stroke-width:0;}#mermaid-svg-6yc0FXrSSLlIO7Lp .icon-shape,#mermaid-svg-6yc0FXrSSLlIO7Lp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6yc0FXrSSLlIO7Lp .icon-shape p,#mermaid-svg-6yc0FXrSSLlIO7Lp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6yc0FXrSSLlIO7Lp .icon-shape .label rect,#mermaid-svg-6yc0FXrSSLlIO7Lp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6yc0FXrSSLlIO7Lp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6yc0FXrSSLlIO7Lp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6yc0FXrSSLlIO7Lp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} PASS
FAIL
编译工程
生成 bootloader.bin
生成 partition-table.bin
生成 app.bin
esptool merge_bin 合并
output_factory_v1.0.0.bin 整包固件
Python 脚本遍历串口
自动识别设备并烧录
开启 --verify 校验
校验结果
下一台设备
标记 NG 并重试
3. 量产烧录关键配置项
- 烧录校验 :必须开启
--verify,烧录后回读比对,确保烧录完整正确,避免虚焊、接触不良导致的部分烧录 - 波特率选择:量产优先用921600波特率,兼顾速度与稳定性;接触好的夹具可到2Mbps
- eFuse烧录:安全启动、Flash加密、禁用调试等熔丝配置,在测试通过后烧录,一旦烧录不可撤销
- SN写入:每个设备唯一序列号,烧录时写入指定Flash地址或NVS分区,用于追溯

【配图2:多工位脱机烧录架构图】
配图说明:展示一拖八脱机烧录器主机→8路独立烧录通道→对应测试夹具的拓扑;标注触发按钮、状态指示灯、OK/NG信号输出,以及离线固件存储、自动校验的功能逻辑,直观呈现中小批量量产的标准烧录架构。
三、产线校准与自动化功能测试
1. 为什么必须产线校准
芯片与硬件存在个体差异,出厂参数是典型值,批量产品必须逐台校准,才能保证性能一致性:
- 射频校准:WiFi发射功率、频率偏移、接收灵敏度,每个芯片都有差异,校准后写入NVS,保证射频性能达标
- ADC校准:参考电压偏差,校准后修正采集精度
- RTC校准:内部RC时钟偏差,校准后提升走时精度
- 传感器校准:外接传感器的零点、增益偏差,修正系统误差
ESP32的射频校准必须执行,未校准的设备射频性能差异很大,可能出现信号弱、连接不稳、超标等问题。
2. 自动化功能测试设计
产线测试不能靠人工操作判断,必须用测试固件实现自动化测试,上电自动运行所有测试项,输出明确的PASS/FAIL结果。
测试固件设计原则
- 独立于正式固件:测试固件仅用于产线,不含业务逻辑,不流出市场
- 自动执行:上电无需人工干预,自动顺序执行所有测试项
- 结果明确:通过串口输出详细日志,最终输出统一的PASS或FAIL
- 状态指示:配合LED指示灯,绿灯常亮通过,红灯常亮失败,直观易判
实战2:产线自动化测试固件示例
实现功能:上电自动测试LED、按键、UART回环、WiFi扫描,自动判断结果,输出最终测试结论。
c
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_wifi.h"
#include "esp_log.h"
#define TAG "FCT_TEST"
#define LED_PIN GPIO_NUM_2
#define KEY_PIN GPIO_NUM_0
#define UART_NUM UART_NUM_1
#define TX_PIN GPIO_NUM_10
#define RX_PIN GPIO_NUM_9
static uint8_t g_pass_cnt = 0;
static uint8_t g_fail_cnt = 0;
// 测试项:LED输出
static void test_led(void)
{
gpio_set_level(LED_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(LED_PIN, 0);
ESP_LOGI(TAG, "[TEST] LED测试: PASS");
g_pass_cnt++;
}
// 测试项:按键输入
static void test_key(void)
{
// 产线夹具自动按下按键,检测电平
if(gpio_get_level(KEY_PIN) == 0) {
ESP_LOGI(TAG, "[TEST] 按键测试: PASS");
g_pass_cnt++;
} else {
ESP_LOGE(TAG, "[TEST] 按键测试: FAIL");
g_fail_cnt++;
}
}
// 测试项:UART回环
static void test_uart_loopback(void)
{
uint8_t test_data[] = "UART_TEST";
uint8_t rx_buf[16] = {0};
uart_write_bytes(UART_NUM, test_data, strlen((char*)test_data));
vTaskDelay(pdMS_TO_TICKS(50));
int len = uart_read_bytes(UART_NUM, rx_buf, sizeof(rx_buf), pdMS_TO_TICKS(100));
if(len == strlen((char*)test_data) && memcmp(rx_buf, test_data, len) == 0) {
ESP_LOGI(TAG, "[TEST] UART回环: PASS");
g_pass_cnt++;
} else {
ESP_LOGE(TAG, "[TEST] UART回环: FAIL");
g_fail_cnt++;
}
}
// 测试项:WiFi扫描
static void test_wifi_scan(void)
{
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_start();
uint16_t ap_num = 0;
esp_wifi_scan_start(NULL, true);
esp_wifi_scan_get_ap_num(&ap_num);
if(ap_num > 0) {
ESP_LOGI(TAG, "[TEST] WiFi扫描: PASS (发现%d个AP)", ap_num);
g_pass_cnt++;
} else {
ESP_LOGE(TAG, "[TEST] WiFi扫描: FAIL");
g_fail_cnt++;
}
esp_wifi_deinit();
}
void app_main(void)
{
// 初始化硬件
gpio_config_t led_cfg = {
.pin_bit_mask = 1ULL << LED_PIN,
.mode = GPIO_MODE_OUTPUT,
};
gpio_config(&led_cfg);
gpio_config_t key_cfg = {
.pin_bit_mask = 1ULL << KEY_PIN,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
};
gpio_config(&key_cfg);
uart_config_t uart_cfg = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
};
uart_param_config(UART_NUM, &uart_cfg);
uart_set_pin(UART_NUM, TX_PIN, RX_PIN, GPIO_NUM_NC, GPIO_NUM_NC);
uart_driver_install(UART_NUM, 1024, 1024, 0, NULL, 0);
ESP_LOGI(TAG, "===== 产线功能测试开始 =====");
// 顺序执行所有测试项
test_led();
test_key();
test_uart_loopback();
test_wifi_scan();
// 输出最终结果
ESP_LOGI(TAG, "===== 测试结果 =====");
ESP_LOGI(TAG, "通过: %d 失败: %d", g_pass_cnt, g_fail_cnt);
if(g_fail_cnt == 0) {
gpio_set_level(LED_PIN, 1); // 绿灯常亮表示通过
ESP_LOGI(TAG, "最终结论: PASS");
} else {
ESP_LOGE(TAG, "最终结论: FAIL");
while(1) { // 红灯闪烁表示失败
gpio_set_level(LED_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(200));
gpio_set_level(LED_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(200));
}
}
}
下面是产线校准与自动化功能测试的完整流程:
#mermaid-svg-8qCJP8KFMubWN1Pb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8qCJP8KFMubWN1Pb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8qCJP8KFMubWN1Pb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8qCJP8KFMubWN1Pb .error-icon{fill:#552222;}#mermaid-svg-8qCJP8KFMubWN1Pb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8qCJP8KFMubWN1Pb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8qCJP8KFMubWN1Pb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8qCJP8KFMubWN1Pb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8qCJP8KFMubWN1Pb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8qCJP8KFMubWN1Pb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8qCJP8KFMubWN1Pb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8qCJP8KFMubWN1Pb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8qCJP8KFMubWN1Pb .marker.cross{stroke:#333333;}#mermaid-svg-8qCJP8KFMubWN1Pb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8qCJP8KFMubWN1Pb p{margin:0;}#mermaid-svg-8qCJP8KFMubWN1Pb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8qCJP8KFMubWN1Pb .cluster-label text{fill:#333;}#mermaid-svg-8qCJP8KFMubWN1Pb .cluster-label span{color:#333;}#mermaid-svg-8qCJP8KFMubWN1Pb .cluster-label span p{background-color:transparent;}#mermaid-svg-8qCJP8KFMubWN1Pb .label text,#mermaid-svg-8qCJP8KFMubWN1Pb span{fill:#333;color:#333;}#mermaid-svg-8qCJP8KFMubWN1Pb .node rect,#mermaid-svg-8qCJP8KFMubWN1Pb .node circle,#mermaid-svg-8qCJP8KFMubWN1Pb .node ellipse,#mermaid-svg-8qCJP8KFMubWN1Pb .node polygon,#mermaid-svg-8qCJP8KFMubWN1Pb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8qCJP8KFMubWN1Pb .rough-node .label text,#mermaid-svg-8qCJP8KFMubWN1Pb .node .label text,#mermaid-svg-8qCJP8KFMubWN1Pb .image-shape .label,#mermaid-svg-8qCJP8KFMubWN1Pb .icon-shape .label{text-anchor:middle;}#mermaid-svg-8qCJP8KFMubWN1Pb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8qCJP8KFMubWN1Pb .rough-node .label,#mermaid-svg-8qCJP8KFMubWN1Pb .node .label,#mermaid-svg-8qCJP8KFMubWN1Pb .image-shape .label,#mermaid-svg-8qCJP8KFMubWN1Pb .icon-shape .label{text-align:center;}#mermaid-svg-8qCJP8KFMubWN1Pb .node.clickable{cursor:pointer;}#mermaid-svg-8qCJP8KFMubWN1Pb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8qCJP8KFMubWN1Pb .arrowheadPath{fill:#333333;}#mermaid-svg-8qCJP8KFMubWN1Pb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8qCJP8KFMubWN1Pb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8qCJP8KFMubWN1Pb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8qCJP8KFMubWN1Pb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8qCJP8KFMubWN1Pb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8qCJP8KFMubWN1Pb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8qCJP8KFMubWN1Pb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8qCJP8KFMubWN1Pb .cluster text{fill:#333;}#mermaid-svg-8qCJP8KFMubWN1Pb .cluster span{color:#333;}#mermaid-svg-8qCJP8KFMubWN1Pb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8qCJP8KFMubWN1Pb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8qCJP8KFMubWN1Pb rect.text{fill:none;stroke-width:0;}#mermaid-svg-8qCJP8KFMubWN1Pb .icon-shape,#mermaid-svg-8qCJP8KFMubWN1Pb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8qCJP8KFMubWN1Pb .icon-shape p,#mermaid-svg-8qCJP8KFMubWN1Pb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8qCJP8KFMubWN1Pb .icon-shape .label rect,#mermaid-svg-8qCJP8KFMubWN1Pb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8qCJP8KFMubWN1Pb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8qCJP8KFMubWN1Pb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8qCJP8KFMubWN1Pb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
上电启动测试固件
射频校准
ADC校准
RTC校准
传感器校准
LED测试
按键测试
UART回环测试
WiFi扫描测试
所有测试项通过?
输出 PASS,绿灯常亮
输出 FAIL,红灯闪烁
重新烧录正式固件
进入不良品分级处理
关键说明
- 产线夹具配合测试固件,自动按下按键、短接回环,无需人工操作,测试速度快、一致性好
- 所有测试项必须有明确的通过/失败阈值,不能模糊判断
- 测试固件禁止包含业务核心代码、敏感参数,避免流出造成知识产权损失
四、不良品分拣与产线追溯体系
1. 不良品分级与处理
产线不良品按严重程度分级处理,避免浪费:
- A级不良:致命功能失效,如无法启动、核心功能失效、安全不达标;直接报废或返厂维修,禁止流入市场
- B级不良:性能不达标,如射频功率偏低、精度超差;可返工校准,重新测试通过后可放行
- C级不良:外观、标识、包装问题;现场修复,复检通过放行
2. 全链路追溯体系
追溯是量产质量管理的核心,出问题时可精准定位批次与范围,最小化召回成本:
- 唯一SN码:每个产品分配唯一序列号,烧录时写入Flash固定地址,可通过指令读取
- 批次关联:每个批次记录固件版本、物料批次、生产日期、产线工位、操作人员
- 数据留存:每个产品的测试数据、校准值、不良记录全部上传MES系统,留存至少3年
- 逆向追溯:通过SN可反查全部生产数据;通过批次可正向查所有产品流向
3. 追溯码设计建议
SN码建议包含信息:
产品型号(2位) + 生产年份(2位) + 周号(2位) + 流水号(4位)
例如:D226350001代表D2产品,26年第35周,第0001台。

【配图3:量产质量管控与追溯流程图】
配图说明:从烧录写入SN开始,经过校准、功能测试、安全测试,每个环节通过则进入下一站,失败则进入对应不良品分级处理;最终合格产品生成追溯记录上传系统,直观呈现质量管控与追溯的完整闭环逻辑。
五、工程最佳实践
1. 烧录管理
- 统一使用合并固件,单文件烧录,避免多文件地址错误
- 每批次首件必须做全功能验证,确认固件版本、配置、功能正确后再批量烧录
- 烧录必须开启校验,按比例抽检回读,确保烧录一致性
- eFuse烧录必须单独工位,测试通过后再执行,执行前双人确认,避免批量报废
2. 测试管理
- 测试固件与正式固件严格分离,版本独立管理,产线测试完成后必须重新烧录正式固件
- 测试项覆盖所有出货功能点与性能指标,临界参数明确量化阈值
- 定期校准测试夹具与仪器,保证测试结果准确可靠
- 不良品每天复盘,分析根因,优化制程与设计,持续提升良率
3. 追溯管理
- SN码全局唯一,重码零容忍,烧录前自动校验
- 批次信息完整,固件、物料、人员、时间、设备全记录
- 不良数据定期统计,定位高发问题,驱动设计与制程改进
- 售后返回产品可通过SN快速调取生产数据,定位问题原因
六、新手高频踩坑汇总
- 烧录地址错误,批量设备无法启动
- 分开烧录多个文件时地址配置错误,或者合并固件地址偏移不对
- 解决:量产统一使用0地址起始的合并固件,减少地址配置环节
- 未做射频校准,产品射频性能差异大
- 直接烧录固件不校准,芯片个体差异导致部分设备信号弱、连接不稳
- 解决:产线逐台做射频校准,校准值写入NVS,保证性能一致性
- 测试固件流出市场,带来安全风险
- 产线测试后忘记烧回正式固件,测试固件带调试接口、后门
- 解决:测试工位后增加正式固件烧录工位,或者测试固件自动过期失效
- 没有追溯体系,出问题无法定位批次
- 产品没有唯一SN,出问题不知道哪批生产、用了什么物料
- 解决:建立SN编码规则,量产逐台写入,测试数据关联留存
- 烧录不校验,虚焊导致不良品流出
- 烧录时关闭校验,夹具接触不良导致部分烧录,出厂后功能失效
- 解决:强制开启烧录校验,失败自动重试并标记NG
- eFuse误烧录,批量芯片报废
- 量产前未验证eFuse配置文件,错误烧录导致芯片永久锁定
- 解决:先单芯片验证配置,确认无误后再批量烧录;烧录工位增加二次确认
下一篇预告(41篇)
ESP-IDF保姆级入门41|产品级故障排查与稳定性优化全解:死机复位排查/内存泄漏定位/性能瓶颈分析/长期稳定性测试,掌握量产运维与问题定位方法论,实现产品级高可靠稳定运行!