1. 从点到点布线到共享总线:CAN出现的真实理由
汽车电子工程师圈子流传一句老话:没有CAN总线之前,修车靠的是眼睛和万用表,有了CAN之后,多了一个示波器,也多了一堆奇怪的“伪故障”。这话不夸张。在CAN总线大规模普及之前,一辆普通轿车的线束总长度可以超过一两公里,门板、仪表台、发动机舱里塞满了密密麻麻的铜线,每一个开关、传感器和执行器都要单独拉一对线到对应的控制器。功能越加越多,线束就越来越重、越来越贵,装配出错率也越来越高。这时候工程师们意识到,传统的点对点布线已经走到头了。
CAN总线最核心的贡献,是把“每个节点之间单独通信”改成了“所有节点挂在一条共享总线上,通过广播方式发送报文”。这意味着无论车内有多少个控制器,只要它们之间需要交换数据,就可以共用同一条双绞线,节点之间不需要物理上的“一对一”连接。从信号灯控制到变速箱换挡,再到ABS防抱死、车身稳定系统,全部可以在这条总线上跑。网络上每个节点既能发消息也能收消息,但同一时刻只能有一个节点真正占据总线发送数据,其余节点处于接收监听状态。
这个从“星型/网状的物理连接”到“总线型共享链路”的转变,不是简单地把线并在一起就完事了。它背后要解决三个关键问题:第一,多个节点同时抢总线,怎么避免冲突和数据混乱?第二,一条总线上挂了十几个节点,怎么让每个节点只关心自己需要的数据?第三,信号在长线缆上传输时,如何抵抗汽车环境里强烈的电磁干扰,保证数据不错、不乱?CAN协议的发明人当初就是围绕这三个问题设计了一套完整的机制,让车载网络从“一对一专用链路”变成了“一对多共享总线”,这也是今天所有车载通信协议的基础逻辑。
从整车厂的视角看,CAN带来的好处是立竿见影的。线束总量减少、整车重量降低、装配效率提高,更重要的是可扩展性变强了:新款车型要增加一个传感器、一个控制器,只需要把它挂到总线上,再在软件里定义好ID和报文内容,而不需要重新走一根贯穿全车的线。对维修诊断来说,故障码、数据流也可以从统一的诊断接口读取,不再需要逐根线探查。这个设计思路,奠定了后来几十年汽车电子电气架构的基本形态。
不过,共享总线也有代价。总线带宽是有限的,经典CAN只有最高1Mbit/s的速率,实际车上常用的500kbit/s、250kbit/s甚至更低。一个报文的长度通常只有几十个bit到一百多个bit,所以在一条繁忙的总线上,所有节点都要学会“抢时间片”。为了公平地抢,CAN发明了基于标识符优先级的非破坏性仲裁机制。这套机制是整个CAN协议最巧妙、也最容易被初学者忽略的部分,我放到后面专门讲。
理解CAN为什么会出现,再看它内部的每一个细节,就会觉得每一条规则都不是拍脑袋定的。电压怎么变化、仲裁怎么实现、错误怎么处理,全都是在回答“如何在一条共享线缆上可靠传输数据”这个问题。下面我从物理层开始,一层层剥开。
2. 物理层拆解:怎么用两条线的电压差表达0和1
很多新手看CAN电路图时,最困惑的就是那两根线:CAN_H(CAN High)和CAN_L(CAN Low)。它们不是单纯的一根信号线加一根地线,而是一对采用差分传输的双绞线。差分的意思,是信号电平看的是这两根线之间的电压差,而不是任意一根对地的绝对电压。这个设计和RS232、UART那种“一根线对地拉高拉低”的单端传输有本质区别。
2.1 显性电平与隐性电平:差压变化的核心
CAN总线上只有两种逻辑状态:显性(Dominant)和隐性(Recessive)。隐性对应逻辑“1”,显性对应逻辑“0”。在标准ISO 11898-2定义的高速CAN物理层里,隐性状态下,CAN_H和CAN_L都大约被偏置到2.5V,两根线之间的差分电压V_diff大约为0V;显性状态下,CAN_H被拉到约3.5V,CAN_L被拉到约1.5V,差分电压V_diff大约为2V。
所以,CAN总线的电压差是怎么改变的?从宏观上看,发送节点通过控制收发器内部晶体管的导通和截止,把总线上两根线的驱动状态切换成“差分电压约0V”或“差分电压约2V”。从微观上看,显性电平是由发送节点的CAN收发器主动驱动产生的:内部的推挽输出级把高侧晶体管和低侧晶体管同时导通,让CAN_H、CAN_L分别被拉向电源轨和地轨附近,形成一个稳定的约2V的差压;而隐性电平则是两个晶体管都截止,总线靠两端120欧终端电阻网络和收发器的偏置电路自然回落到约2.5V的中性状态,此时差压趋近0。
理解这一点很重要:显性不是“让CAN_H变高这么简单”,而是“把CAN_H往上推、把CAN_L往下拉”两个动作同步完成。这就解释了为什么测量CAN波形时,不能只拿一根表笔对地测CAN_H,然后想当然认为CAN_L一直不变。用示波器两个通道分别测CAN_H和CAN_L,再启用数学通道A-B,才能看到差分电压的真实跳变。实际测试中我见过不少新工程师,单端看CAN_H波形觉得“3.5V和2.5V看着像UART”,结果把差分电压算错,判读波特率出错,最后整个网络通信失败。
2.2 终端电阻与偏置网络:为什么120欧这么关键
CAN总线要求在物理链路的两端分别并联一个120欧姆的终端电阻,而不是在每一个节点上都加。这两个电阻和总线分布电容、电感一起,构成了传输线的特征阻抗匹配。如果终端电阻缺失或阻值不对,信号到达线缆末端就会发生反射,反射波叠加在原信号上,造成波形过冲、振铃,严重时会让显性电平误判成隐性电平,产生位错误。
实测一条没有终端电阻的500kbit/s CAN总线,报文距离和波特率稍微跑远一点,示波器上就能看到方波的边沿出现了明显的“台阶”或“毛刺”,也就是反射叠加。这也是为什么很多CAN卡或者工装板上会预留一个120欧的拨码开关:单节点调试时打开终端,多节点挂到实际车台上时就要确认两端已经接入终端电阻,中间节点千万不要再接120欧,否则等效阻抗变成60欧,信号反射会变得更加严重。
2.3 从2.5V到3.5V/1.5V:隐性被显性驱动
收发器内部还有一套偏置电路。即使没有任何节点发送显性电平,处于正常工作状态的总线也会稳定在隐性电平。这是因为所有节点收发器内部都有隐性偏置电路,它们相当于许多个高阻值电阻并联,把总线拉到2.5V左右。发送方驱动显性时,要通过低阻抗输出级克服这些偏置电阻,把CAN_H拉到3.5V、CAN_L拉到1.5V。所以,显性电平是“主动驱动”的结果,隐性电平是“被动释放”的结果。这个逻辑对应到仲裁机制上,就是显性位可以“覆盖”隐性位,谁能输出显性,谁就赢得总线仲裁。
画波形的时候,建议习惯用颜色区分:把CAN_H画成红色,CAN_L画成蓝色,差分波形画成黑色。正常通信中,CAN_H在2.5V到3.5V之间跳变,CAN_L在2.5V到1.5V之间跳变,两者方向相反,看起来像一对镜像的方波。差分电压在0V到2V之间跳变。如果波形不是这样,先查供电、接地和终端电阻,再查收发器芯片,通常能解决八成物理层问题。
CAN物理层里面还有一个容易被忽略的参数:位时间。波特率决定一个位持续多久,比如500kbit/s时一个位是2微秒。但这2微秒又被细分为同步段、传播段、相位缓冲段1、相位缓冲段2,用于采样点设置。采样点太早可能采到前一个位的残余,采样点太晚可能采到下一个位的起始跳变,网络中距离最远的两个节点之间的信号传播延迟也会影响同步。所以,网络节点增多、线缆变长之后,波特率并不总能跑满标称值,实际设计时需要留有余量。
3. 数据链路层:仲裁、帧结构与错误处理机制
物理层确保了电平能传,数据链路层则负责解决“这条总线上,大家都在喊,谁先喊、怎么喊、喊错了怎么补救”的问题。CAN的数据链路层设计堪称经典,它没有采用传统的“先听后说+冲突检测后重传”的以太网方案,而是发明了一种非破坏性的逐位仲裁机制。
3.1 标识符即优先级:仲裁是怎么发生的
在CAN总线里,每个报文都有一个标识符(Identifier,简称ID)。不同节点要同时发送报文时,它们从起始帧的第一个位开始逐位比较:如果一个节点发送的是显性位(0),另一个节点发送的是隐性位(1),显性位会覆盖隐性位,发送隐性位的节点发现总线上实际电平与自己发送的电平不一致,就知道自己输了这场仲裁,立即转为接收状态,等下一轮总线空闲再重发。
这就像会议室里很多人同时开口,但有一个规则:“谁的声音更低,谁优先”。CAN标准帧的ID长度为11位,扩展帧为29位。ID数值越小,二进制中领先出现的0越多,仲裁时越容易胜出。所以,整车厂在设计报文矩阵时,会把对实时性要求极高的报文(如安全气囊触发、发动机转速、刹车控制)分配小ID,把非实时的车身舒适性报文分配大ID。在一条500kbit/s的CAN网络上,为了保证关键报文延迟可控,ID分配表往往是整车网络设计最先定下来的文件之一,比代码还要早。
这里有个经验之谈:仲裁机制虽然巧妙,但并不意味着总线上所有节点都发送时优先级低的就永远发不出去。CAN控制器在仲裁失败后会等待总线空闲,然后自动重发。但如果低优先级报文持续遇到高优先级报文抢占,可能出现“饿死”现象。设计时应估算每条报文的发送周期和最长延迟,必要时把相近优先级的报文分到不同网络段或调整发送周期。
3.2 标准帧布局:SOF到EOF每个段都有讲究
一个CAN标准数据帧由以下字段组成:
- 帧起始SOF:1个显性位,标记一帧的开始,同时也用于所有节点的硬同步。
- 仲裁场:11位ID(标准帧)或29位ID(扩展帧)加RTR位。RTR位表示数据帧还是远程帧,不过在实际车载应用中远程帧用得很少。
- 控制场:IDE位(扩展标识符指示)、DLC(数据长度代码)。DLC由4个bit组成,表达0到8字节数据长度。
- 数据场:0到8字节,这是应用层真正要传的数据。
- CRC场:15位CRC序列加1位CRC界定符。CRC覆盖从SOF到数据场的所有内容,用于检测传输错误。
- ACK场:发送方在ACK槽输出隐性位,接收方如果正确收到帧,就把ACK槽拉成显性位。注意,这里只要总线上任意一个节点成功接收,发送方就能收到ACK。所以,CAN的“确认”不是点对点确认,而是“总线级”确认。
- EOF:7个隐性位,标志帧结束。
- IFS:3个隐性位的帧间隔。
理解ACK机制对排查网络故障特别有用。如果报文一直在总线上发,但没有任何节点正确接收,发送端看不到显性ACK,会重发或者报错。实测波形里观察ACK槽是否被拉低,就能判断总线上是否存在“能听到发送却无法正确解码”的节点。这类问题经常出现在波特率配置不一致、位同步参数差异较大的场景中。
3.3 位填充与错误处理:总线是怎么“自愈”的
CAN协议规定,在SOF到CRC之间,如果连续出现5个相同电平的位,发送方必须插入1个反相电平的位,接收方在解码时要自动剔除这个填充位。这就是位填充。之所以这么做,是为了给接收节点的锁相环提供足够的跳变沿,保证时钟同步。没有位填充,长时间连续出现同一种电平,接收端内部时钟漂移就可能导致采样错位。
正因为有了位填充和CRC,接收节点才能比较全面地发现错误。一旦发现错误,节点会立即在总线上发送错误帧——一组显性位组成的序列。错误帧的作用不是“通知发送方重传”,而是强制破坏当前帧,让总线上所有节点都知道这帧数据无效,发送节点随后会自动重发。这个机制让CAN总线具有极强的抗干扰能力:偶发错误不会直接导致网络瘫痪,而会被系统自行纠正。
但如果某个节点持续出错,它会进入Bus Off状态——控制器把自己从总线上隔离,不再发送任何数据,直到硬件复位或满足恢复条件。很多整车偶发故障的根因就藏在Bus Off里:某个控制器因为供电毛刺、晶振偏差或者软件配置错误频繁发送错误帧,而其他节点被迫跟着重传,总线负载率飙升,消息延迟变大,最终表现为某个功能偶发失灵。排查这类问题,不能只看应用层有没有收到正确数据,要抓总线层有没有错误帧、Bus Off事件。
3.4 报文周期与负载率:车厂为什么要精确计算
一条典型动力CAN总线的波特率是500kbit/s,一帧标准数据报文通常为47到111个位不等。算上帧间间隔、位填充和可能的错误重传,实际有效载荷并不高。车载网络设计时一般会把总线负载率控制在30%到50%之间,留出足够的余量应对诊断报文、网络管理和临时突发消息。负载率过高时,优先级低的报文可能会被严重延迟,甚至出现丢帧。这里我建议所有做CAN开发的人都下载一份CANoe或者开源工具can-utils,先用虚拟总线把你的报文矩阵跑一遍,观察各节点发送周期下的总线负载率,再回灌到真实ECU看延迟,很多问题在开发早期就能暴露。
CAN数据链路层的这些设计细节,单独看都不难,难的是把仲裁、错误处理和位填充放到一条共享总线上同时运转时,保持实时性和可靠性。这也是为什么CAN协议能在汽车行业活了几十年,直到今天依然是车载网络的基本盘。
4. 车辆协议栈:从OBD-II到UDS/J1939,真正的会话在应用层
很多人一提到CAN总线,就觉得“只要把波特率配好了,报文能发出来就算通了”。这是把CAN当成一个串口来用,完全忽略了CAN之上还有一大堆面向应用的协议。CAN协议只负责传输“0和1组成的帧”,至于帧里的数据代表什么、怎么触发一次诊断、怎么读取故障码、怎么标定参数,都是上层协议的事。车辆工程里常说的“车辆协议”,主要指的就是这一层。
4.1 OBD-II:从排放诊断口看整车状态
OBD-II最初是为了排放检测而生的。在OBD-II协议下,诊断仪通过标准的16针OBD接口(也叫DLC接口)连接到车辆的CAN总线,通常使用ISO 15765-4定义的基于CAN的诊断传输层,波特率常见500kbit/s或250kbit/s。诊断仪向ECU发送带特定CAN ID的请求报文,ECU回送响应报文。OBD-II的服务模式(Mode)定义得很直观:Mode 01是读取当前实时数据,Mode 02是读取冻结帧数据,Mode 03读取排放相关故障码,Mode 04清除故障码,Mode 09读取VIN、CALID等车辆识别信息。
举个例子,一辆支持OBD-II的车,标准读取发动机转速的流程大致是这样:诊断仪发送一个11位CAN ID为0x7DF的请求帧,数据场第一字节为0x01(Mode 01),第二字节为0x0C(PID 0x0C,发动机转速),然后等待目标ECU以0x7E8等ID返回响应。响应数据里转速的计算公式是(A*256+B)/4转/分钟。这些PID公式都是标准公开的,任何一个做OBD开发的工具都能查到。
OBD-II的价值在于它的标准化程度很高,但它能访问的信息很有限。它主要是面向排放相关系统和少量基础车身数据的,距离完整的整车诊断和标定还有很大距离。真正的整车厂内部诊断,靠的是UDS。
4.2 UDS:诊断服务的“通用语”
UDS(Unified Diagnostic Services,统一诊断服务)在ISO 14229中定义,是目前全球主流车厂最通用的诊断协议。UDS不是一种总线类型,而是应用层服务。它可以跑在CAN上(底层常用ISO 15765-2传输层),也可以跑在CAN FD、LIN、FlexRay甚至以太网上。UDS服务用所谓“SID”(服务标识符)来区分功能,常见服务包括:
- 0x10:诊断会话控制。ECU上电后默认在默认会话,很多高级功能必须在扩展会话或编程会话下才能执行。
- 0x22:按ID读取数据。诊断仪指定DID(数据标识符),ECU返回对应数据,比如软件版本号、零件号、标定版本。
- 0x2E:按ID写入数据。比如写入配置参数。
- 0x27:安全解锁。许多写操作和标定操作之前,必须先通过种子和密钥算法验证权限。
- 0x31:例程控制。触发ECU内部的一段程序,比如执行某个部件的自学习。
- 0x34、0x36、0x37:请求下载、传输数据、请求退出传输。这是刷写ECU固件的标准三段式流程。
UDS里没有“广播给所有ECU”这种概念,一切都是一问一答。诊断仪先发请求,ECU回复肯定响应(SID+0x40)或者否定响应(0x7F+服务SID+否定码NRC)。比如0x10服务请求成功后,响应里会带上会话类型和P2超时时间;如果ECU收到了一个不被支持的SID,会回一个NRC 0x11(服务不支持)。
实车调试UDS时最常遇到的场景是:请求发出去,ECU没有任何响应,然后诊断仪一直等超时。这种问题百分之七八十出在物理层或配置层——目标ECU的物理寻址ID配错了、应用寻址和功能寻址范围没对应上、或者波特率不一致。剩下两成才是ECU内部逻辑问题。所以做UDS开发的第一步,永远是先把CAN ID和波特率表拿准。
4.3 J1939:商用车和工程机械的“大管家”
J1939是基于CAN的又一重要上层协议,主要在卡车、客车、农业机械、工程机械上使用。J1939的最高波特率是250kbit/s,但它通过协议约定把CAN的11位ID扩展为29位ID,并重新定义每个比特的含义。J1939的报文核心是PGN(Parameter Group Number,参数组编号),每个PGN里包含一组参数,每个参数又有SPN(Suspect Parameter Number)。比如发动机转速、车速、冷却液温度,都有固定的SPN编码和缩放公式。J1939定义了大量标准报文,但更重要的是一套完整的网络管理机制:节点可以声明自己的名字(NAME),通过地址声明和地址请求建立通信关系,还支持多包传输,因为CAN单帧最多传8字节,一些大参数组需要拆成多帧组包传输。
如果你以前只做过乘用车CAN,第一次接触J1939会明显感觉到两者的“气质”不同。乘用车CAN网络更多是整车厂私有报文矩阵,诊断用UDS;而J1939偏公共开放,更注重设备与设备之间的互操作,比如一台拖拉机和一台农具挂接后,不需要人工配置就能通过地址声明和对PGN的订阅实现协同工作。这也是为什么在农机和工程机械圈里,J1939的地位举足轻重。
4.4 CAN FD与更高阶的上层协议
经典CAN的8字节数据场和1Mbit/s速率,在当今自动驾驶和OTA需求面前已经不够用了。于是博世推出了CAN FD(CAN with Flexible Data-rate)。CAN FD在保留经典CAN仲裁机制和ID优先级逻辑的前提下,把数据场长度扩展到最多64字节,并在数据场部分把位速率切换到最高8Mbit/s甚至更高。仲裁段仍按原速率运行,保证和经典CAN节点兼容,数据段则提速。这意味着同样的总线带宽可以承载更多有效数据,特别适合高精度地图、传感器融合和控制指令批量化传输。
现在不少新型控制器同时支持经典CAN和CAN FD,诊断协议也扩展出了DoIP(基于以太网的诊断)和基于CAN FD的UDS over CAN FD。对开发者来说,选型时要特别小心:CAN FD的“弹性数据速率”虽然香,但总线上所有节点都必须支持FD速率才能正常工作;如果网络里混有只支持经典CAN的老节点,FD报文会因为被老节点当成错误帧而被打断。所以FD网络的兼容性设计比想象中更麻烦。
车辆协议栈这张“全景图”里,还有以太网(100/1000BASE-T1)、LIN、FlexRay、A2B等等,它们各自在不同场景下发挥优势。但在短距离、高可靠、强实时控制场景里,CAN和CAN FD依然是不可替代的基础。看协议不能只看表面格式,还要看它适配的工程问题:OBD-II面向排放验证,UDS面向诊断和刷写,J1939面向设备协同,CAN FD面向高带宽数据迁移。搞清楚了这一点,拿到一帧报文时才会有“既见树木,又见森林”的感觉。
5. 案例实战:达妙电机通过CAN实现关节位置控制的报文链路
最近这几年,机器人圈子里有不少人开始用达妙(DM)电机做四足机器人、机械臂和关节模组。达妙电机能火起来,除了本身扭矩密度大、集成度高之外,很大一个原因就是它把CAN总线控制做得非常直观。只要你理解CAN和它的上层控制协议,哪怕从来没写过机器人控制代码,也能在半小时内把一个关节电机转起来。这一节我结合自己的调试经验,把这条链路完整拆开。
5.1 达妙电机的CAN报文约定与基础ID管理
达妙电机通常有一个主控板(或者叫驱动板),对外提供CAN总线接口。每个电机在总线上有一个唯一的CAN ID,一般通过拨码开关或者软件参数来设置。上位机(MCU或工控机)作为总线上唯一的“主站”,向各个电机发送控制报文,电机把状态数据回传,本质上是一问一答或者周期广播两种模式。
以常见的达妙电机控制模式为例,回传的状态报文通常包含电机位置、速度、扭矩等数据,其位置和速度是编码器采样后经过换算得到的。控制端的指令报文则需按照驱动板手册定义的格式填写,通常包括控制字、目标位置、目标速度、最大扭矩等字段。
这里要特别提醒一个容易犯错的点:CAN是共享总线,单帧数据量很有限。关节电机控制中,位置可以是一个int32类型的原始编码器计数,速度是int16,扭矩是int16,再加上控制字和校验字段,一帧8字节可能刚好够用。有些用户为了省事,直接把浮点数拆成4字节塞进报文,不考虑字节序(Big-Endian还是Little-Endian),结果电机端解析出来是天文数字。达妙这类电机驱动板一般遵循固定的发送/接收数据格式,建议先拿官方的上位机软件抓一帧正常通信报文,在CAN分析仪里看清楚每个字节的含义,再去写自己的协议解析,不要凭空猜。
5.2 位置闭环控制中,CAN报文里装的是什么
假设我们要让一只机械臂的肩关节转动到80度位置,整个控制回路是这样的:上位机根据运动规划算出发给每个关节的目标位置和目标速度,通过CAN总线把报文周期性地发给对应电机的驱动板;驱动板内部的FOC(磁场定向控制)算法再根据位置环、速度环、电流环逐级调节电机相电流,最终让电机输出指定的扭矩,到达并稳定在目标位置。
在这个过程里,CAN报文只是“最外面的信封”,信封里的内容通常包含:
- 控制模式编号:速度模式、位置模式、扭矩模式,或者混合模式。
- 目标位置:单位可能是转的圈数、弧度或编码器计数。注意电机手册里给出的比例关系。
- 目标速度/最大速度:限制电机运动快慢。
- 最大扭矩(电流限制):防止电机碰到障碍物时硬怼,烧驱动或损坏减速器。
- 使能位/控制字:让驱动板进入使能状态,一般先发禁用、再发使能,确保安全。
控制周期很关键。机器人关节控制通常要求1kHz到4kHz的控制频率,也就是每0.25ms到1ms就要发一帧CAN报文。以500kbit/s波特率为例,一个标准数据帧从SOF到EOF大约需要0.2ms左右。如果总线上挂了多个电机,每个电机都占一个周期,那么就要精确计算总线的总负载,保证每个控制帧都能在控制周期内送达。我曾经在一个六自由度机械臂项目里,开始用500kbit/s总线下发六个关节的指令,控制周期要求1kHz,算下来负载率已经到60%左右。测试时发现其中一个关节偶发跳动,抓总线后看到普通数据帧之间被插入了不少错误帧和重传帧,实际负载率超出预期。后来把波特率提到1Mbit/s并优化了报文ID优先级,问题才消失。所以,电机控制的总线设计,一定不能只看“好不好发”,还要看“有没有时间发、发完有没有余量”。
5.3 从“报文对”到“转起来”:一个最小可跑通的流程
如果你想在桌面上跑通达妙电机的最小CAN控制链路,我的建议顺序是:
- 确认硬件连接:电机驱动器CAN_H、CAN_L和你的CAN卡对应连接,两端接入120欧终端电阻(如果只有两个节点,分别打开两端的终端,或使用总线式接法)。
- 设置波特率:达妙电机默认波特率常见1Mbit/s或500kbit/s,用官方配置工具或上位机确认。CAN卡一端必须设置成完全相同的速率。
- 扫ID或看回传:上电后电机驱动板一般会主动广播状态帧,用CAN分析仪查看总线上是否有来自电机ID的报文。如果一帧都看不到,先查供电是否到位、终端电阻是否接好。
- 发送使能指令:根据协议手册发送使能帧,观察电机是否有“锁轴”动作。上电未使能时电机轴是可以自由转动的,使能后会明显感觉到阻力。
- 发送位置指令:先发送目标位置为当前编码器计数的报文,让它保持原位;再发送一个目标位置增加的量,比如“转10度”对应的计数增量,观察电机是否平滑运动。
- 调整参数:如果电机抖动,多半是位置环或速度环增益太高;如果电机响应慢,可能是目标速度限得太低,或者控制周期过长。这些参数通常在驱动板参数表里可调,通过CAN报文也能实时修改。
这个过程里最容易出现的“蠢”问题是:CAN卡和电机的GND没有正确共地。有些CAN收发器虽然隔离,但不隔离的设计里,两端参考地不一致会导致共模电压异常,轻则波形畸形,重则烧毁收发器。所以做实验时,先把两块板子的电源地可靠连接,再DB9或端子排连线。
5.4 精准控制不只看CAN,还得看整个环路
很多人在CAN上花了很多精力,以为报文发得越快、越频繁,关节就越精准。真相是:CAN只是传送了“目标值和状态值”,真正的精准控制靠的是驱动板内部的电流环、速度环和位置环带宽。CAN控制周期再高,如果驱动板内部环路没有调好,照样会有超调、震荡和静态误差。反过来说,驱动板内部闭环调得再好,如果CAN报文周期抖动严重、发送延迟不均,也会给控制算法引入额外噪声。所以,达妙电机这类“CAN接口+驱动一体”的关节模组,本质上是把底层FOC闭环做完了,留给上层的是一个“周期更新目标值”的任务。你要做的不是去改电机内部的PID,而是保证CAN链路在确定性的周期内稳定地把目标值送到。
这里再分享一个小技巧:在机器人上做关节控制时,可以给CAN报文打时间戳,或者在每个控制周期开始时触发电机的状态回传,这样可以计算从“命令发出”到“驱动器确认”的往返延迟。测出来的延迟如果超过一个控制周期,就需要优化发送时机或减少总线上无关的广播报文。延迟、抖动、丢帧,这三个指标比单纯看波特率更能反映一个电机控制网络的健康状况。
6. 车载CAN测试台架搭建与排查经验
CAN这个东西,写代码时觉得简单,一到实车测试就露馅。波形毛刺、帧错误、丢包、Bus Off、节点静默……问题五花八门。我这几年搭过不少CAN测试台架,从最简单的USB-CAN盒到带记录功能的高性能CAN卡,积累了一些很实际的排查经验。这一节不铺垫理论,直接讲台架怎么搭、抓波形怎么下手、哪些坑是高频的。
6.1 一套够用的CAN测试台架需要什么
最基础的一套CAN测试环境包括:
- 一个CAN分析仪:USB-CAN卡是最常见的,好的支持双通道、带隔离、能发送标准帧/扩展帧/CAN FD,采样率可调,最好带硬件时间戳和DBC解析功能。
- 一台运行分析软件的PC:主流工具包括PCAN-View、CANalyzer、CANoe、周立功ZCANPRO、开源的cangaroo、BUSMASTER,以及Linux下的can-utils。
- 一个可调电源:给ECU、传感器、电机驱动板供电。一定要用带限流的电源,避免接线错误瞬间烧板子。
- 示波器:至少两通道,带宽100MHz以上。用于物理层波形分析,尤其是CAN_H/CAN_L和差分信号。
- 终端电阻和线缆:120欧电阻若干,短双绞线若干。自制测试线时最好用双绞线而不是平行线,以保证共模抑制能力。
台架的物理连接上,我强烈建议在CAN分析仪旁边加一个单独的120欧终端拨码开关,方便在只有单节点测试时模拟“总线另一端有匹配电阻”的状态。有些USB-CAN盒内部已经有终端电阻可调,用之前看一眼说明书,别默认开着,也别默认关着。曾经有同事因为CAN盒终端的开关拨错,插到台架后把整条总线的信号反射得没法看,折腾了半天才发现问题出在测试工具本身。
6.2 抓波形时的第一反应:先看物理层,再看协议层
遇到CAN通信故障,我的排查顺序固定三板斧:
第一板斧,用示波器看空闲总线状态。正常隐性电平应该在2.5V左右,CAN_H和CAN_L之间差压接近0V。如果空闲时CAN_H或CAN_L明显偏离2.5V,可能是有节点损坏、收发器击穿或者偏置电阻异常。
第二板斧,抓一段通信波形,看显性电平的幅值。正常高速CAN显性差压应该在1.5V到3.0V之间,典型2.0V。低于1.2V时接收端可能就识别不了;高于3.5V要怀疑线缆短路或者节点驱动能力过强。另外看波形边沿是不是干净,如果边沿有严重的振铃,多半是终端电阻缺失或阻抗不匹配。
第三板斧,用CAN分析仪看总线上的错误帧和帧间隔。如果错误帧连续出现,先数一下错误ID,再看错误帧出现的时间规律。常见的错误类型包括位错误(总线电平与发送电平不一致)、填充错误、CRC错误、格式错误、ACK错误。位错误常常是多个节点发了冲突但没有正常仲裁,或者收发器在总线上检测到异常电平;ACK错误则多半是整个网络里没有节点正确收到帧,可能是波特率不对,也可能是掩码过滤把帧滤掉了。
这板斧用完,基本能定位八成问题。剩下两成隐藏在更隐蔽的地方:电源纹波干扰、CAN线束和高压线束的耦合串扰、接插件接触不良等。这类问题往往表现为“冷车正常、热车故障”或者“怠速正常、加速故障”。排查时要让示波器长时间记录波形,配合CAN卡的事件日志,把偶发错误帧和车身工况关联起来。
6.3 高频踩坑清单:波特率、ID过滤、地电位、总线负载
把过去几年在项目里遇到的CAN问题拉一个清单,以下几类是出现频率最高的。
波特率不匹配:总线上节点的波特率不是由软件“猜”出来的,必须先确认。有的ECU用500k,有的用250k,混接在一起就会出现大量错误帧。用CAN分析仪自带的对总线波特率估计功能可以快速扫描,但最终还是要以节点手册为准。要注意某些ECU波特率存在容差,测量时用示波器看一位持续时间,再换算成实际波特率,比软件估算更可靠。
ID过滤设置错误:CAN控制器硬件接收滤波器如果设置过严,会直接把不关心的帧丢弃,导致上层软件“看不到”报文。很多新手在调试第三方ECU时,默认滤波学过宽或过窄都会出问题。最稳妥的办法是先把硬件滤波完全关闭,用软件层的过滤确认需求,再在硬件里配精确的接收ID表。
地电位漂移:长距离CAN布线或者多个电源供电的台架,如果各节点参考地之间存在较大电位差,显性电平的绝对电压会被抬高或拉低,导致接收端差动输入超出共模范围。这种情况下,CAN_H和CAN_L对地波形可能都偏移到4V或者1V以下,但差分波形看起来还算正常。使用隔离式CAN收发器是最直接的解决手段,或者确保所有节点通过粗地线可靠连接。
供电不稳与毛刺:电机启动、继电器吸合瞬间,整车电源总线会产生很大的瞬态电压跌落或尖峰。如果ECU的CAN收发器供电来自未加足够滤波的电源轨,总线波形就会出现相位抖动。解决方法是加大收发器电源端去耦电容,或者在PCB设计阶段把CAN供电和功率地分开布局。
负载率过高:前面提到过,当总线负载率长期超过80%,错误重传会让负载率进一步恶化。有一个直观的观测口径:在CAN分析仪的统计面板里看错误帧率(Error Frame Rate)和总线负载率。正常网络错误帧率应该是零或极低,如果错误帧率超过1%,别急着改代码,先排查物理层。物理层干净之后,错误帧率自然会掉到零。
6.4 总线仿真与DBC:从“会收帧”到“会解析”
很多项目拿着抓到的原始CAN帧给上位机,可上位机看不懂。这时候DBC文件就派上用场了。DBC是CAN报文数据库的格式,定义了每个CAN ID对应的报文名称,每个信号在数据场里的起始位、长度、字节序、缩放因子、偏移量和取值范围。有了DBC,CAN分析仪就能直接显示“发动机转速 = 1500rpm”而不是“报文0x0CF00400数据 88 A5 00 00 00 00 00 00”。
自己写DBC时要特别小心字节序问题。CAN标准采用Motorola格式(大端)和Intel格式(小端),同一个信号在这两种格式下起始位不同。很多工程师第一次用Vector工具时,被BusByte和Motorola的位序号绕晕,导致解析出来数值完全不对。我的经验是:先拿一个已知信号做标定,比如一个0x8000的原始值对应实际物理量为512,调DBC里起始位和缩放,直到解析值和实物量对应,再去批量导入其他信号。
如果是自己写嵌入式代码解析CAN帧,一定不要用“看一遍协议文档直接开整”的方式。先把所有报文用分析仪抓一遍,对照DBC或者Excel矩阵按字节展开,确认字节序和符号位,再写解析函数。特别是涉及有符号数时,负数的补码处理容易出错,每条信号都做好断言测试。
整个CAN测试的核心思路,其实就是“先物理、再协议、再应用”。物理层不过关,协议层再对也没用;协议层不对,应用层看到的全是怪数据。把这些环境、工具、排查顺序理清楚,遇到任何一条CAN总线的怪问题,都不会慌。
7. 写在最后:从CAN总线到车辆协议,一通百通
如果你完整读到了这里,回头看整个CAN总线与车辆协议的全景,其实会有一个很清晰的脉络:物理层用差分电平在恶劣环境下保障了“0/1”的可靠传输,数据链路层用仲裁、帧结构和错误机制保障了“谁先发、发什么、错了怎么办”,而真正的“语义”则在上层协议里。OBD-II告诉你发动机转速是多少,UDS告诉你故障码在哪,J1939让不同制造商的设备能握手协作,达妙电机这类专用驱动协议则把位置、速度、扭矩变成了关节上真实有力的动作。
我自己做CAN开发这几年,最大的体会是“别只盯着报文看”。CAN总线是一条布满暗礁的河,报文是河面上的船,但船能不能顺畅走,取决于河床的物理层、水流的数据链路层,以及岸上指挥交通的应用层。你只看船,发现船搁浅了,其实问题出在水位线太低——终端电阻没接好。所以,遇到问题先退一步,按物理层、数据链路层、应用层逐层排查,往往比执拗地盯着一个字段更快。
现在新项目里越来越多地用到CAN FD、车载以太网和SOA架构,但CAN并没有被淘汰。它的确定性、低成本、可靠性和丰富的工具链,依然让它活跃在底盘控制、动力总成、车身控制和很多非车领域。学会了CAN,再去看别的车载协议,很多底层的思路都是相通的:怎么处理多节点竞争、怎么保证实时性、怎么做错误恢复、怎么在上层定义语义。底层逻辑通了,新协议上手只是一层窗户纸的事。
如果你正打算入门CAN或者正在为一个CAN通信问题挠头,我建议你先不要急着动代码,拿示波器好好看一次总线波形,再拿分析仪抓一帧正常报文和一帧错误帧,亲手把每个字节拆出来对一遍。这个过程做完,你对CAN的理解会超过很多只会调库发帧的“调包侠”。后面再遇到车辆协议里的各种缩写和术语,你会发现,它们都只是这个总线世界里不同房间的钥匙而已。