2. CAN数据帧结构:标准帧与扩展帧、ID仲裁机制、数据场与CRC校验

好,咱们今天聊点硬核的。CAN总线上的数据到底长什么样?

很多人学CAN,上来就背帧格式,什么SOF、RTR、DLC……背完就忘。我刚开始也是这样。后来在项目里调了一周的bug,才发现问题出在ID仲裁上。从那以后,我对帧结构的理解就再也不是死记硬背了。

咱们今天就把CAN数据帧拆开,看看每个字段到底在干什么。

2.1 标准帧 vs 扩展帧:到底差在哪?

CAN 2.0规范定义了两种帧格式:标准帧和扩展帧。说白了,区别就在ID的长度上。

对比项 标准帧 扩展帧
ID长度 11位 29位
最大节点数(理论) 2^11 = 2048 2^29 = 5.3亿
帧头开销 更小,实时性好 稍大,但灵活
IDE位 0(显性) 1(隐性)
常见应用 J1939、CANopen 车载诊断、私有协议

我个人的习惯是:能用标准帧就别用扩展帧。为什么?因为11位ID在仲裁时更快,总线利用率更高。但如果你需要标识很多节点,比如在网关里做路由过滤,29位ID就很有用了。

小提示: 标准帧和扩展帧可以在同一条总线上共存。IDE位决定了接收方怎么解析后面的数据。但要注意,扩展帧的优先级通常低于标准帧——因为IDE位在仲裁时是隐性位。

2.2 ID仲裁机制:谁先说话?

CAN总线最牛的地方,就是它的仲裁机制。多个节点同时发数据,不会冲突,而是按优先级来。

怎么仲裁的?看ID。ID越小,优先级越高。

举个例子:

  • 节点A发ID=0x100(二进制:0001 0000 0000)
  • 节点B发ID=0x200(二进制:0010 0000 0000)

两个节点同时开始发。第一位都是0,没问题。第二位,A发0,B发0,也没问题。第三位,A发0,B发1。这时候,A的0是显性位,B的1是隐性位。总线电平被A拉低,B检测到电平跟自己发的不一样,就知道自己输了,立刻停止发送,转为接收模式。

整个过程,没有数据丢失,没有重传。这就是CAN的“无损逐位仲裁”。

核心要点: ID仲裁是在帧起始(SOF)之后,紧跟着ID字段进行的。仲裁胜出的节点继续发送,失败的节点自动转为接收。整个过程在硬件层面完成,软件完全无感。

我曾经在一个项目中,把两个节点的ID设反了。结果一个高优先级的报文总是被低优先级的堵住,导致控制指令延迟。查了两天才发现是ID分配的问题。嗯,从那以后,我每次分配ID都会画一张优先级表。

2.3 数据场:你到底要传什么?

数据场是CAN帧里真正装数据的地方。长度从0到8字节。

为什么是8字节?这是由CAN的位填充机制和实时性要求决定的。8字节以内,可以保证一帧数据在短时间内传完,不会阻塞总线。

数据场的结构很简单:

  • DLC(数据长度码): 4位,表示数据场有多少字节。范围0~8。
  • 数据字节: 0~8个字节,按MSB(最高有效位)优先发送。

我建议你在设计协议时,尽量把数据场填满。比如一个传感器值占2字节,那就把4个传感器的值拼在一帧里发。这样可以减少总线负载。

注意: DLC可以设置成0~8,但如果你设成0,数据场就不存在。接收方不会收到任何数据。有些新手把DLC设成0,却还在数据场里填了值,结果对方死活收不到——因为硬件根本就没发数据场。

2.4 CRC校验:数据到底对不对?

CAN的CRC校验用的是15位CRC,生成多项式是:

x^15 + x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1

这个多项式能检测出:

  • 所有单比特错误
  • 所有双比特错误
  • 所有奇数个错误
  • 所有长度≤15的突发错误
  • 99.997%的其他错误

CRC的计算范围是从SOF到数据场结束。接收方收到后,自己也算一遍CRC,跟收到的CRC字段比对。如果不一样,就说明数据出错了,接收方会发一个错误帧。

我记得有一次,在实验室里用CAN分析仪抓数据,发现偶尔会有CRC错误。排查了半天,发现是线缆太长,信号反射导致的。换了双绞线并加了终端电阻,问题就解决了。

实战经验: 如果你在项目中遇到偶发的CAN通信失败,先别急着怀疑软件。用示波器看看总线波形,检查一下CRC错误计数。如果CRC错误很多,大概率是物理层的问题——线缆、接头、终端电阻、共模干扰,这些才是元凶。

2.5 完整帧结构一览

咱们把标准帧和扩展帧的完整结构画出来,你一看就明白了。

CAN数据帧结构对比:标准帧 vs 扩展帧 CAN数据帧结构对比 标准帧 (11位ID) SOF 11位ID RTR IDE r0 DLC 数据场(0-8B) CRC(15位) ACK EOF 扩展帧 (29位ID) SOF 11位基础ID SRR IDE r1 18位扩展ID RTR r1 DLC 数据场(0-8B) CRC(15位) ACK EOF ID仲裁流程 节点A发送ID=0x100 节点B发送ID=0x200 A胜出,继续发送 B失败,转为接收 关键说明: 1. 仲裁在SOF之后立即开始,逐位比较ID。 2. 显性位(0) > 隐性位(1),ID越小优先级越高。 3. 仲裁失败的节点自动转为接收,不丢失数据。

2.6 避坑指南:我踩过的那些坑

最后,分享几个我亲身经历过的坑,希望能帮你少走弯路。

  • ID分配不合理: 我曾经把高优先级的控制报文ID设得很大,结果总是被低优先级的诊断报文抢了总线。后来重新规划了ID分配,把控制类报文放在0x000~0x0FF,诊断类放在0x100~0x1FF,问题解决。
  • DLC设置错误: 有一次,我发了一帧数据,DLC设成2,但数据场里填了4个字节。接收方只取了前2个字节,后2个字节丢了。查了半天才发现是DLC没更新。
  • CRC校验不过: 在实验室里,线缆太长,信号反射导致CRC错误。加了终端电阻后,错误率从5%降到了0.01%。
  • 扩展帧和标准帧混用: 有些CAN控制器不支持扩展帧,或者需要特殊配置。如果你在项目中混用,一定要确认所有节点都支持。
重要提醒: 在实际项目中,一定要用CAN分析仪抓一下总线上的实际波形。不要只看代码里的配置。我曾经遇到过,代码里配的是标准帧,但实际发出去的却是扩展帧——因为某个寄存器没配置对。抓波形是验证帧结构最直接的方法。