资讯动态

CAN/CAN-FD总线核心原理与调试实战:从帧结构到采样点

发布时间:2026/10/25 1:07:48 来源:尧图企业网站定制
1. 为什么车上到处都是CAN总线“从沙子到车辙”这个系列写到4.3终于轮到CAN和CAN-FD。在汽车电子、工业控制、机器人这些领域CAN几乎是默认的通信底座。一根双绞线、两个终端电阻、几十个节点多主发送、自动仲裁、错误重发——这套机制看着简单用起来门道不少。这篇博文不打算抄协议手册我想把理解CAN必需的几个核心机制、CAN-FD到底改了什么以及自己调试时踩过的坑一次说清楚。适合刚接触总线的入门者也适合一直用CAN但对仲裁、采样点、Bus Off这些概念停留在“软件能跑就行”层面的工程师。1.1 线束灾难逼出来的一对差分线早年车内电子设备少一个控制器想读另一个控制器的信号最简单的方式就是拉一根专线。发动机控制器给变速箱控制器传一两个状态信号问题不大。可等到电子系统数量爆发之后每路信号都拉专线的结果就是整车线束又长又重故障率高装配困难线束成本甚至能排进整车零部件成本的前列。CAN总线的初衷非常朴素把“点到点”的连线改成“一根线上挂多点”的总线结构所有节点共享同一对双绞线靠协议保证不冲突而不是靠物理上分开。具体到物理层CAN用的是差分信号。CANH和CANL两条线显性电平下CANH大约3.5V、CANL大约1.5V差分大概2V隐性电平下两条线都被拉到2.5V附近差分接近0V。在汽车这种12V电源、电机干扰遍地都是的环境里差分设计天然对共模干扰有抑制能力接地电位差的影响也小很多。这跟普通UART的单端信号线相比抗干扰能力完全不在一个量级。1.2 CAN能做什么不能做什么CAN最核心的价值可以概括为四点多主通信、实时性、可靠性和低复杂度。任何一个节点只要总线空闲就可以发起发送多个节点同时发送时硬件通过仲裁机制自动解决冲突不需要上层软件协商。数据帧自带CRC校验节点发现自己出错、或者发现总线上有错误都会按协议发送错误帧并且内部维护错误计数器一旦错误计数过高就自动退出总线避免一个坏节点把整条总线拖死。但CAN不是万能的。经典CAN最高只到1Mbps车内大量网络实际运行在500k甚至250k这个带宽放今天非常紧张。一帧数据最多8字节传一个几十KB的诊断文件或者OTA升级包要拆成几千帧效率很低。另外CAN本身没有身份认证和加密机制现代汽车网络安全基本上都要靠上层协议弥补。一句话低层协议保证你不会传错不保证信息一定安全。2. 把CAN帧彻底拆开看2.1 帧头帧尾11位ID和29位IDCAN 2.0定义了两种帧格式。标准帧的仲裁段是11位ID扩展帧是29位ID。扩展帧在基础ID之后多出18位扩展ID并增加了SRR、IDE这些标志位RTR的位置也相应调整。ID的作用不只是标识报文更重要的是决定优先级——数值越小优先级越高。同一时刻不同节点抢总线ID小的先赢。一个经典CAN数据帧的结构大致是1位SOF显性起始仲裁段加控制段里包含ID、RTR、IDE和DLC中间是0到8字节数据然后是15位CRC加上1位CRC分隔符接着是ACK槽和ACK分隔符最后7位EOF。需要注意实际帧长度并不是固定的因为总线上应用了一个叫“位填充”的规则从SOF到CRC段连续出现5个相同电平就自动插入1个反相电平用来保证接收端始终有足够边沿做同步。所以一个8字节数据帧实际总线长度可能在46位到150位之间浮动具体取决于数据区里0和1的分布。这里有个工程上容易忽略的点远程帧RTR。RTR位在标准数据帧里是显性远程帧里是隐性。远程帧的作用是一个节点请求另一个节点发送指定ID的帧。但在实际车载网络里远程帧几乎是被禁用的因为请求节点可以拿一个ID去“骚扰”其他节点还可能引发总线突发拥堵安全审计的时候也很难看。我做过的项目协议栈里直接要求RTR必须恒为显性也就是不允许任何节点发远程帧。2.2 仲裁一群人都要说话谁先闭嘴谁输CAN总线的仲裁是整个协议里最精彩的设计。物理层上显性位逻辑0是拉低总线隐性位逻辑1是释放总线所以如果两个节点同时往总线上发不同的电平结果必然是显性覆盖隐性。这个特性叫“线与”。仲裁的过程就是利用这个特性两个节点同时发送时从每帧的SOF开始每个发送节点每发一个位就回读一次总线电平。如果自己发的是隐性1但读回来是显性0说明有其他节点正在发0自己抢不过立刻停下发送转为接收状态。等总线重新空闲之后再由硬件把刚才没发完的帧自动重发。所以ID小的节点一定赢因为它在更早的位上发出了显性0。这个机制对上层完全透明软件不需要处理“仲裁失败”这件事。更重要的是仲裁失败不产生错误帧也不会计入错误计数器它只是“让路”不是“出错”。我见过不少刚开始接触CAN的同学看到逻辑分析仪上的“仲裁丢失”提示以为是总线故障其实是正常现象。2.3 位定时与采样点波特率不是随便填的数字CAN通信的波特率不是简单填一个比特率数字它由位时间决定。一个位时间会被切分成若干时间量子tq整体顺序是1个同步段接着传播段、相位缓冲段1、相位缓冲段2采样点落在相位缓冲段1和相位缓冲段2之间。位时间的tq数等于1 BS1 BS2波特率等于外设时钟除以预分频再除以这个tq总数。采样点的选择很关键。如果采样点太早信号边沿还没稳定就只能靠猜太晚接近位末尾重同步余量不够。业内经验一般推荐75%到90%经典CAN高速场景常用87.5%附近低速网络可以放宽到80%。但受外设寄存器位宽限制实际情况并不是你想配多少就有多少。举个例子。假设某MCU的CAN外设时钟为20MHz目标波特率500kbps预分频取2则实际参与位定时的时钟是10MHz一个位时间需要10MHz / 500k 20个tq。想采样点落在80%就要让同步段加BS1占16个tq所以BS1选15BS2选41 15 4 20采样点(115)/20 80%。如果同一个时钟想配87.5%由于(1BS1)/20无法精确得到87.5%17/2085%只能换预分频让tq总数变成40甚至80再选更细的粒度。调试时经常出现“两边都写了500k但一边80%一边75%线短没问题线一长就偶发错误”的情况原因就在这里。3. CAN-FD到底改了什么3.1 8字节装不下一整个世界CAN-FD出现的背景就是经典CAN的两大硬伤数据短、带宽低。以前用诊断仪刷写ECU固件几十KB的数据要分成几千帧一帧帧发时间长不说中间任何一帧出错、重发、总线拥堵体验都非常差。OTA普及之后这个问题直接成了瓶颈。CAN-FD保留了经典CAN的仲裁机制和帧格式大体框架核心改动是数据场最长可以到64字节数据场可以使用更高的位速率CRC升级到17位或21位并且增加了几位控制位来区分帧类型、标识速率切换和节点错误状态。需要注意CAN-FD的DLC编码和经典CAN并不完全一样。经典CAN里DLC码9到15虽然可以在帧里出现但数据长度按8字节处理CAN-FD则把这几个码重新利用起来9对应12字节10对应16字节11对应20字节12对应24字节13对应32字节14对应48字节15对应64字节。所以解析CAN-FD报文时不能直接拿DLC当字节数去填要查编码表这也是很多人在报文解析阶段踩过的坑。3.2 帧格式、CRC和速率切换CAN-FD帧在控制段里多了三个位FDF、BRS和ESI。FDF用来标识这是FD帧BRS是“比特率切换开关”BRS位为显性时发送方在BRS之后立即切换到数据速率数据场和部分CRC段用更快的比特率传输直到CRC分隔符前再切回仲裁速率ESI表示当前节点的错误状态是active还是passive。这样设计的好处是仲裁段仍然用比较低的速率保证总线上所有节点的同步和仲裁稳定性而真正耗时的数据段跑高速兼顾吞吐量和兼容性。数据速率可以比仲裁速率高很多常见配置是仲裁段500kbps、数据段5Mbps甚至更高。但数据速率不是想拉多高就拉多高它受两个因素限制一个是收发器芯片的显性/隐性状态转换时间另一个是总线线缆长度带来的信号反射和边沿退化。换句话说你买了个支持CAN-FD的MCU但收发器还是老款经典CAN收发器那数据速率跑2M都可能出现波形问题。CAN-FD方案在硬件选型时必须配套支持CAN-FD的收发器。CRC也改了。经典CAN只有固定15位CRC对最多8字节的帧够用CAN-FD数据场长度变化很大所以用两档数据场不超过16字节时用17位CRC超过16字节时用21位CRC。不仅如此CAN-FD的CRC计算还要对传输格式做特殊处理保证长数据帧的错误检测能力仍然充足。具体实现由控制器硬件完成应用层不用关心但做协议一致性测试时就要注意不能用经典CAN的CRC思路去算。3.3 和经典CAN混装时的兼容性坑CAN-FD帧对经典CAN节点是不友好的。让一个只支持经典CAN的节点去收发CAN-FD帧它在解析控制段时可能会当成扩展帧或其他格式判断不下来最后直接报格式错误。所以在一个混合网络里面所有挂在同一物理总线上的节点必须都支持CAN-FD才能跑FD帧否则就要把FD功能关掉大家统一用经典CAN模式。很多支持CAN-FD的MCU复位后可能默认工作在经典CAN模式要手动打开FD功能。这个细节在两块板子联调时特别容易出问题一边以为默认就是FD另一边只发了经典帧两边都能通一旦有人发了一帧FD老节点立刻总线报错。更隐蔽的是网关转换CAN-FD报文要下放到经典CAN网络时如果数据长度超过8字节网关必须做拆分或丢弃。DLC映射、字节序重排、发送顺序都会影响上层信号含义这一层往往要专门的网关配置工具去管理。4. 用最小系统跑通第一帧报文4.1 硬件板子、收发器、双绞线和终端电阻先搭最小可通信环境。需要两块带CAN控制器的MCU板子再配两个CAN收发器芯片或者直接用两个现成的CAN节点模块。收发器的作用很直接MCU的CAN控制器输出的是逻辑电平的TX/RX信号收发器负责把它们转成差分信号放到CANH和CANL上同时把差分信号接收回来再转成逻辑电平。连接方式非常简单A节点的CANH接B节点的CANHCANL接CANL两根线绞在一起最好测试环境几米长度足够。总线两端各放一个120欧终端电阻。千万别以为一个节点测试就不需要或者两个节点之间只用一边接电阻。CAN总线上的信号需要终端匹配来吸收反射没有终端电阻或者阻抗不匹配体现在波形上就是边沿振荡、毛刺多通信时好时坏。我见过很多“程序没问题但总不通”的案例最后查下来要么没接电阻要么两个电阻放到了同一端。快速验证终端是否正常可以在总线任意位置用万用表量CANH和CANL之间的直流电阻正常应该是约60欧。4.2 配置CAN外设算好位时间参数图形化代码生成工具一般都有CAN外设配置页面需要填的基本就是波特率、采样点、中断开关。计算原则前面说过这里再走一遍流程。我用过的参数CAN外设时钟20MHz目标500kbps预分频2所以实际位定时时钟10MHz一个位时间20个tq。填BS115、BS24采样点80%再设SJW4就完成了。不同厂家的工具叫法略有差别有的用BS1/BS2有的用PhaseSeg1/PhaseSeg2概念本质一样。初始化完成之后写一个发送函数/* 伪代码发送一帧标准数据帧 */ can_frame_t tx_frame; tx_frame.id_type CAN_ID_STANDARD; tx_frame.id 0x0A1u; tx_frame.dlc 8u; tx_frame.data[0] 0x01u; tx_frame.data[1] 0x02u; tx_frame.data[2] 0x03u; tx_frame.data[3] 0x04u; tx_frame.data[4] 0x05u; tx_frame.data[5] 0x06u; tx_frame.data[6] 0x07u; tx_frame.data[7] 0x08u; can_driver_send(tx_frame);发送完成后观察发送完成标志位或者进发送中断。如果总线没有其他节点一帧发出去也会在ACK段得不到应答控制器会持续重发最后因为错误计数上升进入错误状态。所以在单节点自测时正确做法是让分析仪作为另一个节点挂在总线上或者用回环模式测试。这也是很多人第一次调试时困惑的点单独一个节点发数据为什么发送标志一直不成功因为总线上没人替它ACK。4.3 抓包解析从总线里看到字节流转把USB-CAN分析仪接到同一对CANH/CANL上在工具里选择标准帧、填写相同波特率开始捕获。上面那段程序运行后分析仪应能捕捉到ID0x0A1的数据帧。这类工具通常会显示ID、DLC、Data共8个字节以及帧类型。看到这些信息不代表报文一定是对的还要做信号级验证把你想要表达的物理量按信号定义从字节流里提取出来。比如车速信号定义在数据场第3字节和第4字节16位无符号小端排列那么解析时就要把第3字节作为低字节、第4字节作为高字节拼起来再乘上比例因子。实际工程中这些字节序、起始位、长度、缩放的定义不会写在代码注释里而是放在DBC文件里维护。DBC是事实标准的信号描述文件记录了每个报文ID里有哪些信号、信号从第几位开始、占了几位、是Motorola序还是Intel序。调试时拿到一个DBC用支持DBC的分析工具导入就能直接看到解析后的车速、转速、温度这些物理值比自己对着字节猜靠谱得多。4.4 示波器上看一眼真正的CAN波形把示波器使用差分测量方式正端接CANH、负端接CANL就能看到CAN总线的差分波形。正常通讯时显性位的差分电平大约2V隐性位接近0V。从中可以直观看到帧起始、ID区域、数据场和ACK槽。如果波形上升沿和下降沿很缓或者回钩严重多半是终端电阻问题如果发送方明明在发数据但总线上完全看不到电平变化多半是收发器供电、STB引脚电平这类小问题。有一台示波器在手边调试CAN总线会省很多时间。5. 总线出问题时怎么快速定位5.1 先排掉最明显的嫌疑CAN出问题时我的习惯是按照“物理层—配置层—协议层”的顺序排查。下面这张表覆盖了我遇到的大部分典型故障现象最常见原因快速验证方法完全不能通信终端电阻缺失、收发器供电不对、CANH/CANL接反万用表量CANH到CANL直流电阻约60欧检查差分线连接偶发错误帧波特率配置不一致、采样点差异、线束过长或干扰核对双方位时间参数用示波器观察波形边沿一个节点掉线该节点进入Bus Off查看错误计数器检查该节点是否总在发错误帧通讯但数据全乱位序不对、DLC解析错误、字节序错用DBC文件对照回放逐条解析总线上异常繁忙网络管理帧风暴、某个节点在重复错误帧用分析仪看总线负载率按错误类型统计物理层永远是第一嫌疑。CANH和CANL如果接反了差分信号反相通信直接失败而且很难直接看出来。两个节点距离稍远的时候最好用万用表量一下两端的CANH对地电压、CANL对地电压收发器正常工作时隐性电平应该在2.5V附近显性时CANH约3.5V、CANL约1.5V。如果一边明显不对先从供电和收发器查起。5.2 错误帧、仲裁失败和Bus Off不是一回事这是很多人分不清的三个概念。仲裁失败前面说过是正常现象不产生错误帧也不会让错误计数器增加。错误帧表示某个节点检测到了总线错误可能是CRC不对、位填充违规、ACK没收到、位发送错误、格式错误等。任何一个节点发现错误都会发出显性错误标志其他节点收到后也同步报错错误计数器按照协议规则变化。错误计数器分发送错误计数和接收错误计数发送错误加8、接收错误加1这类增量规则属于基本约定。当某个节点的错误计数超过127它就会从Error Active变成Error Passive如果发送错误计数超过255节点进入Bus Off彻底退出总线不再发送任何东西。恢复机制各厂实现不同有些需要应用层请求复位CAN外设。所以在整车调试时如果某个ECU的通信异常是“过一会儿就消失、停一段时间又出现”往往就是节点在Bus Off和恢复之间反复横跳此时要重点查它的错误计数器增长规律判断错误类型是发送错误还是接收错误。5.3 一个真实排查案例采样点不一致有一次测试两块板子程序能发能收但偶尔丢一两帧并不是每次都能复现。上位机统计每次通信时长发现丢帧总在总线负载比较高的时候出现平时没事。后来把两块板子的CAN寄存器都读出来对比发现两边虽然都写了500kbps但一个采样点配在75%另一个配在85%。负载低的时候位时间足够宽信号稳定两边都能采对负载一高连续发帧加上线束长度接近两米信号边沿退化采样点靠前的那个节点容易把还没稳定的电平采进来于是偶发位错误。解决办法是统一两边的采样点参数并且把总线线束剪短到一米以内。之后连续压测几个小时再也没有丢帧。这个案例想说明一个道理CAN配置不是“波特率一样”就够了采样点、SJW、终端电阻这些细节决定了它在极限工况下的表现。协议栈代码能跑通只算入门能把参数调到任意工况都稳定才是真正会用CAN。6. 长期调试CAN总线的几个心得6.1 我踩过的几个坑第一个坑终端电阻忘了接异地联调时还坚信“程序没错”。后来发现板子之间的CAN波形完全不是方波两个120欧电阻补上之后一切正常。第二个坑分析仪上位机显示波特率500k但设备实际跑的是500k的CAN-FD帧老分析仪不支持CAN-FD解析出来全是乱码。解决方法是先确认分析仪型号是否支持CAN-FD再决定用不用FD模式。第三个坑DBC字节序设置错误把Motorola序和Intel序搞混解析出的车速负值直接让仪表显示乱跳。从那之后我在所有项目里都会要求对DBC做一次静态评审把关键信号的字节序、起始位、缩放比例全部对照一遍。还有一个很容易忽视的坑CAN外设的时钟源。有些MCU的CAN外设时钟来自PLL调试时为了降频改了系统时钟CAN波特率跟着变但软件配置里的预分频没改结果波特率差了一大截两侧对不上。这个坑在没有示波器和分析仪的环境下可能要排查好几个小时。排查思路是重新计算把“外设时钟 / 期望波特率”拿来跟配置里的tq总数比对看是否一致。6.2 做CAN调试我习惯了固定流程先把所有节点的波特率、采样点、终端电阻情况列成一张表贴在调试台旁边。配对前先用分析仪单独监听每块板子确认它发的帧ID和DLC符合预期两两对接后再验证双向通信最后用示波器看波形和总线空闲电平。每次改配置我都会把改动记到变更日志里而不只是改在工程里。排查问题时先看物理层再用分析仪抓错误帧统计。这套流程看起来繁琐但能避免“一顿操作猛如虎最后发现只是线松了”的尴尬。最后说点个人的体会。CAN这套协议能活几十年靠的不是花哨而是把“可靠通信”这件事设计得极其扎实。理解仲裁、采样点、错误计数这些基础概念比背下一百个寄存器更有用。我在实际项目中遇到的大部分疑难杂症归根到底都能回到物理层波形和位定时这两个原点。把基础打牢CAN-FD的网关路由、上层协议栈后续学起来都会顺畅很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