
做嵌入式开发或者汽车电子调试绕不开的第一个总线协议大概率就是CAN。尤其这两年新能源车、机器人、工业控制都在大量铺CAN节点面试应届生时问CAN协议种类和CAN数据帧都不算超纲因为它是整个控制器局域网通信的“基本盘”。这篇文章我从协议分类、帧结构、仲裁机制到实际抓包解析一条龙写一遍尽量不废话全是动手时能直接用的东西适合刚接触CAN的工程师、电子专业学生以及那些已经把CAN调通但没细究过帧里每个bit含义的人。1. 先理清方向CAN协议种类到底从哪里分叉1.1 按物理层速率区分的三种常见类型很多新手以为CAN只有一种其实“CAN”是个家族。最常见的分类维度有两个一个是物理层速率和拓扑一个是数据链路层的版本演进。先从物理层说起。第一类是高速CAN对应ISO 11898-2标准速率从125kbit/s到1Mbit/s采用双绞线加120欧终端电阻的差分信号传输。这是目前汽车动力系统、底盘控制里最主流的方案要求线束长度一般不超过40米严格说1Mbit/s时建议小于30米。第二类是低速容错CAN对应ISO 11898-3标准速率通常在10kbit/s到125kbit/s最大特点是一根线断开或者对地短路时还能继续通信适合车身舒适系统这类对可靠性要求高、实时性要求不高的场景。第三类是单线CAN主要出现在通用系的SAE J2411规范里只有一根信号线以地为参考优点是省线束缺点是抗干扰差、速率上限低现在用得越来越少。这里要特别强调高速CAN和低速容错CAN的物理层收发器不通用电气特性差异很大。如果你把低速容错CAN的收发器芯片装到高速CAN节点上总线上会出现各种间歇性错误帧而且极难排查。我自己就踩过这个坑工装板上不小心贴错了TJA1054结果整个总线一会儿正常一会儿疯狂报错换了TJA1040立刻安静。1.2 按协议版本演进划分CAN 2.0A、CAN 2.0B、CAN FD与CAN XL物理层之外更常说的“种类”其实是数据链路层的版本。业内经常挂在嘴边的就是CAN 2.0A、CAN 2.0B和CAN FD。CAN 2.0A是1991年Bosch发布的经典标准定义的是11位标识符的标准帧。CAN 2.0B扩展了29位标识符的扩展帧同时完全向下兼容2.0A——这里说的兼容不是硬件自动识别而是通过帧里的IDE位来区分当前帧是标准帧还是扩展帧。现实中汽车动力CAN基本都用2.0B因为节点多、报文类型多11位只有2048个组合扩展后可以到5亿以上划分网络层和功能域时灵活得多。CAN FDCAN with Flexible Data-rate是后起之秀解决了经典CAN两个痛点数据场最多只有8字节速率上限1Mbit/s。CAN FD把数据场扩展到最多64字节并且支持在仲裁段用经典速率、数据段切换到最高8Mbit/s的可变速率这就是它名字里Flexible Data-rate的含义。CAN FD的帧在电路上和CAN 2.0B不完全一样必须使用支持CAN FD的控制器和收发器好在现在大部分新出的MCU比如STM32F3/F4/G4系列、英飞凌AURIX系列都原生支持。最后提一嘴CAN XL它是CAN FD的下一代数据场最大2048字节速率更高但目前量产应用还很少多数人暂时只需要知道名字。2. CAN数据帧逐字段拆解标准帧和扩展帧到底差在哪2.1 标准帧的完整结构走读搞清楚种类之后重头戏来了CAN数据帧里每一个bit是干什么的。先以11位ID的标准数据帧为例从SOF开始逐字段过一遍。SOF帧起始1位显性电平逻辑0。它有两个作用一是标志一帧数据的开始二是用于所有节点的位同步。总线上所有节点平时都处于隐性电平逻辑1一旦检测到显性跳变就知道有人要发帧了。仲裁场这是整个帧里最关键的字段除了11位ID还包括1位RTRRemote Transmission Request。数据帧的RTR是显性0远程帧的RTR是隐性1这个区分在后面仲裁优先级时非常重要。控制场包含1位IDEIdentifier Extension标识符扩展位标准帧里为显性0、1位保留位r0和4位DLCData Length Code数据长度码。DLC的取值范围是0到8对应数据场0到8字节虽然4位理论上能表示0到15但经典CAN标准里超过8的值没有定义不允许使用。老工程师都知道如果调试时看到DLC9以上的报文要么是协议栈配置错了要么是把CAN FD误解析成了经典CAN千万不能当正常数据处理。数据场0到8字节存放实际要传的内容。注意DLC只是“长度声明”数据场实际发出几个字节就由DLC决定接收方不会去猜所以应用层协议里发送端和接收端必须约定长度。CRC场15位CRC校验码加1位隐性CRC定界符。CRC的计算覆盖从SOF到数据场结束的所有bit生成多项式是固定的不需要你手动实现控制器硬件自动完成。这个字段的意义在于CAN协议没有使用复杂的分组确认机制而是依靠CRCACK的组合保证可靠性算是在实时性和错误检测能力之间做的一个折中。ACK场由2位组成ACK Slot加ACK定界符。发送节点在ACK Slot里发出一个隐性位然后释放总线所有接收节点如果收到的帧通过CRC校验就会在这个时刻主动拉一个显性位来“回应”发送者。如果总线上没有任何节点回应ACK发送节点就会判定发送失败触发重发。这个机制是理解“为什么总线至少要两个节点才工作正常”的关键。EOF帧结束7位隐性位之后还有至少3位IFSInter-Frame Space帧间空间用于分隔两帧报文。EOF里的隐性位不允许填充让接收方有一个确认帧结束的稳定窗口。2.2 扩展帧的差异点与SRR位29位ID的扩展帧长什么样它和标准帧最大的区别就在仲裁场和控制场。扩展帧从SOF开始先发11位基本ID然后跟一个SRR位Substitute Remote Request替代远程请求位固定为隐性1。为什么要这个位因为它占据的位置和标准帧的RTR位一模一样这样仲裁时扩展帧会在这个位置表现出隐性位。接着是IDE位扩展帧里IDE必须为隐性1这一步等于告诉总线上所有节点“我接下来还要发18位扩展ID不是标准帧。”然后才是18位扩展ID和RTR位。仲裁的时候如果总线上同时出现一个标准帧和一个扩展帧且它们前11位ID完全相同那么标准帧的RTR是显性0扩展帧的SRR是隐性1标准帧会在SRR位赢得仲裁继续发送。这意味着标准帧的优先级天然高于相同ID的扩展帧。这个细节是CAN规范里的硬性规定设计网络时必须心里有数否则很可能出现你预期的高优先级报文被低优先级报文抢先的情况。为了方便对比我列一张实际工程中经常用的字段对照表字段标准帧11位ID扩展帧29位IDSOF1位显性1位显性基本ID11位11位SRR无1位隐性IDE控制场里1位显性1位隐性扩展ID无18位RTR数据帧显性 / 远程帧隐性数据帧显性 / 远程帧隐性r0/r1r0保留位r0r1两个保留位DLC4位4位数据场0-8字节0-8字节CRC15位15位算法不同ACK/EOF/IFS相同相同2.3 位填充机制为什么波形不是理想方波还有一个新手特别容易忽略的机制——位填充Bit Stuffing。CAN规定在SOF到CRC场结束之间如果连续出现5个相同的电平发送方必须自动插入一个相反电平的填充位。也就是说如果你连续发5个显性位第6个位置就会出现一个强制隐性位让总线上至少每6个bit就有一次电平跳变。这样做的目的有两个第一是保持接收节点的时钟同步。CAN没有单独的时钟线接收方靠总线电平的跳变不断调整自己的采样点如果长时间没有跳变晶振误差累积就会导致采样错位。第二是提供错误检测的辅助手段接收方如果发现同一个电平连续出现了6个以上就能立刻判断这是填充错误并触发错误帧。这意味着你直接用示波器看CAN_TX引脚或者总线波形时看到的不会是理想的高低电平组合数据段里会出现规律的额外跳变这在第5节实操解析时会再次遇到。2.4 为什么经典CAN数据场最多只有8字节很多人问过既然CRC都算了为什么DLC不能直接做到64字节非要等CAN FD原因主要有三个一是经典CAN的仲裁是逐位进行的一帧数据越长总线被占用的时间就越长这对实时性要求高的动力系统不可接受。8字节在1Mbit/s下大约130微秒已经是“够用且不拖累调度”的折中值。二是错误重发机制决定的帧越短出错后重发的代价越小。三是接收缓冲区设计经典CAN控制器邮箱通常只有8字节数据宽度做成16字节或更长会让芯片面积和成本明显上升。CAN FD之所以能扩展是把整个协议重新设计了包括更长的CRC、更灵活的长度编码和变速率机制代价是兼容性下降所以实际项目选型要看需求。3. 除了数据帧远程帧、错误帧和过载帧各管什么3.1 远程帧请求数据但自己不带数据CAN总线上传输的帧一共四类数据帧、远程帧、错误帧和过载帧。大家平时见到的99%都是数据帧但另外三种在关键时刻能救命。远程帧的作用是“请求数据”某个节点想获取其他节点的数据就可以发送一个RTR为隐性的远程帧被请求方收到后回应一个数据帧。远程帧没有数据场DLC字段表示请求的数据长度。它的仲裁机制和数据帧共用ID空间但要注意如果总线上同时出现相同ID的数据帧和远程帧数据帧的RTR是显性0会赢得仲裁所以“数据帧优先于远程帧”是芯片硬件里固化的规则。实际工程中远程帧用得越来越少了。原因是它有一个潜在问题如果被请求节点没反应过来或者软件处理不及时请求方会一直重发占掉总线带宽。现在多数CANopen和J1939这类高层协议都不鼓励用远程帧而是直接由数据生产者周期发送或者用专门的请求帧加应答帧来设计。如果你在项目里遇到有人坚持用远程帧一定要确认应用层有超时保护否则节点故障时整个总线都可能被拖死。3.2 错误帧与错误计数器总线质量的隐形守护者错误帧是CAN协议里最容易被误解的概念。它不是一个普通节点主动“发”出来的业务帧而是当任何一个节点检测到错误时立刻在总线上发出的6位连续显性位这6个显性位会破坏正在传输的帧强制所有节点终止当前帧的接收。CAN控制器内部有两个错误计数器发送错误计数TEC和接收错误计数REC。正确发送/接收一次帧计数器减1出错一次根据错误类型加8或者加1。计数器数值决定了节点处于三种状态之一错误主动Error Active、错误被动Error Passive和总线关闭Bus Off。错误主动节点可以主动发送错误标志错误被动节点只能发送隐性错误标志且发送前要等待8个隐性位总线关闭节点则是彻底脱离总线不再参与通信。这是一套非常精巧的故障隔离机制但它在工程上的黑暗面是如果一个节点因为硬件问题疯狂发错误帧它会先反复增加TEC直到自己进入Bus Off过程可能只需要几百毫秒而此时总线上已经堆积了大量错误帧其他节点都被干扰到无法正常通信。排查这类问题最快的方法就是用CAN分析仪看错误帧计数和节点的错误状态寄存器而不是用示波器一帧一帧数。3.3 过载帧什么时候会触发过载帧在四个帧类型里存在感最低但它解决的是一个非常实际的硬件问题接收节点来不及处理。当接收节点的内部缓冲区已经满了或者前一个帧的处理尚未完成时节点会主动发出过载帧向发送方表达“我这边处理不过来等一下”。过载帧的结构是由两个显性过载标志加8位隐性定界符组成和错误帧类似区别在于过载帧是预谋的、非破坏性的不会被当作错误来计数。实际项目中过载帧大量大量出现往往意味着软件设计有问题。比如某个节点中断处理太慢或者总线波特率设置和实际节点性能不匹配。正常工作的CAN网络中过载帧应该几乎看不到。如果你用CAN分析仪抓包时发现过载帧计数持续增长先检查接收节点的FIFO深度和中断负载而不是急着加大缓冲区——把缓冲区加到128帧意义不大问题的根源在处理速度上。4. 仲裁与优先级背后的底层逻辑4.1 多主通信与CSMA/CA机制CAN总线和UART、SPI完全不同它没有主机和从机的区分。任意节点在总线空闲时都可以尝试发送这就是典型的多主通信。为了避免多个节点同时发送导致冲突CAN采用了一种被称为CSMA/CA载波侦听多路访问/冲突避免的机制每个发送节点在发送前先监听总线发现总线忙就延后一旦开始发送还要边发边读回总线电平实时比较“我发出去的电平”和“总线上实际的电平”。这个“边说边听”的设计是CAN能实现无损仲裁的基础。无损的意思是仲裁失败的节点不会收到罚站惩罚它会在当前帧结束后自动重新发送自己的数据帧整个过程中没有任何数据被破坏。相比于以太网那种“发了发现冲突然后随机退避重传”的机制CAN的确定性要好得多这在实时控制场景里极其有价值。4.2 显性覆盖隐性ID越小优先级越高仲裁的具体过程可以这样理解CAN总线上显性电平0能够覆盖隐性电平1。多个节点同时发送时从SOF后的第一个ID bit开始每个节点逐位发送自己的ID同时读取总线电平。如果某个节点发送了隐性1但读到总线上是显性0就立刻知道自己仲裁失败了马上停止发送转为接收模式。举个例子节点A发送ID0x100二进制0001 0000 0000节点B同时发送ID0x200二进制0010 0000 0000。两个ID的最高位都是0继续比较下一位A是0B是1。当A输出0、B输出1时B发送的隐性1会被A发送的显性0覆盖B发现总线电平和自己不一致退出竞争A继续发送。所以在这个机制下ID的数值越小优先级越高。工程上习惯给最高优先级的报文分配最小的ID比如安全气囊的碰撞信号通常会放在一个非常靠前的ID上而车窗升降这种低实时性报文则放在大ID段。4.3 远程帧、数据帧优先级与设计的实际考虑当我前面讲标准帧和扩展帧仲裁时核心结论是显性位永远赢过隐性位。扩展一下在一个已经规划好的网络里如果存在多种类型的帧共用同一个ID的情况仲裁优先级从高到低依次是数据帧RTR0 远程帧RTR1、标准帧IDE0 扩展帧IDE1。这里的排序是建立在ID相同的前提下。在实际设计网络时我建议把ID当作协议的一部分来管理而不是随意分配。常见的做法是按功能模块划分ID区间比如0x100-0x1FF给动力域0x200-0x2FF给底盘域0x300-0x3FF给车身域再在区间内按信号实时性分配。这样做的好处有两个一是仲裁行为非常直观二是排查问题时看ID段就知道是哪个子系统发出来的。千万不要为了省事让各模块自己随便编ID等联调的时候两个节点ID冲突总线上的现象会非常诡异——报文一会儿有一会儿没有CRC和ACK都不稳定极难定位。5. 实操抓一段总线报文手动解析一个CAN数据帧5.1 工具准备与接线注意理论讲一堆不动手等于白看。我建议手头准备一个USB-CAN分析仪不管是周立功还是国产几十块的都没问题再加两个120欧的终端电阻或者直接用一个带内置终端电阻的CAN卡。连接时注意CAN_H接CAN_H、CAN_L接CAN_L两个节点之间用双绞线最稳。供电一定要共地CAN收发器的参考地不一致会把差分信号拉到完全不可用的状态这是新手最容易踩的坑。打开上位机软件设置波特率的时候要先确认与你将要测试的节点一致。最常见的波特率是500kbit/s和250kbit/s如果实际节点是500k而你设成250软件里会看到满屏的接收错误计数一个正常报文都出不来。5.2 用一个例子逆向解码全程假设我们用分析仪抓到了一个标准数据帧的十六进制字节序列这是把整个帧按字节切出来的直观表示我在这里用一个非常典型的报文举例。先在分析软件里看到这样一条原始记录ID: 0x123 DLC: 8 Data: 01 02 03 04 05 06 07 08这是已经解析好的结果控制器把CRC、ACK这些都验证掉了所以只上报ID和数据。那么这条帧在总线上实际是怎么传输的第一步写ID0x123展开成11位二进制是100100011不是00100100011注意是从高位往低位CAN通信中ID先发MSBMost Significant Bit。第二步加RTR0这是数据帧仲裁场就变成100100011 0第三步控制场标准数据帧的IDE0r00DLC8也就是0000 1000。这里DLC的4位是二进制1000也就是十进制的8。第四步数据场就是01 02 03 04 05 06 07 08这8个字节按顺序发送。第五步控制器硬件会自动追加15位CRC、1位CRC定界符、ACK场、7位EOF。这些在正常接收时由硬件完成校验工程师不一定需要逐位手工算但是理解它们的存在对排查CRC错误很有帮助。如果这时候你抓的是逻辑分析仪波形你会看到在DLC之后的数据区波形不是01 02 03 04的方波直接对应关系中间会插入前面说的位填充位。举个例子如果数据字节是0x00连续8个显性0那么控制器会在第5个0之后主动插入一个隐性1来打破连续电平所以示波器上看到的位模式就多了一个bit。这就是为什么很多新工程师对照数据和波形总是对不上恍然大悟之后才明白原来是填充位在“捣乱”。5.3 命令行快速验证从ASCII到半自动解析如果在Linux环境下做嵌入式调试我习惯直接用SocketCAN工具链验证节点。配置CAN0接口到500kbit/s并启用sudo ip link set can0 type can bitrate 500000 sudo ip link set can0 up发送一帧标准帧ID0x123数据为01 02 03 04 05 06 07 08cansend can0 123#0102030405060708监听总线上的所有帧candump can0这三条命令是调试时的“三板斧”。如果配置完can0后发送失败并提示“no ACK received”十有八九是总线上没有其他节点在听或者终端电阻没接好。这时候除了检查硬件还可以用candump看看是否有收到任何报文缩小问题范围。6. 常见问题与排查技巧实录6.1 总线没有ACK响应现象发送端一直报发送失败CAN分析仪上看不到错误帧以外的任何帧。原因通常有两个一是总线上只有发送节点自己没有其他节点接收ACK Slot一直是隐性发送端无法确认成功二是终端电阻缺失信号反射导致报文到了接收端已经被畸变得无法通过CRC校验接收端自然不会回复ACK。排查步骤先用万用表量CAN_H和CAN_L之间的直流电阻正常应在60欧左右两个120欧并联如果量到120欧说明只有一个终端电阻如果量到几百K甚至开路说明两个都没有或者线断了。然后再确认总线上至少有两个正常节点在线。6.2 波特率不匹配时的典型表现现象CAN分析仪里疯狂出现错误帧偶尔能看到一两个好像正常的报文但ID和数据都像乱码。这是因为每个bit的采样点位置对不上接收节点不停地把位边沿采到错误的地方。排查方法用示波器看CAN_H和CAN_L之间的差分波形测量一个显性位的时长。比如测到2.0微秒那么波特率就是1/2.0us 500kbit/s这就是真实波特率。CAN协议规定标准波特率容差一般在±0.5%以内所以如果计算出来是503k仍然可能通信不稳。6.3 两个节点ID冲突会怎样现象总线上出现规律性错帧节点A发送自己的数据时节点B也在发送相同ID的数据两边互相不知道。这时候从分析仪上看该ID的报文周期的时机会抖动甚至出现连续发两次的现象而且CRC偶尔报错。根本原因在于相同ID的两路数据在空中发生仲裁后谁的数据都不完整接收方很可能收了A帧的片段又接着收B帧的片段导致上位机解析逻辑混乱。解决方案在协议设计阶段就规划好ID分配表每个节点的每个逻辑报文必须有唯一ID。调试时如果发现ID被占用可以用CAN分析仪的总线负载率检测和错误帧分析辅助锁定冲突节点逐个拔掉怀疑节点的供电来定位。6.4 位填充导致的伪毛刺误判这个坑特别深。很多工程师第一次用示波器看CAN总线波形时看见数据段中间有一段电平跳变之间的间隔明显比其他位置短第一反应是“信号反射来了”然后开始改终端电阻甚至改走线。实际上如果这段波形处在SOF到CRC之间且某一侧连续出现了5个相同位那就是正常的位填充不需要做任何处理。判断标准很简单填充位是规则出现的只要数清楚连续相同电平个数不超过5这个跳变就是协议规定的正常行为。真正需要警惕的是超过连续6个bit没有跳变那才是异常。6.5 供电地与收发器地不一致现象是总线间或性通信异常甚至上电后分析仪一直接收到错误帧。原因是两个节点虽然通过CAN_H和CAN_L连接但参考地不是同一个点差分信号在线缆上产生共模电流CAN收发器的共模输入范围通常在-12V到12V之间如果地电位差超过容限接收端的比较器就会输出错误电平。所以接CAN总线时除了信号线一定要确保各节点之间参考地可靠连接。整机做系统集成时强弱电共地问题尤其容易出现别只看CAN线先拿万用表量一下两个节点地之间的电压超过0.5V就要怀疑地回路。6.6 采样点与位定时参数在CAN波特率已经正确的情况下错误率还是会时高时低这时候就要关注位定时配置了。一个CAN位的时长由同步段、传播段、相位缓冲段1和相位缓冲段2组成采样点位置大约在位的70%到85%之间具体值要看总线上最长距离和节点晶振精度。绝大多数MCU的CAN外设初始化库默认采样点都在80%左右这是可以通吃的配置。但如果你在做高速CAN FD或者总线长度很长建议用CAN分析仪自带的位定时分析功能实际测量最佳采样点再写进软件配置里。BOSCH推荐的采样点范围对于1Mbit/s高速CAN通常是75%到87.5%我在STM32上常用80%在S32K系列上会更偏向87.5%实测都稳定。最后分享一个经验排查CAN问题先看物理层再看协议层顺序千万不能反。我之前遇到一个现场工程师花了两天时间调滤波和ID配置最后发现是其中一个节点的终端电阻引脚焊盘虚焊导致阻抗不对。CAN这东西硬件不给力软件再怎么优化都是白搭。基础概念吃透之后再看CANopen、J1939这些上层协议就会轻松很多因为它们全部寄生在今天讲的这四种帧类型和仲裁规则之上。希望这篇对在CAN总线上摸爬滚打的你有帮助。