2. CAN数据帧结构:标准帧与扩展帧、ID仲裁机制、数据场、CRC校验
好,咱们今天聊聊CAN数据帧的结构。说实话,这是整个CAN通信最核心的部分。你想想看,一辆车上几十个ECU,它们怎么知道什么时候该发数据?发出去的数据别人能不能看懂?万一两个节点同时发消息怎么办?这些问题的答案,都藏在数据帧的结构里。
2.1 标准帧 vs 扩展帧
CAN总线有两种帧格式:标准帧和扩展帧。我个人习惯把它们叫做「11位ID的帧」和「29位ID的帧」。为什么会有两种?说白了,就是早期11位ID够用,后来发现不够了,就搞了个扩展的。
| 特性 | 标准帧 | 扩展帧 |
|---|---|---|
| ID长度 | 11位 | 29位 |
| 可用的ID数量 | 2^11 = 2048个 | 2^29 = 5亿多个 |
| 帧头开销 | 较小 | 较大(多出IDE位和扩展ID) |
| 兼容性 | 所有CAN控制器都支持 | 需要控制器支持CAN 2.0B |
我在一个车载项目里遇到过这样的坑:某个供应商的ECU只支持标准帧,而我们整车网络用的是扩展帧。结果那个ECU死活收不到报文。后来查了半天才发现,是IDE位(Identifier Extension)没配置对。嗯,这里要注意——标准帧和扩展帧混用时,IDE位必须正确设置。
关键区别:标准帧的IDE位为显性(0),扩展帧的IDE位为隐性(1)。CAN控制器就是靠这个位来区分两种帧的。
2.2 ID仲裁机制——谁先说话?
这是CAN总线最巧妙的设计之一。你想想看,多个节点同时往总线上发数据,总得有个先来后到吧?CAN的仲裁机制,说白了就是「谁的数字小,谁就赢」。
为什么会这样?因为CAN总线用的是「线与」逻辑。显性位(逻辑0)会覆盖隐性位(逻辑1)。仲裁的时候,每个节点一边发自己的ID位,一边监听总线。如果发现自己发的和总线上的不一样,说明有优先级更高的节点在发,自己就乖乖退出发送。
我个人经验:设计ID分配时,一定要把紧急消息的ID设小。比如刹车系统的ID设为0x001,车窗控制设为0x100。这样刹车消息永远优先。我曾经见过一个项目,把空调控制的ID设得比刹车还小,结果急刹车时空调还在抢总线...这设计,你想想看,多危险。
仲裁过程可以简单概括为:
- 所有节点同步发送SOF(帧起始)
- 逐位比较ID,从MSB到LSB
- 遇到显性位(0)的节点继续,隐性位(1)的节点退出
- 仲裁失败的节点自动转为接收模式,等待下次重发
这里有个细节:仲裁过程中,数据是不会丢失的。失败的节点会在总线空闲后自动重发。嗯,这个机制保证了通信的可靠性。
2.3 数据场——真正干活的地方
数据场就是帧里装实际数据的地方。标准帧和扩展帧的数据场结构完全一样,最多8个字节。为什么是8个?我记得早期的CAN设计者做过统计,汽车上大部分控制信号,8个字节足够了。多了反而影响实时性。
数据场的结构很简单:
- DLC(数据长度码):4位,表示数据场有多少个字节。范围0-8。
- 数据字节:0-8个字节,每个字节8位。
注意:DLC虽然可以表示0-15,但CAN协议规定数据场最多8个字节。如果DLC大于8,有些控制器会报错,有些则直接忽略。我建议你永远不要设DLC大于8,否则兼容性问题会让你头疼。
数据场的字节序也是个坑。不同的ECU厂商,有的用大端(Motorola格式),有的用小端(Intel格式)。比如一个16位的车速值0x1234:
- 大端:Byte0 = 0x12, Byte1 = 0x34
- 小端:Byte0 = 0x34, Byte1 = 0x12
我曾经在调试一个混合动力系统时,就因为字节序没对齐,车速显示一直是错的。后来在DBC文件里统一标注了字节序,才解决问题。
2.4 CRC校验——数据有没有被搞坏?
CRC(循环冗余校验)是CAN帧的「防伪标签」。它用15位的CRC序列来检测传输过程中有没有比特错误。
CAN的CRC算法是这样的:
- 生成多项式:x^15 + x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1
- 计算范围:从SOF到数据场的最后一个字节
- 结果:15位CRC序列 + 1位CRC分隔符(隐性位)
你可能会问:15位CRC够用吗?我告诉你,CAN的CRC设计得很巧妙。它能检测出:
- 所有单比特错误
- 所有双比特错误
- 所有奇数个比特的错误
- 所有长度≤15的突发错误
- 99.997%的其他错误
实际经验:我在一个工业控制项目里,遇到过总线干扰导致CRC频繁出错的情况。当时查了半天,发现是屏蔽层接地没做好。加了磁环之后,CRC错误率从千分之一降到了百万分之一以下。嗯,硬件设计对CRC的影响,往往比软件大得多。
接收节点收到帧后,会用同样的算法重新计算CRC。如果计算结果和发送过来的CRC序列不一致,就说明数据被破坏了。这时候接收节点会发送一个错误帧,要求发送节点重发。
这里有个小技巧:如果你在调试CAN通信,发现某个节点总是发错误帧,先别急着怀疑软件。用示波器看看总线波形,很多时候是物理层的问题——比如终端电阻不对、线缆太长、或者共模干扰太大。
2.5 完整的数据帧结构
把上面这些拼起来,一个完整的CAN数据帧长这样:
标准帧(11位ID):
SOF | 11位ID | RTR | IDE | r0 | 4位DLC | 0-8字节数据 | 15位CRC | CRC分隔符 | ACK | ACK分隔符 | EOF
扩展帧(29位ID):
SOF | 11位基本ID | SRR | IDE | 18位扩展ID | RTR | r1 | r0 | 4位DLC | 0-8字节数据 | 15位CRC | CRC分隔符 | ACK | ACK分隔符 | EOF
每个字段的含义:
| 字段 | 长度 | 说明 |
|---|---|---|
| SOF | 1位 | 帧起始,显性位,同步所有节点 |
| ID | 11或29位 | 标识符,决定优先级 |
| RTR | 1位 | 远程帧请求,显性表示数据帧 |
| IDE | 1位 | 扩展标识符,显性为标准帧 |
| DLC | 4位 | 数据长度码,0-8 |
| 数据场 | 0-64位 | 实际数据 |
| CRC | 15位 | 循环冗余校验 |
| ACK | 2位 | 应答,接收节点拉低表示收到 |
| EOF | 7位 | 帧结束,全部为隐性位 |
我的建议:刚开始学CAN的时候,别急着背这些字段。先理解仲裁和CRC这两个核心机制。仲裁决定了「谁先说话」,CRC保证了「说话没出错」。这两个搞懂了,CAN通信你就掌握了80%。剩下的字段,用的时候查手册就行。
好了,这一章的内容就到这里。下一章我们会讲CAN的位时序和同步机制——说白了就是「怎么让所有节点步调一致」。到时候我会分享一个我在采样点配置上踩过的坑,保证让你印象深刻。