ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从底层驱动到车载通信:CAN与CAN-FD协议详解及实战调试

从底层驱动到车载通信:CAN与CAN-FD协议详解及实战调试

1. 为什么一个做底层驱动的人,最后都绕不开CAN总线

"从沙子到车辙"这个系列名字起得挺有意思,从硅砂到车轮印,说的其实就是芯片从原材料到最终跑在路上的完整链路。而到了4.3这个节点,聊CAN和CAN-FD,说明前面已经把MCU内核、时钟、电源、基础外设这些地基打完了,现在开始碰车载通信的主动脉了。

我最早接触CAN是在一个车身控制模块的项目上,当时用的是一个带CAN控制器的MCU,外挂了一颗收发器。第一次用示波器看CAN_H和CAN_L的差分波形时,说实话有点懵——这玩意儿跟UART、SPI完全不是一个思路。UART是点对点,SPI是主从加片选,而CAN是多主、广播、带仲裁的总线。你往总线上一挂,谁都能说话,但谁先说话是靠ID优先级硬碰硬抢出来的。这个设计在车载环境里极其关键,因为车上几十个ECU共享两条线,如果没有一套可靠的仲裁和错误处理机制,数据早就乱成一锅粥了。

CAN-FD(Flexible Data-rate)是CAN的升级版,核心变化就两个:数据段速率可以往上拉,单帧数据长度从8字节扩展到64字节。别小看这两个变化,它直接决定了你能不能在一个帧里塞下一整块标定数据或者一段诊断报文,而不需要拆成七八帧去发。对于现在动辄上百个信号的车载网络来说,这是实打实的效率提升。

这篇文章主要面向已经有一定嵌入式基础、准备上手CAN/CAN-FD的开发者,或者已经在用但想搞清楚底层细节的人。我会从协议核心机制讲起,然后落到实际配置和调试,最后把我在项目里踩过的坑整理出来。你不需要事先精通CAN,但至少得知道什么是中断、什么是寄存器、示波器怎么用。

2. CAN协议的核心机制拆解

2.1 差分信号与显性隐性:CAN的物理层逻辑

CAN总线用两根线:CAN_H和CAN_L。它们传输的是差分信号,也就是说,接收端看的是两根线之间的电压差,而不是单根线对地的电压。这个设计的好处非常直接——共模干扰会被抵消掉。车上电机、继电器、点火线圈一大堆噪声源,单端信号早就被淹没了,差分传输是唯一靠谱的选择。

具体电平定义是这样的:当CAN_H和CAN_L电压差接近0V时,总线状态为隐性(Recessive),逻辑上对应1;当CAN_H拉高、CAN_L拉低,压差大约2V时,状态为显性(Dominant),逻辑上对应0。注意这个命名——显性就是"强势"的意思,只要有一个节点发显性位,总线就是显性。这个特性是后面仲裁机制的基础。

我见过有新手把CAN_H和CAN_L接反了,结果就是完全通信不上,示波器上看波形也是反的。所以接线之前一定要确认收发器的引脚定义,不同厂家的收发器标注方式可能不一样。

终端电阻是另一个必须提的点。CAN总线两端各需要一个120Ω的终端电阻,并联之后总线等效阻抗是60Ω。这个电阻的作用是吸收信号反射,保证波形干净。如果你只接了一个节点做实验,不加终端电阻可能也能通信,但波形会有明显的振铃,一旦线拉长或者节点增多,通信就会变得不稳定。我一般建议不管什么情况,先把终端电阻焊上再说。

2.2 帧结构逐字段拆解:从SOF到EOF

一帧标准CAN数据帧的结构,从前往后依次是:

  • SOF(Start of Frame):1位显性,告诉总线上所有节点"我要开始发了"。
  • 仲裁段:包含11位标识符(标准帧)或29位标识符(扩展帧),加上RTR位。这一段决定了帧的优先级。
  • 控制段:包含IDE位、保留位r0和4位DLC(Data Length Code)。DLC告诉你后面跟几个字节的数据,经典CAN里有效值是0到8。
  • 数据段:0到8字节的实际数据。
  • CRC段:15位CRC校验加1位界定符,用来检测传输错误。
  • ACK段:发送方发隐性,接收方如果正确接收就拉显性,表示确认。
  • EOF(End of Frame):7位隐性,标志帧结束。

