跳转至

Xmodem 文档

作者:李源龙

一、XMODEM 协议概述

XMODEM 是一种由 Ward Christensen 于 1977 年设计的异步文件传输协议,广泛应用于早期的调制解调器通信以及嵌入式系统(如 BootLoader 固件升级)中。该协议以 128 字节为数据块进行传输,并通过校验和(Checksum)或循环冗余校验(CRC)机制实现错误检测。其核心机制是“接收方驱动”的等待式 ARQ(自动重传请求),即接收方通过发送 NAK 或‘C’字符启动传输,并对每个成功接收的数据块回复 ACK 确认,若校验失败则回复 NAK 请求重发,以此确保传输的可靠性。虽然该协议因其简单易实现而具有持久生命力,但其逐块确认机制在高速或高延迟网络环境中效率较低。

1.1 核心特性

核心特性
具体表现与设计目的
基本传输机制
采用固定大小的数据块(通常为128字节)作为传输单位。这种分块传输方式适合在低速、不可靠的信道上管理传输过程。
差错控制
使用校验和或CRC校验确保数据正确性。原始版本使用1字节的算术校验和,改进版XMODEM-CRC采用更可靠的16位CRC校验,显著提升了错误检测能力。
通信模式
半双工通信和“接收方驱动” 的等待式ARQ(自动重传请求)。接收方通过发送NAK或‘C’字符启动传输,发送方每发送一个数据块后必须等待接收方的确认(ACK)信号,才会发送下一个块。
错误处理与重传
通过ACK/NAK确认机制进行流量控制和错误恢复。接收方校验正确则回复ACK,错误则回复NAK请求重发;超过最大重试次数或遇到严重错误时,可发送CAN字符取消传输。
协议变种
衍生出多个改进版本,如XMODEM-1K(将数据块扩大至1024字节,提高效率)、YMODEM(支持批处理文件传输)和ZMODEM(采用流式传输,效率更高)。

1.2 帧格式说明

字段组成部分
长度 (字节)
具体说明与取值
帧头 (Header)
1
SOH (0x01): 表示后续数据区为128字节12。STX (0x02): 表示后续数据区为1024字节(属于XMODEM-1K协议)12。
包序号 (Packet Number)
1
标识当前数据包的序列号,从1开始计数,到255后翻转为03。
包序号反码 (~Packet Number)
1
包序号的按位取反(计算方式:255 - 包序号),用于验证包序号的正确性13。
数据区 (Data)
128 或 1024
实际要传输的文件数据。如果数据长度不足,会用CTRL-Z (0x1A) 填充至固定长度24。
校验和 (Checksum)
2
用于错误检测。可以是CRC-16校验(更可靠)或8位累加和校验(更简单),具体使用哪种由传输启动方式决定23。

1.3 控制字符表

控制字符
ASCII值 (Hex)
名称与功能说明
SOH
0x01
数据头(Start Of Heading)​​:标志一个128字节数据块的开始。
STX
0x02
数据头扩展:标志一个1024字节数据块的开始,用于XMODEM-1K版本以提高效率。
EOT
0x04
传输结束(End Of Transmission)​​:由发送方发出,表示所有数据已正常传输完毕。
ACK
0x06
确认(Acknowledgment)​​:由接收方发出,表示数据包已成功接收且校验正确,请求发送下一个包。
NAK
0x15
否认(Negative Acknowledgment)​​:功能1:接收方用它启动传输,表示希望使用累加和校验方式。24功能2:在传输过程中,表示收到的数据包校验错误,请求发送方重传当前包。
CAN
0x18
取消(Cancel)​​:通信任意一方均可发出,表示要求无条件终止本次文件传输。
C​​
0x43
CRC请求:接收方发送大写字母 'C' 来启动传输,表示希望使用更可靠的CRC-16校验方式。
CTRL-Z
0x1A
填充字符:当文件最后一个数据包的有效数据不足128或1024字节时,用于填充剩余空间。

1.4 传输流程

二、演示功能概述

本篇文章演示的内容为:使用 Air8101 开发板的 UART1 连接 PC 端的串口调试工具 sscom,通过 xmodem 协议发送数据到电脑端,文档提供了两种方案,第一种方案是和模块一起烧录在脚本区的文件发送出来。第二种是从 http 上 get 下载的文件发送出来。

三、准备硬件环境

参考:硬件环境清单第二章节内容,准备以及组装好硬件环境

连线对应表

| Air780HV 开发板 | USB 转 TTL 线 |

| ------------------------ | ----------------- |

| 18/U1TXD | uart_rx |

| 17/U1RXD | uart_tx |

| gnd | gnd |

使用 USB 转 TTL 线连接 Air8101 开发板的 U1TXD 、U1RXD 和 GND

四、准备软件环境

1. Luatools 工具

2. 内核固件文件(底层 core 固件文件):LuatOS-SoC_V10001_Air8101.soc;参考项目使用的内核固件

3. luatos 需要的脚本和资源文件

脚本和资源文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air8101/demo/xmodem

lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;

准备好软件环境之后,接下来查看如何烧录项目文件到 Air8101 开发板,将本篇文章中演示使用的项目文件烧录到 Air8101 开发板中。

5.sscom 串口工具可以直接从网上下载:下载地址

五、代码 API 和代码解析

5.1 代码 API

xmodem - 合宙模组资料中心

5.2 代码解析

main.lua

主要加载 xmodem_demo 模块

--[[
@module  main
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
@version 1.0
@date    2025.10.14
@author  李源龙
@usage
本demo演示的核心功能为:
用Air8101开发板利用xmodem协议,将模块内的文件从串口发送到对端
主要提供了两种方式:
1、文件存到脚本区里面,通过xmodem协议,把脚本区文件发给对端
2、通过http下载文件到文件系统区,通过xmodem协议,把文件系统区文件发给对端

更多说明参考本目录下的readme.md文件
]]

