Xmodem 协议介绍及应用(基于 ESP-IDF)

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,并且它们的 licensePublic 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。

  • ESP 设备
    ESP 设备包括 ESP芯片ESP模组ESP开发板等。

  • USB串口 杜邦线
    连接 PC 和 ESP 设备,用来烧写/下载程序,通过串口 UART 传输文件协议,查看 log 等。

4. 环境搭建

如果您熟悉 ESP 开发环境,可以很顺利理解下面步骤;如果您不熟悉某个部分,比如编译,烧录,需要您结合官方的相关文档来理解。如您需阅读 ESP-IDF 编程指南文档等。

4.1 编译器环境搭建

  • ESP8266 平台:根据官方链接Get toolchain,获取 toolchain。
  • ESP32 平台:根据官方链接工具链的设置,下载 toolchain。

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_1kfalse,数据按照 128 字节发送。 如果设置 support_xmodem_1ktrue,就会按照 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 系统命令 rzsz 配合测试。这两者支持 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

猜你喜欢

转载自blog.csdn.net/espressif/article/details/113740416