3、固件升级方式对比:本地升级(JTAG/SWD)、远程升级(OTA/以太网)、串口升级(XMODEM/YMODEM)
做嵌入式这么多年,我接触过的升级方式少说也有七八种。但真正在实际项目中用得最多的,其实就是今天要聊的这三类:本地升级、远程升级、串口升级。每种方式都有自己的脾气,选错了,轻则开发周期拉长,重则产品返厂。
我个人习惯是,在项目立项阶段就把升级方案定下来。为什么?因为这事牵扯到硬件设计、Bootloader 大小、通信协议,甚至产线流程。你想想看,如果产品已经封箱发货了,才发现不支持远程升级,那得多尴尬。
3.1 本地升级:JTAG 与 SWD
本地升级,说白了就是用调试器直接烧录。这是最原始、最可靠的方式,也是我入行时学的第一种升级方法。
JTAG(Joint Test Action Group) 是 IEEE 1149.1 标准接口,用了 5 根线:TMS、TCK、TDI、TDO,外加一个可选的 nTRST。SWD(Serial Wire Debug) 是 ARM 公司搞的简化版,只用 2 根线:SWDIO 和 SWCLK。我个人更偏爱 SWD,因为省引脚,在 PCB 布局紧张时特别好用。
核心特点对比:
| 特性 | JTAG | SWD |
|---|---|---|
| 引脚数 | 4-5 根 | 2 根 |
| 速度 | 最高可达 50MHz | 通常 4-10MHz |
| 调试能力 | 支持多核调试 | 单核为主 |
| 边界扫描 | 支持 | 不支持 |
| 适用场景 | 复杂系统、FPGA | ARM Cortex-M 系列 |
我在项目中遇到过一件事:有个同事用 JTAG 调试 STM32F4,结果死活连不上。查了半天,原来是 TCK 引脚被复用成了 GPIO,初始化代码里把它拉低了。嗯,这里要注意——调试接口的引脚,千万别在应用代码里乱动,否则就是给自己挖坑。
我的建议:量产阶段,如果条件允许,尽量保留 SWD 接口。哪怕只留两个测试点,也比没有强。我曾经因为没留调试口,产品出问题后只能拆外壳、飞线,那叫一个痛苦。
3.2 远程升级:OTA 与以太网
远程升级,现在基本是物联网产品的标配了。OTA(Over-The-Air) 通常指无线升级,比如通过 Wi-Fi、蓝牙、LoRa、NB-IoT 等。以太网升级 则是有线方式,常见于工业网关、路由器这类设备。
这两种方式的核心逻辑是一样的:设备从远端服务器下载固件包,校验后写入 Flash。区别只在于物理层和传输协议。
OTA 升级的关键点:
- 双备份机制:我强烈建议用 A/B 分区方案。一个分区跑当前固件,另一个分区下载新固件。下载完成后,标记启动分区,然后重启。万一新固件起不来,还能回滚到旧版本。
- 断点续传:尤其是用蜂窝网络升级时,信号不稳定是常态。没有断点续传,下载到 90% 断了,又得从头来,用户会骂娘的。
- 签名校验:这个绝对不能省。我曾经见过一个产品,OTA 固件没做签名,结果被黑客注入了恶意代码,整批设备变成了肉鸡。
警告:远程升级时,一定要考虑升级失败后的应急方案。我习惯在 Bootloader 里加一个「强制恢复模式」——长按某个按键上电,设备就进入等待串口升级的状态。这样即使 OTA 把系统刷成砖,也能救回来。
以太网升级相对简单一些。常用的协议有 TFTP、HTTP、FTP。我个人推荐用 HTTP,因为穿透性好,而且可以复用现有的 Web 服务器。代码实现也不复杂:
// 伪代码:HTTP 固件下载
int http_firmware_download(const char* url) {
http_client_t client;
http_response_t resp;
// 1. 建立连接
if (http_connect(&client, url) != 0) {
return -1; // 连接失败
}
// 2. 发送 GET 请求
http_get(&client, url, &resp);
// 3. 逐块写入 Flash
uint8_t buffer[1024];
uint32_t offset = 0;
while (1) {
int len = http_read_body(&client, buffer, sizeof(buffer));
if (len <= 0) break;
flash_write(FIRMWARE_PARTITION, offset, buffer, len);
offset += len;
}
// 4. 校验完整性
if (verify_checksum() != 0) {
return -2; // 校验失败
}
// 5. 标记新固件有效
set_boot_flag(NEW_FIRMWARE);
return 0;
}
你可能会问:OTA 和以太网升级,哪个更可靠?说实话,没有绝对答案。以太网有线连接,物理层稳定,但布线麻烦。OTA 方便,但受环境干扰大。我个人的经验是:工业场景优先用有线,消费类产品用无线。
3.3 串口升级:XMODEM 与 YMODEM
串口升级,听起来有点古老,但在很多场景下依然是救命稻草。比如设备第一次烧录 Bootloader、OTA 升级失败后的恢复、产线批量烧录等。
XMODEM 是最早的串口文件传输协议之一,由 Ward Christensen 在 1977 年发明。它把数据分成 128 字节的块,每块带一个校验和。简单、可靠,但速度慢——因为它是停等协议,发完一块必须等 ACK 才能发下一块。
YMODEM 是 XMODEM 的改进版,由 Chuck Forsberg 开发。主要改进有两点:一是支持 1024 字节的大块传输,速度更快;二是支持批量文件传输,可以一次传多个文件。
XMODEM vs YMODEM 对比:
| 特性 | XMODEM | YMODEM |
|---|---|---|
| 块大小 | 128 字节 | 128 或 1024 字节 |
| 校验方式 | 8 位校验和 | 16 位 CRC |
| 批量传输 | 不支持 | 支持 |
| 传输效率 | 低(约 60%) | 高(约 90%) |
| 代码复杂度 | 简单 | 中等 |
我曾经在一个项目中用 XMODEM 升级 2MB 的固件,115200 波特率下,整整传了 3 分多钟。后来换成 YMODEM,同样的波特率,1 分钟不到就搞定了。你想想看,产线上每天烧录几百台设备,这时间差距就是成本。
避坑指南:实现串口升级时,一定要注意超时处理。我曾经遇到过一个问题:上位机发送数据时,下位机正在擦除 Flash 扇区,导致串口缓冲区溢出,丢了一个字节。结果整个固件校验失败,又得重传。后来我在代码里加了流控,擦除 Flash 前先发一个 XOFF 信号,让对方暂停发送。
串口升级的代码实现,核心就是状态机。我习惯这样设计:
// 串口升级状态机
typedef enum {
STATE_IDLE, // 空闲
STATE_WAIT_SOH, // 等待起始字节
STATE_RECV_DATA, // 接收数据块
STATE_SEND_ACK, // 发送确认
STATE_ERROR // 错误处理
} xmodem_state_t;
void xmodem_process(uint8_t byte) {
static xmodem_state_t state = STATE_IDLE;
static uint8_t buffer[128];
static uint8_t block_num;
static uint16_t index;
switch (state) {
case STATE_IDLE:
if (byte == SOH) { // 128 字节块开始
state = STATE_WAIT_SOH;
index = 0;
} else if (byte == EOT) {
// 传输结束
send_byte(ACK);
state = STATE_IDLE;
}
break;
case STATE_WAIT_SOH:
block_num = byte;
state = STATE_RECV_DATA;
break;
case STATE_RECV_DATA:
buffer[index++] = byte;
if (index >= 128) {
// 校验和检查
if (calc_checksum(buffer) == byte) {
flash_write(offset, buffer, 128);
send_byte(ACK);
} else {
send_byte(NAK);
}
state = STATE_IDLE;
}
break;
}
}
最后说一句:别小看串口升级。虽然它慢,但它稳定。我见过不少产品,OTA 升级失败后,全靠串口救回来。所以,哪怕你的产品主打远程升级,也一定要在硬件上保留串口接口。这不是保守,这是经验。