4. 烧录协议帧结构:帧头、帧尾、长度域、命令域、数据域、校验域的设计原则

各位同学,咱们今天聊点实在的。烧录协议的核心,说白了就是怎么把数据包从主机端安全、可靠地送到芯片里。你想想看,串口烧录不像网络通信那么复杂,但正因为简单,反而容易在细节上翻车。我这些年调试过的烧录协议少说也有十几种,踩过的坑能写满一个笔记本。今天就把这些经验掰开揉碎了讲给你们听。

4.1 帧结构的基本组成

一个标准的烧录协议帧,通常包含六个部分:帧头、长度域、命令域、数据域、校验域、帧尾。嗯,这里要注意,不是所有协议都把这六个域凑齐,但核心逻辑是一样的。

我个人习惯把帧结构画成一条流水线:

+--------+--------+--------+----------+--------+--------+
| 帧头   | 长度   | 命令   | 数据     | 校验   | 帧尾   |
| (1-2B) | (1-2B) | (1B)   | (N字节)  | (1-2B) | (1-2B) |
+--------+--------+--------+----------+--------+--------+

这张图看着简单,但每个域的设计都有讲究。咱们一个一个拆开说。

4.2 帧头设计原则

帧头的作用就一个:告诉接收端「我要开始发数据了」。但怎么设计这个「开始信号」,里面门道不少。

  • 固定值原则:帧头通常用0xAA、0x55、0x7E这类特殊值。为什么?因为这些值在二进制里是01010101或10101010,不容易被噪声干扰。我在项目中遇到过用0x00做帧头的,结果芯片上电时全是0,直接误触发,那叫一个惨。
  • 多字节帧头:有些协议用两个字节做帧头,比如0xAA 0x55。这样能进一步降低误判概率。你想想看,随机噪声连续出现两个特定字节的概率有多低?
  • 帧头与数据冲突:如果数据里恰好出现了帧头值怎么办?这就引出了转义机制。我建议在数据域里遇到帧头值时,自动插入一个转义字节,接收端再把它还原回去。
我的经验: 帧头别用0xFF。为什么?因为串口空闲时电平就是高,0xFF容易被误判为帧头。我曾经在这个问题上折腾了两天,最后发现是示波器上看到的「数据」其实是空闲电平。

4.3 长度域设计

长度域告诉接收端「这次要发多少数据」。看似简单,但长度域的设计直接影响协议的健壮性。

  • 长度域位置:我习惯把长度域放在帧头后面、命令域前面。这样接收端收到帧头后,立刻就能知道要准备多大的缓冲区。
  • 长度域大小:烧录协议的数据域通常不大,1个字节(最大255字节)基本够用。但如果你的固件很大,可能需要2个字节。我记得有一次做OTA升级,固件超过256KB,长度域用了3个字节才搞定。
  • 长度域包含什么:这里有个容易混淆的点——长度域到底包含哪些部分?有的协议只算数据域,有的把命令域也算进去,还有的把校验域也算上。我个人建议只算数据域长度,这样最清晰。
避坑指南: 我曾经见过一个协议,长度域把自己也算进去了。结果接收端解析时,长度值比实际数据多了一个字节,导致最后一位数据被当成校验域处理。这种设计千万别学。

4.4 命令域设计

命令域就是告诉芯片「你要干什么」。烧录协议里常见的命令有:擦除、写入、读取、校验、跳转等。

  • 命令编码:用1个字节就够了,0x01表示擦除,0x02表示写入,0x03表示读取。别用太复杂的编码,调试时看着头疼。
  • 命令与数据的关系:有些命令不需要数据域,比如擦除命令。但为了协议统一,我建议即使数据域为空,也保留长度域为0。这样解析逻辑更简单。
  • 扩展性:预留一些命令码给未来使用。我吃过这个亏——协议发布后想加一个新功能,结果命令码全用完了,只能打补丁。

4.5 数据域设计

数据域是帧里最灵活的部分,长度可变。烧录时,数据域里放的就是固件的二进制内容。

  • 数据对齐:有些芯片要求数据按4字节对齐,否则写入会出错。我建议在协议层就处理好对齐,而不是依赖驱动层去补零。
  • 数据分片:固件通常很大,不能一次性发送。需要把固件切成多个帧,每帧带一个偏移地址。这个地址可以放在数据域的开头,也可以单独用一个域。
  • 数据填充:如果最后一帧数据不足,需要填充0xFF或0x00。我个人习惯填充0xFF,因为Flash擦除后的默认值就是0xFF。

