1. 为什么CAN总线是车载系统的“神经系统”
做车辆研发、机器人控制或者后装诊断这些年,几乎每天都要跟CAN总线打交道。从发动机ECU、变速箱、ABS到车窗、空调,再到新能源汽车的电池管理系统(BMS)和电机控制器,内部互相通信的骨干基本都是CAN。说它是车载系统的“神经系统”一点都不夸张:车上所有电子单元(ECU)靠它交换状态、指令和诊断信息,一个信号错了或者一条总线断了,轻则某个功能失效,重则整车控制器直接进入故障保护状态。
这篇文章写的是我对CAN协议和车辆协议的理解,包括协议分层的思路、物理层电压变化、常见的测试手法,再拿达妙电机这类智能关节模块做例子,讲清楚怎么通过CAN总线把位置、速度、扭矩控制起来。无论你是刚入门的车辆电子工程师,还是做机器人嵌入式开发,或者只是想把OBD诊断搞明白,都应该能从里面找到能直接上手的东西。
需要先说清楚,CAN总线本身不是某一款芯片的功能,而是一整套国际标准协议。从头捋一遍,你会发现它其实不复杂,难的是面对真实总线上那种各种干扰、电平波动和ID管理混乱的时候,还能快速把问题定位出来。这也是我写这篇文章的目的:不只是讲“协议长什么样”,更希望让大家知道“为什么这么设计”,以及“现场出了问题怎么判断”。
2. 物理层拆解:CAN总线的电压差到底是怎么变的
2.1 CAN_H和CAN_L两条线的分工
很多人第一次学CAN,看波形就蒙了:为什么明明叫“数字总线”,示波器上看到的却不是干净利落的0V/3.3V方波?这是因为CAN物理层用的是差分信号,不是单端电平。
所谓差分,就是同时使用两根线来传输一个逻辑电平:一条叫CAN_H,一条叫CAN_L。接收端真正关心的不是单根线对地的电压,而是两根线的电压差(CAN_H电压减去CAN_L电压)。这样做最大的好处是抗干扰能力强。车上的点火线圈、电机驱动器、电火花干扰到处都是,如果靠一根线对地传输,地噪声很容易把信号淹没;而差分信号在双绞线里受到的干扰基本是共模的,两个线同时被抬升或拉低,差值却几乎不变,接收端依然能准确判断电平。
一台正常工作的CAN总线,静止状态下CAN_H和CAN_L都应该在2.5V附近,这叫隐性电平。当某个节点要发送数据时,发送器会主动把CAN_H往上拉、CAN_L往下压,形成大约2V的差值,这叫显性电平。在示波器上看起来不是“满幅跳变”,而是两条线从中间往上下两边分开再合拢。这跟RS232那种0V到5V的跳变完全不同,一开始不习惯很正常。
2.2 显性电平与隐性电平的电压差计算
按ISO 11898-2标准,隐性状态下CAN_H和CAN_L的典型电压都在2.5V左右,电压差接近0V;显性状态下CAN_H被拉到不低于3.5V,CAN_L被拉到不高于1.5V,电压差约为2V。芯片数据手册里通常会给2V左右的典型差分电压,实际使用示波器量到1.5V到3V都算正常,前提是总线拓扑和终端电阻没出问题。
这里有一个关键点:显性位是“主动驱动”的,由发送节点的CAN收发器同时开路上拉CAN_H、下拉CAN_L,相当于用两个管子把差分对硬生生撑开。隐性位则相反,大家都不出力,靠总线两端的120欧终端电阻把两个电平“拉拢”回2.5V附近。所以从能量角度看,发送显性位的时候功耗更大,总线上也更容易出现振铃;发送隐性位则是自然回落。
在实际调试中,我经常用万用表直接测CAN_H和CAN_L之间的电阻来判断物理层状态:正常端接的总线,在整车断电或者断开所有节点的情况下,CAN_H和CAN_L之间的直流电阻应该在60欧左右——因为总线两端各有一个120欧电阻并联。如果你量到120欧,说明只有一端接了终端电阻;量到无穷大,那就说明至少有一端是断开的。这招在排查“总线静默”问题的时候屡试不爽。
2.3 为什么差分会随负载和节点数量变化
总线上节点一多,差分输出就会受到负载影响。CAN收发器的输出能力有限,当总线上有几十个节点并接,再加上线缆的分布电容,发送显性位时电压差下降、上升沿变缓都很正常。反过来,总线过短而终端电阻又没装好,波形上会出现明显的过冲振铃。所以测量波形不能只看“有没有约2V差”,还要看边沿斜率、振铃幅度和差分电压的稳定区间。
我还遇到过一种情况:总线上明明有波,但接收端就是报错。后来发现是某个节点的CAN_H和CAN_L接反了,导致整个总线的差分电压在特定帧期间被反向拉低。用示波器量时,光看单根线电压根本发现不了,必须同时看CAN_H和CAN_L两路信号,才能看到“两根线往同一方向跑”这种明显错误。这也是我一直建议初学者养成“默认抓差分波形”习惯的原因。
3. 数据链路层:CAN报文到底长什么样
3.1 标准帧与扩展帧的结构差异
物理层搞定电平之后,接下来就是CAN协议的核心——数据链路层。这一层规定了怎么把一帧数据打包、怎么让多个节点同时发送而不冲突、以及怎么保证出错的数据不会一直发送。
目前市面最常见的是CAN 2.0协议,分为标准帧和扩展帧两种格式。标准帧的标识符(ID)是11位,最多可以区分2048个不同ID;扩展帧的标识符是29位,能区分5亿多个ID。二者在帧头格式上不同,但在总线上可以混合共存,因为协议通过IDE位来区分,节点可以同时接收两种格式。
一帧CAN报文从结构上拆开看,主要包括这些部分:
| 字段 | 长度 | 作用 |
|---|---|---|
| SOF | 1 bit | 帧起始,固定显性电平,同步所有节点 |
| ID | 11/29 bit | 标识符,决定优先级和数据含义 |
| RTR | 1 bit | 区分数据帧和远程帧 |
| DLC | 4 bit | 数据长度,0到8字节 |
| DATA | 0-8字节 | 实际要传输的数据 |
| CRC | 15 bit | 循环冗余校验,检验数据是否正确 |
| ACK | 2 bit | 应答,接收节点确认收到 |
| EOF | 7 bit | 帧结束 |
看到这里你可能会问:一帧最多才8字节,够用吗?这就是CAN设计的巧妙之处。它牺牲了单帧数据量,换来了高实时性和高可靠性。对于转速、车速、门锁状态这种短数据,8字节绰绰有余。需要传输大数据的场景,协议栈会在应用层做分包和重组,把逻辑上的一大块数据拆成多帧发送。
3.2 仲裁机制:为什么高优先级永远抢线
CAN总线上所有节点共享一对线,任何时刻都可能有两个节点同时开始发送。那到底听谁的?CAN的答案很优雅:硬件级仲裁,发送过程中直接“赛跑”。
在帧起始后,每个节点发送自己的ID位,同时监听总线电平。CAN的物理层规定:显性电平(差分约2V)等于逻辑0,隐性电平等于逻辑1。由于显性电平会“覆盖”隐性电平——只要有一个节点拉低总线,其他节点读到的就是0——所以当多个节点同时发送时,谁在这一位上是显性,谁就赢了;遇到隐性位的节点发现总线电平跟自己发的不一致,立刻退出仲裁,转为接收状态。
这就是为什么ID数值越小,优先级越高。因为ID都是高位先发,数值小的ID在前面位次的“0”更多,更容易压过数值大的ID。实际整车通讯里,优先级高的数据比如安全气囊触发、碰撞信号,都会分配很小的ID,保证在总线拥堵时也能第一时间抢占线路。这个机制也决定了ID规划非常讲究,不能只考虑“不重复”,还要考虑“谁应该先走”。
3.3 位填充、CRC与错误处理机制
总线信号在传输过程中可能会被电机火花、开关浪涌等外部干扰打乱。为了应对这种情况,CAN协议设计了多层防线。
一个是位填充。协议规定,连续发送5个相同电平后,发送方必须自动插入一个相反电平,接收方在解码时再把这个填充位去掉。这样做有两个作用:一是给接收端的时钟同步提供跳变沿,因为连续长时间不变的电平会让采样点失去参考;二是避免长串相同位被误判成空闲状态或产生其他歧义。
另一个是CRC校验。每帧报文末尾都带15位CRC校验码,接收端用同样的算法计算,如果对不上就直接丢弃本帧并且返回错误帧。假如一个节点反复收到错误帧,它会进入bus-off状态,暂时退出总线,避免“一颗老鼠屎坏了一锅粥”。这种设计让CAN在真实工业环境中非常皮实,也不会因为单点故障导致全车通信瘫痪。
4. 车辆协议全景:OBD-II、UDS与J1939
4.1 从OBD-II接口入手最直观
如果你手里有辆乘用车,找一下方向盘下方的OBD接口,通常是一个16针梯形插座。这个接口就是接入车辆CAN总线最直接的入口。OBD-II原本是为了满足排放检测法规而设计,后来扩展成了通用的车辆诊断入口。标准定义了几种不同的物理层协议,但在2008年后的绝大多数新车上,走的基本都是CAN,引脚6是CAN_H,引脚14是CAN_L。
用USB-CAN分析仪插上OBD接口,把波特率设成500kbps,马上就能在软件里看到总线上源源不断的报文。最常见的那些ID,比如0x0C、0x1A、0x2C这些,不同车企定义不一样,但都能通过读取数据的变化来猜测对应含义。我一开始调车的时候,就是把转向灯、车窗、刹车踏板逐项操作,一边操作一边盯报文列表,很快就能摸清楚哪些ID对应哪些功能。
这里要提醒新手:OBD接口虽然暴露了总线,但并不是所有ECU都会在上面跑。很多整车厂会把动力CAN、车身CAN、舒适CAN分成多条独立总线,OBD接口只接到其中一条诊断总线上。想测其他总线,就得找到对应的接点,或者通过网关转发。网关的存在也是CAN网络设计中的关键一环,它的主要任务就是隔离不同速率、不同安全等级的总线段,防止一个区域出问题导致全车瘫痪。
4.2 UDS诊断协议怎么读故障码
光能看到CAN报文还不够,真正要读故障码、做ECU刷写,就得聊UDS(Unified Diagnostic Services,ISO 14229)。UDS是跑在CAN上层的一套应用协议,它定义了如0x10会话控制、0x22按ID读数据、0x2E写数据、0x31例程控制、0x3E保持连接等诊断服务。
UDS的通信模型是典型的请求-应答式:诊断仪发送一个请求帧,ECU回复一个响应帧。请求帧和响应帧的ID通常是成对出现的,比如请求ID是0x7E0,响应ID就是0x7E8。如果发动机ECU出故障,诊断仪通过发送0x19服务请求读取故障码,ECU会返回DTC(Diagnostic Trouble Code)列表,像P0101这种“质量空气流量传感器电路范围/性能问题”,就属于这类。
很多后装设备、车载T-Box、自动驾驶盒子,本质上都是靠着UDS这条通道去读车速、里程、电池状态和校准信号。做这个方向的开发,建议先从0x22和0x19这两个服务入手,理解了会话管理和安全等级之后,再往上碰0x2E和刷写类服务。
4.3 J1939与商用车上的CAN
如果从乘用车转到商用车,你会发现虽然底层还是CAN,但上层协议换成了SAE J1939。J1939用在卡车、工程机械、农用设备等环境,它的特点是波特率固定为250kbps,ID定义和乘用车完全不同。
J1939把29位扩展ID做了详细划分:前几位是优先级,中间是PDU格式和PDU特定域,后面是源地址和目标地址。它把发动机转速、水温、油位这些参数都定义成了标准参数组(PGN),比如发动机液位/压力归为一个PGN,电子发动机控制器1又归为另一个。这种标准化的好处是,只要设备支持J1939,无论装在哪家的卡车底盘上,都能读到一致的数据。
我做工程机械项目时经常遇到一个坑,就是客户拿乘用车OBD工具去测商用车,怎么都搜不到报文。原因就是波特率不对,J1939是250kbps,乘用车大多是500kbps,工具设置不对自然抓不到数据。所以拿到一辆车,先别急着猜,看协议文档或者用支持自动波特率识别的分析仪扫一遍,能省下不少时间。
4.4 车企私有CAN网络规划逻辑
除了标准协议,每家车企还会在量产车型上做自己的私有CAN网络规划。比如很多新能源车会把整车分为动力CAN、车身CAN、自动驾驶域CAN,各段速率不同、物理位置不同,之间用域控制器或网关做数据路由。这样既能降低单条总线负载率,又能控制不同安全等级的数据互相隔离。
私有协议的部分一般IP、源码拿不到,只能靠逆向抓包。我的经验是:先看周期报文,周期固定且ID稳定的多半是状态广播类,比如车速、转速;再看事件报文,只有在操作时才出现的,多半是门锁、车窗、灯光控制类。把这些报文分类之后,再结合硬件原理图去猜字节含义。这个过程需要耐心,但也是理解车辆协议最有效的练习方式。
5. 实战:CAN总线测试与波形分析
5.1 测试CAN总线需要哪些基础设备
CAN总线测试听起来复杂,但基础工具其实不复杂。我认为至少需要这三样:
- 一个双通道或四通道示波器,带宽100MHz以上就足够大多数车载CAN测试,重点是用差分探头或两个无源探头同时抓CAN_H和CAN_L
- 一个USB-CAN分析仪,从几十块的入门款到几千块的专业款都有,配合上位机软件可以抓包、发包、看错误帧
- 一个万用表,用来测终端电阻、通断和电平检查
示波器用来观察物理层质量,CAN分析仪用来观察数据层交互,万用表用来做基础电气检查。三者是互补关系,缺一个都可能让你在现场绕远路。很多人图方便只带一个分析仪,结果遇到“有报文但数据乱”的情况,完全无从下手,因为根本看不到波形长什么样。
5.2 用示波器抓CAN波形的完整步骤
抓CAN波形,我是这么操作的。先把示波器通道1接CAN_H,通道2接CAN_L,地线夹接车辆接地(注意不要直接夹信号地,要确认公共地关系)。把时间轴调到50微秒/格,电压轴调到1V/格左右。如果是500kbps波特率,单bit时间是2微秒,一个完整标准帧大约100多微秒,刚好能铺满两到三格屏幕。
触发方式建议设成下降沿或上升沿触发,用CAN_H通道做触发源。如果总线上一直在跑周期性报文,很快就能看到连续的帧波形。我习惯先把光标放在两个显性位之间,测一下单bit时间,再用1除以bit时间,验证波特率有没有猜错。比如测出一个bit是2微秒,那波特率就是500kbps;如果是4微秒,就是250kbps。
波形抓到现在,重点看三件事:差分电压幅度是不是正常;边沿是不是干净,有没有明显振铃;隐性回退是否平缓,有没有长时间漂移。如果发现波形上有“塌陷”的显性位,大概率是有节点在错误地驱动总线,或者在总线上发生了多个节点同时发送但仲裁失败的瞬态。
5.3 典型CAN故障波形快速判断表
我从这些年调试经历里整理了一张速查表,遇到问题可以先对照一下:
| 现象 | 可能原因 | 快速验证方法 |
|---|---|---|
| 总线完全静默,无任何波形 | 总线短路、无节点供电 | 万用表测CAN_H对CAN_L电阻、测供电电压 |
| 有波形但差分幅度明显偏低 | 终端电阻缺失、节点过多、线缆过长 | 检查总线上两端120欧电阻,断开部分节点 |
| 波形边沿过冲且振铃严重 | 缺少终端电阻或接线走线过长 | 在总线末端加终端电阻,检查拓扑是否“菊花链” |
| 偶发错误帧但波形看着正常 | 某一节点地电位不稳、接头松动 | 单独检查每个节点的地线,紧固接头 |
| 单根线电压几乎不变 | 收发器损坏、差分线短路到地或电源 | 分别测CAN_H和CAN_L对地电压 |
做CAN测试时一定要记得,波形是“物理层结果”,错误帧是“数据层结果”。物理层的小问题通常会先在错误帧计数里爆发,比如总线上CRC错误突然变多,先说“数据层有事”,然后才轮到示波器上找原因。反过来,如果错误帧频繁出现而波形看起来完全正常,就要想是不是协议层配置错了,比如波特率虽对但采样点位置不合适。
5.4 错误帧和bus-off现象怎么排查
CAN总线上出现错误帧,在分析仪里通常能看到红色的错误计数器跳动。这里要区分两种情况:一种是某个节点内部错误计数器高导致它主动报错,另一种是总线外部的信号质量问题导致错误。判断方法很简单:把疑似故障的节点单独拆下来,用分析仪模拟总线上另一个节点通信,看错误帧是不是依旧存在。如果消失,说明问题不在这个节点,而是它和总线的交互环节。
最棘手的是bus-off状态。节点发送错误太多,错误计数器超过255,节点会主动离线,不再参与通信。这个状态会让ECU看起来“死掉”一样,而且很多ECU掉线之后需要重新上电或回车钥匙才能恢复。所以在测试台上调试时,我习惯在分析软件里周期性发送一个“心跳”报文,一旦哪个节点离线,马上能通过心跳消失的时间点判断它是什么时候进的bus-off,再配合示波器去看那个时间点前后的波形。
6. 达妙电机通过CAN实现精确关节控制
6.1 为什么机器人关节控制也选CAN
最近两年,做四足机器人、机械臂、人形机器人的朋友经常会提起达妙电机这类一体化关节模组。它们体积小、力矩密度高,内部集成了电机、减速器、编码器和驱动电路,对外只留一个CAN接口和数据线。那为什么偏偏选CAN,而不是RS485或者以太网?
核心原因是实时性和同步性。机器人关节通常需要1kHz甚至更高的控制频率,每个控制周期内主控都要给所有关节下发目标位置、速度或力矩,同时收回当前角度、速度等信息。CAN总线在多主通信上没有主从关系,任何一个节点随时可以发数据,非常适合这种周期性指令加分散反馈的场景。而且CAN帧的仲裁机制天然保证了优先级,紧急的安全停止指令可以用低ID抢占总线,响应时间非常有保障。
达妙电机这种关节模组,内部通常已经是“电机加编码器加驱动器”的闭环,而外部的CAN控制其实是叠加了一个“上层位置环”或“速度环”。主控只需要告诉它目标值,电机内部的算法会自动去跟,不需要主控高频率读取编码器再做PID。这也是我为什么建议初学者不要一上来就在主控里做高频电流环,先把CAN通信跑通,再用电机自带的闭环能力,后面再根据负载情况决定要不要自定义控制算法。
6.2 达妙电机的CAN报文格式与ID规划
达妙电机不同型号的寄存器映射和报文ID会略有差异,我在调一款常见关节模组时用到的典型配置可以给大家参考,但真做项目时务必以对应手册为准。
我习惯把主控下发数据的ID规划成0x140到0x1FF这一段的扩展ID区域,每台电机分配一个固定的控制ID。比如三台电机分别占0x141、0x142、0x143,主控按控制周期依次或按优先级发送。电机反馈的ID一般映射到0x240到0x2FF区间,比如0x241、0x242、0x243,主控通过接收这些ID获取当前角度、速度和力矩数据。
从报文内容上看,控制帧通常是8字节,前面两个字节是目标位置或目标速度的高低位,中间两个字节是速度或力矩设定值,最后两个字节是刚度或阻尼参数。不同模式(位置模式、速度模式、力矩模式)下,每个字段的意义会切换,在程序里就必须非常小心地解析,别把力矩字段当成速度字段去填。
6.3 控制帧和反馈帧的实操用法
我举个具体例子。假设我用CAN分析仪往ID 0x141发一帧数据,目标位置设为90度,速度设为30转/分,再让内部刚度设成中等,那8字节可能看起来像这样:
0x1 0x41 : 0x20 0x4E 0x00 0x1E 0x00 0x0A 0x00 0x00这里面0x204E是角度编码值经过换算后的结果,0x001E是速度,0x000A是刚度系数。实际怎么换算,还是得查对应型号的手册,因为不同型号的编码器分辨率不同,角度值和原始数据之间的比例可能差好几倍。
电机返回的反馈帧同样8字节,前两个字节是当前角度的编码值,中间是当前速度,后面两个字节是当前真实力矩。主控收到之后,可以先判断ID是不是自己的目标电机,再按同样的换算式把这个原始值转成实际角度。比如反馈原始值是0x1020,分辨率是每圈16384,那当前角度的计算方式就是0x1020除以16384再乘以360度。
这里有一个调试要点:控制周期不能太短。很多人一上来就把发送间隔设成100微秒(10kHz),结果总线上全是报文,CPU也忙不过来,最后控制效果反而变差。我建议先从1kHz开始,也就是每1毫秒发一次控制帧,观察关节跟踪效果,再逐步提高。对大多数机械结构和电机响应模型来说,1kHz到2kHz已经足够平滑,没必要盲目追求高频。
6.4 达妙电机CAN控制的坑与调试建议
我在调达妙电机时踩过的几个坑,列出来给大家参考。
第一个坑是波特率不匹配。模块出厂可能默认波特率不是你以为的那个值,用分析仪自动识别波特率或查阅手册确认。波特率配错的表现是,主控发送之后电机毫无反应,分析仪上全是错误帧,甚至完全抓不到任何有效数据。
第二个坑是终端电阻。如果只有一台电机和一个主控,在总线上只有两个节点,也依然需要在两端各接一个120欧电阻。不少人在实验桌上只用一根短杜邦线连接,忽略终端电阻,结果高速通信时波形振铃严重,开环测试正常,闭环位置控制一上去就抖动。解决方式很简单:在CAN分析仪或主控板一端接焊好的120欧电阻,电机端如果设计上没有内置终端电阻,可以在连接器附近外接一个,注意不是所有电机模块都内置。
第三个坑是ID冲突。如果总线上有两个电机配置成了同一个ID,控制帧发出去两个电机都会响应,反馈帧也会从两个节点同时发回来,导致仲裁混乱,位置和力矩读出来的数据完全不可信。上电之前最好先用分析仪扫一遍总线上的ID列表,确认没有重复的源地址,再开始联调。
7. 我调试CAN总线这么多年的几条心得
CAN总线知识看起来零散,实际上是一条非常清晰的链路:物理层电平决定信号能不能传,数据链路层决定帧能不能收,应用层协议决定数据有没有意义。很多新手把大量时间花在背帧结构上,反而忽略了用示波器看波形、用万用表查线路这些基本功,到现场一测就露馅。
我自己的习惯是:遇到任何CAN通信问题,先问三件事——波特率对不对,终端电阻在不在,ID有没有冲突。这三点排查完,八成的基础通信问题都能解决。剩下两成,再去看时序、采样点、接地和信号完整性。
如果你现在正准备做车辆协议分析或者机器人关节控制项目,我的建议是从一个最简单的实验开始:拿一个CAN分析仪,接上一台达妙电机,先把ID和波特率配置对,然后试着只发送一帧固定控制数据,观察电机有没有动作。等这一步通了,再一点一点加控制环优化,相信你很快就会对CAN总线有真正的掌控感。