拓冰建站拓冰建站
首页 / 资讯中心 / 正文

认识CAN协议家族与数据帧:从经典CAN到CAN FD的逐位拆解

先聊聊我踩过的一个坑。早年间在一家车厂做台架联调两套ECU都是全新板子供电、CAN_H/CAN_L接线、终端电阻都验证过没毛病波特率也统一配成了500kbps可无论怎么发对端就是收不到完整报文。折腾了一下午最后用示波器一抓才发现一端用的是经典CAN 2.0A另一端固件里开着CAN FD模式——两边都以为自己在说“CAN协议”实际上一个在说老方言一个在说新方言谁也听不懂谁。这件事给我的教训特别深搞CAN协议如果不先把“协议种类”和“帧结构”这两个地基打牢后面所有调试验证都会变成瞎猜。这篇内容就是围绕这个地基展开的。我会把CAN总线协议家族里最常见的几种CAN种类讲清楚再把CAN数据帧从SOF到EOF逐字段拆开揉碎配合实际调试中常见的误区和工具验证方法给正在入门车载总线、工业控制通信、或者被总线问题折磨到头疼的朋友一份能直接落地的参考。无论你是做嵌入式软件、硬件设计还是产线测试搞清楚这些总线调试的很多玄学问题就能变成清清楚楚的逻辑问题。1. 同样叫CAN为什么有的快有的慢协议家族的演变逻辑很多人第一次接触CAN总线时都会有个困惑明明都叫CAN为什么有的芯片手册写“CAN 2.0B”有的写“CAN FD”有的又写“CAN XL”这几种到底什么关系选型时该用哪个1.1 从CAN 2.0A到CAN 2.0B11位ID与29位ID的由来CAN协议最早是博世公司在1986年前后为车载网络提出的串行通信协议。最初版本后来被标准化成ISO 11898大家习惯上叫它经典CAN再根据报文标识符ID的长度分成两种CAN 2.0A使用11位标识符也叫标准帧。CAN 2.0B使用29位标识符也叫扩展帧同时兼容11位标识符。这里要强调一点2.0B并不是比2.0A“快”而是ID空间更大。CAN 2.0B的控制器硬件可以识别CAN 2.0A的报文反过来CAN 2.0A的控制器遇到扩展帧大概率会直接丢弃或者报错。这就解释了当年联调现场两边明明都是“CAN设备”却可能互不通信的部分原因。那为什么非要搞出29位ID纯从车载网络的演进看早期一辆车的ECU数量少11位ID最多支持2048个不同标识符节点少时完全够用。后来车辆功能越来越多网关、诊断、多媒体、ADAS各个域都要在总线上通信11位ID规划起来捉襟见肘扩展帧就派上了用场。注意ID不仅是一个“地址”更关键的它还承担着优先级仲裁的职责——这个后面细讲。1.2 CAN FD不光提速更重要的是“长数据”经典CAN最高速率一般是1Mbps数据场最长8字节这在发动机电控、ABS这类传统控制场景里够用。但到了OTA升级、bootloader刷写、ADAS摄像头原始数据回传这些场景8字节一帧的通信效率就成了瓶颈——刷一个几百KB的固件用8字节一帧去传总线上基本都是数据帧在飞效率低不说还容易把总线占满。CAN FDCAN with Flexible Data-rate就是冲着这个痛点来的。它有两个核心变化可变速率仲裁段保持经典CAN速率比如500kbps保证和现有网络兼容、仲裁机制不破数据段可以切换到更高速率比如2Mbps、5Mbps传输更快。数据场变长单帧数据长度从8字节扩展到最多64字节同样的有效数据量需要的帧数大幅减少。CAN FD的出现不是为了取代经典CAN而是作为一种增强选项。实际项目中比较稳妥的做法是整个网络里所有节点都支持CAN FD才启用FD帧如果总线上还有老节点就得把控制器配成经典CAN模式或者兼容模式。这一点在整车网络升级时特别容易踩坑因为混跑状态下FD帧和经典帧的仲裁机制虽然兼容但老节点可能无法正确解析长数据段。1.3 CAN XL与CAN SIC更往后的演进方向CAN XL是CAN FD之后的新版本目标是把有效载荷推到最大2048字节速度还能进一步提升。CAN SICCAN Signal Improvement Capability主要是改善总线信号质量的技术解决高速率下的信号振铃问题。不过说实话CAN XL目前在普通工业项目和大多数车载ECU上还不算普及绝大多数工程师日常打交道的还是CAN 2.0和CAN FD。我的建议是做项目选型时先把前两者的区别吃透CAN XL了解原理、知道趋势即可不必盲目追新因为新的控制器和收发器成本、生态成熟度都还在爬坡期。为了更直观地对比我把几个核心参数整理成一张表协议类型标识符长度数据场最大长度数据段速率典型应用场景CAN 2.0A11位8字节≤1Mbps传统ECU通信、工业总线控制CAN 2.0B11位或29位8字节≤1Mbps多节点车载网络、诊断通信CAN FD11位或29位最多64字节可与仲裁段不同最高达5Mbps以上固件升级、大数据量传输CAN XL11位或29位最多2048字节更高面向未来车载骨干网、域控通信表格之外有个关键点需要记住选CAN FD不是为了在朋友圈晒参数而是看你的业务是否需要传输大块数据。如果只是几个传感器周期性发温度、压力、转速8字节绰绰有余继续用经典CAN完全没问题如果是给控制器刷写程序那CAN FD带来的时间收益是肉眼可见的。2. 数据帧逐字段拆解从SOF到EOF每个位都在做什么理解CAN协议最扎实的办法就是把一个数据帧从第一个位到最后一个位完整地读一遍搞清楚每一个字段为什么存在、为什么这样设计。这一章按实际发送顺序来拆这样后续拿示波器对波形时你可以一个字段一个字段地对照。2.1 SOF与仲裁字段谁先说谁说了算一帧数据的最开始是SOFStart of Frame帧起始一个显性位表示“我要开始发帧了”。它解决了总线空闲与占用状态的切换问题总线空闲时是隐性电平SOF这个显性位把总线从“休息”状态拉进“工作”状态。紧跟着SOF的是仲裁字段这是CAN协议里最精妙的部分没有之一。标准帧的仲裁字段是11位ID 1位RTR扩展帧是29位ID分段排列 SRR IDE RTR。ID的具体位数不重要重要的是它的排列规则ID数值越小优先级越高。多个节点同时发数据时怎么决定谁先用总线CAN用逐位仲裁。打个比方一屋子人同时开口说话每个人先报一个编号编号数字小的人继续说其他人一听自己的编号比对方大就自动闭嘴退让。这个“边说边听”的过程就是CAN的非破坏性仲裁机制。发送节点在发送ID的每个bit时都在读取总线电平如果自己发的是隐性位1却读到总线上是显性位0说明有其他节点在发优先级更高的帧自己立刻转为接收状态不破坏对方的帧。这也是为什么我说ID不只是地址而是“话语权”的排序标准。实际工程里整车网络中往往把最关键的报文分配最小的ID比如碰撞信号、安全气囊信号必须保证它在总线竞争时具备最高优先级一毫秒都不能被其他报文拖住。2.2 控制字段与DLC不是你想带多少字节就能带多少仲裁字段之后是控制字段。标准帧的控制字段由IDE位、保留位r0和4位DLC组成扩展帧还有额外的保留位r1。这里重点说DLCData Length Code数据长度代码。DLC用4个bit表示范围是0000到1000对应0到8字节数据。很多人第一次看到“协议最大支持8字节”就觉得每帧必须填满8字节其实完全不是这样。比如一个转速信号数据两字节就够DLC就设成2接收方根据DLC就能解析出后面有效数据只有2字节而不是去读8字节。有的老工程师习惯把DLC固定设成8多发几个填充字节这在多数网络里能跑通但大量无效载荷会拉低总线有效利用率设计新网络时不太推荐。为什么经典CAN的数据场定死为8字节这个历史包袱有技术原因CRC字段与数据场长度强相关数据长度越短CRC校验的可靠性和实时性越好另外8字节基本覆盖了传统控制报文的典型信息量再长就要付出更多传输时间实时性也会被拖累。CAN FD把数据场做到64字节同时把CRC策略改了就是为了平衡大数据量与校验可靠性。2.3 数据字段与字节序同一个报文两种解析结果的根源数据字段是真正要传的内容长度由DLC决定。这个区域看起来简单但实际项目里引起最多争议的就是它——因为大小端字节序问题。举个例子一个16位车速信号数值是0x1234。有的工程师习惯高字节在前发送buffer里存0x12 0x34另一些芯片的CAN驱动则默认低字节在前发出来是0x34 0x12。两边如果没对齐字节序接收方解出来的车速会差得十万八千里而且这种错非常隐蔽——它不会导致CRC报错帧照样正常只是值不对。我的习惯是在设计DBC或通信矩阵时把每个信号的大小端格式、起始位都写清楚然后在软件层面统一加一层“信号打包/解包”的函数不直接在收发中断里拼字节。这样即使换个平台也只要改底层驱动这一层上层信号定义不动能省掉大量联调时“我认为你对你认为我对”的扯皮。2.4 CRC字段和ACK槽保证不出错还要确认没白发数据字段之后是CRCCyclic Redundancy Check循环冗余校验字段。经典CAN的CRC是15位连同1位CRC分隔符隐性位覆盖从SOF到数据字段的全部内容。接收方对收到的帧做同样的CRC计算两者不一致就说明帧被破坏了接收方不会给我回ACK而会触发错误机制。这里有个很多入门文章一笔带过的精妙设计ACK槽Acknowledge Slot。发送方在ACK槽位置发送的是隐性位而所有正确收到该帧的节点都会在ACK槽把总线拉成显性——也就是说只要总线上有一个节点正确接收发送方就能读到显性ACK知道“我的帧被接收到了”。反过来如果帧在总线上因为干扰、错误等原因没有被任何节点正确接收发送方读到的是隐性电平就会知道自己这次发送失败了后面可以按错误处理逻辑重发。这个机制看似简单但我在实际调测中见过不少迷惑行为有朋友用CAN分析仪挂在总线上当接收者然后抱怨“为什么板子发的报文分析仪收不到、但板子觉得自己发成功了”原因往往是分析仪没有真正参与接收并回ACK或者终端电阻、物理层有问题导致收发器没能正确读回总线电平。排查这类问题先看ACK槽有没有被拉显性比乱换波特率高效得多。2.5 EOF与位填充规则7个隐性位背后的“防呆”设计帧的收尾是EOFEnd of Frame帧结束7个隐性位。这7个隐性位一方面把帧和帧之间的间隙隔开另一方面也是接收方判断“一帧是否完整结束”的标志之一。在CRC字段之前CAN还有一个非常重要的规则叫位填充Bit Stuffing连续发送5个相同电平的bit后必须插入一个反相的电平bit。比如正在发5个连续显性位“00000”第6位就强制插成隐性位“1”。接收方收到5个相同电平的bit后会主动把第6个相反电平当作填充位丢掉。这是为什么主要目的是防止接收方失去同步。CAN没有独立的时钟线接收方是从总线电平跳变沿来不断同步自己的位时序的。如果长时间不跳变接收方对位的采样点判断就可能漂移出错。位填充强制制造跳变沿让接收方的同步能持续保持。插入填充位必然带来一些带宽开销但换来的同步稳定性和检错能力在汽车这种电磁干扰强的环境里非常值得。注意位填充从SOF开始一直作用到CRC字段结束EOF和ACK区域是不做填充的。EOF固定7个隐性位如果超过7个隐性位接收方就会认为总线是空闲状态或者产生了填充错误。这也是为什么物理层在EOF期间一旦出现干扰抖动接收方会很容易判别出帧异常。把整个帧的字段分布列出来方便记忆字段长度/内容主要作用SOF1个显性位标志帧起始仲裁字段11位ID标准帧或29位ID扩展帧 RTR/SRR/IDE标识报文 逐位仲裁定优先级控制字段IDE、保留位、4位DLC声明数据长度数据字段0~8字节经典CAN或0~64字节CAN FD实际传输内容CRC字段15位CRC 1位CRC分隔符校验数据完整性ACK槽1位ACK 1位ACK分隔符接收方确认帧有效接收EOF7个隐性位表示帧结束期间无位填充3. 数据帧之外远程帧、错误帧、过载帧与帧间隔的配合逻辑大部分入门教程会把90%的篇幅花在数据帧上导致很多人以为CAN总线上只跑数据帧。实际上完整的总线通信是五种帧机制协同工作的结果数据帧、远程帧、错误帧、过载帧以及帧间隔空间。把这五种角色理清楚总线行为你才真正“看得到全貌”。3.1 远程帧理论上很棒实际项目几乎没人用远程帧的作用是一个节点向总线上请求另一个节点发送数据。远程帧与数据帧的区别在于RTR位——数据帧的RTR是显性位0远程帧的RTR是隐性位1。远程帧没有数据字段DLC表示请求的数据长度。理论上这个机制很优雅比如网关想知道某个传感器的最新值直接发一个远程帧传感器听到后就会回应数据帧。但实际工程里我几乎没见过哪个量产项目依赖远程帧来做常规通信。原因主要有三个实时性不可控远程帧只负责“请求”响应方何时发、发不发取决于响应的节点调度响应时间不确定。总线利用率低一次请求一次响应至少占用两帧的传输机会效率不如节点主动按周期上报。仲裁优先级设计复杂远程帧与数据帧同ID竞争时对端很容易触发错误处理很多MCU的CAN驱动对远程帧处理并不完善踩坑概率大。所以现在的主流做法是传感器节点周期性主动上传数据需要哪个数据自己收哪个。远程帧更多出现在教材和协议示例中作为理解RTR位和帧类型区分的教学工具。3.2 错误帧与错误计数器CAN自我保护的核心机制总线上的数据难免受干扰出错。当接收节点发现CRC错误、位填充错误、格式错误等就会向总线发送错误帧。错误帧由两部分组成错误标志和错误分隔符。这里要看节点处于哪种错误状态。主动错误状态Error Active的节点发送的主动错误标志是6个显性位被动错误状态Error Passive的节点只能发送6个隐性位的被动错误标志不能强拉总线。为什么要区分这两种状态因为要防止一个故障节点反复拉低总线把整个网络带崩。CAN控制器内部维护着发送错误计数器TEC和接收错误计数器REC单位叫“错误计数”。接收节点检测到错误REC加1发送节点错误TEC加8发送成功则TEC减1。当任一计数器超过127节点进入被动错误状态如果TEC超过255节点进入总线关闭状态Bus-off该节点自动与总线断开不再参与任何通信直到软件或上层机制将它恢复。这套机制在工业现场非常有用。我碰到过一个案例一条产线总线上有一个节点因为电源波动反复发错误帧把整条总线的吞吐率拖到几乎瘫痪。排查到最后就是查每个节点的TEC/REC状态发现那个节点的错误计数一直在累加处于被动错误边缘。换掉那个节点的收发器后总线立即恢复正常。所以调试CAN网络时不要只看数据帧能不能通还要监控错误计数——这往往是早期故障的最好预警信号。3.3 过载帧和帧间隔被忽略但同样重要的总线秩序过载帧用于接收节点向发送节点表达“我现在忙不过来先暂停一下”。它的结构和错误帧类似也是过载标志加分隔符。实际项目里正常情况下很少触发过载帧除非接收端软件处理不过来内部缓冲区满了或者接收中断被更高优先级任务打断太久。如果总线上频繁出现过载帧先怀疑是软件实时性问题再考虑是不是DLC设计过大、接收中断过于频繁。**帧间隔Interframe SpaceIFS**是帧与帧之间的“换气时间”固定至少3个隐性位。发送节点在两个连续帧之间必须插入这段间隔给接收节点留出时间处理刚收完的帧、准备接收下一帧。这里要特别说明帧间隔不是哪个节点“发”的而是任何节点发送帧前都必须等待的间隙。数据帧、远程帧、错误帧、过载帧之间都要遵守这个秩序。理解了这五种帧的配合你在用CAN分析仪抓包时就不会只盯着Data字段看了。正确顺序是先查看总线上有没有异常错误帧没有再看帧间隔是否正常然后看ACK情况最后才去解析数据内容。排查顺序反了很容易在一个很干净但“其实没收到ACK”的诡异问题上绕半天。4. 标准帧还是扩展帧ID分配与工程实践的取舍技术文档看多了容易产生一种错觉29位ID比11位ID支持更多节点和报文所以“扩展帧更好”。真实项目里恰恰相反——大量OEM直接把CAN网络设计成只使用11位标准帧。这个选择背后的逻辑值得单独展开说。4.1 优先级的“卷”ID数值小话语权大前面提到CAN仲裁机制里ID数值越小优先级越高。假设一个总线上有两帧竞争ID0x100和ID0x200。从最高位开始逐位比较0x100的某些位更早出现显性电平所以0x100会抢到总线0x200的发送节点会自动退避。这个机制决定了ID分配不是“随便挑一串不重复的数字”而是要结合报文的实时性要求来设计。比如安全气囊点爆信号、碰撞信号必须用总线上最小的几个ID车窗升降、空调面板这种对时延不敏感的信号用大一点的ID没关系。另一个容易被忽略的点是ID越小由于仲裁机制它在总线负载高的时候仍然具备稳定的低时延ID大的报文在总线繁忙时可能被连续退避多次。所以设计通信矩阵时给关键控制报文留小ID给信息类非关键报文留大ID这件事要在项目初期就定好后期再调ID牵一发动全身。4.2 11位还是29位选型不是越大越好回到标准帧和扩展帧的选择问题。11位ID最多允许2048个不同标识符29位ID理论上超过5亿个空间上碾压。但实际工程考量要复杂得多11位ID的仲裁效率更高扩展帧整个仲裁字段更长总线占用时间也更多。在波特率固定的情况下扩展帧传输一帧的时间比标准帧更长高负载下会牺牲实时性。绝大多数节点需求用不到29位ID一个典型的车载子网节点数量往往不会超过几十个每个节点按功能划分报文ID11位绰绰有余。工具链与协议栈兼容性很多老的诊断协议、bootloader、产线测试工具对扩展帧的支持不如标准帧那么顺滑混用还要做额外配置。所以工程界挺普遍的做法是默认选用11位标准帧除非协议里明确规定必须用29位扩展帧。比如某些诊断协议UDS的物理寻址/功能寻址、CANopen里的PDO/SDO对象有专门的标准定义再比如跨域网关两段子网之间通信用扩展帧区分源/目标域的情况也有。搞清楚需求再决定用哪种别为了“看起来专业”而选29位。4.3 从设计到落地一个通信矩阵该怎么搭无论标准帧还是扩展帧最终都要落到一张清晰的通信矩阵表上。我习惯用类似这样的结构来设计报文ID帧类型发送节点接收节点周期/触发方式DLC信号列表0x101标准帧VCUBMS周期10ms8扭矩请求、模式0x1A2标准帧BMSVCU周期100ms8SOC、电压、温度0x2B0扩展帧网关诊断仪事件触发4故障码每个信号再单独建一个信号表描述起始位、长度、类型无符号/有符号、浮点/定点、精度、偏移量、取值范围、初始值、无效值等。这步工作看起来繁琐但它是后期联调、生成DBC、写解析代码的依据。很多项目到台架联调阶段才暴露“两边对信号的bit定义理解不一致”的问题根源就是通信矩阵做得不够严谨。我个人的经验是通信矩阵建议用版本管理工具管起来每次修改记录变更原因。别高估人的记忆力一台展车几十上百个信号三个月后你再看当初的设计没文档的话真的会一头雾水。5. 把帧和图对起来抓包验证与位时序实测的角度讲了这么多帧结构最后一定要落到验证上。理论说得头头是道示波器一抓波形全忘了那是白学。这一章我带你用两个最常用的手段把数据帧“可视化”顺便解决总线调试里最典型的那几个问题。5.1 用示波器看差分电平隐性、显性与终端电阻的关系CAN物理层是差分信号用CAN_H和CAN_L两根线的电压差表达逻辑电平。逻辑隐性时CAN_H和CAN_L都约为2.5V差分电压接近0V实际规范要求在显性时差分电压至少1.2V隐性时最高0.5V逻辑显性时CAN_H被拉高到约3.5VCAN_L被拉低到约1.5V差分电压约2V。用示波器两通道分别接CAN_H、CAN_L打开数学通道做CAN_H减CAN_L就能很直观地看到一帧数据的显隐性跳变波形。这里有个调试技巧如果总线上只有一个节点在发送却没有其他节点回ACK波形里会看到帧尾的ACK槽保持隐性没有被拉低如果总线上完成了一次完整通信ACK槽附近会出现一个下凹的显性脉冲。这个细节用普通逻辑分析仪不一定看得明显但示波器一眼就能分辨出来——它就是判断通信链路是否真正确认的关键。终端电阻也很容易在波形上看出来。正常总线的两端各有一个120Ω终端电阻显性电平跳变沿陡峭、幅值饱满如果缺少终端电阻或者接错位置波形边沿会变缓显性幅值可能偏小长距离通信时还会出现信号振铃。很多“时通时不通”的诡异问题最后都是终端电阻没接好导致的。5.2 手动计算波特率和采样点看着波形推帧结构知道怎么看显隐性之后可以用示波器去推算波特率。找一段波形里最窄的显性脉冲或隐性脉冲那通常就是一个bit的时间。比如示波器时间轴上测得一个bit的宽度为1μs波特率就是1/1μs 1Mbps如果是2μs波特率就是500kbps。更进阶一点还可以验证采样点设置是否合理。CAN控制器在每一位时间的某个百分比位置通常70%~80%进行采样比如500kbps下位时间2μs采样点设在75%就是1.5μs处采样。如果示波器上看到位信号在采样点附近出现比较明显的振荡或边沿抖动说明采样点设置可能偏早或偏晚抗干扰能力会下降。车载网络里比较推荐采样点在75%到85%之间既能避开位的起始跳变又有足够余量应对总线延迟。如果你手头有CAN分析仪或者带CAN解码功能的示波器可以把波形自动解成一帧带ID、DLC、Data的报文然后对照前面拆字段的知识从波形上找到SOF、仲裁字段、CRC、ACK的位置。新手最容易获得成就感的一件事就是看着一帧完整报文从自己的占空比计算变成实际波形解码——那种“数据帧真的就是图纸上画的这个顺序”的确认感对建立CAN知识的体系特别有帮助。5.3 实测中的三类“幽灵问题”与快速定位顺序文章最后把我这些年实操中反复遇到的几类总线问题做个梳理方便你日后排查。总线沉默谁都发不出先看示波器有没有电平跳变。如果完全没有跳变大概率是物理层问题短路、断路、终端电阻异常、节点没有上电工作如果波形有跳变但报错再检查波特率是否匹配、是否存在默认位时序与实际波特率偏差过大。能发不能收发送节点认为自己发出去了重点查ACK槽。如果发送节点在ACK槽位置读回路的是隐性电平说明总线上没有节点正确接收该帧或者接收节点的波特率、滤波配置不对。收发都“正常”但数据值是乱的不要再折腾物理层了先查字节序、信号位定义、DLC是否匹配。这种问题通信矩阵和DBC往往是最终的解药。排查顺序我建议固定成物理层示波器看波形→ 数据链路层分析仪看帧、看错误帧→ 应用层看DBC解析结果。每层验证通过再做下一层省时省力也容易定位问题到底出在哪一段。CAN协议给人的第一印象通常是“简单好上手”理论上两根线、一帧数据不复杂。但真正深入进去就会发现帧格式里的每一个字段、协议类型里的每一个变种背后都有非常明确的工程意义和取舍逻辑。把前面这些基础吃透了再面对一片陌生的CAN网络你就不会只靠瞎试波特率来碰运气而是能顺着协议设计的思路一步步推理出问题所在。这不只是调试能力的提升更是对嵌入式通信系统整体理解的一次进阶。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门