2. CAN总线基础:从起源到仲裁,一个老工程师的实战笔记
大家好,我是你们这趟车载网络之旅的向导。今天咱们聊聊CAN总线。说实话,在我十几年的车载网络生涯里,CAN总线就像一位老朋友,虽然年纪大了点,但江湖地位稳得很。很多新入行的朋友觉得CAN总线“老”,想跳过它直接学以太网。我的建议是——千万别。你想想看,一辆车上几十个ECU,大部分还在用CAN通信,不懂CAN,你连故障都定位不了。
2.1 CAN协议的起源:为什么会有它?
上世纪80年代,汽车里的电子系统开始变多。空调、ABS、发动机控制……每个系统都自己拉一堆线。结果呢?线束比发动机还重,故障率也高。德国Bosch公司看不下去了,1983年开始研发一种新的串行通信协议,1986年正式发布——这就是CAN(Controller Area Network)。
它的设计目标很明确:
- 高可靠性:能在电磁干扰严重的车里稳定工作
- 实时性:关键消息(比如刹车信号)不能有延迟
- 低成本:两根线就能搞定所有节点通信
我记得第一次接触CAN总线是在一个国产商用车项目上。当时客户抱怨“刹车灯偶尔不亮”,查了三天没找到原因。后来我用示波器抓CAN总线波形,发现是某个节点发送了错误帧,把总线给“堵”住了。嗯,从那以后,我养成了一个习惯——任何CAN项目,第一件事就是看总线负载率和错误帧计数。
2.2 CAN物理层特性:两根线,不简单
CAN总线物理层说白了就是一对差分线——CAN_H和CAN_L。为什么用差分?抗干扰啊!你想想看,发动机点火时电磁干扰多强,单端信号早就被淹没了。差分信号呢?干扰同时作用在两根线上,接收器只看差值,干扰就被抵消了。
关键参数我列个表,大家记一下:
| 参数 | 高速CAN(ISO 11898-2) | 低速CAN(ISO 11898-3) |
|---|---|---|
| 最大速率 | 1 Mbps | 125 kbps |
| 总线长度(@最大速率) | 40 m | 500 m |
| 显性电平(逻辑0) | CAN_H=3.5V, CAN_L=1.5V | CAN_H=3.6V, CAN_L=1.4V |
| 隐性电平(逻辑1) | CAN_H=2.5V, CAN_L=2.5V | CAN_H=1.75V, CAN_L=3.25V |
| 终端电阻 | 120Ω(两端各一个) | 不需要 |
2.3 CAN报文帧结构:五种帧,各有各的活
CAN总线上一共五种帧:数据帧、远程帧、错误帧、过载帧、帧间隔。咱们重点说前四种,帧间隔比较简单,就是帧与帧之间的休息时间。
2.3.1 数据帧——最常用的“快递员”
数据帧负责把数据从一个节点送到另一个节点。结构分为标准帧(11位ID)和扩展帧(29位ID)。我个人习惯在CANoe里配置时,能用标准帧就不用扩展帧——少传18位,总线负载率能降不少。
数据帧结构如下:
- 帧起始(SOF):1位显性,告诉总线“我要发数据了”
- 仲裁场:11位ID(标准帧)或29位ID(扩展帧)+ RTR位
- 控制场:IDE位、保留位、DLC(数据长度,0-8字节)
- 数据场:0-8字节数据,这才是真正的“货”
- CRC场:15位CRC + 1位CRC界定符,用来检错
- ACK场:接收节点在这里回应“我收到了”
- 帧结束(EOF):7位隐性,表示帧结束
// 一个标准CAN数据帧的位序列示例(ID=0x123,数据=0xAA 0xBB)
// SOF + ID(11bit) + RTR + IDE + r0 + DLC(4bit) + Data(16bit) + CRC(15bit) + CRC Del + ACK + ACK Del + EOF(7bit)
// 0 + 0x123 + 0 + 0 + 0 + 0x02 + 0xAABB + CRC + 1 + 0 + 1 + 1111111
2.3.2 远程帧——只问不拿
远程帧长得跟数据帧几乎一样,区别在于RTR位是隐性(1)。它的作用是:一个节点请求另一个节点发送数据。比如发动机ECU发一个远程帧,请求“水温传感器,把你的数据发给我”。
这里有个细节:远程帧没有数据场,但DLC字段必须填上期望接收的数据长度。我见过不少新手在这里栽跟头——DLC填0,结果对方节点不知道要发多少字节。
2.3.3 错误帧——总线的“警报器”
错误帧由6个连续的显性位(错误标志)和8个隐性位(错误界定符)组成。任何节点检测到错误,就会主动发送错误帧,通知所有节点“刚才那帧有问题,别用了”。
错误类型分五种:
- 位错误:发送节点发现总线上的电平跟自己发的不一样
- 填充错误:连续5个相同位后没有插入反相位
- CRC错误:接收节点计算的CRC跟发送的不一致
- 形式错误:帧格式不对(比如EOF不是7个隐性位)
- ACK错误:没有节点在ACK槽发送显性位确认
2.3.4 过载帧——总线的“请稍等”
过载帧跟错误帧结构类似,但用途不同。当接收节点来不及处理数据时,它会发送过载帧,告诉发送节点“慢一点,我消化不了”。过载帧由6个显性位(过载标志)和8个隐性位(过载界定符)组成。
说实话,我在实际项目中很少见到过载帧。因为现在的MCU处理速度都很快,除非总线负载率超过80%或者节点中断响应太慢,否则不会触发过载。如果你在项目中频繁看到过载帧,先检查一下软件的中断优先级配置。
2.4 CAN总线仲裁机制:谁先说话,谁说了算
这是CAN总线最巧妙的设计之一。多个节点同时发送时,怎么决定谁先发?答案是:ID越小,优先级越高。
仲裁过程是这样的:
- 所有想发送的节点同时发送SOF(显性位)
- 然后逐位发送ID。如果某个节点发送隐性位(1),但总线上是显性位(0),说明有更高优先级的节点在发,这个节点立即停止发送,转为接收
- 直到只剩下一个节点,它独占总线,完成数据发送
说白了,这就是一个“非破坏性逐位仲裁”机制。高优先级的节点不会被打断,低优先级的节点自动退让。
我画了一张图,帮大家理解这个过程:
你看,节点A的ID是0x100(二进制0001 0000 0000),节点B是0x200(0010 0000 0000),节点C是0x300(0011 0000 0000)。在ID的第8位(从高位算起),节点A发送0(显性),节点B和C发送1(隐性)。总线被节点A拉成显性,B和C检测到“自己发的跟总线不一样”,立刻退出。这就是仲裁的整个过程。
- ID越小,优先级越高。0x000是最高优先级,0x7FF是最低(标准帧)
- 仲裁过程不破坏任何数据,高优先级节点“无痛”获胜
- 低优先级节点自动转为接收,不会丢失数据
- 远程帧的RTR位是隐性,所以数据帧(RTR=0)优先级高于远程帧(RTR=1)
最后说一个我自己的经验:设计CAN网络时,一定要把关键信号(刹车、油门、安全气囊)分配最小的ID。我曾经在一个项目中,把车窗控制信号分配了0x100,刹车信号分配了0x700。结果呢?刹车信号经常被车窗信号“堵”在后面。虽然CAN仲裁机制保证了高优先级先发,但ID分配不合理,关键信号的实时性就保证不了。后来我重新规划了ID分配表,把安全相关信号全部放在0x000-0x1FF区间,舒适性信号放在0x200-0x3FF,诊断信号放在0x700以上。从此再没出过问题。