跳转至

ymodem 文档

作者:李源龙 | 最后修改:2026-04-09

一、Ymodem 协议概述

Ymodem 是一种基于串行通信的文件传输协议,由 Chuck Forsberg 于 1980 年代初期在 Xmodem 协议基础上改进而来。其设计目标是解决 Xmodem 在大文件传输效率低(仅支持 128 字节数据块)、缺乏批处理能力等问题,通过引入 1024 字节数据块和批量文件传输机制,显著提升传输效率与可靠性,成为早期嵌入式系统和计算机间文件交换的核心技术之一。

1.1 核心特性

特性
说明
数据块大小
支持 128 字节(SOH 模式)和 1024 字节(STX 模式)
错误检测
使用 CRC-16 循环冗余校验,校验范围包含数据块内容
批处理传输
可一次性发送多个文件,支持文件名和文件大小元数据传递
流量控制
通过 ACK/NAK 机制实现自动重传,防止数据溢出
填充规则
数据不足时用 0x1A 填充,结束帧用 0x00 填充

1.2 帧格式说明

1.2.1 起始帧(133 字节)

字段
字节数

描述
SOH
1
0x01
起始符(固定为 SOH)
块编号
1
0x00
固定为 00
块编号反码
1
0xFF
块编号的二进制反码
文件名
变长
foo.bin
以 0x00 结尾的字符串
文件大小
变长
1024
十进制数值 + 0x00 结尾
填充区
剩余
0x00
补满至 128 字节
CRC 校验码
2
CRCH CRCL
高字节在前,低字节在后

1.2.2 数据帧(1029/133 字节)

字段
字节数

描述
STX/SOH
1
0x02/0x01
1024 字节用 STX,128 用 SOH
块编号
1
0x01
从 01 递增
块编号反码
1
0xFE
块编号的二进制反码
数据区
1024/128
原始数据
不足时用 0x1A 填充
CRC 校验码
2
CRCH CRCL
校验数据区内容

1.2.3 结束帧(133 字节)

字段
字节数

描述
SOH
1
0x01
固定为 SOH
块编号
1
0x00
固定为 00
块编号反码
1
0xFF
固定为 FF
填充区
128
0x00
全 0 填充
CRC 校验码
2
0x0000
固定为 0

1.3 控制字符表

字符
ASCII 码
功能描述
SOH
0x01
128 字节数据块起始标志
STX
0x02
1024 字节数据块起始标志
EOT
0x04
传输结束标志
ACK
0x06
确认接收成功
NAK
0x15
请求重传(校验失败/超时)
CAN
0x18
取消传输
‘C’
0x43
接收方发起传输请求

1.4 传输流程

二、演示功能概述

本篇文章演示的内容为:使用 Air780EHM 核心板的 UART1 连接 PC 端的串口调试仿真工具 SecureCRT,通过 Ymodem 协议接收文件传输。

三、准备硬件环境

准备一块 Air780EHM 核心板:点击购买

3.2 连线对应表

Air780EHM开发板
USB 转 TTL 线
U1TXD
UART_RX
U1RXD
UART_TX
GND
GND

使用 USB 转 TTL 线连接 780EHM 核心板的 U1TX、U1RX 和 GND

四、准备软件环境

1.烧录工具:Luatools 工具

2.本demo开发测试时使用的固件为LuatOS-SoC_V2030_Air780EHM,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件Air780EHM固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;

3.LuatOS 需要的脚本和资源文件:Air780EHM-ymodem

4.LuatOS 运行所需要的 lib 文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件

5.PC 端工具 SecureCRT:下载链接

五、代码 API 和代码解析

5.1 代码 API

ymodem - 合宙模组资料中心

5.2 代码解析

main.lua

主要加载 ymodem_receive 模块

