4. 通信协议基础
刷写数据流设计,说白了就是解决一个问题:怎么把几十兆的二进制文件,安全、可靠、高效地塞进ECU里。
我刚开始做刷写时,以为就是把文件一股脑发过去。结果第一次台架测试就挂了——CAN总线直接爆满,ECU那边也报缓冲区溢出。嗯,从那以后我才真正重视起数据流设计。
这一章,咱们就聊聊数据分段、块大小权衡,还有CRC校验这些实战细节。
4.1 数据分段:为什么不能一把梭?
ECU的RAM有限,Flash写入也有最小单位。你想想看,一个2MB的刷写文件,如果一次性发过去,ECU根本接不住。
所以,刷写数据必须分段。常见的分段策略分三层:
| 层级 | 名称 | 典型大小 | 说明 |
|---|---|---|---|
| L1 | 刷写文件 | 1~64 MB | 完整的二进制镜像,如 .hex、.s19、.bin |
| L2 | 数据块 (Block) | 1~256 KB | 按Flash扇区或逻辑分区切分,每个块独立校验 |
| L3 | 传输段 (Segment) | 4~4096 字节 | 单次CAN/UDS消息承载的数据量,受MTU限制 |
我个人习惯,在UDS 14229-1标准下,用0x34 (RequestDownload) 和 0x36 (TransferData) 服务来实现分段传输。每个块对应一次0x34请求,块内的每个段对应一次0x36请求。
核心原则: 块的大小 = Flash擦除单位(Sector)的整数倍。这样能避免跨扇区校验带来的复杂度。
4.2 传输机制:从块到段的拆解
数据分段之后,怎么传?我画了一张流程图,你看一眼就明白了。
流程其实不复杂:
- 先发0x34:告诉ECU我要写一个块,块的大小、起始地址、CRC算法都在这条消息里。
- 循环发0x36:把块切成M个段,一段一段发。每发一段,ECU回一个响应,确认接收成功。
- 最后发0x37:块传完了,ECU做一次CRC校验,确认完整性。
实战技巧: 我建议每个0x36消息里带一个段序号(BlockSequenceCounter)。有一次我在项目里遇到丢包,ECU靠这个序号发现中间少了一段,直接要求重传,省了整块重来的时间。
4.3 块大小与传输时间:一个权衡游戏
块大小怎么定?这其实是个权衡。我列个表,你一看就懂。
| 块大小 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 小 (1~4 KB) | 出错重传代价低,RAM占用小 | 握手次数多,总传输时间长 | 低速CAN (125 kbps),小RAM ECU |
| 中 (16~64 KB) | 平衡传输效率和重传代价 | 需要中等RAM缓冲区 | CAN FD (500 kbps),主流ECU |
| 大 (128~256 KB) | 握手次数少,总传输时间短 | 出错重传代价高,RAM压力大 | 高速CAN FD / DoIP,大RAM ECU |
我做过一个实际测算:
- CAN 2.0,500 kbps,每帧8字节有效数据,帧间隔+协议开销约1.2ms。
- 一个64 KB的块,需要发8192帧,纯传输时间约9.8秒。
- 如果块大小改成4 KB,同样数据量要发16个块,握手次数多了15次,总时间变成11.2秒。
你看,块越大,传输越快。但代价是什么?
注意: 块越大,ECU需要的RAM缓冲区就越大。我曾经遇到一个项目,ECU的RAM只有8 KB,我硬塞了一个16 KB的块,结果ECU直接报内存溢出。后来改成4 KB块,问题解决。
所以,块大小的上限 = ECU的可用RAM / 2。留一半给协议栈和中断处理。
4.4 CRC校验:完整性保护的最后一关
数据传完了,怎么保证没出错?CRC校验就是干这个的。
UDS标准里,0x37服务会带一个CRC值。ECU收到所有段后,自己算一遍CRC,跟发过来的比对。一致,就写Flash;不一致,就报错,要求重传整个块。
常用的CRC算法有几种:
| 算法 | 位宽 | 多项式 | 检测能力 | 我推荐 |
|---|---|---|---|---|
| CRC-8 | 8 bit | 0x07 | 一般,适合小数据块 | 不推荐,碰撞概率高 |
| CRC-16 | 16 bit | 0x8005 | 较好,适合CAN刷写 | 常用,性价比高 |
| CRC-32 | 32 bit | 0x04C11DB7 | 极强,适合DoIP/大文件 | 高速刷写首选 |
我个人习惯,在CAN刷写场景下用CRC-16。为什么?
- CRC-8太弱,我曾经在测试中发现,随机翻转2个bit,CRC-8有1/256的概率检测不出来。
- CRC-32虽然强,但计算开销大,在低速MCU上会拖慢刷写速度。
- CRC-16在检测能力和计算速度之间取得了很好的平衡。
避坑指南: 我曾经遇到一个坑——刷写工具和ECU用的CRC初始值不一样。工具用0xFFFF,ECU用0x0000,结果每次校验都失败。排查了两天才发现。所以,CRC的初始值、多项式、输出异或值,必须在刷写规范里写死。
4.5 实战中的完整性保护策略
除了CRC,我还会在刷写流程里加几道保险:
- 块级CRC:每个块传完后,0x37里带CRC,ECU校验。
- 文件级CRC:整个刷写文件在工具端算一个CRC,放在刷写文件的末尾或头部。ECU刷完后,再算一次,双重确认。
- Flash写入后回读:ECU写完一个扇区后,读出来跟原始数据比对。虽然慢,但最可靠。
你想想看,如果只靠块级CRC,万一ECU在写Flash时掉电,数据写到一半,CRC校验通过了但数据不完整,怎么办?文件级CRC和回读机制就是兜底的。
我的经验: 在量产刷写中,我一般用块级CRC + 文件级CRC的组合。回读太耗时,只在研发阶段用。量产时,文件级CRC已经能覆盖掉电场景了。
4.6 小结一下
刷写数据流设计,核心就三件事:
- 分段:文件→块→段,三层结构,适配ECU的RAM和Flash特性。
- 权衡:块大小影响传输时间和重传代价,根据总线速度和ECU RAM来定。
- 校验:CRC-16是性价比之选,块级+文件级双重校验保平安。