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

好,咱们今天聊聊CAN总线里最核心的东西——数据帧。说白了,你搞Bootloader刷写,整天跟数据帧打交道。不理解帧结构,你连报文都看不懂,更别提做协议了。

我个人习惯,讲CAN帧之前,先让大家想一个问题:为什么CAN总线能这么可靠?答案就藏在这帧结构里。咱们一层层剥开看。

2.1 标准帧 vs 扩展帧:11位ID和29位ID的江湖

CAN总线有两种帧格式:标准帧和扩展帧。区别在哪?就在ID的长度上。

  • 标准帧:ID是11位。范围0x000~0x7FF,一共2048个ID。
  • 扩展帧:ID是29位。范围0x00000000~0x1FFFFFFF,数量多得多。

你可能会问:为什么要有两种?嗯,历史原因。早期CAN标准(CAN 2.0A)只定义了11位ID。后来发现不够用,尤其是车载网络越来越复杂,于是CAN 2.0B引入了29位ID。

关键区别:标准帧和扩展帧的帧格式不同。扩展帧在控制场里多了一个IDE位(Identifier Extension Bit),用来标识这是扩展帧。仲裁场也多了18位ID。

我遇到过不少新手,上来就问:“我用标准帧还是扩展帧?”我的建议是:看你的项目需求。

  • 如果节点少(比如十几个),标准帧完全够用。ID分配也简单。
  • 如果节点多(几十上百个),或者需要兼容J1939这类高层协议,那就用扩展帧。

我个人经验:做Bootloader刷写时,我倾向于用扩展帧。为什么?因为刷写协议里需要区分很多类型的报文:请求帧、应答帧、数据帧、控制帧……29位ID可以灵活编码,把源地址、目标地址、报文类型都塞进去。11位ID太挤了,容易冲突。

2.2 ID仲裁机制:谁优先级高谁先说话

CAN总线是多主总线。什么意思?就是所有节点都能同时发数据。那冲突了怎么办?靠ID仲裁。

仲裁的规则很简单:ID值越小,优先级越高。

具体怎么仲裁?我简单说一下:

  1. 所有节点同时往总线上发数据。
  2. 每个节点发完一位ID后,监听总线电平。
  3. 如果自己发的是显性电平(0),但总线上是隐性电平(1),说明有更高优先级的节点在发数据。自己立刻停止发送,转为接收。

说白了,就是“谁先发0,谁赢”。因为CAN总线是“线与”逻辑:显性电平(0)会覆盖隐性电平(1)。

避坑指南:我曾经在一个项目里,把两个节点的ID设成了0x100和0x101。结果发现0x100的节点总是抢不到总线。查了半天才发现,0x100的ID虽然数值小,但它的第一位是1(隐性),而0x101的第一位是0(显性)。所以仲裁时0x101反而赢了。记住:仲裁是从ID的最高位开始比的!

你想想看,这个机制有什么好处?实时性。高优先级的报文(比如刹车信号)永远能第一时间发出去。低优先级的报文(比如车窗控制)等一等也无所谓。

做Bootloader刷写时,我建议把刷写相关的报文ID设得比普通报文高(即ID值更小)。这样刷写过程中不会被其他报文干扰。嗯,这里要注意:别设得太高,否则会影响关键安全报文(比如刹车、转向)。

2.3 数据场:最多8字节,够用吗?

CAN数据帧的数据场,最多8个字节。很多人第一反应:8字节?这也太少了吧!

确实,跟以太网(1500字节)比,CAN的数据场小得可怜。但这是故意的。为什么?为了保证实时性和可靠性。

  • 数据少,发送时间短,总线延迟低。
  • 数据少,出错概率低,重传成本也低。

那8字节够用吗?看你怎么用。

举个例子,刷写一个固件,固件大小可能是几百KB甚至几MB。你不可能一帧发完。怎么办?分包发送。

我常用的做法是:

  • 每帧数据场放4~8字节的固件数据。
  • 加上一个2字节的序列号,用来标识这是第几个包。
  • 接收方收到后,按序列号重组。

数据场结构示例(刷写数据帧):

字节0~1:序列号(16位,大端)
字节2~7:固件数据(6字节)

你可能会问:为什么不用8字节全放数据?嗯,序列号很重要。没有序列号,你无法保证数据包的顺序,也无法检测丢包。我曾经见过一个项目,为了省两个字节,没加序列号,结果刷写时偶尔会乱序,导致固件校验失败。后来还是老老实实加上了。

2.4 CRC校验:15位CRC,能保证不出错吗?

CAN帧的末尾有一个15位的CRC校验码。它的作用是检测数据在传输过程中有没有被干扰。

CRC的计算范围包括:帧起始、仲裁场、控制场、数据场。但不包括CRC场本身和ACK场。

15位CRC,能检测出多少错误?

  • 所有单比特错误。
  • 所有双比特错误。
  • 所有奇数个比特的错误。
  • 所有长度≤15的突发错误。
  • 99.997%的长度为16的突发错误。
  • 99.998%的长度≥17的突发错误。

说白了,CAN的CRC非常强。我做了这么多年CAN开发,几乎没遇到过CRC校验失败的情况。但注意,不是100%可靠。理论上,还是有极小的概率出现“CRC碰撞”——数据错了,但CRC算出来是对的。

我的建议:在Bootloader刷写协议里,除了CAN硬件自带的CRC,最好再加一层应用层的校验。比如,每帧数据加一个累加和,或者整个固件加一个MD5/SHA256。这样即使CAN硬件漏检了,应用层也能发现。这叫“双重保险”

我曾经在一个项目里,只依赖CAN硬件的CRC,结果刷写完成后,固件运行偶尔会崩溃。查了三天,发现是某条报文在传输过程中被干扰了,但CRC没检测出来。从那以后,我所有的刷写协议都加了应用层校验。嗯,吃一堑长一智。

2.5 总结一下

CAN数据帧的结构,说白了就是:

  • ID:决定优先级,也决定你是谁。
  • 数据场:最多8字节,装你的业务数据。
  • CRC:15位,保证数据没被篡改。

做Bootloader刷写时,我建议你:

  1. 用扩展帧,ID编码灵活。
  2. ID值设小一点,保证刷写优先级。
  3. 数据场里留两个字节给序列号。
  4. 加应用层校验,别只依赖硬件CRC。

下一章,咱们聊聊CAN的位时序和同步。这东西看着枯燥,但搞懂了,你就能理解为什么CAN总线能跑那么远、那么稳。