2、CAN数据帧详解:标准帧与扩展帧、数据帧结构
各位同学,咱们今天来啃一块硬骨头——CAN数据帧。说实话,我刚入行那会儿,看着CAN协议手册上那一堆场、位、仲裁,头都大了。后来在BMS项目里被CAN总线坑过几次,才真正把这些东西刻进脑子里。今天我就把那些踩过的坑和总结的经验,一股脑倒给你们。
2.1 标准帧 vs 扩展帧:到底差在哪?
CAN总线有两种帧格式:标准帧(11位ID)和扩展帧(29位ID)。你可能会问,为什么要有两种?
嗯,这得从CAN的历史说起。早期CAN总线用在汽车里,节点不多,11位ID够用了。后来BMS系统越来越复杂,一个电池包里有几十个模组,再加上温度传感器、电流传感器、继电器……11位ID根本不够分。于是扩展帧就诞生了。
核心区别就两点:
- ID长度不同:标准帧11位,扩展帧29位。扩展帧多出来的18位叫“扩展标识符”。
- 帧格式不同:扩展帧在控制场里多了一个IDE位(标识符扩展位)和一个SRR位(替代远程请求位)。
我在BMS项目里遇到过一个问题:某个电芯模组用的是标准帧,而主控单元只认扩展帧。结果通信死活不通,查了两天才发现是帧格式不匹配。所以啊,设计系统时一定要统一帧格式,别混用。
避坑指南:我曾经在一个项目中,为了省带宽,把标准帧和扩展帧混着用。结果CAN控制器在仲裁时,标准帧的优先级永远高于扩展帧(因为标准帧的IDE位是显性)。这导致某些关键数据总是被低优先级的扩展帧抢了先。后来我学乖了:要么全用标准帧,要么全用扩展帧,别搞混搭。
2.2 数据帧结构:从SOF到EOF,一个比特都不能错
一个完整的CAN数据帧,由7个场组成。我习惯把它想象成一封挂号信:有信封、有地址、有内容、有校验、有回执。咱们一个一个拆开看。
2.2.1 SOF(帧起始)
SOF就是一个显性位(逻辑0)。它告诉总线上所有节点:“嘿,我要开始发数据了!”
你想想看,如果总线上同时有多个节点要发数据,SOF就是起跑线。谁先拉低总线,谁就抢到了发言权。这个机制叫“非破坏性位仲裁”,后面会细讲。
2.2.2 仲裁场
仲裁场是CAN帧的灵魂。标准帧的仲裁场是11位ID + RTR位(远程传输请求位)。扩展帧的仲裁场是29位ID + SRR位 + IDE位 + RTR位。
仲裁规则很简单:ID越小,优先级越高。 为什么?因为显性位(0)会覆盖隐性位(1)。ID数值小的帧,高位先发0,自然就赢了。
我在BMS里设计ID分配时,把报警帧的ID设得最小(比如0x001),数据帧次之(0x100),诊断帧最大(0x700)。这样一旦有故障,报警帧能立刻抢占总线。
个人经验:我建议你在设计ID时,留出足够的余量。比如BMS里电芯电压数据用0x100-0x1FF,温度数据用0x200-0x2FF。万一以后要增加传感器,直接扩展就行,不用重新规划ID。
2.2.3 控制场
控制场有6位:IDE位、保留位r0、DLC(数据长度码,4位)。
IDE位决定是标准帧还是扩展帧。DLC告诉接收方“我要发几个字节的数据”。注意,DLC的范围是0-8,CAN帧最多带8字节数据。
嗯,这里有个坑:DLC可以设成0,表示不发送数据。这在远程帧里有用,但数据帧里很少用。我见过有人把DLC设成0,结果接收方一直等数据,导致总线卡死。
2.2.4 数据场
数据场就是你要传输的实际数据,0-8个字节。BMS里最常见的是把电压、电流、温度打包成几个字节发出去。
举个例子,一个电芯电压是3.712V,我会把它放大1000倍变成3712,然后拆成两个字节:0x0E 0x80。接收方再拼回去除以1000,就还原了。
// 发送电芯电压示例(假设电压值已放大1000倍)
uint16_t voltage_scaled = 3712; // 3.712V
uint8_t data[2];
data[0] = (voltage_scaled >> 8) & 0xFF; // 高字节
data[1] = voltage_scaled & 0xFF; // 低字节
// 然后通过CAN发送data[0]和data[1]
2.2.5 CRC场
CRC场包含15位CRC校验码和1位CRC界定符。CAN用的是CRC-15算法,多项式是0x4599。
说白了,CRC就是给数据算一个指纹。接收方收到数据后,用同样的算法算一遍,如果指纹对不上,说明数据被干扰了,直接丢弃。
我在BMS现场遇到过:电机启动时,CAN总线上的CRC错误率飙升。后来发现是屏蔽层接地不良,导致电磁干扰。加了磁环、改了接地方式后,CRC错误率降到了0。
注意:CRC只能检测错误,不能纠正错误。一旦CRC校验失败,CAN控制器会自动重发。但如果重发次数超过上限(通常16次),控制器会进入总线关闭状态。这时候整个节点就“掉线”了。BMS里如果某个电芯模组频繁CRC错误,我建议直接把它隔离,别让它拖垮整个网络。
2.2.6 ACK场
ACK场只有2位:ACK槽和ACK界定符。发送方在ACK槽发隐性位(1),接收方如果正确收到数据,就在ACK槽拉成显性位(0)。
你想想看,这就像你寄快递,快递员签收后给你回个“已收到”。如果没人签收(ACK槽还是隐性),发送方就知道数据没被接收,会重发。
我曾经在调试BMS时,发现某个节点总是收不到数据,但总线波形正常。查了半天,原来是那个节点的CAN控制器没配置成“接收模式”,导致它不会发ACK。嗯,这种低级错误,犯过一次就不会再犯了。
2.2.7 EOF(帧结束)
EOF是7个隐性位(1)。它告诉总线:“这帧数据发完了,下一个节点可以抢总线了。”
EOF之后还有3位ITM(帧间空间),用于总线恢复。这10个隐性位保证了总线在帧与帧之间处于空闲状态。
2.3 标准帧与扩展帧的对比表格
| 特性 | 标准帧 | 扩展帧 |
|---|---|---|
| ID长度 | 11位 | 29位 |
| 仲裁场 | ID + RTR | ID + SRR + IDE + 扩展ID + RTR |
| 最大节点数 | 约2000个 | 约5亿个 |
| 数据场长度 | 0-8字节 | 0-8字节 |
| 帧长度 | 最短44位 | 最短64位 |
| 优先级 | 高于扩展帧 | 低于标准帧 |
2.4 实战中的注意事项
- ID分配要分层:BMS里,报警帧优先级最高,控制帧次之,数据帧最低。别把数据帧的ID设得太小,否则报警帧抢不过它。
- DLC别乱设:数据场长度必须和实际数据匹配。设多了浪费带宽,设少了数据丢失。
- CRC不是万能的:虽然CAN的CRC很强,但在强电磁干扰环境下,还是建议在应用层再加一层校验(比如和校验)。
- ACK必须检查:发送方一定要检查ACK位是否被拉低。如果连续几帧都没收到ACK,说明接收方可能掉线了。
好了,CAN数据帧的细节就讲到这里。说白了,标准帧和扩展帧就是地址长度的区别,而数据帧的7个场,每个都有它的使命。你在BMS项目里写CAN驱动时,多想想这些场的含义,很多bug就能提前避免。下一章咱们聊聊CAN的位时序和同步机制,那才是真正考验功底的地方。