4.6 校验域设计

校验域是协议的「安全锁」。串口通信受干扰时,数据可能出错,校验域能帮你发现这些问题。

  • 常见校验方式:
    • 累加和:最简单,把所有字节加起来取低8位。但碰撞概率高,我一般不用。
    • CRC8/CRC16:推荐使用。CRC16的碰撞概率极低,适合烧录协议。我在一个量产项目中用CRC16,跑了三年没出过校验错误。
    • 异或校验:比累加和好一点,但不如CRC可靠。
  • 校验域位置:我建议放在数据域后面、帧尾前面。这样接收端可以先收完所有数据,再统一做校验。
  • 校验范围:通常校验从帧头之后开始,到数据域结束。帧头和帧尾一般不参与校验,因为它们是固定值。
重要提醒: 校验域只能检测错误,不能纠正错误。如果校验失败,接收端应该回复NAK,让发送端重传。千万别自作聪明搞纠错,烧录协议不是通信协议,重传是最可靠的方案。

4.7 帧尾设计

帧尾的作用和帧头相反——告诉接收端「数据发完了」。帧尾通常也用固定值,比如0x55、0x0D 0x0A(回车换行)。

  • 帧尾与帧头不同:帧头和帧尾的值最好不一样。如果都用0xAA,接收端可能分不清是帧头还是帧尾。
  • 帧尾的辅助作用:有些协议用帧尾做二次校验。比如帧尾固定为0x0D 0x0A,如果接收端收到0x0D 0x00,就知道数据肯定出问题了。

4.8 常见帧格式对比

下面这张表是我整理的一些常见烧录协议帧格式,大家可以对比着看:

协议名称 帧头 长度域 命令域 数据域 校验域 帧尾
ST Bootloader 0x7E 1字节 1字节 N字节 异或校验 无
NXP ISP 0xAA 0x55 2字节 1字节 N字节 CRC16 0x0D 0x0A
自定义简易协议 0xAA 1字节 1字节 N字节 累加和 0x55
Modbus RTU 无 隐含 1字节 N字节 CRC16 无

从这张表能看出什么?ST的Bootloader没有帧尾,靠超时判断结束;NXP的ISP用了双字节帧头和帧尾,可靠性更高;Modbus RTU干脆没有帧头和帧尾,靠时间间隔区分帧。没有绝对的好坏,只有适不适合你的场景。

4.9 帧结构设计的总原则

说了这么多,最后总结几条我自己的设计原则:

  1. 简单优先:能用1个字节解决的,别用2个。烧录协议不是网络协议,越简单越不容易出错。
  2. 容错设计:帧头帧尾用特殊值,校验用CRC16,长度域要合理。这三条做好了,99%的通信问题都能避免。
  3. 调试友好:协议设计时就要考虑调试。比如帧头用0xAA 0x55,你在串口助手里一眼就能看出来。用0x00 0x00?调试时眼睛都看瞎了。
  4. 预留扩展:命令码留几个空位,长度域留一点余量。别问我为什么知道,问就是改过三次协议。
一个小技巧: 设计帧结构时,先在纸上画一遍收发流程。发送端怎么组帧?接收端怎么解析?每一步都写清楚。我每次设计新协议都这么干,能发现很多隐藏问题。

好了,帧结构的设计原则就讲到这里。下一节我们会聊具体的烧录流程,到时候会用到今天讲的这些知识。记住,好的帧结构是烧录协议成功的一半。

烧录协议帧结构设计核心逻辑 完整帧结构 帧头 长度域 命令域 数据域 校验域 帧尾 五大设计原则 简单优先 能用1B不用2B 容错设计 CRC16 + 特殊值 调试友好 一眼能看出帧结构 预留扩展 命令码留空位 统一规范 解析逻辑一致 常见陷阱 帧头用0xFF(空闲电平) 长度域把自己算进去 没有预留命令码 好的帧结构 = 简单 + 可靠 + 易调试 + 可扩展

可可精品资料库