2. CAN协议详解:从标准帧到错误处理
大家好,我是你们的老朋友。今天咱们来啃一块硬骨头——CAN协议。说实话,我刚入行那会儿,觉得CAN协议就是一堆帧、仲裁、校验的堆砌,背下来就行了。直到我在台架上被一个莫名其妙的错误帧折磨了三天,才明白:纸上得来终觉浅,绝知此事要躬行。
好,废话不多说。咱们从最基础的帧结构开始,一步步把CAN协议吃透。
2.1 CAN 2.0A/B:标准帧与扩展帧
CAN协议有两个版本:2.0A和2.0B。说白了,区别就在ID长度上。
- CAN 2.0A:只支持11位ID,也就是标准帧。
- CAN 2.0B:支持11位ID和29位ID,也就是标准帧+扩展帧。
你可能会问:为什么要有扩展帧?嗯,我举个例子。早期车上节点少,11位ID够用。现在一辆车几十个ECU,11位ID根本不够分。29位ID一出来,地址空间从2048个飙到5亿多个,够你用一辈子了。
标准帧结构(11位ID)
标准帧长这样:
SOF | 11位ID | RTR | IDE | r0 | DLC | 数据段(0-8字节) | CRC | ACK | EOF
每个字段的作用:
| 字段 | 长度 | 说明 |
|---|---|---|
| SOF | 1 bit | 帧起始,显性电平,同步用 |
| ID | 11 bits | 标识符,决定优先级 |
| RTR | 1 bit | 远程帧请求,0=数据帧,1=远程帧 |
| IDE | 1 bit | 标识符扩展位,标准帧里为0 |
| r0 | 1 bit | 保留位 |
| DLC | 4 bits | 数据长度,0-8字节 |
| Data | 0-64 bits | 实际数据 |
| CRC | 15 bits | 循环冗余校验 |
| ACK | 2 bits | 应答槽 |
| EOF | 7 bits | 帧结束 |
扩展帧结构(29位ID)
扩展帧就是把ID从11位扩展到29位。结构上多了个SRR位和IDE位:
SOF | 11位基础ID | SRR | IDE | 18位扩展ID | RTR | r1 | r0 | DLC | 数据段 | CRC | ACK | EOF
关键区别:扩展帧的IDE位为1,标准帧的IDE位为0。仲裁时,标准帧优先级高于扩展帧。为什么?因为IDE位在仲裁场里,标准帧的IDE=0(显性),扩展帧的IDE=1(隐性),显性赢。
我个人习惯:能用标准帧就别用扩展帧。扩展帧虽然地址多,但帧长,总线利用率低。我在一个项目中见过有人非要用29位ID传个开关信号,纯粹是浪费带宽。
2.2 CAN总线仲裁机制
仲裁,说白了就是抢总线。多个节点同时发数据,谁赢?
规则很简单:ID越小,优先级越高。显性电平(0)压倒隐性电平(1)。
举个例子:
- 节点A发ID=0x100(二进制:001 0000 0000)
- 节点B发ID=0x200(二进制:010 0000 0000)
从第3位开始,A发0(显性),B发1(隐性)。A赢了,B自动退出发送,转为接收。
避坑指南:我曾经遇到过一个问题——两个节点ID相同,同时发数据。结果总线一直冲突,错误帧满天飞。后来查规范才发现:同一总线上不允许有相同ID的节点同时发送。所以设计时一定要保证ID唯一性。
还有一点要注意:远程帧的RTR位为1(隐性),数据帧的RTR位为0(显性)。所以如果数据帧和远程帧ID相同,数据帧赢。这个我在调试一个传感器节点时踩过坑,远程帧发出去没人理,后来发现是ID冲突了。
2.3 位填充与CRC校验
位填充机制
位填充是为了保证时钟同步。CAN总线用NRZ编码,如果连续发太多相同的位,接收方可能丢失同步。
规则:连续发送5个相同位后,自动插入一个相反位。
比如你要发:11111 00000
实际发:111110 000001
接收方看到6个相同位,就知道第6位是填充位,去掉就行。
注意:CRC界定符、ACK槽、EOF这些固定格式的字段不进行位填充。我见过有人把EOF里的填充位也算进去,结果CRC算出来不对,折腾了半天。
CRC校验
CAN的CRC是15位的,生成多项式是:
x^15 + x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1
计算范围:从SOF到数据段结束(不包括填充位)。
实际项目中,你不需要手算CRC。控制器硬件自动算。但你要知道:CRC错误是最常见的错误类型之一。我在HIL测试中经常故意注入CRC错误,看ECU能不能正确识别并丢弃错误帧。
经验之谈:如果你在台架上发现某个节点频繁发错误帧,先查CRC。很多时候是时钟精度不够,导致采样点偏移,CRC算出来不对。我遇到过用1%精度的晶振跑高速CAN(1Mbps),结果CRC错误率高达5%。换成0.1%精度的晶振,问题消失。
2.4 CAN错误处理机制
CAN的错误处理,说白了就是:谁犯错,谁背锅。每个节点都有两个计数器:
- TEC:发送错误计数器
- REC:接收错误计数器
错误状态分三种:
| 状态 | 条件 | 行为 |
|---|---|---|
| 错误主动 | TEC < 127 且 REC < 127 | 正常收发,检测到错误发主动错误帧 |
| 错误被动 | TEC > 127 或 REC > 127 | 只能发被动错误帧,且发送前要等8个隐性位 |
| 总线关闭 | TEC > 255 | 完全离线,不参与任何总线活动 |
错误类型有5种:
- 位错误:发送时监控总线,发现电平不一致
- 填充错误:连续6个相同位(违反位填充规则)
- CRC错误:接收方算出的CRC和发送方不一致
- 形式错误:固定格式字段(如EOF)出现错误电平
- ACK错误:发送方没收到应答
避坑指南:我曾经调试过一个项目,某个节点总是进入总线关闭状态。查了半天,发现是它的CAN收发器供电不稳,导致发送时电平异常,频繁触发位错误。TEC一路飙升到255,直接离线。解决方案:在收发器电源上加个电容滤波,问题解决。
还有一点:错误帧的优先级是最高的。一旦检测到错误,节点立刻发6个显性位(主动错误帧),打断当前传输。这意味着总线上的错误会迅速传播。所以设计时一定要控制错误率,别让一个坏节点拖垮整条总线。
嗯,CAN协议的核心内容就这些。从帧结构到仲裁,从位填充到错误处理,每个环节都环环相扣。你想想看,一个协议能活30多年,靠的就是这些精巧的设计。
下一章咱们聊CAN FD,那个更有意思。先消化这些,有问题随时问我。