CAN总线基础:从协议起源到帧结构拆解
大家好,我是老张。今天咱们聊聊CAN总线的基础。说实话,我入行那会儿,CAN总线已经挺成熟了。但真正吃透它,还是在一次现场排查中——一辆车的网关莫名其妙丢报文,查了三天,最后发现是物理层电平出了问题。从那以后,我养成了一个习惯:不管多忙,基础概念必须反复夯实。
这一章,咱们就从CAN的起源讲起,一路拆到它的帧结构。你想想看,搞懂这些,后面做测试才能心里有底。
2.1 CAN协议的起源:为什么会有它?
CAN,全称Controller Area Network,控制器局域网。上世纪80年代,汽车里的电子设备越来越多。空调、ABS、发动机控制……每个模块都自己拉一堆线。结果呢?一辆车线束重达几十公斤,故障率还高。
德国博世公司看不下去了。1986年,他们推出了CAN协议。核心目标就一个:用两根线,让所有节点都能通信。
CAN协议的设计初衷:
- 多主通信:任何节点都能主动发消息,不用等主机问。
- 实时性高:优先级高的报文,抢占总线的时间极短。
- 可靠性强:错误检测机制很完善,坏帧自动重发。
- 成本低:两根双绞线,就能挂几十个节点。
我记得第一次看CAN的原始论文,里面有一句话让我印象很深:“总线上的每个节点,都能同时发送和接收。” 当时觉得不可思议。后来才明白,这背后就是差分信号的功劳。
2.2 CAN物理层特性:差分信号与总线电平
物理层,说白了就是信号怎么在线上跑。CAN用的是差分信号。什么意思?两根线,一根叫CAN_H,一根叫CAN_L。信号不是靠对地电压,而是靠两根线之间的电压差来传递。
为什么要这么设计?抗干扰。你想想看,车里的电磁环境多恶劣。电机、点火线圈、大功率电器……随便一个都是干扰源。如果单端信号,地电平一抖,信号就废了。差分信号不一样——干扰同时作用在两根线上,电压差基本不变。这就是CAN能在车里活下来的原因。
我的经验: 做测试时,如果发现CAN通信时好时坏,先别急着查软件。拿示波器量一下CAN_H和CAN_L的差分电压。我遇到过好几次,都是因为线束太长、终端电阻没接好,导致差分幅值不够。
CAN总线的电平有两种状态:显性(Dominant)和隐性(Recessive)。
| 状态 | CAN_H电压 | CAN_L电压 | 差分电压 | 逻辑值 |
|---|---|---|---|---|
| 隐性 | 2.5V | 2.5V | 0V | 1 |
| 显性 | 3.5V | 1.5V | 2V | 0 |
注意看,隐性时两根线都是2.5V,差分电压为0,逻辑上代表“1”。显性时CAN_H拉到3.5V,CAN_L拉到1.5V,差分电压2V,逻辑上代表“0”。
这里有个关键点:显性位会覆盖隐性位。如果两个节点同时发,一个发显性(0),一个发隐性(1),总线最终呈现显性(0)。这就是CAN总线仲裁的基础。我刚开始做测试时,总搞不明白为什么优先级低的节点会自动退出发送。后来用示波器一看,瞬间懂了——总线电平被高优先级的显性位“拉”过去了。
避坑指南: 我曾经在测试中遇到一个奇怪现象:总线偶尔出现毛刺,导致CRC错误。查了半天,发现是CAN_H和CAN_L的线长不一致,差了几厘米。差分信号对线长匹配很敏感,尤其是高速CAN(1Mbps)。布线时,两根线必须等长、紧贴走。
2.3 CAN数据帧结构:逐位拆解
CAN的报文,叫数据帧。标准帧有11位ID,扩展帧有29位ID。咱们先讲标准帧,这是最常用的。
一个完整的数据帧,由以下几个字段组成:
- SOF(Start of Frame):帧起始,1位显性位。告诉所有节点:我要发数据了。
- ID(Identifier):标识符,11位。决定优先级,数值越小优先级越高。
- RTR(Remote Transmission Request):远程帧请求,1位。显性表示数据帧,隐性表示远程帧。
- IDE(Identifier Extension):标识符扩展位,1位。显性表示标准帧,隐性表示扩展帧。
- DLC(Data Length Code):数据长度码,4位。表示数据场有多少字节,范围0~8。
- Data:数据场,0~8字节。实际要传输的数据。
- CRC(Cyclic Redundancy Check):循环冗余校验,15位+1位分隔符。检查数据是否出错。
- ACK(Acknowledge):应答场,2位。接收节点在ACK Slot位拉低电平,表示收到。
- EOF(End of Frame):帧结束,7位隐性位。
下面这张图,是我自己画的CAN数据帧结构。你看一眼,比文字直观多了。
2.4 关键字段详解
ID与优先级
ID是CAN帧的灵魂。11位ID,范围0x000~0x7FF。数值越小,优先级越高。为什么?因为仲裁时,显性位(0)会覆盖隐性位(1)。ID高位先发,谁的ID高位先出现0,谁就赢。
举个例子:节点A发ID=0x100(二进制0001 0000 0000),节点B发ID=0x200(二进制0010 0000 0000)。高位比较,第3位A是0,B是1。A的显性位把B的隐性位覆盖了,B检测到总线电平与自己发送的不一致,立刻退出。A继续发完。
我的习惯: 设计CAN网络时,我会把关键控制报文(比如刹车、转向)分配小ID,把诊断、配置等非实时报文分配大ID。这样能保证紧急信息永远优先通过。
RTR与远程帧
RTR位用来区分数据帧和远程帧。数据帧的RTR=0(显性),远程帧的RTR=1(隐性)。远程帧有什么用?一个节点可以发远程帧,请求另一个节点发送数据。比如,发动机ECU可以发一个远程帧,请求ABS发送当前轮速。
不过说实话,我在实际项目中很少用远程帧。大部分场景都是节点主动周期性发送数据。远程帧用得不好,反而容易造成总线拥堵。
DLC与数据场
DLC是4位,范围0~8。注意,DLC只能表示0到8,不能表示更多。数据场长度由DLC决定,但实际发送的字节数可以少于DLC。比如DLC=8,但只发了3个有效字节,剩下5个字节填充0。不过我不建议这么干——不规范,容易让测试人员抓狂。
CRC校验
CRC是15位,加上1位CRC分隔符(隐性)。CRC的生成多项式是固定的:x^15 + x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1。发送节点计算CRC,接收节点重新计算。如果对不上,就报错。
我曾经遇到一个案例:某款ECU的CRC计算逻辑有bug,导致特定ID的报文总是CRC错误。排查了三天,最后发现是软件工程师把CRC初始值写错了。嗯,从那以后,我每次测试都会专门写个脚本,随机发报文验证CRC。
ACK应答
ACK场有2位:ACK Slot和ACK分隔符。发送节点在ACK Slot发隐性位。如果接收节点正确收到了帧,它会在ACK Slot拉低电平(显性)。这样发送节点就知道至少有一个节点收到了。
如果发送节点在ACK Slot检测到隐性位,说明没有节点应答。它会立即重发。这就是CAN的自动重发机制。
避坑指南: 我曾经在测试中发现,某个节点总是发完一帧就重发,导致总线负载飙升。用CAN分析仪抓包一看,ACK Slot一直是隐性。查到最后,发现是接收节点的CAN控制器初始化没完成,根本没监听总线。所以,测试时一定要确认所有节点的CAN控制器都正常启动了。
2.5 总结一下
这一章咱们聊了CAN的起源、物理层的差分信号和总线电平,还有数据帧的每个字段。你可能会觉得内容有点多。没关系,CAN协议就是这样——细节决定成败。
我个人建议,学CAN最好的方法就是动手。拿一个CAN分析仪,接上两个节点,发几帧数据,用示波器看波形。亲眼看到SOF的显性位、ID的二进制序列、ACK Slot被拉低……这些概念就刻在脑子里了。