这里面有几个细节值得展开说。DLC和实际数据长度不一致是一个经典陷阱。比如你DLC写了8,但实际只填了4个字节的数据,接收方会读到4个字节的垃圾数据。有些CAN控制器会检查这个,有些不检查,所以发送前一定要确保DLC和实际数据长度匹配。

ACK段的机制也很有意思。发送方在ACK槽发隐性,任何一个正确接收到这帧的节点都会拉显性。所以发送方只要看到ACK槽是显性,就知道至少有一个节点收到了。注意,这里说的是"正确接收",如果接收方CRC校验失败,它不会拉显性,发送方就会检测到ACK错误,然后触发重传。

2.3 非破坏性仲裁:ID越小优先级越高

CAN最精妙的设计之一就是非破坏性仲裁。原理是这样的:多个节点同时开始发送时,它们先发SOF,然后逐位发送仲裁段。每个节点在发送每一位的同时也在监听总线。如果某个节点发的是隐性(1),但总线上是显性(0),它就知道自己输了,立刻退出仲裁,转为接收模式。赢的那个节点完全感知不到冲突,继续正常发送。

这意味着ID数值越小,优先级越高。因为ID越小,二进制里前面的0越多,越容易在仲裁中胜出。在实际项目里,这个优先级分配是有讲究的。安全相关的报文,比如刹车、转向,ID要分配得小一些;舒适性相关的,比如车窗、空调,ID可以大一些。我见过一个项目因为把安全气囊的报文ID分配得比娱乐系统的还大,导致碰撞时气囊报文被延迟了几十毫秒,虽然最后没出大事,但这个设计是绝对不合格的。

2.4 错误处理与故障界定:CAN的容错哲学

CAN有一套相当完善的错误检测和恢复机制。每个节点内部有两个错误计数器:TEC(发送错误计数器)和REC(接收错误计数器)。检测到错误时,对应的计数器加1或加8(取决于错误类型);成功发送或接收时,计数器减1。

根据计数器的值,节点会在三种状态之间切换:

状态TEC/REC范围行为
错误主动TEC<128且REC<128正常通信,检测到错误时发主动错误帧
错误被动TEC≥128或REC≥128仍能通信,但检测到错误时只发被动错误帧
总线关闭TEC≥256完全脱离总线,不再发送任何帧

这个机制的意义在于故障隔离。如果一个节点因为硬件问题疯狂发错误帧,它会先进入错误被动状态,减少对总线的干扰,最终进入总线关闭状态,彻底闭嘴。其他正常节点不受影响。这比某些总线一出错就全网瘫痪的设计高明得多。

我在实际调试中遇到过一次总线关闭的情况,原因是一个节点的收发器供电不稳,导致它持续检测到位错误。排查的时候先看TEC计数器,发现它在飞速上涨,基本就能定位到是哪个节点的问题了。

3. CAN-FD带来了什么实质变化

3.1 速率切换:仲裁段慢,数据段快

CAN-FD最核心的变化是引入了可变速率。一帧CAN-FD里,仲裁段(包括SOF、ID、控制段的前半部分)仍然用标准速率发送,通常是500kbps或1Mbps。但到了数据段,通过BRS位(Bit Rate Switch)的切换,速率可以提升到2Mbps、5Mbps甚至8Mbps。

为什么要这样设计?因为仲裁需要总线上所有节点都能正确采样,速率太高的话,线束延迟和收发器延迟会导致采样点偏移,仲裁就不可靠了。而数据段不涉及仲裁,只有发送方和接收方参与,速率可以拉高。

这个速率切换对收发器的要求也更高了。普通CAN收发器在5Mbps下波形已经明显退化,需要用CAN-FD专用的收发器。我在选型的时候一般会看收发器的环路延迟参数,这个值越小,能支持的最高速率越高。

3.2 数据长度扩展:从8字节到64字节

经典CAN一帧最多8字节,发一个64字节的标定数据需要拆成8帧,加上帧间隔和仲裁开销,实际有效吞吐率很低。CAN-FD把DLC扩展到了4位,可以表示0到15,对应数据长度0、1、2、3、4、5、6、7、8、12、16、20、24、32、48、64字节。

