上海樂鑫科技代理商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