搞汽车电子或者嵌入式开发的人,几乎没有人能绕开CAN总线。我第一块带CAN功能的板子,是给某车载控制器做通信测试,当时三个节点死活调不通,示波器上全是乱糟糟的波形,排查了两天才发现是终端电阻的位置放错了。这个教训让我意识到,CAN总线协议看上去不复杂,但物理层、协议层和实际布线之间的坑远比想象中多。这篇文章把我这些年调CAN总线的经验完整梳理一遍,从差分电平到帧结构,从RTR/SRR位到采样点计算,再到节点电路设计和常见故障排查,一次拆透。
这篇内容适合刚接触CAN的工程师、汽车电子方向的学生,也适合那些已经用过CAN但想补全底层原理的人。看完之后你至少能回答这几个问题:CAN为什么用两根线就能可靠通信?仲裁是怎么做到不冲突的?RTR位和SRR位到底有什么用?所谓分支线长度到底指哪一段?还有,拿示波器怎么快速定位总线故障。我会尽量用大白话讲,复杂的地方配例子和现场经验。
1. 物理层的本质:差分信号和终端电阻
1.1 为什么是两根线而不是一根线
CAN总线的物理层不是普通的单端信号,而是差分信号。CANH和CANL两根线,平时都维持在2.5V左右,这个状态叫隐性,逻辑上表示1;当某个节点要发送显性位时,它会同时把CANH拉到约3.5V、把CANL拉到约1.5V,两根线之间形成约2V的压差,逻辑上表示0。接收端真正关心的是CANH减CANL的差值,不是某根线对地的绝对电压。
这个设计带来的好处非常实际。汽车发动机舱里电磁干扰极其严重,点火线圈、电机、继电器开关,哪个都是噪声源。如果信号是单端传输,比如像UART那样靠一根线的高低电平表示数据,外部电磁干扰叠加到线上,接收端很容易误判。而差分传输用两根线,外部干扰在两根线上产生的电压几乎相同,相减之后就被抵消掉了。双绞线进一步强化这个特性,绞得越密,两根线受到的干扰越一致。所以CAN总线必须用双绞线,你拿普通排线替代,短距离低波特率勉强能通,高速长距离就一定出问题。
实际测量时,用示波器看CANH和CANL,静默状态下两条线都在2.5V附近,波形重叠成一条线;有数据时,一条线往上跳、一条线往下跳,拉开约2V。用示波器的数学通道A减B来看,显性位就是约0V变成约2V的脉冲,非常干净。
1.2 终端电阻为什么必须焊在两端
CAN总线的特性阻抗是120欧姆左右,所以ISO 11898标准规定,总线的物理两端必须各接一个120欧姆终端电阻。所谓终端电阻,就是吸收信号到末端时的能量反射。你可以把它想象成水管末端的水锤效应:水在管道里流动,如果末端是堵死的,水撞到墙会反弹回来形成冲击波;如果末端是开放的,水流出去反而没有反射。电信号在传输线上也是这个道理,末端阻抗不匹配,信号就会反射回来,叠加到原信号上形成台阶和振铃。
很多初学者会在每个节点上都放一个120欧姆电阻,这是错的。如果三个节点都接,等效阻抗是40欧姆,显性电平会被拉低,反射也没有被正确吸收,总线负载过重,轻则距离缩水,重则波形直接畸变到没法通信。正确做法是只在总线的最两端各放一个。中间节点不焊终端电阻。
还有一个非常容易踩的坑:终端电阻不一定要在某个“主节点”上,而必须在这个物理线缆的两个端点。如果总线上有三个节点,布线方式是A到B到C串下来,那终端就在A和C;如果是星形布线,两条支路末端各放一个,中间汇聚点不放。判断标准永远是线缆的末端,不是哪个节点更重要。
1.3 并联分支的长度到底指哪一段
热搜里有个问题问“CAN总线并联分支的长度是指哪个长度”,这个确实是实际布线最容易含糊的地方。分支长度指的不是总线主干线的长度,而是从主干线上某个连接点,到节点收发器之间的那一段“T型引出线”,业内叫stub,也就是“总线干道”和“节点车位”之间的连接线。
打个比方,主干线就像一条双向六车道公路,每个节点是从公路拐进停车场的匝道。匝道短,车流进出顺畅;匝道长了,拐弯时车流会堵在主干道上。信号在主干线上传播时,遇到支线末端那一段“死胡同”会产生反射,反射回来的能量正好在采样点附近出现,就会造成误码。支线越长,反射回来得越晚,对采样窗口的影响越大。
实测情况是,500K波特率下,一个节点支线拉到5米左右,总线上就会出现偶发错误帧;把支线缩短到1米以内,问题立刻消失。ISO 11898建议高速应用时支线不超过0.3米,低速(125K以下)可以放宽到几米。注意,支线长度是整个节点接入总线的那段物理引线,不是两个总线接插件之间的距离。如果你在一个连接器处同时引出多个传感器,每个传感器到连接器的线都是独立支线,需要分别控制长度。
所以在做CAN节点布局时,我的习惯是:先确定总线的物理走线路径,所有节点贴着主干线布置,能直接插在主干线上就不要额外拉线。如果实在避免不了长支线,就得降波特率,或者使用CAN中继、集线器设备把总线分段。
2. 协议层的骨架:仲裁、帧结构与RTR/SRR位
2.1 非破坏性仲裁:为什么ID越小越优先
CAN总线是多主总线,任何节点空闲时都可以发送。如果两个节点同时开始发,谁说了算?答案是靠非破坏性仲裁,靠位逻辑的“显性覆盖隐性”来决胜。总线上如果同时出现显性0和隐性1,最终呈现的是显性0;每个发送节点都在发的同时监听总线,一旦发现自己发的是隐性位但总线上读到的是显性位,就知道自己仲裁输了,立即停止发送,转为接收状态。
仲裁的实质是比较帧ID的每一位。ID从高位到低位逐位比较,显性0赢过隐性1,所以ID数值越小,二进制里高位0出现得越早,优先级越高。例如ID=0x123(二进制0001 0010 0011)和ID=0x456(0100 0101 0110),第一位ID为0的那个节点就会获胜。这个机制和以太网的CSMA/CD完全不同,CSMA/CD是冲突后随机退避,会浪费总线时间,而CAN在仲裁过程中不破坏数据,赢的一方继续发完整个帧,输的一方下个总线空闲再重发。所以CAN总线在高负载下依然有确定性,这是它能在汽车这种实时性要求高的场合长期占据主流的根本原因。
2.2 五种帧类型一次说清
CAN协议定义了五种帧:数据帧、远程帧、错误帧、过载帧和帧间隔。日常用得最多的是数据帧。错误帧是节点发现错误时自动发出的,由6个显性位和8个隐性位组成,会把当前正在发的正常帧打断,相当于“总线出了状况,所有人都停一下”。过载帧是在接收端处理不过来时请求延迟下一个数据帧的,实际项目里很少见到,普通调试基本不用管。
| 帧类型 | 作用 | 触发条件 |
|---|---|---|
| 数据帧 | 承载应用数据 | 节点主动发送或响应请求 |
| 远程帧 | 请求对方发送数据 | 某个节点需要数据,但自己不主动发 |
| 错误帧 | 打断错误报文 | 任何节点检测到错误 |
| 过载帧 | 延迟下一个帧 | 接收端过载或间隙过小 |
| 帧间隔 | 帧与帧之间的分隔 | 数据帧/远程帧之后 |
2.3 标准数据帧逐位拆解
先看标准数据帧,它由这些字段组成:
SOF(1个显性位)→ 仲裁场(11位ID + 1位RTR)→ 控制场(IDE、r0、DLC共6位)→ 数据场(0到8字节,按DLC决定)→ CRC场(15位CRC + 1位CRC分隔符)→ ACK场(ACK槽 + ACK分隔符共2位)→ EOF(7个隐性位)→ IFS(3个隐性位)。
SOF表示一帧开始,所有节点通过它同步。ID就是报文标识符,比如0x201代表“车速报文”,0x123代表“发动机状态”。RTR位紧跟在ID后面,它是最容易被问到的位置,含义是Remote Transmit Request。RTR位为0表示这是数据帧,携带数据;RTR位为1表示这是远程帧,不携带数据,只是请求总线上对应ID的节点发送数据。控制场里的DLC四个位表示数据场长度,范围是0到8。
数据场就是真正要传的字节。CRC场是循环冗余校验,用来检查传输过程中有没有位翻转。ACK场很有意思,发送节点在ACK槽发送一个隐性位,总线上任何一个节点只要正确收到了这一帧,就会在这个位置拉一个显性位作为回应。如果发送节点发现ACK槽还是隐性,说明总线上一帧都没人收到,就会报ACK错误。这就是为什么只有两个节点调试时,对端不供电你就发不出去——没人应答。
2.4 扩展帧与SRR位:为什么SRR要设计成隐性
标准帧的ID只有11位,最多2048个标识符。后来需求变多,发展出扩展帧,ID扩展到29位,标准帧和扩展帧的格式必须区别开,于是IDE位承担了这个角色。扩展帧里ID由11位基础ID加18位扩展ID组成,仲裁场里的位顺序是:基础ID → SRR → IDE → 扩展ID → RTR。
SRR是Substitute Remote Request,替代远程请求位。重点来了:SRR这个位的位置,正好对应标准帧里RTR的位置。当初设计扩展帧时,为了尽量兼容标准帧的收发逻辑,在这个位固定放一个隐性1。为什么必须是隐性?因为如果扩展帧在这个位置放一个显性0,当总线上同时出现一个基础ID相同的标准数据帧和扩展数据帧时,扩展帧的SRR=0会赢得仲裁,这就会打破设计意图——协议原始定义希望标准帧当ID相同时优先生效。SRR固定为隐性,再加上紧随其后的IDE也是隐性,这样标准帧RTR位的显性0就能在仲裁中胜过扩展帧的SRR和IDE,标准帧优先。
顺便说一句,扩展帧的RTR并不在这个位置,它跑到扩展ID后面去了。所以同是RTR位,标准帧在ID后第1位,扩展帧在扩展ID后第1位,千万别搞混。调试的时候,很多报文解析软件显示RTR/SRR/IDE几个位时,如果你只对着标准帧的理解去看扩展帧,会一头雾水。
远程帧在实际工程中其实用得不多。原因很简单:第一,远程帧本身不携带数据,它要求目标节点立刻回复,如果两个节点同时发出相同ID的远程帧,目标节点要连续回应多个数据帧,总线负担反而增加;第二,汽车行业的通信矩阵基本都是周期性主动发送,很少用“请求-响应”模式。但考试或者面试经常问RTR位,所以协议层面的理解一定要扎实。
2.5 位填充:连续五个相同位之后的反转
CAN还有一个容易忽略但非常关键的机制:位填充。从SOF到CRC场结束,如果连续出现5个相同电平的位,发送方会自动插入一个相反电平的位。为什么这么做?一是给接收端提供更多电平跳变沿,方便时钟同步;二是避免长串相同电平导致总线长时间没有翻转,进而引起电容积累和直流漂移。
举个例子,原始数据里连续发送0000000,实际总线上会变成0000010000010,插入的第6位和第12位是填充位。接收端收到后会自动把它剔除,还原原始数据。CRC校验、ACK场、EOF这些区域不进行位填充,这是协议明确的规则。所以抓波形时看到一位“多出来”的反转,不要觉得是错误,很可能是正常的填充位。
3. 位时间、采样点与波特率配置
3.1 一个位时间被切成了几段
CAN协议里的一个bit,并不是简单的一段电平,而是一个时间轴,被分成同步段、传播段、相位缓冲段1和相位缓冲段2。同步段固定为1个时间量子tq,它用来让所有节点对齐位边界;传播段用来补偿信号在线缆上的传输延迟;相位缓冲段1和2用来微调采样点位置,吸收各个节点时钟的微小偏差。采样的时刻就在相位缓冲段1结尾和相位缓冲段2开头之间。
采样点的位置非常重要,它决定你在一个bit的什么时刻去读取电平。如果采样点太靠前,信号还没稳定,容易受到反射和边沿毛刺影响;如果太靠后,留给相位缓冲段2的余量不足,波特率误差容忍度就变差。我建议一般场合用75%到87.5%之间,汽车高速CAN常用87.5%。设置完波特率之后,一定要换算一下采样点,别只盯着波特率对不对。
3.2 一个可复用的500Kbps配置计算
以STM32的bxCAN外设为例,假设APB1时钟是36MHz,目标是500Kbps。先计算每个位需要的时间:1/500K等于2微秒。为了得到合理的采样点,我通常先确定位时间由几个tq构成,再反推预分频系数。
我选择9个tq组成一个位时间,其中同步段1tq、相位缓冲段1为7tq、相位缓冲段2为1tq,这样采样点在(1+7)/9约等于88.9%。位时间2微秒除以9,每个tq约222纳秒。那么CAN时钟频率应该是1/222纳秒约4.5MHz,APB1时钟36MHz除以4.5MHz等于8,所以预分频系数是8。注意,部分控制器寄存器里写入的分频值可能比实际系数小1,具体看数据手册,我在这里按实际分频系数说明,配置时以参考手册为准。
CAN_InitTypeDef CAN_InitStructure; CAN_InitStructure.CAN_SJW = CAN_SJW_1TQ; CAN_InitStructure.CAN_BS1 = CAN_BS1_7TQ; CAN_InitStructure.CAN_BS2 = CAN_BS2_1TQ; CAN_InitStructure.CAN_Prescaler = 8; // 实际分频系数8 CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; CAN_Init(CAN1, &CAN_InitStructure);这里有一个通用公式:波特率 = CAN时钟频率 / 预分频 / (同步段 + BS1 + BS2)。改波特率的时候,只要把目标位时间对半分,先定采样点,再反推预分频就行。很多人一上来就改预分频,BS1和BS2保持默认,结果波特率对了但采样点不对,表现为短距离能通、长距离或多节点时偶发错误帧。总线越长,传输延迟越大,传播段不够就会出问题,所以长距离应用建议适当增加BS1,也就是把采样点往后推。
3.3 总线长度与波特率的经验对照
CAN总线的理论极限距离和波特率强相关,因为信号在一公里线缆上的传播延迟接近5微秒,位时间越短,对延迟越敏感。参考ISO 11898-2,1Mbps时理论长度40米,500Kbps大约100米,250Kbps大约250米,125Kbps可以到500米。这里说的都是保守值,实际取决于线缆质量、节点数量、支线长度和终端情况。
| 波特率 | 理论最大总线长度(参考) | 实际工程建议 |
|---|---|---|
| 1Mbps | 40m | 控制在30m以内,支线尽量小于0.3m |
| 500Kbps | 100m | 控制在80m以内,支线小于1m |
| 250Kbps | 250m | 控制在200m以内,支线小于3m |
| 125Kbps | 500m | 控制在400m以内,注意压降和共地 |
| 20Kbps | 1000m以上 | 长距离传输别忘共地,必要时加网关 |
超过这个距离,一般不是完全不通,而是错误帧率明显上升,隐藏表现为三天两头丢一帧。排查的时候先降波特率试一下,如果降速后问题消失,八成是物理层时序裕量不足。
4. 汽车CAN节点电路设计实战
4.1 一个标准节点由哪些部分构成
一个完整的CAN节点硬件基本包括:主控MCU(内部集成CAN控制器,或者外部SPI接MCP2515)、CAN收发器、终端电阻(如果这个节点在总线端点)、共模扼流圈、ESD防护器件、接口连接器,其次是电源轨。汽车上如果是12V供电,还需要DCDC或LDO把电压降到5V/3.3V。
MCU内部有CAN控制器,但它输出的是TXD和RXD这种逻辑电平,不能直接驱动总线。CAN收发器才是连接控制器和物理总线的桥梁,它负责把TXD的逻辑0/1转换成CANH/CANL的差分信号,并把总线上的差分信号还原成RXD的串行数据。所以看原理图时先找收发器,它两边一边接MCU的CAN_TX和CAN_RX,一边接CANH和CANL。
4.2 收发器选型对比与引脚细节
市面上常见的CAN收发器有TJA1050、TJA1042、TJA1051、MCP2562和SN65HVD230。TJA1042是5V供电,带待机模式,应用非常广;TJA1051T/3多一个VIO引脚,可以直接对接3.3V MCU,不用额外做电平转换;MCP2562也是Microchip常见的低成本方案;SN65HVD230是3.3V供电,常用于工业设备,能直接和3.3V MCU连接。
| 型号 | 供电 | 待机/静默控制 | 适合场景 |
|---|---|---|---|
| TJA1042 | 5V | STB引脚 | 车载高速CAN,最常见 |
| TJA1051T/3 | 5V | STB引脚,带VIO | 车载3.3V MCU平台 |
| MCP2562 | 5V | STBY引脚 | 低成本车载/工业 |
| SN65HVD230 | 3.3V | RS引脚控制静默 | 3.3V设备、工业现场 |
这里有一个非常容易被忽略的细节:TXD引脚的电平逻辑和普通TTL相反。CAN控制器的TXD为高电平表示隐性,为低电平表示显性。MCU刚上电时,GPIO如果默认输出低电平,总线会一直被拉成显性,其他节点全都发不了数据。解决办法是:MCU上电期间让TXD引脚保持高阻或上拉高电平,有些板子直接在TXD上串一个10k电阻上拉到收发器VCC,确保MCU进入正常程序之前总线处于隐性状态。
另外,收发器的STB或RS引脚不能悬空,必须由MCU的GPIO控制或者直接接固定电平。悬空的控制引脚可能让收发器莫名进入待机模式,表现为这个节点发不出去、也收不到。
4.3 外围电路:共模电感、TVS和终端电阻
在收发器与总线连接器之间,成熟的汽车节点设计通常会放一个共模扼流圈,常见型号如ACT45B-510-2P,这是一个共模电感,对差分信号阻抗很小,对共模噪声呈现高阻抗,专门用来抑制汽车上来自电机、点火系统的共模干扰。不加它,EMC测试往往很难过。
共模电感靠总线连接器的外侧,还要加TVS二极管。我常用的PESD1CAN是专门为CAN设计的ESD防护器件,钳位电压低、电容小,适合放在连接器引脚附近。TVS的作用是钳位静电放电和浪涌电压,防止收发器被打坏。很多人省掉TVS,短时间也不出问题,但上车之后,静电几次就可能把收发器打穿,表现为主板上的CAN收发器RXD永远输出低电平,总线被错误帧刷屏。
终端电阻前面讲过了,只焊在总线两端节点。如果板子设计时不确定自己是不是端节点,可以预留三个电阻位:两个120欧和一个0欧跳线。做最终端节点时装120欧,中间节点就装0欧直通或干脆不装。千万别做成一上电就三个120欧并联。
4.4 从MCU到总线的信号路径梳理
我把一个端节点的信号路径完整列一下,方便你画原理图时对号入座:
MCU的CAN_TX接到收发器TXD,MCU的CAN_RX接收发器RXD。收发器的TXD和RXD之间有时会加串联电阻,一般10到33欧,用来抑制振铃,但不要加太大,否则边沿变缓。收发器STB脚由MCU的GPIO控制,平时拉低进入正常模式。收发器的CANH/CANL经过共模电感,再经过TVS管,最后到达总线连接器。连接器内侧跨接120欧终端电阻。电源方面,如果需要3.3V MCU和5V收发器混用,注意TJA1042的RXD输出5V电平,3.3V MCU引脚如果不是5V容忍,需要串电阻分压或者用TJA1051T/3的VIO直接供电3.3V。
画PCB时,CANH和CANL尽可能走差分对,等长布线,离DC-DC开关节点远一点。TXD和RXD不要贴着晶振走。总线连接器外壳接参考地,屏蔽线如果用了,单端接地,不要两端都接形成地环路。
5. 通信协议实例:从报文设计到代码收发
5.1 报文ID和信号矩阵的设计思路
协议层看起来是“收发字节”,实际工程里更讲究“信号矩阵”,也就是DBC文件背后的思维方式。一个CAN报文ID是一组信号容易被解析的载体,信号定义包括:起始字节、起始位、位长度、字节序、缩放因子和偏移量。物理值等于原始值乘以缩放因子再加偏移量。
比如我要定义一帧车速报文,ID=0x201,DLC=8。Byte0是计数器,每一帧加1,溢出回0;Byte1是状态字,bit0表示上电自检通过,bit1表示故障报警;Byte2到Byte3组成无符号16位车速信号,低字节在前,缩放因子0.01,也就是原始值500按公式换算成5.00km/h;Byte5到Byte6是电量百分比,原始值按0.1缩放;Byte7是校验和,把前7个字节异或得到。
这种设计的好处是,上位机拿到整个报文后,按照DBC定义就能自动解析出物理量,不用关心字节怎么拼。你在项目里即使不引入DBC工具,也应该照着这个思路设计报文:ID代表“这是什么数据”,数据场只放定义好的信号,不要乱塞。
5.2 发送端代码:周期上报车速报文
发送端以STM32 HAL库为例,先配置好CAN外设,然后周期调用发送函数。关键是填充CAN_TxHeaderTypeDef结构体,包括标准帧ID、帧格式、帧类型和数据长度。发完之后检查返回值,成功会返回邮箱索引,失败说明总线忙或节点进入Bus-off。
CAN_TxHeaderTypeDef txHeader; uint8_t txData[8]; uint32_t mailbox; uint8_t counter = 0; txHeader.StdId = 0x201; txHeader.IDE = CAN_ID_STD; txHeader.RTR = CAN_RTR_DATA; txHeader.DLC = 8; uint16_t rawSpeed = 500; // 500 * 0.01 = 5.00 km/h uint16_t rawSoc = 920; // 920 * 0.1 = 92.0% txData[0] = counter++; txData[1] = 0x03; // bit0 自检通过,bit1 无故障 txData[2] = rawSpeed & 0xFF; txData[3] = (rawSpeed >> 8) & 0xFF; txData[4] = 0; txData[5] = rawSoc & 0xFF; txData[6] = (rawSoc >> 8) & 0xFF; txData[7] = 0; for (int i = 0; i < 7; i++) { txData[7] ^= txData[i]; } if (HAL_CAN_AddTxMessage(&hcan, &txHeader, txData, &mailbox) != HAL_OK) { // 发送失败,查询错误状态 }实际项目中,发送节奏不要用delay死等,应该在定时器中断或者RTOS任务里按周期触发。如果CAN邮箱已满,说明上一帧还没发出去,你的发送周期可能设置得太短,或者总线波特率低于设计值。
5.3 接收端过滤器配置:只收需要的报文
接收端如果不加过滤,所有帧都会进FIFO,MCU每帧都做中断处理,CPU开销大,而且高优先级帧的响应实时性会变差。CAN控制器的硬件过滤器可以帮你提前丢掉不关心的报文。以STM32的bxCAN为例,我要精确匹配0x201这一帧标准数据帧,配置如下:
CAN_FilterTypeDef sFilterConfig; sFilterConfig.FilterActivation = ENABLE; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; sFilterConfig.FilterBank = 0; sFilterConfig.FilterIdHigh = (0x201 << 5) & 0xFFFF; // 标准ID存在高16位的高位段 sFilterConfig.FilterIdLow = 0; sFilterConfig.FilterMaskIdHigh = (0x7FF << 5) & 0xFFFF; // mask全1,必须逐位匹配 sFilterConfig.FilterMaskIdLow = 0; HAL_CAN_ConfigFilter(&hcan, &sFilterConfig);屏蔽位的逻辑是:屏蔽位为1的位必须匹配,屏蔽位为0的位不关心。所以我要精确匹配ID 0x201时,屏蔽寄存器的高11位全部置1。如果我想同时接收0x200到0x2FF这一段,可以把掩码低8位清零,高3位置1,这样ID高3位固定001,低8位任意。灵活运用掩码,一个过滤器就能覆盖一组报文。
接收中断回调里,正常做法是把接收到的报文存入环形队列,由主循环或任务去解析,不要在中断里做复杂处理,否则会频繁打断主程序,导致总线上其他帧得不到及时处理。
5.4 远程帧的软件处理
如果你确实要用远程帧,接收方要识别RTR位。当收到一个RTR=1、ID=0x201的远程帧时,软件需要主动调用发送函数,把对应ID的数据帧发出去。控制器硬件不会自动替你回数据帧,这是很多人第一次调远程帧时发现不工作的原因。
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rxHeader; uint8_t rxData[8]; HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData); if (rxHeader.IDE == CAN_ID_STD && rxHeader.RTR == CAN_RTR_REMOTE && rxHeader.StdId == 0x201) { // 向0x201发送数据帧响应 sendSpeedFrame(); } }实际车载网络几乎不用远程帧,但在工控设备里偶尔会用到,比如主站周期性请求从站上传状态。真要用的时候记住:远程帧的DLC表示你期望对方回复的数据长度,目标节点回的数据帧DLC应当与请求一致。
6. 常见问题排查与实战避坑
6.1 错误帧与Bus-off:电路“罢工”的真相
CAN节点内部有两个错误计数器:发送错误计数器和接收错误计数器。每检测到一个错误,相关计数器加8;每正确收发一帧,计数器减1。当错误计数超过127,节点从主动错误状态降级为被动错误状态,只能发隐性错误标志;超过255,节点直接进入Bus-off离线状态,完全退出总线,不再参与任何通信,直到检测到128次连续11位隐性位并完成恢复流程。
实际调试时,如果你发现总线上错误帧刷屏,先接上CAN分析仪看错误帧的类型。CRC错误说明数据被干扰,但节点间时序可能还行;位错误说明总线上有其他节点同时发送,ID冲突或者终端问题;ACK错误说明发送节点一帧都没被确认,先检查对端是否开机、有没有接终端电阻。
Bus-off节点通常表现为自己发不出去,也不响应任何请求,但其他节点看起来又很正常。这种“单节点消失”最隐蔽。有些收发器或控制器支持快速恢复,有些则需要软件复位CAN外设。我建议在应用层做一个监控:如果很长时间没有收到某个节点的心跳,主节点可以尝试对该节点发一个复位指令,让它主动重新初始化CAN控制器。
6.2 示波器排查:一抓一个准
没有CAN分析仪时,示波器也能做大量排查。把通道1接CANH,通道2接CANL,用数学通道做A减B。抓到的正常波形应该是:空闲时差分电压0V,帧开始后有规则脉冲,显性位幅度约2V,脉冲边沿陡峭,没有台阶和振铃。
如果显性电平明显偏低,比如只有1.2V,大概率是终端电阻位置不对,或者节点数量远超收发器驱动能力,又或者总线有轻微短路。如果波形边沿有台阶,尤其是长总线上,要么是终端电阻没接,要么是支线过长。如果总线上静止时不是0V,说明有节点在发错误帧或者某个节点TXD卡死,逐一断开节点排查。
还有一个土办法,断电状态下用万用表量整条总线的CANH和CANL之间的直流电阻,正常应该大约60欧,就是两个120欧并联。如果量出来是120欧左右,说明只有一端有终端电阻;如果量出来接近0欧,有一个节点短路了;如果接近40欧,说明有三个终端电阻并联。这个检查不需要上电,特别适合现场快速判断。
6.3 共地问题:差分信号也怕参考电位漂移
CAN用的是差分信号,抗共模干扰能力强,但这不意味着各节点可以不共地。收发器内部的差动输入级有共模输入范围,比如TJA1042的范围内在-12V到+12V,但如果两个节点距离远,地线上的电流造成两个节点GND之间有几十伏的电位差,超出范围一样收不到数据。
现场典型的故障是:单独测试每个节点都正常,两个节点一接总线就不通,或者一天丢帧几次。量一下两个节点电源地的直流电压差,如果超过1V就要留意,超过5V基本必出问题。解决办法是总线节点之间要有共同的参考地,用带屏蔽层的双绞线时屏蔽层单端接地,或者在系统设计时把各节点的电源地统一。
6.4 故障定位速查表
| 现象 | 可能原因 | 优先排查 |
|---|---|---|
| 总线上所有节点都发不出数据 | 某节点TXD被拉低,总线持续显性 | 逐个断开节点,量CANH/CANL差分电压 |
| 发送失败,错误帧计数器不断涨 | 终端电阻缺失、支线过长、采样点不当 | 万用表量CANH-CANL电阻,示波器看波形 |
| ACK错误 | 总线上没有其他正常节点或接收节点未上电 | 至少接两个节点,用CAN分析仪监听 |
| CRC错误频繁 | 干扰、线缆质量差、共地不良 | 检查双绞线、接地、屏蔽层处理 |
| 单个节点消失,其他节点正常 | 该节点进入Bus-off | 复位该节点,查找错误根源 |
| CANH和CANL对地电压异常 | 收发器损坏或总线短路 | 断电后量CANH和CANL对地电阻 |
| 长距离通信偶发丢帧 | 位时间裕量不足、传播段不够 | 提高采样点,检查终端和支线 |
最后说一个我现在的职业习惯:每次测试CAN节点前,先断电用万用表量CANH和CANL之间的电阻,确认是60欧左右再上电;上电后先量CANH和CANL对地电压,确认总线处于隐性状态再把节点接入。这两步加起来不到一分钟,但能排除掉绝大多数物理层的低级问题。调CAN这些年,真正复杂的协议问题很少,大部分故障都是终端电阻、支线和共地这三个环节,快准狠地把物理层确认清楚,再谈协议层,能省掉大量无意义的抓包时间。