Xmodem 协议介绍及应用(基于 ESP-IDF)
目录
1. 介绍
Xmodem 和 Ymodem 是串口通信中广泛用到的异步文件传输协议。这个协议包括了文件的识别、传送的起止时间、错误的判断与纠正等内容。Xmodem、Ymodem 和 Zmodem 协议是最常用的三种通信协议。详情可以参考ymodem.txt。本文只介绍 Xmodem 和 Ymodem 协议,Zmodem 协议后续添加。
1.1 使用场景
Xmodem 和 Ymodem 协议是串口文件传输协议,顾名思义可用于通过串口相连的 ESP 设备与 MCU 之间的文件传输。当 MCU 设备作为接收端时,ESP 设备通过 WIFI、BLE 或者其他方式获取 MCU 固件或者配置文件,通过串口文件传输协议传输到 MCU 端,MCU 根据接收到的固件或者配置文件进行升级或配置;当 MCU 设备作为发送端时,通过串口文件传输协议将 MCU 的日志或者配置文件等传输到 ESP 设备端,ESP 设备上传文件至云平台或者服务器。
例如: ESP 设备将从 OTA 平台获取到的固件通过 Xmodem 协议传输到 MCU 设备,从而实现 MCU 固件的 OTA 升级
1.2 协议介绍
1.2.1 Xmodem 协议
Xmodem 协议最早是以 128 字节块的形式传输数据,并且每个块都使用校验和进行错误检测。后面衍生出使用循环冗余校验方式 (CRC16) 和支持 1024 字节块的传输协议 (Xmodem-1k)。
1.2.2 Ymodem 协议
Ymodem 协议是 Xmodem 协议的改良版,以 1024 字节块的形式传输数据,并且支持传输多个文件。一般 Ymodem 协议是使用 CRC16 进行校验。
1.3 协议特点
Xmodem 和 Ymodem 协议传输由接收程序和发送程序完成。先由接收程序发送协商字符协商校验方式,协商通过之后,发送程序开始发送数据包。接收程序接收到一个完整的数据包之后,按照协商的校验方式对数据包进行校验,校验通过之后发送确认字符,校验失败则发送否认字符。发送程序收到确认字符后继续发送下一包,收到否认字符后重传数据包。
1.4 协议解析
Xmodem 和 Ymodem 从控制符定义和帧包格式上是基本一致的。
1.4.1 控制符定义
定义 | 取值 | 作用 |
---|---|---|
SOH | 0x01 | modem 128 字节头标志 |
STX | 0x02 | modem 1024 字节头标志 |
EOT | 0x04 | 发送结束标志 |
ACK | 0x06 | 应答标志 |
NAK | 0x15 | 非应答标志 |
CAN | 0x18 | 取消发送标志 |
CRC16 | 0x43 | 使用 CRC16 校验标志 |
1.4.2 帧包格式
Byte 1 | Byte 2 | Byte 3 | Byte 4- Byte 131 | Byte 132- Byte 133 |
---|---|---|---|---|
头标志 | 包序列 | ~包序列 | 包数据 | CRC16(2 Byte) |
说明:
- 该帧是 Xmodem 使用 CRC16 校验方式,如果使用 Xmodem-1k 或者 Ymodem,帧格式 Byte 4 - Byte 131 (128 字节) 需要增大为 Byte 4 - Byte 1027 (1024字节)。
- Xmodem 如果使用校验和,帧格式 Byte 132 - Byte 133 只需要占用一个字节。
- Byte 3 是 Byte 2 按位取反,Byte 2 取值范围 0 - 255,超过 255 后从 0 递增。
1.4.3 交互流程
流程里 NAK, ACK, CAN, CRC16 和 EOT 是没有包头和数据,只是一个单独的字符数据。
1.4.3.1 Xmodem 校验和交互流程
Sender | Flow | Receiver |
---|---|---|
<— | NAK | |
Timeout after 3 seconds | ||
<— | NAK | |
| SOH | 0x01 | 0xFE | Data[0-127] | Checksum | | —> | Packet ok |
<— | ACK | |
| SOH | 0x02 | 0xFD | Data[0-127] | Checksum | | —> | Miss the packet |
<— | NAK | |
| SOH | 0x02 | 0xFD | Data[0-127] | Checksum | | —> | Packet ok |
Miss the ACK | <— | ACK |
| SOH | 0x02 | 0xFD | Data[0-127] | Checksum | | —> | Packet ignore |
<— | ACK | |
| SOH | 0x03 | 0xFC | Data[0-127] | Checksum | | —> | Checksum error |
<— | NAK | |
| SOH | 0x03 | 0xFC | Data[0-127] | Checksum | | —> | Packet ok |
<— | ACK | |
| EOT | | —> | Packet ok |
Finished | <— | ACK |
1.4.3.2 Xmodem CRC16 交互流程
计算 CRC16 校验的除数多项式为X ^ 16 + X ^ 12 + X ^ 5 + 1,信息报中的 128 数据字节将参加 CRC16 校验的计算,在发送端 CRC16 的高字节在前,低字节在后。
Sender | Flow | Receiver |
---|---|---|
<— | ‘C’ | |
Timeout after 3 seconds | ||
<— | ‘C’ | |
| SOH | 0x01 | 0xFE | Data[0-127] | CRCH | CRCL | | —> | Packet ok |
<— | ACK | |
| SOH | 0x02 | 0xFD | Data[0-127] | CRCH | CRCL | | —> | Miss the packet |
<— | NAK | |
| SOH | 0x02 | 0xFD | Data[0-127] | CRCH | CRCL | | —> | Packet ok |
Miss the ACK | <— | ACK |
| SOH | 0x02 | 0xFD | Data[0-127] | CRCH | CRCL | | —> | Packet ignore |
<— | ACK | |
| SOH | 0x03 | 0xFC | Data[0-127] | CRCH | CRCL | | —> | Checksum error |
<— | NAK | |
| SOH | 0x03 | 0xFC | Data[0-127] | CRCH | CRCL | | —> | Packet ok |
<— | ACK | |
| EOT | | —> | Packet ok |
Finished | <— | ACK |
说明:

