先抛个观点:很多工程师把 CAN 总线当串口用,波特率配置好就直接往上扔数据,真出了问题却无从下手。这不怪你,市面上讲 CAN 的资料要么是协议规范翻译腔,要么是开发板的流水账,真正把"为什么这么设计"讲清楚的很少。这篇我打算换一个讲法,从底层物理信号一路聊到汽车电路图上的实际报文,把 CAN 总线协议里那些没人细讲的规则——比如 RTR 位、SRR 位、仲裁优先级、并联分支长度的口径——一次讲透。适合三类人:刚接触车载网络的学生,需要调试量产车网络的工程师,以及想在工控项目里复用 CAN 的嵌入式开发者。
1. 先建立总线直觉:CAN 为什么能扛住车载的恶劣环境
1.1 多主不是"轮流发言",而是"抢着发言"
我们被 UART、SPI、I2C 这类主从协议训练得太久了,潜意识里总认为总线上必须有个"领导"来分配机会。CAN 最颠覆认知的地方就是:它根本没有主从关系,任何节点只要总线空闲,随时可以发起发送。
这种设计带来的问题是:如果两个节点同时发送怎么办?CAN 的答案是"硬件仲裁"——先发的不一定赢,ID 小的才赢。你可以把它类比成一间会议室里同时有几个人开口,规则不是按音量大小,而是按身份等级,级别低的人一听到级别高的在说话,立刻闭嘴等下一轮。这个机制不需要软件介入,完全由收发器和控制器在物理层自动完成。
这带来一个直接好处:新增节点不需要改总线配置,插上去就能用。机动车的 ECU 数量逐年增加,从 20 个到 100 多个,CAN 这种"开放式多主"架构是它能活到今天的主要原因。
1.2 差分电平与显性/隐性:抗干扰的物理本钱
CAN 物理层用的是两根线:CAN_H 和 CAN_L。通信时看的不是某根线对地的电压绝对值,而是两根线之间的压差,这就是常说的差分信号。
具体电平关系是:
| 逻辑状态 | CAN_H 电压 | CAN_L 电压 | 差分电压 |
|---|---|---|---|
| 隐性(逻辑 1) | 约 2.5V | 约 2.5V | 约 0V |
| 显性(逻辑 0) | 约 3.5V | 约 1.5V | 约 2V |
注意一个反直觉的点:CAN 里的"显性"是逻辑 0,不是逻辑 1。这个约定直接影响仲裁机制,以后看到协议栈里的"显性优先"要立刻反应成"0 优先"。
差分信号之所以在汽车里所向披靡,是因为干扰通常是共模的——点火线圈、电机刷火、大功率负载切换产生的噪声会同时耦合到两根线上,两根线一起抬压或者一起降压,但差值基本不变。接收端只看差值,等于把干扰给滤掉了。这是单端信号(比如 UART 的 TX/RX)很难做到的。
如果哪天你发现 CAN_H 和 CAN_L 接反了,现象是节点全部进入总线关闭状态或大量报错帧。因为差分极性反了,发送端发出的显性电平到达接收端变成负差分,逻辑完全翻转。
1.3 120 欧姆终端电阻:为什么必须落在线的两端
双绞线在高速信号下是有特征阻抗的,典型值在 100 到 130 欧姆之间。CAN 规范要求在线缆物理两端各并联一个 120 欧姆电阻,来吸收到达线端的信号能量,防止反射。
很多人以为这两个电阻只是"加上去就能工作",其实有两个细节容易被忽略。
第一个细节:终端电阻必须放在总线的最远两端,而不是哪个节点方便就放哪个。如果网络拓扑是 A 端 ECU1、中间 ECU2、B 端 ECU3,那么电阻放在 ECU1 和 ECU3 内部,ECU2 不加。测量 CAN_H 和 CAN_L 之间的直流阻值,正常应接近 60 欧姆(两个 120 欧并联)。如果量到 120 欧姆,说明只有一端有终端电阻;如果量到 0 或接近 0,说明某个节点内部被击穿或错误并联了电阻。
第二个细节:终端电阻不能到处乱加。有人为了"保险",在每个节点板上都焊了 120 欧,结果总线等效负载变成 30 欧、20 欧,收发器输出电流增大,差分电压被拉低,隐性电平都回不去,波形完全畸变。我见过好几起"加电阻加到总线瘫痪"的案例,都是好心办坏事。
2. 标准帧与扩展帧的帧结构:RTR 位和 SRR 位的真实分工
2.1 一帧数据从头到尾:SOF 到 EOF 的每个字段
CAN 2.0A 标准帧的结构可以用一张表说清楚:
| 字段 | 位数 | 作用 |
|---|---|---|
| SOF | 1 | 帧起始,显性位,标志总线进入忙状态 |
| 标识符 ID | 11 | 帧身份,同时决定仲裁优先级 |
| RTR | 1 | 远程传输请求位,区分数据帧与远程帧 |
| IDE | 1 | 标识符扩展位,标准帧固定为显性 |
| r0 | 1 | 保留位 |
| DLC | 4 | 数据长度,0 到 8 个字节 |
| Data | 0-64 | 用户数据 |
| CRC | 15+1 | 校验和,含 1 位隐性界定符 |
| ACK | 2 | 确认槽 + 界定符 |
| EOF | 7 | 帧结束,全部隐性位 |
CAN 2.0B 扩展帧则是另一种布局:SOF 后跟 11 位基础 ID,然后是一个 SRR 位、一个 IDE 位、18 位扩展 ID,再是 RTR、r1、r0、DLC。所以扩展帧的标识符总共是 29 位。
这里有个初学者容易搞混的地方:标准帧格式里数据最多只能 8 字节,扩展帧也是 8 字节。CAN 协议没有"长的扩展帧数据"这种说法,29 位 ID 和更长的数据长度是两个维度。如果需要一次传超过 8 字节的负荷(比如固件升级),要在应用层自己分包或者用 ISO-TP 传输协议。
2.2 RTR 位:数据帧与远程帧的切换点
RTR 位位于 ID 之后。它在标准帧和扩展帧里都存在,但位置不同:标准帧里紧跟 11 位 ID;扩展帧里紧跟 29 位 ID 的最后一段。
RTR 的取值意义很直接:
- RTR = 0(显性):这是数据帧,后面带 Data 字段。
- RTR = 1(隐性):这是远程帧,不带数据字段,用来向某个 ID 的节点"请求一帧数据"。
远程帧在工程里最典型的应用场景是:一个低功耗节点平时不主动上报,中央控制器想知道它的状态时,发一个远程帧把它"叫醒"应答。当然现在很多设计直接改用周期上报加网关转发,但远程帧仍然在诊断场景里大量使用,比如 UDS 的请求响应模型和它思路类似。
需要注意一个坑:远程帧的 DLC 必须写入你期望对方回复的数据长度,而不是随便填 0。比如你想请求 4 字节的转速数据,远程帧 DLC 要写 4,对方收到后才能按正确长度回复。很多初学调试远程帧毫无反应,查了一圈发现是 DLC 写错。
2.3 SRR 位:扩展帧里那个"牺牲自己"的隐性位
扩展帧的仲裁场里有一个叫 SRR 的位,全称是 Substitute Remote Request,翻译过来叫"替代远程请求位"。它的名字很容易让人误解成"远程请求的替代品"。
真实作用是这样的:为了保证 29 位扩展帧能兼容 11 位标准帧的仲裁,扩展帧在原本 RTR 的位置放了一个恒为隐性的位,这个位就是 SRR。协议规定它必须是隐性(逻辑 1),不允许发送显性。
为什么要这么设计?设想一个 11 位标准帧 ID 为 0x123,和一个 29 位扩展帧基础 ID 也是 0x123 同时发送。如果扩展帧在这个位置放 RTR 且是显性,那么两者仲裁结果不可控;现在扩展帧这个位恒隐性,只要标准帧发的是数据帧(RTR 显性),标准帧立刻赢下仲裁。也就是说,SRR 是一个"主动让位"的位,它保证同 ID 情况下标准帧总是优先,维持向后兼容性。
这也是面试里很爱问的一道题:扩展帧里 SRR 是隐性还是显性?标准帧的 IDE 是显性还是隐性?答案分别是:SRR 恒隐性,标准帧 IDE 恒显性。顺着这个逻辑走,你就能理解为什么扩展帧仲裁时遇到 SRR 位会"输给"同 ID 的标准帧。
2.4 CRC 校验域与 ACK 应答:谁在确认谁
CAN 帧的 CRC 是 15 位,覆盖从 SOF 到数据字段的所有位,计算多项式固定。这 15 位后面还跟着一个隐性的 CRC 界定符。接收节点算出来的 CRC 不匹配,会立刻发出错误帧,把整帧通信都打断。
但最值得玩味的是 ACK 槽。发送节点在 ACK 槽位发送的是一个隐性位,它不自己确认自己;任何接收到这帧且 CRC 校验通过的节点,都会在 ACK 槽把总线拉成显性。如果发送节点在 ACK 槽采样到的还是隐性,说明总线上没有任何其他节点正确收到了这帧,它会报一个 ACK 错误并尝试重发。
我之前调试时犯过一个经典错误:用单个闭环节点自发自收,配置成不回显 ACK 的模式,然后抱怨"为什么 TX 一直报错"。记住,CAN 的"收到确认"不是发送方自己确认,而是总线上"他者"的确认。这在示波器上看 ACK 槽波形时特别明显——如果 ACK 槽始终是平的隐性电平,说明总线上只有一个节点在说话,其他节点要么没接、要么配置错误、要么在报错风暴里出不来。
3. 仲裁过程拆解:为什么 ID 越小越优先,远程帧为什么输给数据帧
3.1 线与逻辑:显性位在物理上"压过"隐性位
仲裁机制能成立,依赖的是物理层的"线与"特性:多个节点同时发送时,只要有一个节点输出显性(CAN_H 高、CAN_L 低差压 2V),总线上呈现的就是显性电平;只有所有节点都输出隐性,总线才表现为隐性。
也就是说,隐性电平是"妥协"的结果,显性电平是"强者"的结果。在 CAN 总线上,广播信道被多路访问,但冲突不是通过丢弃数据来解决,而是通过位级的"霸道"来完成优先级仲裁。
这个特性在调试时还有一个很实用的意义:你可以用万用表静态量 CAN_H 和 CAN_L 的对地电压来大致判断总线状态。总线空闲时,两根线都接近 2.5V;如果有持续的显性电平压着,说明有节点异常占用了总线。我曾经靠这个现象排查出一个看门狗失效、死循环疯狂发帧的节点——总线平均电压被拉低到 1.8V 以下。
3.2 一场完整仲裁的推演:0x200 如何赢过 0x300
举个例子,两个标准帧数据帧同时上线,一个 ID 是 0x300(二进制 011 0000 0000),另一个是 0x200(二进制 010 0000 0000)。注意 CAN 发送是从最高位开始逐位比较:
从 bit10 开始比,两个节点第一拍都发 0,第二拍都发 1,第三拍 0x300 发 1(隐性),0x200 发 0(显性)。总线上这个 bit 呈现显性,0x300 的发送节点发现自己发出的隐性位和总线显性不一致,立刻判断仲裁失败,转为接收状态,停止发送后续内容。0x200 接着发完剩余所有位。
所以结论很直接:标识符的数值越小,二进制高位的 0 出现得越早,越容易在仲裁中胜出。也就是说,CAN 网络中 ID=0x001 的节点永远比 ID=0x7FF 的节点优先。
还有一类仲裁容易被忽略:RTR 位参与仲裁。两个节点同时竞争总线,一个发 ID 相同的远程帧(RTR 隐性),另一个发 ID 相同的数据帧(RTR 显性),数据帧会在 RTR 位胜出。这背后的工程逻辑很实际:如果远程帧赢了,它后面紧跟着还要再发数据帧,等于总线干了两趟活;而数据帧赢了,直接就把数据送完,总线上少一次竞争,实时性更好。所以协议倾向于让带数据的帧优先。
3.3 工程上怎么分配 ID 优先级
理解仲裁机制之后,ID 分配就成了一件有策略的事,千万别随手赋 ID。我一般按这个思路排:
| 优先级 | ID 范围 | 典型用途 |
|---|---|---|
| 最高 | 0x001 - 0x0FF | 安全相关:安全气囊、制动协调、碰撞信号 |
| 高 | 0x100 - 0x1FF | 动力总成:发动机转速、扭矩、档位 |
| 中 | 0x200 - 0x2FF | 底盘/车身:车灯、车窗、门锁 |
| 低 | 0x300 - 0x3FF | 舒适/信息娱乐:空调、多媒体 |
| 很低 | 0x700+ | 诊断报文 UDS、网络管理 |
一条核心原则:对实时性最敏感、影响安全的数据给最小 ID。因为仲裁延迟代价最大的是安全关键报文。你在车身舒适域里乱塞一个 ID 0x003 的诊断帧,一旦和制动信号撞车,制动信号可能被挤到下一轮,这在汽车上不可接受。
工业现场 CAN 也是同理。设备控制字、伺服状态字这类周期性实时数据一定要放在低 ID 段,参数配置、固件升级这样的大块非实时流量放高 ID 段,避免大流量阻塞高优先级控制帧。
4. 位时序里藏着通信速度与总线长度的矛盾
4.1 波特率、时间量子与采样点配置
CAN 的一个位时间不是一整块,而是被切成若干份,每一份叫一个时间量子(TQ)。一个位时间通常由四段组成:
- 同步段(Sync Segment):固定 1 TQ,用于同步总线上的各个节点。
- 传播段(Propagation Segment):用于补偿物理层和线缆的传播延迟。
- 相位缓冲段 1(PS1)
- 相位缓冲段 2(PS2)
采样点位于 PS1 和 PS2 的交界处。工程上常用两个参数来描述这个边界:TSEG1(Sync + Prop + PS1 的长度)和 TSEG2(PS2 的长度),采样点在 TSEG1 的末尾。
举个实例。假设微控制器 CAN 外设时钟是 16MHz,想跑 500kbps。先确定一个位时间 2 微秒。如果分频系数 BRP 设为 3,那么 TQ = (3+1) / 16MHz = 0.25 微秒,一个位时间需要 8 个 TQ。接下来分配:Sync=1,PropSeg=3,PS1=2,PS2=2,共 8 个 TQ。采样点位置 = (1+3+2) / 8 = 75%。
这个 75% 不是随便定的。CAN 规范普遍要求采样点在位时间的 75% 到 85% 之间,原因有二:一是给时序漂移留足余地,二是给传输延迟留出缓冲。采样点太早,远端节点的边缘误差容易落在采样点之后;采样点太晚,PS2 没剩多少缓冲,时钟漂移和干扰更容易导致错误。
4.2 总线长度上限是怎么来的:往返延迟与采样点的博弈
位时序和总线长度的关系很直接。信号沿双绞线的传输速度大约 5 纳秒每米。一个 500kbps 的位时间是 2 微秒,1Mbps 的位时间是 1 微秒。如果总线太长,发送端的位边沿传到最远端节点时已经晚了,远端节点按自己的采样点去采样,采到的可能是上一个位或者一个畸变的边沿。
经典经验数据是这样的:
| 波特率 | 理论最大总线长度(大约值) |
|---|---|
| 1 Mbps | 40 米 |
| 500 kbps | 130 米 |
| 250 kbps | 270 米 |
| 125 kbps | 530 米 |
很多人在设计阶段只关心"波特率能不能跑",不关心总线长度,结果现场一拉线就是 100 米,跑 500kbps 各种错误帧。解决方向有两条:要么降波特率,要么把采样点往后调。降到 125kbps、采样点适当调到 80% 附近,很多 500 米以内的现场总线都还能稳定工作。
4.3 边沿时间与线缆质量:一个容易被忽略的变量
位时序的配置和计算通常都以"理想方波边沿"为前提,但实际线缆、连接器和节点收发器的上升沿/下降沿都是有限斜率的。边沿越缓,采样点附近的电压不确定性越大,尤其是高波特率下,一个位时间只有 1 微秒,信号边沿如果占了 200 纳秒,留给采样的稳定窗口就非常窄了。
这里有一个实测技巧:用示波器看 CAN_H 与 CAN_L 的差分波形,正常情况下应该是陡峭的方波。如果边沿变斜,或者波形的拐角出现明显振铃,先别急着调波特率,去检查终端电阻是否在正确位置、有没有异常的分支线缆、连接器接触是否良好。我在调试一个长线工程时,发现高速 CAN 下总线总是间歇性异常,最后查到原因是一个防水接头里的端子松动,接触电阻不稳定,波形时好时坏。这种问题用示波器一眼就能看出来,查波特率配置反而越陷越深。
5. 并联分支长度该算哪一段:一个被问烂却常答错的问题
5.1 分支长度的口径:到底量哪一段
"CAN 总线并联分支的长度是指哪个长度"这个问题几乎被问烂了,因为中文资料里"分支"这个词一直含混。这里把定义理顺:
一根总线上有主干(Backbone),主干上通过 T 型连接引出若干支线,每个支线的末端挂一个节点。所谓"并联分支的长度",指的就是从主干引出点(焊点、T 型接头、接线端子处)到节点收发器引脚之间的那一段线缆长度。它不包括主干长度,也不包括两个节点之间绕过的路径,更不是一根支线到另一根支线的距离。
如果画成文字示意,大概是:
主干CAN_H ──┬───────────────┬───────────────┐ │分支1 │分支2 │ 节点A 节点B 节点C这里分支长度就是"A 到主干引出点的线、B 到主干引出点的线"。
5.2 为什么分支越长越危险:反射与阻抗不连续
分支线缆的存在会导致阻抗不连续。高速信号沿 120 欧姆主干传播到一个末端悬空的分支点,相当于遇到了一段阻抗变化的路口,一部分信号能量会被反射回来,叠加到原始信号上,形成台阶、振铃甚至误触发显性/隐性判断。
更麻烦的是,分支上的每个节点本身就是另一个反射源。分支越长,反射波到达主干的时间越晚,越容易落在后续位的采样窗口里,导致接收端在某些位采样错误。在一个波特率 500kbps 的系统里,1 个位时间是 2 微秒,而每米线缆的往返延迟就有大约 10 纳秒。分支哪怕只有 1 米,带来的延迟虽然不大,但反射叠加造成的波形畸变可能远超理论延迟本身。
所以工程上的处理原则是:分支尽可能短,最好把它也当成主干的一部分直接串进去。CIA(CAN in Automation)给出的建议是,分支线长度设计上控制在 0.3 米以内;高速 CAN(500kbps 以上)条件下,0.1 米以内更稳妥。0.3 米这个数在很多线束规范里被当成默认值。
5.3 布线拓扑的参考做法:菊花链优先,星型要谨慎
最稳的拓扑是菊花链:每个节点的收发器信号直接连在主干上,不额外引出长支线。也就是所有节点沿着一条线串联下来,终端电阻放在这条线的最两端。
如果必须做星型拓扑——比如几个控制单元在物理上集中在驾驶舱中心,往外辐射——建议使用有源 CAN 集线器或中继器,而不是单纯把多根支线并联到一个点上。纯粹的无源星型连接在高波特率下反射非常严重,不只节点多,而且每一次反射都叠加在中心点,波形质量极差。低速 CAN 125kbps 下勉强能忍,500kbps 以上基本不可用。
排障时还有个经验法则:总线通信不稳定时,先用排除法把最长的那根分支剪掉(或把它移动到主干的另一端),看错误帧是否立刻减少。往往一根超长支线引发的反射能让整条总线的错误帧统计飙升几十倍。
6. 总线保护电路:除了终端电阻,CAN 节点还需要什么
6.1 车载环境的电应力从哪来
CAN 在实验室台架上怎么跑都健康,真正量产装车后考验才刚开始。车上常见的威胁有几种。
第一种是静电放电。连接器插拔、维修人员接触线束都可能把几千伏的静电灌进 CAN_H/CAN_L。收发器芯片内部有部分 ESD 保护,但能力有限,扛不住反复的强放电。
第二种是抛负载。汽车发电机、感性负载切换时会在电源线上产生几十伏甚至上百伏的瞬态尖峰,这个尖峰通过系统的地回流和线束耦合,可能串到 CAN 差分线上。
第三种是线束意外短路。CAN_H 或 CAN_L 可能与 12V/24V 电源正极短接,也可能与地短接。收发器的绝对最大额定值范围通常到 -27V 到 +40V 左右,一旦短路到电源正极,长时间异常电压会逐步损坏收发器。
6.2 典型保护方案:TVS 管、共模电感、滤波电容
一个工程中常用的 CAN 保护电路,信号流向大致是:
连接器 → TVS 管阵 → 共模电感 → 滤波电容 → 收发器引脚
具体到每个器件:
| 器件 | 位置 | 作用 | 典型选型参数 |
|---|---|---|---|
| TVS 管(双向) | 连接器附近,CAN_H/L 对地各一只,或差分对之间 | 钳位浪涌电压、吸收 ESD 能量 | 工作电压 12-24V,钳位电压低于收发器上限,功率按浪涌能量选 |
| 共模电感 | TVS 之后、收发器之前 | 抑制共模干扰,改善 EMC | 阻抗 100 欧姆@100MHz 左右,直流电阻越小越好 |
| 小容量陶瓷电容 | 收发器引脚附近 | 滤除高频噪声 | 并联在带屏蔽双绞线或地之间,100pF - 1nF,别过大 |
| 终端电阻 | 拓扑两端节点 | 匹配阻抗 | 120 欧姆,1% 精度 |
选 TVS 时要特别留意两个参数:钳位电压和工作电压。钳位电压必须低于收发器能承受的最高压,否则没能保护住芯片;工作电压又必须高于正常通信时的总线电压范围。CAN 隐性时两根线都在 2.5V 左右,显性时一根线 3.5V 一根 1.5V,所以工作电压选 5V 以上的器件是安全的。具体型号选择时可以找 5V 工作电压、钳位在 10V 上下的双向 TVS。
共模电感很多人会忽略,但在汽车电子里它对 EMC 的作用非常明显。它可以滤掉两根线上方向相同、大小相等的共模噪声,而不影响方向相反的差分信号。CAN 总线中差模信号走的是差分路径,共模噪声才是主要干扰源,所以共模电感几乎是为 CAN 量身定制的。
6.3 一个可参考的汽车 CAN 电路图注释
用文字描述一个比较常见的节点保护电路:
连接器的 CAN_H 引脚出来,先后接一个双向 TVS 到地;CAN_L 引脚同样接一个双向 TVS 到地。两根线再穿过一个共模电感,电感输出端接到收发器(比如 TJA1042 或 TJA1050)的 CANH、CANL 引脚。收发器的 TXD/RXD 接 MCU 的 CAN 控制器或内置 CAN 外设的引脚。
关键点有四个:
- TVS 必须放在连接器和共模电感之间,不能让浪涌先经过共模电感再被钳位,否则浪涌电流可能损坏电感或造成严重的电压过冲。
- 终端电阻的放置原则:如果这个节点是拓扑的物理端点,就把它接在收发器引脚附近或者干脆在 PCB 上只预留电阻位置;不是端点就不要默认贴上去。
- 收发器电源脚和 MCU 的电源去耦电容不要省,它们对总线边沿质量的影响比你想象的大。
- CAN 控制器的 TX 引脚到收发器 TXD 之间可以串一个几十欧姆的电阻,减少振铃,这在高速 CAN 中是有实际效果的。
我自己做节点板时还会在 PCB 上留一组 0603 焊盘串联的 0 欧电阻,方便量产阶段断开某一路做隔离调试。这个小习惯在整车上查 ID 冲突和错误帧来源时能节省大量时间。
7. 通信协议实例:从报文到真实功能,一个升窗信号怎么走完全程
7.1 报文解码:0x255 号报文的每一位代表什么
说了这么多底层机制,最后用一个具体报文把整个流程串起来。为了讲清楚思路,我设计一个模拟的车窗控制报文,ID 0x255,DLC=8,周期 20ms。收到这样一串原始字节:
02 13 05 00 00 00 00 00按协议表解码:
| 字节(Byte) | 值 | 含义 |
|---|---|---|
| Byte0 | 0x02 | 开关状态:0x01 升窗,0x02 降窗,0x00 停止 |
| Byte1 | 0x13 = 19 | 电机电流估算,0.1A/bit → 1.9A |
| Byte2 | 0x05 | 位置百分比:5% |
| Byte3-7 | 0x00 | 保留 |
拿到这帧后,车窗控制器 MCU 会怎么做?先判断 ID 0x255 是不是我感兴趣的 ID,再校验 CRC,然后读取 Byte0:等于 0x02,于是驱动电机反转,实现降窗;同时把 Byte1 的电流值换算成 1.9A,和过流保护阈值比较;把 Byte2 的 5% 位置更新到当前状态变量。整个动作从总线上出现显性 SOF 位,到电机开始反转,中间只有几十微秒到几毫秒的延迟。
这就是一个标准的汽车 CAN 通信协议实例:发送节点按周期或事件把数据打包成帧,接收节点按预先约定的 DBC 或通信矩阵去解析位域。DBC 本质就是一张"哪个字节哪个 bit 代表哪个物理量"的翻译对照表,工程上常说的"标定报文"就是把这张表烧进前台工具。
7.2 抓包与示波器验证的关键动作
现场调试 CAN 要养成的习惯有三条。
第一,示波器优先做差分测量。用两个探头分别夹 CAN_H 和 CAN_L,用数学通道做 A-B 减法,能看到干净的差分波形。直接拿单个探头对地看 CAN_H 波形,往往会被共模噪声掩盖。
第二,关注 ACK 槽。抓帧时找到一帧的 ACK 段,正常情况下 ACK 槽会有一个明显的显性下凹,这是接收节点给的回应。如果它一直是平的隐性,说明你面对的是一个"独自写信但没人读"的节点。
第三,查波特率时先看位宽度。示波器上量一个位的实际宽度,1Mbps 时每位约 1 微秒,500kbps 时约 2 微秒。别一上来就翻代码里的配置,先量波形比什么都直观。
7.3 排障经验:总线瘫了先查哪几项
如果你接手一辆"CAN 时不时掉线"的车,按下面顺序查,大部分问题半小时内能定位。
先量终端电阻:断开整车电源,在总线任意位置量 CAN_H 与 CAN_L 之间阻值,正常 60 欧姆上下。量到 120 说明终端电阻掉了一端;量到接近 0 说明有节点收发器击穿短路或线束破损互碰。
再量静态电压:上电后总线空闲时,CAN_H 和 CAN_L 应在 2.5V 附近。如果一根明显偏低,比如 CAN_H 只有 1.2V,常见原因是某个节点的收发器输出级损坏,或者 CAN_H 该走线的位置对地短路。
再看波形:用示波器看边沿有没有台阶、振铃。台阶常见于反射,检查分支和终端电阻;振铃之后伴随错误帧,则要怀疑共模电感的参数是否合适,或者屏蔽层的接地处理是否到位。
最后查软件层:打开 CAN 控制器的错误计数器,如果 TX Error Counter 一直在跳,说明发送总是得不到 ACK,多半是总线上只有一个节点或物理连接中断;如果 RX Error Counter 跳,说明本节点收到了大量错误帧,优先怀疑波特率不匹配或线缆噪声过大。先物理后协议,先量后猜,这是我调 CAN 几年下来最实用的方法。
这些年调过的项目和踩过的坑不算少,给我最大的感受是:CAN 的协议栈并不复杂,真正决定一个系统稳不稳的,往往是物理层的终端电阻、分支走线、保护电路这些"看起来不起眼"的细节。找一辆实车或者搭一套开发板,用示波器观察一段真实通信波形,把标准帧、扩展帧、仲裁过程对着报文手册验证一遍,远比死记协议字段更有用。希望这篇 CAN 总线详细讲解能帮你少走几趟弯路。