注意这里不是线性的,DLC=9对应12字节,DLC=10对应16字节,以此类推。这个映射关系在配置寄存器的时候要特别注意,写错了就会导致收发双方对数据长度的理解不一致。

64字节的单帧能力在诊断和标定场景下非常实用。UDS诊断里的TransferData服务,经典CAN需要多帧传输,CAN-FD可以一帧搞定。标定协议XCP on CAN-FD也是同理,效率提升非常明显。

3.3 CRC多项式变化与填充位计数

CAN-FD的CRC多项式跟经典CAN不一样。经典CAN用15位CRC,CAN-FD根据数据长度不同,用17位或21位CRC。具体来说,数据长度小于16字节用17位CRC,大于等于16字节用21位CRC。这个变化是为了应对更长数据段带来的更高误码率风险。

还有一个细节是填充位计数。经典CAN里,填充位是每5个相同极性位后插入一个相反位,接收方直接丢弃填充位。但CAN-FD的CRC计算包含了填充位的计数值,也就是说,接收方需要统计填充位的数量,把它作为CRC校验的一部分。这个设计是为了防止填充位被恶意篡改导致CRC失效。

3.4 兼容性与共存:CAN-FD节点能否挂在经典CAN总线上

这个问题我被问过很多次。答案是:CAN-FD节点可以接收经典CAN帧,但经典CAN节点无法接收CAN-FD帧。因为CAN-FD帧里用了经典CAN中保留的位(比如EDL位、BRS位),经典CAN控制器看到这些位会认为是格式错误,然后发错误帧。

所以如果你要把一个CAN-FD节点接入现有的经典CAN网络,必须把它的FD功能关掉,让它以经典CAN模式工作。或者整个网络全部升级到CAN-FD。混搭是可以的,但FD节点必须能识别并兼容经典帧格式。

4. 从零配置一个CAN节点的完整流程

4.1 硬件选型与原理图检查要点

假设我们要在一个车载控制器上实现CAN通信,硬件层面需要确认这几样东西:

  • MCU是否内置CAN控制器:现在大多数车规MCU都内置了,比如常见的几个系列。如果没有,需要外挂SPI转CAN的控制器。
  • 收发器选型:经典CAN用普通收发器就行,CAN-FD必须用支持FD的收发器。选型时关注最大速率、环路延迟、共模电压范围、ESD等级。
  • 终端电阻:确认总线两端是否有120Ω电阻。如果是节点设备,一般不需要自己加;如果是测试设备或者总线末端,必须加。
  • 共模电感:在收发器和连接器之间加共模电感,可以有效抑制共模噪声。很多参考设计里都有,但新手容易漏掉。

原理图检查的时候,重点看收发器的TXD和RXD是否跟MCU的CAN_TX和CAN_RX正确连接。有些收发器的TXD内部有上拉,有些没有,这会影响MCU引脚的配置。另外,收发器的STB或EN引脚如果没接对,收发器可能一直处于待机模式,通信不上。

4.2 位时序计算:采样点到底设在哪里

位时序是CAN配置里最容易出错的地方。一个位时间被分成四个段:

  • 同步段(Sync_Seg):固定1个Tq。
  • 传播段(Prop_Seg):补偿总线延迟和收发器延迟。
  • 相位缓冲段1(Phase_Seg1):用于重新同步。
  • 相位缓冲段2(Phase_Seg2):用于重新同步。

采样点位于Phase_Seg1和Phase_Seg2之间。采样点的位置非常关键,一般建议设在位时间的75%到80%之间。设得太早,信号还没稳定;设得太晚,留给Phase_Seg2的余量不够,重新同步能力差。

举个例子,假设CAN时钟是8MHz,Tq=125ns,目标波特率500kbps,位时间就是2μs,也就是16个Tq。如果采样点设在75%,那么Sync_Seg=1Tq,Prop_Seg+Phase_Seg1=11Tq,Phase_Seg2=4Tq。具体怎么分配Prop_Seg和Phase_Seg1,要看总线长度和收发器延迟。总线越长,Prop_Seg要越大。

