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;
}
3.2 YMODEM协议:XMODEM的增强版
YMODEM是XMODEM的「升级版」。它解决了XMODEM的几个痛点:
- 支持文件名和文件大小传输
- 支持批量传输多个文件
- 数据包大小可切换到1K字节
U-Boot的loady命令用的就是YMODEM协议。我个人觉得,YMODEM比XMODEM更适合烧录场景,因为你能在终端上看到文件名和进度,心里踏实。
3.2.1 会话流程
YMODEM的会话比XMODEM多了一个「握手阶段」。我画个流程图给你看:
流程看起来比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()把它转成整数。
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的代码,看看这些协议是怎么具体实现的。到时候我会带着你一行一行地读代码。