3. 烧录协议核心概念:XMODEM/YMODEM协议简介、数据包结构、校验机制

好,咱们进入烧录协议的核心部分。说实话,XMODEM和YMODEM这两个协议,年纪比很多工程师都大。但你别看它们老,在嵌入式领域,尤其是U-Boot的串口烧录场景下,它们依然是「老当益壮」的主力。

我个人习惯把这类协议叫做「文件搬运工」。它们不关心你烧的是什么——是裸机程序、Linux内核,还是文件系统镜像。它们只负责一件事:把数据从PC端,通过串口这根「独木桥」,可靠地搬到开发板的RAM里。

3.1 XMODEM协议:最朴素的可靠传输

XMODEM是1977年由Ward Christensen开发的。嗯,那时候我还没出生。但它的设计思想至今仍在沿用。

核心特点:

  • 数据包大小固定:标准模式128字节,1K模式1024字节
  • 停止等待协议:发一个包,等一个确认,再发下一个
  • 校验方式:早期用Checksum,后来扩展了CRC16

你想想看,串口本身没有流控,或者只有硬件流控。XMODEM用软件方式解决了「丢包重传」的问题。我在项目中遇到过,有些老旧的串口芯片在高速率下会丢字节,XMODEM的ACK/NAK机制就派上了大用场。

3.1.1 数据包结构

XMODEM的数据包结构非常简洁。我把它拆开给你看:

+--------+--------+--------+--------+--------+--------+--------+--------+
|  SOH   | 序号   | 序号取反|  数据块 (128字节)          | 校验码  |
| (0x01) | (1字节)| (1字节) |                            | (1/2字节)|
+--------+--------+--------+--------+--------+--------+--------+--------+

各字段说明:

字段 长度 说明
SOH 1字节 数据包起始标志,固定0x01。如果是1K模式,用STX(0x02)
序号 1字节 从1开始,循环使用0-255
序号取反 1字节 255 - 序号,用于校验序号完整性
数据块 128字节 实际数据。最后一个包不足128字节时用CTRL-Z(0x1A)填充
校验码 1或2字节 Checksum模式1字节,CRC模式2字节

这里有个细节要注意:序号是从1开始的,不是0。我曾经在移植U-Boot的XMODEM接收器时,把序号初始值写成了0,结果PC端发送软件一直报错「包序号不匹配」。排查了半天才发现这个坑。

3.1.2 校验机制

XMODEM支持两种校验方式:

1. Checksum(累加和)

这是最原始的方式。把128字节数据全部加起来,取低8位。简单,但碰撞概率高。

uint8_t calc_checksum(uint8_t *data, int len) {
    uint8_t sum = 0;
    for (int i = 0; i < len; i++) {
        sum += data[i];
    }
    return sum;
}

2. CRC16(循环冗余校验)

U-Boot里默认用的是CRC16-CCITT,多项式是0x1021。可靠性比Checksum高一个数量级。

uint16_t calc_crc16(uint8_t *data, int len) {
    uint16_t crc = 0;
    for (int i = 0; i < len; i++) {
        crc ^= (uint16_t)data[i] << 8;
        for (int j = 0; j < 8; j++) {
            if (crc & 0x8000)
                crc = (crc << 1) ^ 0x1021;
            else
                crc <<= 1;
        }
    }
    return crc;
}
我的建议:在U-Boot中,尽量使用CRC模式。虽然计算量稍大,但现代MCU跑这点计算完全不是问题。而且,串口传输本身容易受干扰,CRC能帮你过滤掉大部分「脏数据」。

3.2 YMODEM协议:XMODEM的增强版

YMODEM是XMODEM的「升级版」。它解决了XMODEM的几个痛点:

  • 支持文件名和文件大小传输
  • 支持批量传输多个文件
  • 数据包大小可切换到1K字节

U-Boot的loady命令用的就是YMODEM协议。我个人觉得,YMODEM比XMODEM更适合烧录场景,因为你能在终端上看到文件名和进度,心里踏实。

3.2.1 会话流程

YMODEM的会话比XMODEM多了一个「握手阶段」。我画个流程图给你看:

YMODEM会话流程 接收端 (U-Boot) 发送端 (PC工具) 发送 'C' (0x43) 发送文件名包 (SOH) 发送 ACK 发送数据包 (STX/SOH) 发送 ACK 循环直到文件结束 发送 EOT (0x04) 发送 ACK

流程看起来比XMODEM复杂,但核心逻辑是一样的。区别在于:

  • 接收端先发一个'C'字符,表示「我准备好了,用CRC模式」
  • 发送端先发一个特殊的「文件名包」,包含文件名和文件大小
  • 文件传完后,再发一个EOT表示结束

3.2.2 文件名包结构

YMODEM的文件名包也是128字节,但结构不同:

+--------+--------+--------+--------+--------+--------+--------+--------+
|  SOH   | 序号=0 | 序号取反|  文件名 (以0结尾) | 文件大小(ASCII) | 填充  |
| (0x01) | (0x00) | (0xFF) |                   |                 | (0x00)|
+--------+--------+--------+--------+--------+--------+--------+--------+

举个例子,文件名包的数据部分可能是:

"u-boot.bin\0" + "123456\0" + 剩余填充0x00

这里「123456」是文件大小的ASCII表示。U-Boot解析时会用atoi()把它转成整数。

注意:文件名包的文件大小字段是ASCII字符串,不是二进制整数。我见过有人直接传二进制数值,结果U-Boot解析出来是个天文数字,直接分配内存失败。

3.3 U-Boot中的实现要点

U-Boot的串口烧录代码主要在common/serial.c和drivers/serial/目录下。但协议实现是在common/xyzModem.c里。

我挑几个关键点说说:

1. 超时处理

串口传输最怕死等。U-Boot里用了一个getc_nowait()函数,配合定时器实现超时。如果1秒内没收到数据,就重发NAK。

2. 缓冲区管理

U-Boot的XMODEM接收器用了一个环形缓冲区。数据从串口中断进来,放到缓冲区里;协议处理线程从缓冲区取数据。这样不会丢字节。

3. 错误恢复

如果连续收到10个NAK,或者CRC校验失败超过一定次数,U-Boot会放弃传输,返回错误。这个阈值可以配置,我一般设成20次,给串口线「多一点机会」。

核心要点总结:

  • XMODEM:128字节包,Checksum或CRC,简单可靠
  • YMODEM:支持文件名/大小,1K包,批量传输
  • U-Boot默认用CRC16-CCITT,可靠性更高
  • 超时和重试机制是协议健壮性的关键

嗯,这些协议看起来简单,但实际调试时坑不少。我记得有一次,串口线太长,信号衰减导致CRC频繁出错。后来换了根屏蔽线,问题就解决了。有时候,问题不在代码里,而在物理层。

下一节我们会深入U-Boot的代码,看看这些协议是怎么具体实现的。到时候我会带着你一行一行地读代码。

可可精品资料库