2. CAN数据帧详解:标准帧与扩展帧

好,咱们进入正题。CAN总线上的数据,说白了就是靠「帧」来传递的。就像寄快递,你得有包裹、有地址、有签收单。CAN数据帧就是那个包裹,结构非常讲究。

我个人习惯把CAN数据帧分成两大类:标准帧扩展帧。它们的区别,说白了就是「地址位」的长度不同。标准帧用11位ID,扩展帧用29位ID。你想想看,11位能表示2048个ID,29位能表示5亿多个,这差距可不是一星半点。

核心区别速览:

  • 标准帧:11位标识符,ID范围 0x000 ~ 0x7FF
  • 扩展帧:29位标识符,ID范围 0x00000000 ~ 0x1FFFFFFF
  • IDE位(标识符扩展位):标准帧为0,扩展帧为1

我在BMS项目中遇到过一个问题:某款电池包用的是标准帧,但整车控制器非要发扩展帧。结果呢?电池包根本不理它,因为ID对不上。嗯,这里要注意——标准帧和扩展帧在总线上可以共存,但它们的ID空间是独立的。也就是说,标准帧的0x123和扩展帧的0x00000123,在CAN控制器看来是两码事。

数据帧结构:从SOF到EOF

一个完整的CAN数据帧,由7个场组成。我习惯把它拆成「头、身、尾」三部分来记。咱们一个一个看。

1. SOF(帧起始)

SOF就是一个显性位(逻辑0)。它告诉总线上的所有节点:「注意了,我要开始发数据了!」

为什么只有一个位?因为CAN总线是「线与」机制,只要有一个节点拉低总线,其他节点都能感知到。这个设计很巧妙,省去了复杂的同步头。

2. 仲裁场

这是CAN帧里最核心的部分。标准帧的仲裁场包含11位ID + 1位RTR;扩展帧则是29位ID + 1位SRR + 1位IDE + 1位RTR。

我给大家画个重点:仲裁场的优先级由ID决定,ID越小优先级越高。为什么?因为显性位(0)会覆盖隐性位(1)。你想想看,如果两个节点同时发数据,ID从高位到低位逐位比较,谁的0多谁就赢。

避坑指南:我曾经在项目里把BMS的CAN ID设成了0x7FF,结果发现它永远抢不到总线。后来才意识到,0x7FF全是隐性位,优先级最低。所以BMS这种关键节点,ID一定要设小一点,比如0x100左右。

3. 控制场

控制场由6位组成:IDE位 + 保留位(r0/r1) + DLC(数据长度码)。

DLC用4位表示数据场的字节数,范围0~8。注意,CAN数据帧最多只能带8个字节。为什么是8?因为CAN总线设计之初,考虑的是实时控制场景,8个字节足够传递大部分控制信息了。你要是想传大包,得拆成多个帧。

DLC值数据字节数
00字节(远程帧)
1~81~8字节
9~15保留(实际仍按8字节处理)

注意:有些CAN控制器允许DLC大于8,但实际只发8字节。我见过有人把DLC设成15,结果接收方解析出错。老老实实用0~8,别搞花活。

4. 数据场

这就是真正装数据的地方。0~8个字节,按大端序(Big-Endian)排列。BMS里常用的数据格式有:

  • 总电压:2字节,单位0.1V,比如0x0BB8表示300.0V
  • 电流:2字节,有符号,单位0.1A
  • 温度:1字节,偏移量40℃,比如0x50表示40℃
  • SOC:1字节,单位1%,范围0~100

我个人习惯把数据场的字节分配做成一张表,贴在工位上。这样写代码时一眼就能看到,不用反复翻文档。

5. CRC场

CRC场包含15位CRC校验码 + 1位CRC分隔符(隐性位)。CAN的CRC算法是CRC-15,多项式是x¹⁵ + x¹⁴ + x¹⁰ + x⁸ + x⁷ + x⁴ + x³ + 1。

嗯,这里要注意——CRC覆盖的范围是从SOF到数据场结束。也就是说,仲裁场、控制场、数据场都在校验范围内。如果CRC校验失败,接收节点会发错误帧。

我曾经在调试时发现,某批CAN收发器的CRC计算有bug,导致偶尔丢帧。排查了三天才找到原因。所以,CRC不是万能的,但没了它万万不能

6. ACK场

ACK场由2位组成:ACK槽 + ACK分隔符。发送节点在ACK槽发隐性位,接收节点如果正确收到帧,就拉低总线(显性位)。这样发送节点就知道「有人收到了」。

但注意:ACK只表示至少有一个节点正确接收,不表示所有节点都收到了。在BMS中,如果某个从控模块没响应,主控是不知道的。所以,我们通常会在应用层加应答机制。

实战技巧:我习惯在BMS的CAN报文里加一个「心跳计数器」,每帧递增。接收方如果发现计数器不连续,就知道丢帧了。这比单纯依赖ACK靠谱得多。

7. EOF(帧结束)

EOF由7个隐性位组成。为什么是7个?因为CAN的位填充机制要求连续5个相同位后插入一个相反位。7个隐性位足够让所有节点检测到帧结束。

EOF之后还有3位ITM(帧间空间),用于总线恢复。所以一个完整的CAN帧,从SOF到ITM结束,最短也要47位(标准帧)或67位(扩展帧)。

标准帧 vs 扩展帧:怎么选?

这个问题,我经常被问到。我的建议是:

  • 简单系统(节点数<10):用标准帧就够了,11位ID完全够用
  • 复杂系统(节点数>20):建议用扩展帧,29位ID方便分组和过滤
  • 混合系统:可以混用,但要做好ID规划,避免冲突

在BMS项目中,我一般这样分配ID:

ID范围用途
0x000~0x0FF主控模块(优先级最高)
0x100~0x1FF从控模块(电压/温度采集)
0x200~0x2FF绝缘检测/继电器控制
0x300~0x3FF诊断报文(UDS)

你想想看,这样分配后,CAN控制器可以用ID掩码过滤,只接收自己关心的报文。比如主控只收0x100~0x1FF的从控数据,其他一概不理。这样能大大降低CPU负载。

总结一下:

CAN数据帧的结构,说白了就是「起始位 + 地址 + 控制 + 数据 + 校验 + 应答 + 结束」。每个场都有它的设计目的,缺一不可。我在BMS开发中,最常打交道的就是数据场和仲裁场——一个管内容,一个管优先级。搞懂了这两个,CAN通信就掌握了七八成。

下一章,咱们聊聊CAN的位时序和同步机制。这东西看着枯燥,但搞不懂它,你连波特率都配不对。嗯,到时候我拿个实际案例给你拆解。