3、CAN Bootloader架构设计:上位机、下位机、通信协议栈三层架构
好,咱们今天聊点实在的。CAN Bootloader到底该怎么搭?
很多人一上来就写代码,结果写到一半发现上下位机对不上,协议栈乱成一锅粥。我早期也犯过这个错,后来才明白——架构先行。
说白了,一个靠谱的CAN Bootloader,就三层:上位机、下位机、通信协议栈。这三层各司其职,缺一不可。我习惯把它们比作「发件人、收件人、快递规则」。你想想看,没有规则,发件人乱写地址,收件人看不懂包裹,那还怎么玩?
3.1 上位机:升级的发起者
上位机,就是你的PC工具,或者手机APP。它的任务很明确:把固件发下去。
我个人习惯把上位机拆成三个模块:
- 文件解析模块:读入.hex或.bin文件,提取出原始二进制数据。
- 协议封装模块:把数据切成CAN报文能装下的小包,加上序号、校验、命令字。
- 通信调度模块:负责发报文、收应答、超时重传、进度显示。
这里有个坑。我记得有一次,上位机把.hex文件解析错了,地址偏移搞反了,结果下位机把程序写到了错误的位置,板子直接变砖。嗯,从那以后,我每次都会在文件解析后打印一份地址映射表,人工核对一遍。
3.2 下位机:升级的执行者
下位机就是你的MCU,它要完成三件事:接收、校验、写入。
下位机的架构,我一般分成这几层:
- CAN驱动层:负责收发CAN报文,处理硬件中断。说白了就是让CAN口能正常工作。
- 协议解析层:从CAN报文里提取命令、数据、序号,判断是哪个包,有没有丢包。
- Flash驱动层:负责擦除、写入、校验Flash。这是最敏感的一层,写错了板子就废了。
- 状态机管理层:管理整个升级流程——空闲、等待、接收、校验、完成、回滚。
为什么要用状态机?因为CAN通信是不可靠的,报文可能丢、可能乱序。状态机能帮你理清「现在该干什么」。我在项目中遇到过,有人用全局变量做流程控制,结果一个中断进来,变量被改了,流程直接跑飞。从那以后,我坚持用状态机,每个状态只做一件事,做完再切。
3.3 通信协议栈:连接上下位的桥梁
通信协议栈,说白了就是「你们俩怎么说话」。CAN Bootloader的协议栈,我习惯定义成三层:
| 层级 | 功能 | 典型内容 |
|---|---|---|
| 物理层 | CAN 2.0B标准,250kbps或500kbps | CAN ID、DLC、数据场 |
| 传输层 | 分包、组包、序号管理、超时重传 | 包序号、总包数、数据长度 |
| 应用层 | 命令定义、状态反馈、校验机制 | 命令字、状态码、CRC32 |
这里我重点说一下应用层。命令字怎么定义?我一般用这种方式:
// 命令字定义
#define CMD_START 0x01 // 开始升级
#define CMD_DATA 0x02 // 传输数据
#define CMD_END 0x03 // 结束升级
#define CMD_ACK 0x04 // 应答
#define CMD_NACK 0x05 // 否定应答
#define CMD_STATUS 0x06 // 查询状态
每个命令后面跟着数据。比如CMD_DATA,数据场里包含:包序号(2字节)、数据长度(1字节)、有效数据(最多8字节)。
为什么会这样设计?因为CAN报文一帧最多8字节数据。你要传一个几百KB的固件,必须拆成很多小包。每个包都要有序号,不然下位机不知道谁先谁后。
3.4 三层如何协同工作?
我举个例子,你就明白了。
假设你要升级一个固件,大小是100KB。上位机先读入.bin文件,切成12800个包(每个包8字节)。然后开始发:
- 上位机发CMD_START,告诉下位机「我要升级了,总共有12800个包」。
- 下位机收到后,擦除Flash,回复CMD_ACK。
- 上位机开始发CMD_DATA,每个包带序号和数据。
- 下位机收到一个包,校验CRC,写进Flash,回复ACK。
- 如果上位机没收到ACK,就重传这个包。最多重传3次,还不行就报错。
- 所有包发完后,上位机发CMD_END。
- 下位机收到后,对整个固件做一次CRC校验,如果正确,回复ACK,然后跳转到新程序。
你看,整个过程就是三层在配合。上位机负责「发什么」,下位机负责「怎么收」,协议栈负责「怎么传」。
3.5 架构设计的几个原则
最后,我总结几条经验:
- 分层清晰:每一层只做自己的事,不要越界。CAN驱动层不要管什么命令字,协议解析层不要管Flash怎么写。
- 接口标准化:上下位机之间的接口,一旦定下来就不要轻易改。我见过一个项目,协议改了三次,每次都要重新烧录所有设备,那叫一个痛苦。
- 预留扩展:比如命令字里留几个保留位,以后加功能不用改协议。数据场里也留点冗余,万一以后要加时间戳呢?
- 日志要全:上下位机都要有日志输出。出问题的时候,日志就是你的救命稻草。我习惯在每层都加一个调试打印开关,平时关掉,调试时打开。
嗯,三层架构说到这里,基本就清楚了。下一章咱们聊聊具体的协议帧格式设计,那才是真正动手的地方。