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的位时序和同步机制——说白了就是「怎么让所有节点步调一致」。到时候我会分享一个我在采样点配置上踩过的坑,保证让你印象深刻。