--[[
@module  main
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
@version 1.0
@date    2025.10.27
@author  李源龙
@usage
本demo演示的核心功能为:
使用Air780EHM核心板的UART1连接PC端的串口调试仿真工具SecureCRT,通过Ymodem协议接收文件。
更多说明参考本目录下的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 = "ymodem_receive"
VERSION = "001.000.000"

log.info("main", "project name is ", PROJECT, "version is ", 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)

-- 加载 ymodem_receive 功能模块
require "ymodem_receive"

-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!

ymodem_receive.lua

1.demo 主要是利用了 ymodem_open 函数,打开 ymodem 接收功能,并且初始化 zbuff,初始化串口,创建一个 ymodem 对象,然后开始往串口发送'C'字符,启动 ymodem 接收功能。

2.利用 ymodem 协议接收串口数据,并且保存到文件中。

3.接收完成后,自动关闭 ymodem 接收功能,并且释放 zbuff,释放串口,释放 ymodem 对象。

--[[
@module  main
@summary ymodem 接收文件应用功能模块 
@version 1.0
@date    2025.10.27
@author  李源龙
@usage
本文件为Air780EHM核心板演示ymodem功能的代码示例,核心业务逻辑为:
1. 创建协程,调用ymodem_open函数打开ymodem接收功能,初始化zbuff,
初始化串口,创建一个ymodem对象,然后开始往串口发送'C'字符,启动ymodem接收功能。
2. 接收串口数据,并且保存到指定路径,接收完成后,自动关闭ymodem接收功能。
]]

-- 根据实际设备选取不同的uartid
local uartid = 1 

local taskName = "ymodem_open"

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

--  定义一个局部变量,用于表示Ymodem协议是否正在运行
local ymodem_running = false 

local rxbuff 

local ymodem_handler

local ymodem_result=false

--定义一个ymodem_close函数,用于关闭ymodem接收功能,包括释放zbuff,关闭串口,释放ymodem对象等操作
local function ymodem_close()
    --释放rxbuff
    rxbuff:free()
    --关闭串口
    uart.close(uartid)
    --释放ymodem处理程序
    ymodem.release(ymodem_handler)
end



--  定义一个ymodem_rx函数,用于接收数据
local function ymodem_rx(id,len) 
    --  从uart接收数据到缓冲区
    while true do
        log.info("uart", "缓冲区", uart.rxSize(id)) -- 缓冲区中的数据数量
        local len = uart.rx(id, rxbuff)
        if len <= 0 then
            break
        end
        log.info("uart", "receive", id, rxbuff:used(), rxbuff:toStr())
    end
    --  打印缓冲区已使用的大小
    log.info(rxbuff:used()) 
    --  调用ymodem.receive函数,接收数据
    local result,ack,flag,file_done,all_done = ymodem.receive(ymodem_handler,rxbuff) 
    ymodem_running = result
    log.info("result:",ymodem_running,ack,flag,file_done,all_done)
    rxbuff:del()
    --成功就发送ack和flag
    if result then
        rxbuff:copy(0, ack,flag)
        uart.tx(id, rxbuff)
    end

    --  所有数据都接收完毕
    if all_done then 
        -- 判断/save.bin文件是否存在
        local exists=io.exists("/save.bin") 

        -- 判断/save.bin文件是否存在,存在则打印日志,显示/save.bin文件大小;不存在则打印日志,显示/save.bin文件不存在
        if exists then
            log.info("io", "save.bin file exists:", exists) 
            --打印文件大小
            log.info("io", "save.bin file size:", io.fileSize("/save.bin")) 
        else
            log.info("io", "save.bin file not exists") 
        end

        --ymodem_running置为false,再次开始接收
        ymodem_running = false
        --ymodem_result置为true,表示接收完成
        ymodem_result=true
        --关闭ymodem,释放资源
        ymodem_close()

    end
    rxbuff:del()
end

local function uart_sent_cb(id)
    log.info("uart", "sent", id) 
end

--开启ymodem接收,主要包括串口初始化,zbuff初始化,ymodem初始化等,初始化完毕之后开始发送'C',等待发送端发送数据
local function ymodem_open()

    --  创建一个缓冲区,大小为1024 + 32,接收数据为1k,32为剩下的协议数据,可能不到32,保险起见,留足够的大小
    rxbuff = zbuff.create(1024 + 32) 

    --  创建一个ymodem处理程序,保存路径为"/",文件名为"save.bin"
    ymodem_handler = ymodem.create("/","save.bin")

    --初始化
    uart.setup(
    uartid,--串口id
    115200,--波特率
    8,--数据位
    1--停止位
    )
    --  监听串口接收事件
    uart.on(uartid, "receive", ymodem_rx) 

    --  监听串口发送事件
    uart.on(uartid, "sent", uart_sent_cb)


    while not ymodem_result do
        --  如果ymodem协议没有在运行,则发送请求;并重置ymodem处理程序
        if not ymodem_running then 
            --YMODEM 传输文件时,接收方会先发送一个字符 'C'来启动传输过程
            uart.write(uartid, "C")
            --发送完之后重置恢复到初始状态
            ymodem.reset(ymodem_handler) 
        end
        sys.wait(500)
    end

end

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

5.3 结果展示

5.3.1 SecureCRT 使用指南:

1.安装好之后打开软件点击 file,选择 connect 按键,右键 Sessions 文件夹,选择 New Session,选择 Serial,选择下一页

2.选择串口,波特率 115200,数据位 8,停止位 1,流控 None,点击下一页,然后点击完成

3.双击刚刚创建的串口,连接串口

5.3.2 1k 文件数据传输:

发送端:在收到模块发送的'C'字符之后,点击 Transfer,选择 Send Ymodem,选择要发送的文件,双击点击,选择 OK,开始发送

发送完成

模组接收端:收到起始帧,开始接收数据

传输完成,接收端打印接收完成信息和文件大小

六、总结

本文演示如何在 Air780EHM 核心板上面,通过 UART1 连接 PC 端的串口调试工具 SecureCRT,进行 Ymodem 协议的文件传输测试。