--[[
必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
PROJECT:项目名,ascii string类型
        可以随便定义,只要不使用,就行
VERSION:项目版本号,ascii string类型
        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
PROJECT = "xmodem_demo"
VERSION = "001.000.000"

-- 在日志中打印项目名和项目版本号
log.info("main", PROJECT, VERSION)

-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
if wdt then
    --配置喂狗超时时间为9秒钟
    wdt.init(9000)
    --启动一个循环定时器,每隔3秒钟喂一次狗
    sys.timerLoopStart(wdt.feed, 3000)
end

-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
-- 启动errDump日志存储并且上传功能,600秒上传一次
-- if errDump then
--     errDump.config(true, 600)
-- end

-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
-- 可以使用合宙的iot.openluat.com平台进行远程升级
-- 也可以使用客户自己搭建的平台进行远程升级
-- 远程升级的详细用法,可以参考fota的demo进行使用

-- 启动一个循环定时器
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
-- 方便分析内存使用是否有异常
-- sys.timerLoopStart(function()
--     log.info("mem.lua", rtos.meminfo())
--     log.info("mem.sys", rtos.meminfo("sys"))
-- end, 3000)

-- 加载网络环境检测看门狗功能模块
require "xmodem_demo"

-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

xmodem_demo.lua

1.先通过 require 进行加载 xmodem 的扩展库,定义发送的文件路径,设置串口号、波特率以及发送类型的选择,还有发送前的消息变量。

2.开启协程,运行 xmodem_run 函数,http_recived_cb()函数的主要功能是判断联网是否成功,成功之后设置保存下载文件路径为文件区的/send.bin 文件,然后进行 http 的 get 下载文件,如果获取成功就打开文件读取文件路径和文件内容。然后把 xmodem 的发送路径改为下载保存到文件区的路径,如果不进行 http 的下载,则默认使用脚本区的 send.bin 文件,地址为/luadb/send.bin。

3.调用 xmodem.send 函数进行数据的发送,设置串口 id,波特率,文件路径以及单次发送的字节,然后根据发送的结果,进行结果打印,最后通过 xmodem.close 函数关闭 xmodem 功能,主要是关闭串口。

--[[
@module  main
@summary xmodem 发送文件应用功能模块 
@version 1.0
@date    2025.07.01
@author  李源龙
@usage
本文件为Air8101开发板演示xmodem功能的代码示例,本文档主要提供了两种方案:
1、把模块脚本区的文件利用xmodem协议通过uart发送到过去

2、进行http下载文件,利用xmodem协议通过uart发送到过去
]]

--加载xmodem模块
xmodem=require ("xmodem")

--设置默认filepath为脚本区的send.bin文件
local filepath="/luadb/send.bin"

local taskName = "xmodem_run"

local uart_id = 1    --串口号

local baudrate = 115200  --波特率

local file_path=filepath --文件路径

local send_type=true  --true表示单次发送128字节,false表示单次发送1024字节

local inform_data="wait C"    --发送前提示信息,告知对方要发送C字符来接收文件

-- 处理未识别的消息
local function xmodem_run_cb(msg)
    log.info("xmodem_run_cb", msg[1], msg[2], msg[3], msg[4])
end

--http获取文件函数
local  function http_recived_cb()
    while not socket.adapter(socket.dft()) do
        log.warn("httpplus_app_task_func", "wait IP_READY", socket.dft())
        -- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
        -- 或者等待1秒超时退出阻塞等待状态;
        -- 注意:此处的1000毫秒超时不要修改的更长;
        -- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
        -- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
        -- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
        sys.waitUntil("IP_READY", 1000)
    end
    local path = "/send.bin"
    -- 以下链接仅用于测试,禁止用于生产环境
    local code, headers, body_size = http.request("GET", "http://airtest.openluat.com:2900/download/send.bin", nil, nil, {dst=path}).wait()
    log.info("http", code==200 and "success" or "error", code)
    if code==200 then
       log.info("HTTP receive ok",body_size)
       file = io.open(path, "rb")
        if file then
            content = file:read("*a")
            log.info("文件读取", "路径:" .. path, "内容:" .. content)
            file:close()
        else
            log.error("文件操作", "无法打开文件读取内容", "路径:" .. path)
        end
        file_path=path
    end

end

--  定义一个xmodem_run函数,用于用xmodem发送文件
local function xmodem_run() 
    --如果需要http下载文件,然后发送下载的文件,可以打开下面的http_recived_cb()函数
    -- http_recived_cb()

    --启动xmodem发送
    local result=xmodem.send(uart_id,baudrate,file_path,send_type)
    --等待时间12秒,等待接收方发送C字符启动发送,发送结束后接收端发送ACK:0x06表示接收完成,文件全部传输完成之后模块发送EOT:0x04表示传输结束,接收端返回0x06表示确认结束
    log.info("Xmodem", "start")

    log.info("Xmodem", "send result", result)
    --判断是否传输成功,传输是否成功,都需要关闭xmodem
    if result then
        log.info("Xmodem", "send success")
        xmodem.close(uart_id)
    else
        log.info("Xmodem", "send failed")
        xmodem.close(uart_id)
    end

end

--创建并且启动一个task
--运行这个task的主函数xmodem_run
sys.taskInit(xmodem_run, taskName,xmodem_run_cb)

5.3 结果展示

发送脚本区的 send.bin 文件:

从服务器下载 http 文件到文件区,然后通过 xmodem 协议发送文件:

六、总结

本文演示如何在 Air8101 开发板上面,通过 UART1 连接 PC 端的串口调试工具 sscom,利用 xmodem 协议发送文件。

七、常见问题

暂无