3、CAN数据帧详解:标准帧与扩展帧、仲裁场、控制场、数据场、CRC场、应答场
好,咱们今天来啃一块硬骨头——CAN数据帧的结构。
说实话,我刚入行那会儿,看着CAN帧的各个场次,脑袋也是嗡嗡的。什么SOF、仲裁场、控制场……感觉像在看天书。但后来我发现,只要你搞懂了一个数据帧是怎么从发送节点跑到接收节点的,CAN总线那点事儿,基本就通了七七八八。
今天我就带你把这帧结构拆开揉碎,咱们一个一个场次地过。
3.1 标准帧 vs 扩展帧:到底差在哪?
CAN总线有两种帧格式:标准帧和扩展帧。说白了,它们最大的区别就是——ID的位数不一样。
- 标准帧:11位ID。这是最原始的CAN 2.0A规范定义的。
- 扩展帧:29位ID。这是CAN 2.0B加进来的,为了应对越来越复杂的网络。
你想想看,11位ID最多能表示2048个不同的ID。早期一个车上没几个ECU,够用了。但现在呢?一辆车几十个甚至上百个节点,11位ID根本不够分。所以扩展帧就派上用场了。
关键区别点:标准帧和扩展帧的帧头格式不同。扩展帧在标准帧的IDE位之后,多插入了18位扩展ID。仲裁场的结构也因此不同。
我在项目中遇到过一个问题:某个供应商的节点只支持标准帧,但我们的网关默认发的是扩展帧。结果那个节点死活不响应。排查了半天,最后发现是帧格式不匹配。嗯,这里要注意——同一个网络上,标准帧和扩展帧可以共存,但同一个报文ID,标准帧和扩展帧被认为是两个不同的报文。
3.2 仲裁场:谁先说话,谁说了算
仲裁场,说白了就是决定「谁先发」的地方。
CAN总线是广播式的,所有节点都能同时往总线上发数据。那如果两个节点同时发,不就打架了吗?不会的。CAN总线用了一个很巧妙的方法——非破坏性逐位仲裁。
仲裁场里主要包含:
- 标准帧:11位ID + RTR位
- 扩展帧:11位基础ID + SRR位 + IDE位 + 18位扩展ID + RTR位
仲裁的规则很简单:ID值越小,优先级越高。因为CAN总线是显性位(逻辑0)覆盖隐性位(逻辑1)。谁先输出0,谁就赢了。
我个人的习惯是:给关键报文(比如刹车、转向)分配小ID,给非关键报文(比如车窗、座椅)分配大ID。这样紧急消息永远能抢到总线。
避坑指南:我曾经在项目里看到有人把两个节点的ID设成一样的。结果这两个节点同时发相同ID的报文,总线上一片混乱。记住:同一网络上,同一ID只能由一个节点发送。接收节点可以多个,但发送节点只能一个。
3.3 控制场:告诉接收方「我带了什么」
控制场紧跟在仲裁场后面。它的作用很简单——告诉接收方:我这次发的是标准帧还是扩展帧,以及数据场里有多少个字节。
控制场的结构:
- 标准帧:IDE位(显性,表示标准帧)+ r0保留位 + DLC(4位数据长度码)
- 扩展帧:r1保留位 + r0保留位 + DLC(4位数据长度码)
DLC的取值范围是0~8,对应数据场0~8个字节。注意,CAN数据帧最多只能带8个字节的数据。这是CAN协议的一个硬限制。
为什么是8个字节?我猜当年设计者觉得8个字节够用了,而且帧长度太长会影响实时性。但放到今天,8个字节确实有点捉襟见肘。所以后来有了CAN FD,数据场可以到64字节。不过那是后话了。
3.4 数据场:真正干活的地方
数据场,就是你要发送的实际数据。0~8个字节,按需使用。
数据场的编码方式,协议本身没有规定。你可以自己定义。比如:
- 字节0:车速,单位km/h
- 字节1:发动机转速,高字节
- 字节2:发动机转速,低字节
- 字节3~7:保留或填充
我个人建议:数据场的编码一定要写文档。我见过太多项目,因为数据定义没写清楚,导致联调时两个节点对不上。比如A节点把车速放在字节0,B节点以为在字节1,结果读出来的数据完全不对。
注意:CAN数据场是MSB优先(大端模式)传输的。也就是说,字节0的高位先发。如果你习惯了小端模式(比如x86架构),写代码时一定要小心字节序的问题。
3.5 CRC场:数据有没有被「污染」?
CRC场,全称是循环冗余校验场。它的作用就是检查数据在传输过程中有没有出错。
CAN协议用的是CRC-15算法,生成多项式是:
x^15 + x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1
CRC计算的范围包括:SOF、仲裁场、控制场、数据场。接收节点收到报文后,会用同样的算法重新计算CRC,然后跟发送过来的CRC值比对。如果一致,说明数据没被破坏;如果不一致,接收节点会丢弃这个报文,并发送错误帧。
我记得有一次调试,发现某个节点偶尔会丢报文。用CAN分析仪抓包一看,CRC错误计数一直在涨。后来发现是那个节点的晶振精度不够,导致位采样点偏移,误码率升高。换了个高精度的晶振,问题就解决了。
小技巧:在开发阶段,可以故意让某个节点发送错误的CRC,来测试其他节点的错误处理机制是否正常。当然,量产车上千万别这么干。
3.6 应答场:确认收到,没毛病
应答场是数据帧的最后一个场次。它只有2个位:
- ACK Slot(应答槽):发送节点发隐性位,接收节点如果正确收到报文,就在这个位输出显性位(覆盖掉隐性位)。
- ACK Delimiter(应答分隔符):固定为隐性位,用于分隔应答场和后面的EOF。
这里有个有意思的点:只要有一个接收节点正确收到了报文,ACK Slot就会被拉成显性。也就是说,发送节点只关心「有没有至少一个节点收到了」,而不关心「具体是哪个节点收到的」。
那如果没有任何节点应答呢?发送节点会发现ACK Slot还是隐性位,就知道报文发送失败了。它会自动重发这个报文。
我曾经在项目里遇到一个诡异的问题:某个节点发出去的报文,总线上其他节点都能收到,但发送节点自己却一直报发送失败。后来发现是那个节点的CAN收发器的ACK应答回路出了问题,导致它检测不到总线上的显性ACK。嗯,硬件问题有时候就是这么坑。
3.7 完整帧结构一览
咱们把整个标准数据帧串起来看看:
| 场次 | 位数 | 说明 |
|---|---|---|
| SOF | 1 | 帧起始,显性位 |
| 仲裁场 | 12(标准帧) | 11位ID + RTR位 |
| 控制场 | 6 | IDE + r0 + DLC(4位) |
| 数据场 | 0~64 | 0~8字节数据 |
| CRC场 | 16 | 15位CRC + 1位CRC分隔符 |
| 应答场 | 2 | ACK Slot + ACK Delimiter |
| EOF | 7 | 帧结束,7个隐性位 |
扩展帧的仲裁场更长(32位),其他场次跟标准帧一样。
好了,数据帧的结构咱们就聊到这儿。你可能会问:光知道结构有什么用?别急,下一章咱们就讲「如何用代码构造和解析一个CAN数据帧」。到时候你就知道,今天这些知识全是基本功,一个都跑不掉。
总结一句话:CAN数据帧就像一封信——仲裁场是收件人地址,控制场是信封规格,数据场是信纸内容,CRC场是防伪封条,应答场是回执单。搞懂了这封信怎么写的,你就能在CAN总线上收发自如了。