尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

串口传输协议对比:Xmodem、Ymodem、Zmodem原理与嵌入式实战

发布时间:2026/9/27 1:06:52

资讯中心
01
ARTICLE

串口传输协议对比:Xmodem、Ymodem、Zmodem原理与嵌入式实战

串口传输协议对比:Xmodem、Ymodem、Zmodem原理与嵌入式实战
做嵌入式开发几乎每个人都有过这样的经历板子焊接完、第一次上电Bootloader 也刷进去了接下来最头疼的就是怎么把编译好的固件安安稳稳地弄进设备。这时候你会和 Xmodem、Ymodem、Zmodem 这一组老朋友打上照面。它们诞生于 1970 年代的拨号 BBS 时代却在今天依然是串口传输事实上的通用标准。这篇文章不打算停留在概念科普而是要把三种协议的原理、帧格式、嵌入式端的落地代码以及一轮真实的传输效率测试全部放出来。对于刚入门嵌入式开发的读者你可以照着用、照着选不至于在 Bootloader 和烧录工具之间来回折腾对于正在做量产固件升级方案的老手也希望里面的实测数据和踩坑记录能给你一些参考。1. 先搞清楚这三种协议各自解决什么问题1.1 从拨号时代走进嵌入式开发的“老三样”Xmodem 的诞生要追溯到 1977 年发明人 Ward Christensen 最初的目的是在两个计算机之间通过电话线和调制解调器传文件。那个年代没有图形界面的拖拽传输终端上敲命令是常态于是控制字符 SOH、ACK、NAK、EOT 就构成了最早的可靠文件传输协议。它的设计思路极其简单发送方一次发一个 128 字节的小块接收方收对了回 ACK收错了回 NAK整个文件发完后发 EOT接收方回 ACK传输结束。这种“发一个等一个、等不到就重发”的停等机制放到今天看非常原始但好处同样突出极端可靠接收端逻辑可以简化到几十行 C 代码。正是这个特点让很多嵌入式 Bootloader 到今天都把 Xmodem 作为必选项。你在用某些芯片厂商的官方烧录工具时背后跑的很可能就是这套协议。Ymodem 则是 Chuck Forsberg 在 1980 年代对 Xmodem 的升级核心变化有两个一是数据块从固定 128 字节扩展为可选的 1024 字节二是引入了文件头块可以携带文件名、文件大小、时间戳这些元数据并且支持一次传输多个文件。因为这一点Ymodem 特别适合嵌入式设备固件升级的场景——接收端拿到文件头就能知道当前传的是哪个文件、总共多少字节也就能决定数据写入 flash 的哪个分区、什么时候结束。Zmodem 同样出自 Chuck Forsberg 之手1986 年发布。它在协议层换了一套思路不再一问一答地停等而是用滑动窗口做连续传输还支持断点续传。链路中断后重新连接不用从文件头重传接收端告诉发送端已经收到多少字节发送端从对应位置续传即可。对几十 MB 的系统镜像或者不稳定链路下的传输这点尤其宝贵。1.2 一张表速览三兄弟的核心差异协议推出时间数据块大小传输模式文件元数据断点续传接收端实现难度典型嵌入式场景Xmodem1977128 字节可选 1K停等不支持不支持极低Bootloader 极简升级Ymodem1980s128/1024 字节停等支持不支持中主流固件升级Zmodem1986可变最高 1024滑动窗口支持支持高高可靠远距离传输看到这张表你就明白为什么协议选型总是老生常谈但必须认真对待。表面看三者都是“串口传文件”但 Ymodem 多出来的文件名和大小信息能省掉接收端一大堆硬编码Zmodem 的断点续传在弱信号、易抖动的链路里又是救命级功能。代价也很直接Zmodem 的实现复杂度远超前两者在嵌入式场景想手写一个稳定可用的 Zmodem 非常吃力。2. 协议原理拆解帧格式、传输流程与关键机制2.1 Xmodem教科书式的停等协议Xmodem 的帧结构核心由三部分组成帧头 块号/块号反码 数据/校验。早期版本用 1 字节校验和后来扩展出 CRC16 校验接收端在发起传输时发送字符 C0x43表示请求 CRC16发送 NAK0x15则代表请求 Checksum。SOH 是 0x01表示后面跟 128 字节数据。STX 是 0x02表示后面跟 1024 字节数据也就是常说的 Xmodem-1K。块号从 1 开始单字节计数到 255 之后回绕到 0块号反码用于快速检错。整个流程可以归纳为发送方启动后等待接收端的 C 或 NAK收到后开始发第一块。接收端收到完整块后做 CRC16 校验正确则回 ACK错误则回 NAK。发送方收到 ACK 就发下一块收到 NAK 重发当前块连续收到多次失败信号后主动取消。文件数据全部发完后发送方发 EOT接收端回 ACK传输结束。Xmodem 的帧格式并不复杂实际调试中最容易出问题的地方反而是字节计数。一帧 128 字节模式下总长度是 1 个 SOH 1 个块号 1 个块号反码 128 字节数据 1 字节校验也就是说一个块总长 132 字节。而 1024 字节模式是 1 个 STX 2 字节块号相关 1024 字节数据 2 字节 CRC16总长 1029 字节。计算效率时这些开销都要算进去不能简单用文件大小除以波特率。这种“发一个等一个”的模式在链路往返时间较大时会非常吃亏。本文第 4 章的实测部分会直观展示 RTT 对 128 字节小块的致命影响这里先埋个伏笔。2.2 Ymodem经典协议里的“增强包”Ymodem 在链路层机制上和 Xmodem 几乎一致也是停等、CRC16、ACK/NAK 重传。它真正聪明的设计是第一个编号为 0 的数据块。发送方在正式文件数据之前会先发一个 128 字节的块块号 0数据内容就是文件名 \0 文件大小字符串 空格 时间戳 空格 文件属性 \0。接收端收到 0 号块后就能拿到文件元信息。比如 app.bin\0 后面跟 1048576 1654321000 0\0。拿到这些信息Bootloader 就可以动态规划缓冲区或者按文件名决定写入 flash 的哪个分区。很多量产方案用 Ymodem 做升级就是看中了这个能力。更进一步Ymodem 还支持批量传多个文件传完第一个文件发 EOT 后接收端回 ACK发送方再发下一个 0 号块直到最终发送一个数据长度为零的 0 号块作为结束。仍然沿用停等机制是 Ymodem 的一个明显短板因此它的速度瓶颈和 Xmodem 类似。唯一的区别是单块数据量大了 8 倍协议开销占比降低在相同 RTT 下整体耗时要比 Xmodem 128B 好不少。2.3 Zmodem把效率卷到极致的进阶协议Zmodem 的协议复杂度比前两者高一个数量级。它定义了完整的会话状态机帧类型一大堆ZRQINIT、ZRINIT、ZFILE、ZSINIT、ZDATA、ZACK、ZFIN 等。传输数据时先协商子包大小128 到 1024 字节不等然后以类似滑动窗口的方式连续发送多个包接收端不必逐个 ACK而是可以延迟、累积确认这也是它能跑满链路的主要原因。Zmodem 另一个杀手级功能是断点续传。文件传输中断后接收端会记录已经写入 flash 或文件的位置重新建立会话时把偏移量告诉发送端发送端直接从这个偏移继续发已传部分不再重传。对几十 MB 的镜像传输或者不稳定的无线串口场景这个能力基本无可替代。Zmodem 还专门处理二进制数据的转义保证所有控制字符在链路层传输时不会干扰状态机。带来的好处是鲁棒性代价是传特殊字节时实际要多发一个字节有效吞吐率会有一点折损。Zmodem 的实现公认比较绕最常见的开源实现是 lrzsz这也是我后续测试中直接采用的方案。有一点需要提前说清楚不要因为 Zmodem 名头响就无脑选它嵌入式资源受限时它未必是最好选择后面实测部分我会展开讲。3. 嵌入式落地接收端代码怎么写、移植怎么搞3.1 先定方案自研还是移植开源库做嵌入式产品在 Xmodem 和 Ymodem 上我建议自己写。原因很简单Xmodem/Ymodem 核心逻辑不过两三百行而且 Bootloader 场景里通常只需要接收端不需要发送端。自研可以做到内存占用完全可控、没有死代码同时把超时策略和产品业务深度绑定。与其引入一个庞大协议栈不如把这个部分握在自己手里。Zmodem 则相反我极其不推荐手写。它的状态机、转义机制、窗口管理、断点续传逻辑没有大几百行甚至上千行 C 代码下不来而且难点在于边界情况极多——半包、重复包、假 ZPAD、中断恢复每一个都能让你调上一段时间。如果嵌入式平台资源允许优先移植 lrzsz 或裁剪后的 Zmodem 库如果资源吃紧我建议用 Ymodem 替代而不是冒险硬上 Zmodem。做技术选型时要清醒认识一个道理协议功能越强维护成本越高对产品长期迭代来说不是越多越好。3.2 一个最小 Xmodem 接收端状态机参考实现下面给出一段接收端核心逻辑。为了突出重点只展示状态机框架不包含完整 CRC 表。实际使用时可以把串口字节逐个喂给xmodem_rx_poll返回值表示当前状态。/* Xmodem 接收端状态机核心框架 */ #define RX_WAIT_BLOCK 0 #define RX_BLOCK_NO 1 #define RX_BLOCK_INV 2 #define RX_DATA 3 #define RX_CRC_H 4 #define RX_CRC_L 5 #define XMODEM_SOH 0x01 #define XMODEM_STX 0x02 #define XMODEM_EOT 0x04 #define XMODEM_ACK 0x06 #define XMODEM_NAK 0x15 #define XMODEM_CAN 0x18 #define RX_BUSY 0 #define RX_DONE 1 #define RX_CANCEL 2 static uint8_t state RX_WAIT_BLOCK; static uint8_t pkt_len; static uint8_t blk_no; static uint16_t idx; static uint8_t crc_hi, crc_lo; int xmodem_rx_poll(uint8_t byte, uint8_t *buffer) { switch (state) { case RX_WAIT_BLOCK: if (byte XMODEM_SOH) { pkt_len 128; state RX_BLOCK_NO; } else if (byte XMODEM_STX) { pkt_len 1024; state RX_BLOCK_NO; } else if (byte XMODEM_EOT) { uart_send_byte(XMODEM_ACK); return RX_DONE; } else if (byte XMODEM_CAN) { return RX_CANCEL; } break; case RX_BLOCK_NO: blk_no byte; state RX_BLOCK_INV; break; case RX_BLOCK_INV: if ((blk_no ^ byte) ! 0xFF) { state RX_WAIT_BLOCK; uart_send_byte(XMODEM_NAK); /* 头校验失败请求重发 */ } else { idx 0; state RX_DATA; } break; case RX_DATA: buffer[idx] byte; if (idx pkt_len) { state RX_CRC_H; } break; case RX_CRC_H: crc_hi byte; state RX_CRC_L; break; case RX_CRC_L: if (crc16_check(buffer, pkt_len, (crc_hi 8) | crc_lo)) { uart_send_byte(XMODEM_ACK); /* 这里把 buffer 中的 pkt_len 字节写入 flash/文件 */ } else { uart_send_byte(XMODEM_NAK); /* 数据错误请求重发当前块 */ } state RX_WAIT_BLOCK; break; } return RX_BUSY; }这段代码要配合串口中断或 DMA 使用每个字节触发一次状态迁移。真实的工程实现还必须注意几点接收端在启动时要发一次 C 或 NAK 通知发送方可以开始每个块之间要有超时机制连续收到多次错误要主动放弃传输。示例代码简化了 buffer 写入位置实际项目里请按块号计算 flash 偏移而不是简单地从起始位置覆盖。3.3 从 Xmodem 到 Ymodem需要多做的几件事如果你已经调通 Xmodem升级到 Ymodem 的工作量没有想象中多。首先要处理的是 0 号块。收到 SOH 块号 0 时不能把它当普通数据块写 flash而要解析文件头typedef struct { char file_name[64]; uint32_t file_size; } ymodem_hdr_t; int ymodem_parse_hdr(const uint8_t *blk0, ymodem_hdr_t *hdr) { uint16_t i 0, j 0; while (i 128 blk0[i] ! \0 j sizeof(hdr-file_name) - 1) { hdr-file_name[j] blk0[i]; } hdr-file_name[j] \0; if (i 128) { return -1; /* 无文件名可能是结束块 */ } i; /* 跳过 \0 */ j 0; char size_buf[16] {0}; while (i 128 blk0[i] ! blk0[i] ! \0 j sizeof(size_buf) - 1) { size_buf[j] blk0[i]; } hdr-file_size strtoul(size_buf, NULL, 10); return (hdr-file_size 0) ? 0 : 1; /* 1 表示结束块 */ }这里最容易踩的坑是文件头块之后的数据块编号从 1 开始但文件名和文件大小字符串之间的分隔符并不固定。规范里说是空格但某些上位机工具会省略时间戳有的只发文件名有的会把时间字段也带上。解析时不要死板地把字段顺序写死宁可先按“文件名截断 整串数字截取”来实现。实测中SecureCRT、Xshell、lrzsz 生成的 Ymodem 首块格式都有差异兼容性处理是必须的否则换一个终端软件就传不了文件。3.4 移植要点缓冲区、超时、内存开销接收端缓冲区建议做成环形队列 状态机的形式。串口中断只负责把字节放进环形队列主循环从队列取字节喂协议状态机。这样做的好处是协议逻辑不需要在中断上下文执行避免长临界区影响系统实时性。缓冲区大小至少能容纳一个最大数据块1024 字节加少量余量。如果芯片 RAM 紧张128 字节的 Xmodem 模式更省内存但传输效率会下降这个权衡要在系统设计阶段就定下来。超时策略在协议移植中往往是被忽视的一块。Xmodem/Ymodem 的经典约定是接收端初始发 C 等待发送方之后每一块接收也都有超时。超时时间要根据波特率计算115200 波特率下 1024 字节帧传输约 90ms超时设 1 到 2 秒比较保险9600 波特率下 1024 字节帧传输约 1.1 秒超时至少给到 5 秒以上。很多传输失败不是协议写错而是超时设太短发送方还在发送 1024 字节数据期间接收端就宣布超时并进入重排状态。4. 传输效率测试同一台设备上的实测对比4.1 测试环境与测试方法设计我做了一组不算复杂但足够说明问题的实测。测试对象是 STM32F407 开发板说实话协议测试对 MCU 性能不敏感任何支持串口的板子都可以复现。电脑端通过 USB 转串口连接开发板。上位机工具分别用了 SecureCRT 和 Ubuntu 下的 minicom lrzsz避免单一工具带来的偶然性。测试文件是一个 1 MiB1048576 字节的随机 bin 固件镜像。波特率统一 115200、8N1、无流控。每种协议测 3 次取最优值排除首次缓存和系统调度干扰。另外我做了一组“模拟较差链路”的对比测试在发送脚本里人为增加每帧确认前 50ms 的延时用来观察 RTT 对三种协议的影响差异。这样设计是为了回答一个现实问题在不同链路质量下协议选择到底能带来多少差距。4.2 实测数据全记录协议块大小理论极限s实测s实测吞吐率KB/s备注Xmodem128B94.097.510.5块多ACK 等待累计明显Xmodem-1K1024B91.593.211.0协议开销极小Ymodem1024B91.693.910.9多了文件头块与结束交互Zmodem1024B91.592.711.1滑动窗口几乎没有确认等待这里的“理论极限”是纯字节数除以有效带宽115200bps、8N1 下有效速率是 11520 字节/秒然后把文件自身和协议帧开销一起算进去。可以看到在 115200 这个常用波特率下三个协议的实际差距在 5% 以内主要瓶颈是波特率本身。Xmodem 128B 多出的时间几乎全部来自 8192 个块每块一次的 ACK 往返以及小帧的协议字节占比。再看模拟高 RTT 的对比每块确认前增加 50ms 延时协议实测s相对常规链路多出Xmodem 128B506约 410s几乎全花在等待Xmodem-1K143约 50sYmodem 1K144约 50sZmodem94基本不受影响这个结果很直观128B 停等协议在 50ms RTT 下几乎被拍死而 Zmodem 的滑动窗口几乎无视 RTT。所以如果项目运行在无线串口、蓝牙透传、TCP 转串口这类链路上Xmodem 128B 基本可以直接排除。4.3 测试结论选协议不能只看“快”从测试可以得出第一个结论在 115200 这类常规波特率、RTT 极低的本地串口链路上三种协议的绝对速度差别不大选型时应该优先看功能而不是效率。你要在传输文件的同时带上文件名和大小Ymodem 就是合理选择你的 Bootloader 只要最小编译体积Xmodem 128B 完全够用。第二个结论是一旦链路 RTT 增大或者波特率进一步降低Xmodem 128B 的劣势会急剧放大。在蓝牙串口、4G 串口服务器这类链路里优先考虑 Zmodem 或至少 Ymodem 1K。永远不要在 RTT 高的链路上用 Xmodem 128B 传大文件这是我自己踩过坑之后最想先说的一句话。第三个容易被忽略的结论是Zmodem 的转义机制会带来额外字节开销。本次测试文件恰好全是随机字节如果数据里大量出现需要转义的字节Zmodem 实际传输的字节数可能比 Xmodem 还高。“Zmodem 必然最快”是个误区它最快的主因是少等待不是少传字节。做效率评估时一定要结合数据内容特征来判断。5. 来自一线的避坑指南与问题排查5.1 传输必败的经典原因先说最常见的两种失败一种是接收端发完 C 后上位机没反应另一种是传了一小半就报错中断。前者大概率是串口参数不匹配波特率、校验位、流控有一项不一致直接表现就是对方不响应。后者往往是 USB 转串口芯片或线缆质量导致丢字节CRC 校验频繁失败上位机默认重试次数耗尽后主动取消了传输。第三个高频坑是文件传输工具做了换行符转换。很多终端软件默认开启 CR/LF 转换传文本文件没问题传二进制固件就破坏数据。SecureCRT 里传二进制文件务必确认 Binary 模式Xshell 也有类似选项minicom 里要关闭本地回显和 CR/LF 转换。这个坑的隐蔽性在于小文件可能侥幸成功大文件几乎必炸。第四个容易忽略的问题是硬件流控。RS232 电平设备经常启用 RTS/CTS但 USB 转 TTL 模块很多不支持真实的硬件流控。两边设置不一致时数据可能被吞掉或者发送方被 CTS 信号阻塞。线上调试优先统一为“无流控”链路紧张时再考虑 RTS/CTS这样能排除掉一大批奇怪问题。5.2 排查步骤与调试技巧我的排查顺序通常是这样先看波形再看重传最后看超时。波形层面用逻辑分析仪抓 TX 和 RX 引脚确认帧头 SOH/STX、块号、校验位在物理链路上是完好的。如果波形就缺字节那就要换线、换 USB 转串口模块、或者降波特率问题基本能找到根因。如果波形正常但上位机显示失败就打开上位机的详细日志模式。SecureCRT 有协议日志lrzsz 可以用 verbose 参数启动看是哪一步超时、哪一块在反复重传。稍有一个技巧做一次回环测试把设备端的 TX 和 RX 短接然后上位机用同一个协议发送文件。如果设备能自己收自己发的文件说明链路底层没问题问题一定在接收端状态机或者 flash 写入逻辑。这个技巧我在每次调试 Bootloader 时都会先用能省掉大量无效排查时间。调试时还要留意块号回绕。块号是单字节255 之后回 0。有的实现里计数变量是 int但写入帧时却按 uint8_t 处理传大文件到 255 号块后校验必然失败。怀疑到这个点时用 300KB 左右的文件就能很快触发验证不用等到整个 1MiB 传完。根据我个人经验量产的 Bootloader 用 Ymodem 是性价比最高的选择文件名和大小信息能实现板端与产线工具的灵活匹配代码量可控兼容性也好。调试阶段的极简烧录则用 Xmodem-1K代码少、逻辑直观。至于维护期的远程升级链路只要硬件资源允许就上 Zmodem 的移植库把断点续传当作保底。协议没有绝对的好坏关键还是把自己的链路 RTT、波特率和资源预算想清楚再对着帧格式把状态机做扎实希望这篇对比和实测数据能帮你少走几步弯路。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。