注意:同一个总线上所有节点的采样点位置必须一致,否则会出现采样错误。如果你在一个已有网络里加节点,先问清楚现有节点的位时序配置。

4.3 过滤器配置:怎么只收我想要的帧

CAN控制器的过滤器决定了哪些帧会被接收到接收FIFO里,哪些会被丢弃。过滤器配置得好,可以大幅减轻CPU负担。

过滤器一般有两种模式:掩码模式和列表模式。掩码模式是指定一个ID和一个掩码,掩码为1的位必须匹配,为0的位不关心。列表模式是列出一组精确的ID,只有完全匹配的才接收。

我一般这样分配:安全相关的报文用列表模式精确接收,保证不漏;诊断报文用一个掩码模式接收一段ID范围;其他不关心的报文直接过滤掉,不让它们进FIFO。

有个坑是过滤器数量有限。一般MCU的CAN控制器只有十几个到几十个过滤器,如果网络里报文太多,可能需要用掩码模式来覆盖多个ID。这时候就要仔细规划ID分配,把相关的报文放在连续的ID段里。

4.4 中断与FIFO管理:别在中断里处理数据

CAN控制器的接收中断触发后,正确的做法是尽快把数据从硬件FIFO搬到软件缓冲区,然后在主循环或者任务里处理。千万不要在中断里做协议解析、数据计算这些耗时操作,否则会阻塞其他中断,导致丢帧。

我通常的做法是:中断里只做一件事——把帧数据拷贝到一个环形缓冲区,然后设置一个标志位。主循环检测到标志位后,从环形缓冲区取数据并处理。环形缓冲区的大小要根据总线负载来定,负载高的时候缓冲区要相应加大。

发送方向也类似。不要在主循环里直接写发送寄存器然后等发送完成,这样会阻塞。正确的做法是把要发送的帧放入发送队列,中断里检查发送完成标志,然后从队列里取下一帧发送。

5. 调试实录:那些让我熬夜的CAN问题

5.1 通信完全不通的排查顺序

遇到CAN通信不上,我一般按这个顺序排查:

  1. 量电压:CAN_H对地大约2.5V,CAN_L对地大约2.5V,两者压差接近0V。如果电压不对,检查收发器供电和接线。
  2. 看波形:用示波器看CAN_H和CAN_L,应该有明显的差分波形。如果没有波形,说明发送方根本没发出来。
  3. 查终端电阻:断电测量CAN_H和CAN_L之间的电阻,应该是60Ω左右。如果是120Ω,说明只接了一个终端电阻;如果是无穷大,说明一个都没接。
  4. 确认波特率:用CAN分析仪监听,看是否有帧。如果没有,可能是波特率不匹配。
  5. 检查过滤器:如果分析仪能看到帧但MCU收不到,大概率是过滤器配置问题。

这个顺序是从物理层到协议层,从简单到复杂,能快速缩小问题范围。

5.2 偶发丢帧:从示波器上找线索

偶发丢帧是最难查的问题之一。我的经验是,先用CAN分析仪长时间抓包,统计错误帧的数量和类型。如果错误帧集中在某个ID或者某个时间段,就能定位到问题节点。

有一次我遇到一个偶发丢帧的问题,分析仪显示每隔几分钟就会出现一次位错误。后来用示波器抓波形,发现是某个节点的收发器在温度升高后,输出波形的高电平幅度下降,导致采样出错。换了收发器之后问题就消失了。这种问题靠看代码是永远找不到的,必须上仪器。

5.3 总线关闭的恢复策略

总线关闭状态是CAN节点最严重的故障状态。进入总线关闭后,节点完全脱离总线,不再发送任何帧。恢复策略一般有两种:

  • 自动恢复:检测到总线关闭后,等待一段时间(比如100ms),然后自动重新初始化CAN控制器,尝试重新接入总线。
  • 手动恢复:需要上层软件显式调用恢复函数。

我一般用自动恢复,但会加一个恢复次数限制。如果短时间内反复进入总线关闭,说明硬件有问题,这时候应该停止恢复,上报故障码,让诊断系统去处理。

5.4 CAN-FD速率切换失败的典型原因

