第2章:CAN数据帧详解:标准帧与扩展帧、数据帧结构、仲裁机制、位填充规则
大家好,我是你们的BMS嵌入式工程师朋友。今天咱们来啃一块硬骨头——CAN数据帧。说实话,我刚入行那会儿,觉得CAN协议就是发发数据嘛,有啥好学的?直到我在一个项目中,因为没搞懂扩展帧和标准帧的区别,导致ECU之间互相“打架”,排查了整整两天……嗯,从那以后,我再也不敢小看这个数据帧结构了。
2.1 标准帧 vs 扩展帧:到底差在哪?
CAN总线有两种帧格式:标准帧和扩展帧。说白了,它们最大的区别就是——ID的长度不一样。
- 标准帧:11位ID,仲裁场总共12位(11位ID + 1位RTR)。
- 扩展帧:29位ID,仲裁场总共32位(11位基础ID + 18位扩展ID + 1位SRR + 1位IDE + 1位RTR)。
你可能会问:“为什么要搞两种?直接用29位不香吗?”
我个人习惯是:在BMS系统中,如果节点不多(比如少于10个),用标准帧就够了。但如果你在做复杂的多ECU系统,比如整车控制器、BMS、MCU、OBC都在一条总线上,那我建议你用扩展帧。为什么?因为29位ID可以让你更灵活地定义优先级和报文类型。
关键区别表
| 特性 | 标准帧 | 扩展帧 |
|---|---|---|
| ID位数 | 11位 | 29位 |
| 仲裁场长度 | 12位 | 32位 |
| 最大节点数 | 2048个 | 5.3亿个 |
| 数据场长度 | 0-8字节 | 0-8字节 |
| 总线利用率 | 较高 | 较低(帧更长) |
我记得有一次,客户要求所有报文都用扩展帧。我一开始觉得没必要,但后来发现,在UDS诊断中,扩展帧的29位ID可以很好地承载“源地址+目标地址+功能类型”的信息,反而让协议解析变得更清晰了。
2.2 数据帧结构:从SOF到EOF,一个都不能少
一个完整的CAN数据帧,由7个部分组成。我习惯把它想象成一封挂号信:
- SOF(帧起始):1位显性位。相当于信封上的“寄出”戳。
- 仲裁场:ID + RTR位。相当于收件人地址。
- 控制场:IDE + r0 + DLC。相当于信件重量说明。
- 数据场:0-8字节。相当于信的内容。
- CRC场:15位CRC + 1位CRC界定符。相当于防伪封条。
- ACK场:1位ACK Slot + 1位ACK界定符。相当于回执。
- EOF(帧结束):7位隐性位。相当于信封封口。
这里有个坑,我必须要提醒你:数据场长度不是固定的。DLC(数据长度码)可以设置0到8,但实际发送的数据字节数必须和DLC一致。我曾经见过一个同事,DLC设了8,但只发了4个字节的数据,结果接收方一直收不到正确的CRC校验……嗯,这种低级错误,咱们就别犯了。
避坑指南:在BMS中,电池电压和温度数据通常用2字节或4字节表示。如果你用标准帧,8字节数据场可以放4组电压数据。但如果你用扩展帧,同样的数据场长度,帧头却多了18位,总线负载会更高。所以,能标准帧就别用扩展帧,除非你有明确的地址扩展需求。
2.3 仲裁机制:谁先说话,谁就赢
CAN总线的仲裁机制,说白了就是“谁的数字小,谁就赢”。为什么?因为显性位(0)会覆盖隐性位(1)。
你想想看,当两个节点同时发送数据时,它们会逐位比较ID。如果节点A的ID是0x100,节点B的ID是0x200,那么在仲裁场的第1位,A发送0,B发送1,A的显性位就把B的隐性位“压”下去了。B检测到总线上的电平和自己发送的不一致,就知道自己输了,立刻停止发送,转为接收模式。
这个机制有个好处:优先级高的报文永远不会被阻塞。在BMS中,像“电池过压报警”这种紧急报文,我会给它分配一个很小的ID(比如0x001),这样它就能在仲裁中胜出,第一时间被总线传输。
我个人习惯是:
- 紧急报警报文:ID 0x001 - 0x01F
- 周期性状态报文:ID 0x020 - 0x0FF
- 诊断报文:ID 0x100 - 0x7FF
这样做的好处是,即使总线上有大量诊断数据在传输,紧急报警也能“插队”发送。我在一个项目中,就因为没给报警报文分配足够低的ID,导致电池过温时报警延迟了200ms……还好只是测试阶段,不然电池可能就废了。
2.4 位填充规则:为什么CAN要“插队”?
位填充,是CAN协议里一个容易被忽略但极其重要的机制。它的规则很简单:连续发送5个相同电平的位后,必须插入一个相反电平的位。
为什么会这样?因为CAN总线需要靠电平跳变来同步时钟。如果连续发送太多相同的位(比如00000000),接收方的时钟就会漂移,导致采样点偏移,最终误码。
举个例子:如果你要发送0x00(二进制00000000),按照位填充规则,发送完第5个0后,必须插入一个1,变成000001000。接收方收到后,会自动把这个填充位去掉,还原成原始数据。
小技巧:在BMS的CAN报文设计中,尽量避免使用全0或全1的数据。比如,电池电压0x0000表示0V,但实际电池电压不可能为0。如果你非要发0x0000,位填充会让帧长度增加,影响总线效率。我一般会用0xFFFF表示无效数据,或者用0x0001作为最小有效值。
位填充会影响哪些场?
- SOF:不填充(只有1位)
- 仲裁场:填充
- 控制场:填充
- 数据场:填充
- CRC场:填充(CRC界定符除外)
- ACK场:不填充
- EOF:不填充
你可能会问:“位填充会不会导致帧长度不确定?”
答案是:会的。一个标准帧,理论上最短是44位(无数据场),最长会因为位填充而变成108位左右。但别担心,CAN控制器硬件会自动处理位填充的插入和删除,你只需要在软件层面保证数据不会太“极端”就行。
我记得有一次,我在调试一个BMS的CAN通信,发现总线上偶尔会出现错误帧。排查了半天,最后发现是某个节点在发送电池SOC数据时,连续发了8个字节的0x00,导致位填充后的帧长度超过了CAN控制器的FIFO深度……嗯,从那以后,我设计数据时都会刻意避免全0或全1的模式。
2.5 总结:这些知识点怎么用在BMS里?
好了,咱们把今天的内容串起来。在BMS的CAN通信开发中,你需要记住:
- 选标准帧还是扩展帧:节点少、报文类型少,用标准帧;节点多、需要复杂地址映射,用扩展帧。
- 数据帧结构:DLC必须和实际数据长度一致,别偷懒。
- 仲裁机制:紧急报文给最小ID,别让报警信息排队。
- 位填充:避免全0或全1的数据模式,减少总线负载和错误风险。
下一章,咱们会深入CAN的远程帧和错误帧,到时候我会分享一个我当年在BMS项目中遇到的“总线锁死”案例,保证让你印象深刻。
公众号:蓝海资料掘金营,微信deep3321