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 升级失败后,全靠串口救回来。所以,哪怕你的产品主打远程升级,也一定要在硬件上保留串口接口。这不是保守,这是经验。