- 相比于 Xmodem 校验和, Xmodem CRC16 是发送控制字符
C
,而校验和发送控制字符NAK
,并且 CRC16 校验字段占 2 Byte。 - 如果使用 Xmodem-1k 协议发送 1024 字节的数据,只需要将数据头标志由 SOH 替换为 STX,数据部分占 1024 字节。
- 如果发送的数据不满 128 字节或者 1024 字节,使用 0x1A 填充。
1.4.3.3 Ymodem 交互流程
Ymodem 协议的起始帧并不直接传输文件的数据,而是将文件名与文件的大小放在数据帧中传输。它的数据帧结构如下:
Byte 1 | Byte 2 | Byte 3 | Byte 4- Byte 131 | Byte 132- Byte 133 |
---|---|---|---|---|
SOH | 0x00 | 0xFF | Filename | Filesize | NUL | CRC16(2 Byte) |
说明:
- 头标志是 SOH,包序列固定是 0x00。
- Filename 是传输的文件名字,比如 hello_world.bin,它在起始帧中的格式为: 68 65 6c 6c 6f 5f 77 6f 72 6c 64 2e 62 69 6e 00,也就是把 ASCII 码转成十六进制,最后的 0x00 代表文件名结束。
- Filesize 是要传输的文件的大小,比如文件大小为 120 KB,转换为 120 * 1024 = 122880 Byte,转化为十六进制为 0x1E00,它在起始帧中的格式为: 31 45 30 30 00,对应 ASCII
1E00
,最后的 0x00 代表文件长度结束。 - 最后 NUL 代表剩余不足 128 Byte 部分用 0x00 填充。
Ymodem 协议的结束帧与起始帧类似,结构如下:
Byte 1 | Byte 2 | Byte 3 | Byte 4- Byte 131 | Byte 132- Byte 133 |
---|---|---|---|---|
SOH | 0x00 | 0xFF | NUL | CRC16(2 Byte) |
文件传输流程:
Sender | Flow | Receiver |
---|---|---|
<— | ‘C’ | |
Timeout after 3 seconds | ||
<— | ‘C’ | |
| SOH | 0x00 | 0xFF | Filename | Filesize | NUL | CRCH | CRCL | | —> | Packet ok |
<— | ACK | |
| STX | 0x01 | 0xFE | Data[0-1024] | CRCH | CRCL | | —> | Packet ok |
<— | ACK | |
| STX | 0x02 | 0xFD | Data[0-1024] | CRCH | CRCL | | —> | Packet ok |
Miss the ACK | <— | ACK |
| STX | 0x02 | 0xFD | Data[0-1024] | CRCH | CRCL | | —> | Packet ignore |
<— | ACK | |
| STX | 0x03 | 0xFC | Data[0-1024] | CRCH | CRCL | | —> | Packet ok |
<— | ACK | |
| SOH | 0x04 | 0xFB | Data[0-127] | CRCH | CRCL | | —> | Packet ok |
<— | ACK | |
| EOT | | —> | Packet ok |
<— | NAK | |
| EOT | | —> | Packet ok |
<— | ACK | |
<— | ‘C’ | |
| SOH | 0x00 | 0xFF | NUL | CRCH | CRCL | | —> | Packet ok |
Finished | <— | ACK |
1.5 方案对比
1.5.1 对比 AT 透传升级 MCU
方案 | AT 透传 | Xmodem |
---|---|---|
数据校验和 | 不支持 | 支持 |
文件的识别 | 不支持 | 支持 |
错误的判断 | 不支持 | 支持 |
序列化传输 | 不支持 | 支持 |
相比于 AT 透传实现 MCU 升级,Xmodem 协议具有每笔数据报文的完整性校验、数据报文的有序传输、数据报文的乱序丢包判断和传输文件的识别等等优势。
1.5.2 对比其他串口文件传输协议升级 MCU
相关其他串口文件传输协议,请参考维基百科。
归纳总结:
- 当前常用的串口文件传输协议就只有 Xmodem、Ymodem 和 Zmodem,并且它们的
license
是Public domain
。 - 它们支持数据错误检查机制: 校验和和出错重传。
使用 Xmodem 和 Ymodem 串口文件传输协议实现 MCU 升级,对于 MCU 侧可按照标准协议文档实现,对于 ESP 设备侧可参考文档后续章节介绍。
2. 目的
本文基于 Xmodem 和 Ymodem 协议规范,针对 ESP8266_RTOS_SDK 和 ESP-IDF 平台开发了基于 UART 传输的文件传输协议组件。支持以下 Xmodem 和 Ymodem 组合协议功能。
Xmodem | Ymodem | |
---|---|---|
校验和 | 支持 | 支持 |
CRC16 | 支持 | 支持 |
1K + CRC16 | 支持 | 支持 |
本文将介绍如何使用该组件提供的接口进行串口文件传输。
3. 硬件准备
- linux 环境
用来编译 & 烧写 & 运行等操作的必需环境。
windows 用户可安装虚拟机,在虚拟机中安装 linux。
4. 环境搭建
如果您熟悉 ESP 开发环境,可以很顺利理解下面步骤;如果您不熟悉某个部分,比如编译,烧录,需要您结合官方的相关文档来理解。如您需阅读 ESP-IDF 编程指南文档等。
4.1 编译器环境搭建
toolchain 设置参考 ESP-IDF 编程指南。
4.2 烧录工具/下载工具获取
- ESP8266 平台:烧录工具位于 ESP8266_RTOS_SDK 下
./components/esptool_py/esptool/esptool.py
- ESP32 平台:烧录工具位于 esp-idf 下
./components/esptool_py/esptool/esptool.py
esptool 功能参考:
$ ./components/esptool_py/esptool/esptool.py --help
5. SDK 准备
- esp-xmodem,通过该 SDK 可实现 Xmodem 和 Ymodem 协议应用。
- Espressif SDK
- ESP32 平台: ESP-IDF,支持 v4.0 之后版本。
- ESP8266 平台: ESP8266_RTOS_SDK,支持 v3.3 之后版本。
6. 功能介绍
功能框架如下:
Xmodem Sender 和 Xmodem Receiver 上层遵循 Xmodem 协议,数据传输通过 transport 层将协议数据写入 UART 串口,然后 Xmodem 主机和从机通过串口通信协议传输数据。
6.1 文件的传输与接收
6.1.1 配置 UART 传输层
esp_xmodem_transport_config_t transport_config = {
.baud_rate = 921600,
#ifdef CONFIG_IDF_TARGET_ESP32
.uart_num = UART_NUM_1,
.swap_pin = true,
.tx_pin = 17,
.rx_pin = 16,
.cts_pin = 15,
.rts_pin = 14,
#endif
};
esp_xmodem_transport_handle_t transport_handle = esp_xmodem_transport_init(&transport_config);
- ESP8266 默认只能使用 UART0 进行传输和接收,由于 UART0 会存在 bootloader 相关打印,为了减少此类打印数据,可以使能
swap_pin
功能,将传输接收口 swap 到 IO15 和 IO13 上,bootloader 的输出还是通过 UART0 口输出。 - ESP32 则可以使用 UART0,UART1 和 UART2,建议使用 UART1 进行文件传输和接收,UART0 用作 LOG 输出。
baud_rate
值越大,传输速率就越快。recv_timeout
是串口读取 ring buffer 的超时时间,默认建议选择 100 ms。
6.1.2 配置 Xmodem role
esp_xmodem_config_t config = {
.role = ESP_XMODEM_SENDER,
.event_handler = xmodem_sender_event_handler,
.support_xmodem_1k = true,
};
esp_xmodem_handle_t sender = esp_xmodem_init(&config, transport_handle);
role
代表是 sender 还是 receiver, 后续调用esp_xmodem_start
会根据role
去选择起 sender 还是 receiver 去处理。support_xmodem_1k
仅对于发送者 (sender) 有效,表示是否支持按照 Xmodem-1k 方式传输数据。如果不设置,默认support_xmodem_1k
为false
,数据按照 128 字节发送。 如果设置support_xmodem_1k
为true
,就会按照 1024 字节发送。如果数据少于 1024 字节,大于 128 字节,就会按照 1024 字节发送,不足 1024 字节部分填充 0x1A 后发送。如果数据少于 128 字节,就会按照 128 字节发送,不足 128 字节部分填充 0x1A 后发送。event_handler
用于注册事件 event,根据相应的 event 处理不同事件,相应逻辑处理可以参考示例。
esp_xmodem_config_t config = {
.role = ESP_XMODEM_RECEIVER,
.crc_type = ESP_XMODEM_CRC16,
.event_handler = xmodem_receiver_event_handler,
.recv_cb = xmodem_data_recv,
.cycle_max_retry = 25,
};
esp_xmodem_handle_t receiver = esp_xmodem_init(&config, transport_handle);
crc_type
是 receiver 支持的校验方式。recv_cb
仅对于接收者 (receiver) 有效,用于底层接收到文件给用户层的回调函数。相应逻辑处理可以参考 (recevier) 示例。
6.1.3 启动 Xmodem
esp_xmodem_start(sender);
- 根据返回值
ESP_OK
判断有没有启动成功. - 连接到 Xmodem receiver 后,会上报
ESP_XMODEM_EVENT_CONNECTED
事件,然后处理相应逻辑。本例中是起了一个http client task 来下载文件。
esp_xmodem_start(receiver);
- 调用该函数后,receiver 会根据
crc_type
的值发 ‘C’ 或者 NAK,一旦有 sender 发送数据,就会上报ESP_XMODEM_EVENT_CONNECTED
事件,并且数据会上报至注册的recv_cb
中。如果是文件传输,会在ESP_XMODEM_EVENT_ON_FILE
事件中上报文件名和文件长度。
7. 编译&烧写&运行
7.1 编译
7.1.1 导出编译器
参考 工具链的设置
设置 IDF_PATH
,运行 $IDF_PATH/install.sh
安装相关工具,执行 $IDF_PATH/export.sh
导出路径。
7.1.2 示例编译
- make 执行如下命令,可以通过
make menuconfig
修改串口烧录配置
cd esp-xmodem/examples/xmodem_receiver
make defconfig
make
- cmake 执行如下命令,可以通过
idf.py menuconfig
修改串口烧录配置
cd esp-xmodem/examples/xmodem_receiver
idf.py build
7.2 烧写
7.2.1 Linux 平台烧写
- 对于 make 执行
make flash
,对于 cmake 执行idf.py flash
。 - 使用
make erase_flash
或者idf.py erase_flash
擦除 flash。 - 使用
make monitor
或者idf.py -p (PORT) monitor
查看串口输出。
7.2.1 Windows 平台烧写
使用 Flash 下载工具(ESP8266 & ESP32) 烧录 Xmodem 示例固件。
-
打开 flash download tool, ESP8266 的烧录配置如下:
-
打开 flash download tool, ESP32 的烧录配置如下:
点击 start 进行烧录, 烧录成功后按 EN 键重启开发板。
7.3 运行
示例可以通过 Linux 系统命令 rz
和 sz
配合测试。这两者支持 Xmodem,Ymodem 和 Zmodem 文件传输协议。如果用户发现 Linux 系统上找不到该命令,可以执行如下命令安装。
sudo apt-get install lrzsz
rz
用于接收 sender 发送来的文件,sz
用于发送文件至 receiver。详细命令可以参考 rz --help
或者 sz --help
。
7.3.1 示例 xmodem_receiver (用于在主机上通过 xmodem 协议给模组从机进行 OTA)
该示例用于充当 receiver 来接收 sender 发送的文件,利用文件进行 OTA。详细操作请参考示例下对应的 README 文件。
对于 sender 可以使用如下命令进行发送 OTA 文件:
sz --ymodem (file name or pure data) >/dev/ttyUSB0 </dev/ttyUSB0
其中 ttyUSB0
是用于与 receiver 进行文件传输的串口。
设备侧log:
...
[17:46:56.538][0;32mI (286) uart: queue free spaces: 10[0m
[17:46:56.575][0;32mI (3286) xmodem_receive: Waiting for Xmodem sender to send data...[0m
[17:46:59.580][0;32mI (6286) xmodem_receive: Waiting for Xmodem sender to send data...[0m
[17:47:02.618][0;32mI (6292) xmodem_receive: ESP_XMODEM_EVENT_CONNECTED[0m
[17:47:02.618][0;32mI (6294) xmodem_receive: This is a file begin transfer[0m
[17:47:02.618][0;32mI (6298) xmodem_receive: ESP_XMODEM_EVENT_ON_FILE[0m
[17:47:02.618][0;32mI (6306) xmodem_receive: file_name is xmodem_receiver.bin, file_length is 176704[0m
[17:47:02.618][0;32mI (6339) xmodem_receive: Starting OTA...[0m
[17:47:02.641][0;32mI (6340) xmodem_receive: Writing to partition subtype 17 at offset 0x110000[0m
[17:47:02.641][0;32mI (9305) xmodem_receive: esp_ota_begin succeeded[0m
[17:47:05.607][0;32mI (9306) xmodem_receive: Please Wait. This may take time[0m
[17:47:05.607][0;32mI (18056) xmodem_receive: Receive EOT data[0m
[17:47:14.388][0;32mI (18059) xmodem_receive: Receive EOT data again[0m
[17:47:14.388][0;32mI (18066) xmodem_receive: This is a file end transfer[0m
[17:47:14.388][0;32mI (18067) xmodem_receive: ESP_XMODEM_EVENT_FINISHED[0m
...
[17:47:14.531][0;32mI (18224) xmodem_receive: esp_ota_set_boot_partition succeeded[0m
7.3.2 示例 xmodem_sender (用于在主机上通过 xmodem 协议接收模组从机进行文件传输)
该示例用于充当 sender 来发送通过 http 下载的文件。Linux 电脑上可以通过命令 python -m SimpleHTTPServer
起一个 Http Server。详细操作请参考示例下对应的 README 文件。
对于 receiver 可以使用如下命令进行接收文件:
rz --ymodem >/dev/ttyUSB0 </dev/ttyUSB0
其中 ttyUSB0
是用于与 receiver 进行文件传输的串口。
设备侧 log:
...
[17:40:45.177][0;32mI (481) example_connect: Connecting to HUAWEI_888...[0m
[17:40:45.188][0;32mI (1768) wifi:state: 0 -> 2 (b0)
[17:40:46.472][0m[0;32mI (1782) wifi:state: 2 -> 3 (0)
[17:40:46.485][0m[0;32mI (1790) wifi:state: 3 -> 5 (10)
[17:40:46.493][0m[0;32mI (1829) wifi:connected with HUAWEI_888, aid = 1, channel 1, HT20, bssid = 34:29:12:43:c5:40
[17:40:46.549][0m[0;31mE (1839) wifi: AES PN: 0000000000000000 <= 0000000000000000[0m
[17:40:46.549][0;32mI (4270) tcpip_adapter: sta ip: 172.168.30.131, mask: 255.255.255.0, gw: 172.168.30.1[0m
[17:40:49.019][0;32mI (4275) example_connect: Connected to HUAWEI_888[0m
[17:40:49.019][0;32mI (4279) example_connect: IPv4 address: 172.168.30.131[0m
[17:40:49.019][0;32mI (4288) xmodem_send: Connected to AP, begin http client task[0m
[17:40:49.019][0;32mI (4298) uart: queue free spaces: 10[0m
[17:40:49.019][0;32mI (14300) xmodem_send: Connecting to Xmodem receiver(1/25)[0m
[17:40:59.011][0;32mI (23638) xmodem_send: ESP_XMODEM_EVENT_CONNECTED[0m
[17:41:08.349][0;32mI (24771) xmodem_send: Send image success[0m