上海乐鑫科技代理商wifi控制模块Xmodem协议介绍及应用(基于ESP-IDF)
1. 介绍
Xmodem 和 Ymodem 是串口通信中广泛用到的异步文件传输协议。这个协议包括了文件的识别、传送的起止时间、错误的判断与纠正等内容。Xmodem、Ymodem 和 Zmodem 协议是常用的三种通信协议。本文只介绍 Xmodem 和 Ymodem 协议,Zmodem 协议后续添加。
1.1 使用场景
Xmodem 和 Ymodem 协议是串口文件传输协议,顾名思义可用于通过串口相连的 ESP 设备与 MCU 之间的文件传输。当 MCU 设备作为接收端时,上海乐鑫科技代理商wifi控制模块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 控制符定义
1.4.2 帧包格式
说明: - 该帧是 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 校验和交互流程
1.4.3.2 Xmodem CRC16 交互流程
计算 CRC16 校验的除数多项式为X ^ 16 + X ^ 12 + X ^ 5 + 1,信息报中的 128 数据字节将参加 CRC16 校验的计算,在发送端 CRC16 的高字节在前,低字节在后
说明: - 相比于 Xmodem 校验和, Xmodem CRC16 是发送控制字符 C,而校验和发送控制字符 NAK,并且 CRC16 校验字段占 2 Byte。 - 如果使用 Xmodem-1k 协议发送 1024 字节的数据,只需要将数据头标志由 SOH 替换为 STX,数据部分占 1024 字节。 - 如果发送的数据不满 128 字节或者 1024 字节,使用 0x1A 填充。
1.4.3.3 Ymodem 交互流程
Ymodem 协议的起始帧并不直接传输文件的数据,而是将文件名与文件的大小放在数据帧中传输。它的数据帧结构如下:
说明: - 头标志是 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 协议的结束帧与起始帧类似,结构如下:
文件传输流程:
1.5 方案对比
1.5.1 对比 AT 透传升级 MCU
1.5.2 对比其他串口文件传输协议升级 MCU
相关其他串口文件传输协议,请参考维基百科。
归纳总结: - 当前常用的串口文件传输协议就只有 Xmodem、Ymodem 和 Zmodem,并且它们的 license 是 Public domain。 - 它们支持数据错误检查机制: 校验和和出错重传。
使用 Xmodem 和 Ymodem 串口文件传输协议实现 MCU 升级,对于 MCU 侧可按照标准协议文档实现,对于上海乐鑫科技代理商wifi控制模块ESP 设备侧可参考文档后续章节介绍。
2. 目的
本文基于 Xmodem 和 Ymodem 协议规范,针对 ESP8266_RTOS_SDK 和 ESP-IDF 平台开发了基于 UART 传输的文件传输协议组件。支持以下 Xmodem 和 Ymodem 组合协议功能。
本文将介绍如何使用该组件提供的接口进行串口文件传输。
3. 硬件准备
linux 环境 用来编译 & 烧写 & 运行等操作的必需环境。
windows 用户可安装虚拟机,在虚拟机中安装 linux。
ESP 设备 ESP 设备包括上海乐鑫科技代理商wifi控制模块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_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, 上海乐鑫科技代理商wifi控制模块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