CAN-FD调试中最常见的问题就是速率切换失败。表现是仲裁段能收到,但数据段全是错误帧。原因通常有这几个:

  • 收发器不支持FD:这是最常见的。普通CAN收发器的环路延迟太大,在数据段高速率下采样错误。
  • 采样点配置不对:数据段的采样点位置需要单独配置,不能直接沿用仲裁段的配置。
  • Tq配置不合理:数据段的Tq通常比仲裁段小,需要重新计算。
  • 线束质量差:CAN-FD对线束阻抗和长度更敏感,如果线束不是双绞线或者屏蔽不好,高速率下误码率会很高。

排查的时候,先用示波器看数据段的波形,如果波形明显畸变,基本就是硬件问题;如果波形正常但通信失败,就是配置问题。

6. 几个容易被忽略的实战细节

6.1 总线负载率的计算与控制

总线负载率是衡量CAN网络健康度的重要指标。计算方法很简单:负载率 = 单位时间内总线上实际传输的位数 / 总线总带宽。

举个例子,500kbps的总线,1秒内最多传输500000位。如果实际传输了200000位,负载率就是40%。一般建议负载率不要超过50%,留出余量应对突发流量。如果超过70%,延迟会明显增加,安全相关的报文可能无法及时送达。

控制负载率的方法有几种:减少周期报文的发送频率、合并信号到同一帧、用CAN-FD提高单帧数据量。我一般会在设计阶段就用Excel或者脚本估算负载率,不要等到测试的时候才发现总线跑满了。

6.2 报文周期与超时监控

每个周期报文都应该有超时监控。接收方如果在预期时间内没有收到某个报文,应该触发超时故障。超时时间一般是报文周期的3到5倍。比如一个100ms周期的报文,超时时间设为300ms到500ms。

超时监控的实现方式是在接收中断里更新一个计数器或者时间戳,在主循环里检查。如果超时,置故障标志,同时可以触发降级策略。比如刹车报文超时,可能就需要进入安全状态。

6.3 网络管理报文的处理

很多车载网络都有网络管理报文,用来协调各个节点的睡眠和唤醒。网络管理报文通常有特定的ID和格式,需要按照协议栈的要求处理。如果你自己从零实现,要注意唤醒帧的发送时机和睡眠条件的判断。搞错了会导致网络无法正常休眠,或者节点被意外唤醒。

6.4 与诊断协议的配合

CAN只是物理层和数据链路层,上面还要跑诊断协议(比如UDS)或者标定协议(比如XCP)。这些协议对CAN的使用有自己的要求,比如多帧传输的流控帧、时间参数等。在配置CAN的时候,要预留足够的缓冲区和过滤器给诊断报文,不要被周期报文占满了。

7. 常见问题速查表

现象可能原因排查方法
完全通信不上接线错误、收发器供电异常、终端电阻缺失量电压、看波形、测电阻
偶发丢帧电磁干扰、收发器温漂、采样点偏移长时间抓包统计错误帧、示波器看波形质量
进入总线关闭硬件故障导致持续错误、TEC超限读TEC/REC计数器、检查收发器和线束
CAN-FD数据段错误收发器不支持FD、采样点配置错误换FD收发器、重新计算位时序
过滤器不生效过滤器模式配置错误、ID掩码写错用分析仪确认帧ID、检查过滤器寄存器
发送阻塞在中断里处理数据、发送队列满优化中断处理、增大发送队列

8. 我个人在实际操作中的几点体会

CAN这个东西,入门不难,但要用好真的需要时间积累。我刚开始做的时候觉得配置几个寄存器就能跑,后来才发现真正的功夫在调试和优化上。硬件设计阶段多花一小时检查原理图,可能省掉后面一周的调试时间。位时序的计算不要凭感觉,老老实实按公式算,算完用示波器验证采样点位置。CAN-FD虽然快,但不要为了追求速率而牺牲稳定性,速率和可靠性之间要找到平衡点。

还有一点,文档和注释一定要写清楚。CAN网络的ID分配、报文周期、信号定义,这些东西如果不在设计阶段文档化,后面维护的时候就是灾难。我接手过一个项目,前任工程师走了之后没人知道某个ID是干什么的,最后只能把整个网络抓包分析了一遍才搞清楚。这种教训,一次就够了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进