4. 烧录协议帧结构:帧头、帧尾、长度域、命令域、数据域、校验域的设计原则
各位同学,咱们今天聊点实在的。烧录协议的核心,说白了就是怎么把数据包从主机端安全、可靠地送到芯片里。你想想看,串口烧录不像网络通信那么复杂,但正因为简单,反而容易在细节上翻车。我这些年调试过的烧录协议少说也有十几种,踩过的坑能写满一个笔记本。今天就把这些经验掰开揉碎了讲给你们听。
4.1 帧结构的基本组成
一个标准的烧录协议帧,通常包含六个部分:帧头、长度域、命令域、数据域、校验域、帧尾。嗯,这里要注意,不是所有协议都把这六个域凑齐,但核心逻辑是一样的。
我个人习惯把帧结构画成一条流水线:
+--------+--------+--------+----------+--------+--------+
| 帧头 | 长度 | 命令 | 数据 | 校验 | 帧尾 |
| (1-2B) | (1-2B) | (1B) | (N字节) | (1-2B) | (1-2B) |
+--------+--------+--------+----------+--------+--------+
这张图看着简单,但每个域的设计都有讲究。咱们一个一个拆开说。
4.2 帧头设计原则
帧头的作用就一个:告诉接收端「我要开始发数据了」。但怎么设计这个「开始信号」,里面门道不少。
- 固定值原则:帧头通常用0xAA、0x55、0x7E这类特殊值。为什么?因为这些值在二进制里是01010101或10101010,不容易被噪声干扰。我在项目中遇到过用0x00做帧头的,结果芯片上电时全是0,直接误触发,那叫一个惨。
- 多字节帧头:有些协议用两个字节做帧头,比如0xAA 0x55。这样能进一步降低误判概率。你想想看,随机噪声连续出现两个特定字节的概率有多低?
- 帧头与数据冲突:如果数据里恰好出现了帧头值怎么办?这就引出了转义机制。我建议在数据域里遇到帧头值时,自动插入一个转义字节,接收端再把它还原回去。
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可靠。
- 校验域位置:我建议放在数据域后面、帧尾前面。这样接收端可以先收完所有数据,再统一做校验。
- 校验范围:通常校验从帧头之后开始,到数据域结束。帧头和帧尾一般不参与校验,因为它们是固定值。
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个字节解决的,别用2个。烧录协议不是网络协议,越简单越不容易出错。
- 容错设计:帧头帧尾用特殊值,校验用CRC16,长度域要合理。这三条做好了,99%的通信问题都能避免。
- 调试友好:协议设计时就要考虑调试。比如帧头用0xAA 0x55,你在串口助手里一眼就能看出来。用0x00 0x00?调试时眼睛都看瞎了。
- 预留扩展:命令码留几个空位,长度域留一点余量。别问我为什么知道,问就是改过三次协议。
好了,帧结构的设计原则就讲到这里。下一节我们会聊具体的烧录流程,到时候会用到今天讲的这些知识。记住,好的帧结构是烧录协议成功的一半。
可可精品资料库