4、CAN总线物理层与数据链路层:CAN 2.0A/B帧结构、仲裁机制、位填充、错误帧与远程帧
好,咱们今天聊聊CAN总线最核心的底层机制。说实话,很多做Android诊断应用开发的工程师,往往只关注应用层的UDS协议,对物理层和数据链路层一知半解。但我在实际项目中吃过亏——有一次在实车上调试,诊断仪死活连不上ECU,查了三天,最后发现是CAN收发器的共模电压出了问题。从那以后,我养成了一个习惯:搞懂底层,才能用好上层。
4.1 CAN 2.0A与2.0B:标准帧与扩展帧
CAN总线协议最早是1986年由Bosch公司开发的。后来演化出两个版本:CAN 2.0A和CAN 2.0B。说白了,它们最大的区别就是——ID的长度。
- CAN 2.0A(标准帧):11位标识符(ID)。报文头比较短,适合简单的控制场景。
- CAN 2.0B(扩展帧):29位标识符(ID)。多出来的18位用于扩展,适合复杂的网络,比如车载诊断。
我个人习惯,在诊断应用中几乎只用扩展帧。为什么?因为29位ID可以承载更多信息,比如源地址、目标地址、服务类型等。你想想看,一个现代汽车上有几十个ECU,11位ID根本不够分。
关键区别:CAN 2.0B的控制器可以收发标准帧,但CAN 2.0A的控制器无法处理扩展帧。所以,如果你的诊断工具要兼容老车,得注意这一点。
4.2 帧结构:数据帧的解剖
一个CAN数据帧,从开始到结束,包含以下几个部分。我习惯把它拆成“头、体、尾”来记。
| 字段 | 长度 | 说明 |
|---|---|---|
| SOF(帧起始) | 1位 | 显性位(0),同步所有节点 |
| 仲裁场 | 11位或29位 | ID + RTR位(远程帧标志) |
| 控制场 | 6位 | IDE位、保留位、DLC(数据长度码) |
| 数据场 | 0~8字节 | 实际传输的数据 |
| CRC场 | 16位 | 15位CRC + 1位CRC界定符 |
| ACK场 | 2位 | ACK槽 + ACK界定符 |
| EOF(帧结束) | 7位 | 隐性位(1) |
嗯,这里要注意:数据场最多8个字节。为什么是8?因为CAN总线设计之初,考虑的是实时控制,不是大数据传输。8字节足够传递一个发动机转速、车速、温度等关键参数。如果你要传大量诊断数据,得用多帧传输(比如ISO 15765-2)。
4.3 仲裁机制:谁先说话?
CAN总线是“多主”架构,任何节点都可以随时发送。那问题来了:如果两个节点同时发送,谁说了算?
答案是:ID越小,优先级越高。
仲裁的过程是这样的:所有发送节点在发送仲裁场时,逐位比较。如果某个节点发送了隐性位(1),但总线上是显性位(0),它就立刻退出,转为接收状态。说白了,就是“谁先发0,谁赢”。
避坑指南:我曾经在项目中遇到过一个诡异的问题——两个ECU同时发送诊断请求,结果优先级低的那个总是超时。后来发现,诊断请求的ID分配不合理,导致高优先级的请求频繁抢占总线。解决方案很简单:给诊断请求分配较低的ID(比如0x7DF),确保它能及时发送。
4.4 位填充:保证同步的“小把戏”
CAN总线使用NRZ(非归零)编码。这种编码有个问题:如果连续发送多个相同位(比如5个连续的0),接收方的时钟可能会失步。怎么解决?
CAN协议规定:连续发送5个相同位后,必须插入一个相反位。这就是位填充。
- 如果连续5个0,第6位强制插入1。
- 如果连续5个1,第6位强制插入0。
接收方在解码时,会自动去掉这些填充位。嗯,这个机制保证了总线上始终有足够的跳变沿,让所有节点保持同步。
注意:位填充只适用于SOF到CRC场之间的数据。EOF和ACK场不进行位填充。另外,如果连续发送6个相同位,会被视为错误——这就是后面要说的“位错误”。
4.5 错误帧:总线上的“警报器”
CAN总线的可靠性,很大程度上归功于它的错误检测机制。任何节点检测到错误,都会立即发送错误帧,通知其他节点。
错误帧由两部分组成:
- 错误标志:6个连续显性位(0)。这会破坏当前帧的位填充规则,强制其他节点也检测到错误。
- 错误界定符:8个连续隐性位(1)。用于恢复总线空闲状态。
错误类型有5种:
- 位错误:发送节点监控总线,发现发送的位与总线状态不一致。
- 填充错误:接收节点检测到6个连续相同位。
- CRC错误:接收节点计算的CRC与发送节点的不一致。
- 形式错误:固定格式的字段(如EOF)出现非法位。
- ACK错误:发送节点在ACK槽没有收到显性位(即没有节点确认)。
我记得有一次在实验室调试,发现CAN总线频繁出现错误帧。用示波器一看,原来是终端电阻没接好,导致信号反射。嗯,硬件问题往往比软件问题更难排查。
4.6 远程帧:请求数据的“问号”
远程帧(Remote Frame)是一种特殊的帧。它没有数据场,用于请求其他节点发送数据。说白了,就是“你发个数据给我看看”。
远程帧的结构和数据帧几乎一样,唯一的区别是:
- RTR位(远程帧标志)为隐性位(1)。
- 数据场为空(DLC可以指定期望的数据长度)。
举个例子:节点A发送一个远程帧,ID为0x123。节点B收到后,如果它配置了ID为0x123的数据帧,就会立即发送。这样,节点A就实现了“按需获取数据”。
实战经验:在诊断应用中,远程帧用得不多。因为UDS协议本身有请求/响应的机制。但如果你在做一些底层监控,比如周期性采集某个传感器的数据,远程帧就很有用了。我曾经用它来减少总线负载——只在需要时才请求数据,而不是让传感器一直发。
4.7 总结与思考
CAN总线的物理层和数据链路层,看似枯燥,但它们是整个车载通信的基石。你想想看,没有仲裁机制,多个ECU同时发送就会乱套;没有位填充,时钟同步就会失效;没有错误帧,一个节点出错就会污染整个网络。
我个人建议,如果你在做Android端的诊断应用开发,至少要学会用CAN工具(比如PCAN、Kvaser)抓取原始报文,然后手动解析帧结构。这样,当你的应用出现“收不到响应”或“总线错误”时,你就能快速定位问题——是ID冲突?是CRC错误?还是物理层信号问题?
下一章,我们会深入CAN控制器的工作原理,以及如何在Android系统中配置SocketCAN接口。到时候,咱们再聊。