找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4010

积分

0

好友

532

主题
发表于 1 小时前 | 查看: 3| 回复: 0

我刚入行的时候一直觉得 CAN 总线是硬件工程师的活,程序员嘛,写写驱动,调调串口,最多看看 SPI、I2C 寄存器。至于 CAN?那不是车厂、工控、电控工程师才关心的吗?

后来我做工业物联网的项目,又遇到 CAN;做机器人项目,又遇到 CAN;做嵌入式医疗设备,还是 CAN。我算是看明白了,但凡对可靠性要求高的嵌入式场景,CAN 总线基本是标配

从那以后我老老实实把 CAN 协议啃了一遍,这玩意儿跟 TCP/IP 完全是两套思维——基于消息的、优先级的、非破坏性仲裁的,跟咱们熟悉的“客户端-服务器”模型八竿子打不着。

所以不管你是在做车载、工控、机器人还是智能硬件,懂点 CAN 总线,真的能救命。

这篇文章重点讲经典 CAN,也就是我们平时说的 CAN 2.0A 和 CAN 2.0B。CAN FD 会在后面单独对比。

废话不多说,开整。

一、CAN 总线到底是什么?

1.1 CAN 总线是什么?

CAN 是一种多主机、广播式、基于报文 ID 仲裁优先级、具备强错误检测能力的现场总线,是一个让多个设备在一条线上吵架、但吵不死的通信协议。

这句话看很长,我们拆开来看:

多主机,意思是总线上没有固定的主设备。不是像 I2C 那样通常由主机发起访问,也不是像传统主从协议那样从机只能等命令。CAN 网络里的每个节点,只要总线空闲,都可以主动发消息。

广播式,意思是一帧 CAN 报文发出去,总线上所有节点都能看到。谁需要,谁接收。谁不需要,硬件过滤掉。

基于报文 ID,意思是 CAN 关心的是“这是什么消息”,而不是“这条消息发给谁”。比如 ID 0x100 代表电机转速,ID 0x200 代表电池电压。节点不一定要知道是谁发的,只要知道这个 ID 的含义。

优先级仲裁,意思是多个节点同时发送时,CAN 不会把总线撞烂,而是通过逐位比较 ID,让优先级高的报文继续发送,优先级低的节点自动退出。

错误检测,意思是 CAN 自己会检查位错误、CRC 错误、格式错误、ACK 错误、填充错误。发现错误后,它还会发错误帧通知大家:这包不对,别信。

“吵架”的意思是——任何一个节点都可以随时往总线上发消息,不用等谁批准。“吵不死”的意思是——就算两个节点同时发消息,总线也能自动决定谁先发,输了的乖乖闭嘴等下次,而且整个过程不丢数据、不重传、不浪费带宽

这就是 CAN 最牛逼的地方。别的总线遇到冲突怎么办?要么重传(浪费时间),要么丢包(数据没了)。CAN 不会这样,CAN 在发送的同时监听总线,一旦发现自己发的跟总线上的不一样,立马退出,让优先级高的继续发。整个过程数据毫发无损——所以叫“非破坏性仲裁”。

CAN总线一句话理解:多设备同时通信、自带冲突避让、抗干扰极强的串行通信总线

1.2 一个典型 CAN 网络长什么样

想象一下:一辆汽车里面有几十上百个 ECU(电子控制单元)——发动机控制一个、ABS 刹车一个、安全气囊一个、车窗一个、空调一个……每个 ECU 就是一个 CAN 节点。

这些节点全部挂在两根线上:CAN_H(高线)和 CAN_L(低线)。就这么两根线,把所有 ECU 串在一起。

MCU 里面集成了 CAN 控制器(或者外挂),控制器负责组帧、拆帧、仲裁、错误检测这些“脑力活”收发器负责把控制器输出的 TTL 逻辑电平转换成 CAN 总线上的差分信号——说白了就是个翻译官,把“0/1”翻译成“CAN_H/CAN_L 的电压差”。

典型CAN网络拓扑结构:一条CAN总线加两端120Ω终端电阻,多个CAN节点并联接入

1.3 CAN 通信和 UART I2C SPI 的区别

UART 是点对点串行通信。TX 接 RX,RX 接 TX,双方约好波特率,发字节流。UART 不关心帧 ID,不关心仲裁,也不关心多节点同时发送。

I2C 是典型的主从总线,SCL/SDA 两根线,主机发起通信,从机按地址响应。它也有线与特性,但应用场景多在板级短距离通信,比如读 EEPROM、传感器、RTC。

SPI 更直接,SCLK、MOSI、MISO、CS。速度快,逻辑简单,但片选线多,主从关系明显。你想多个设备一起挂 SPI,可以,但布线和片选管理会越来越麻烦。

CAN 不一样,CAN 是为了复杂电气环境下的多节点实时通信设计的。汽车里几十个 ECU,工业现场一堆电机、传感器、控制器,不可能每两个设备之间都拉一根独立线。CAN 用两根差分线,把所有节点挂起来,然后靠 ID 和仲裁解决“谁先说话”的问题。

CAN通信与UART、I2C、SPI的区别对比:多主机群聊 vs 一对一私聊 vs 主从通信

二、CAN 总线硬件结构与物理层

很多人 CAN 学不明白,就是跳过了物理层。总觉得底层硬件是硬件工程师的事,程序员不用管。

但我实话实说,工程里 80% 的 CAN 故障,都是物理层问题。不懂硬件原理,你写的驱动代码再完美,设备照样跑不起来。

2.1 CAN 节点硬件组成

一个完整的 CAN 节点,通常由三部分组成 = MCU(微控制器)+ CAN 控制器 + CAN 收发器 + 终端电阻(可选)

MCU 负责跑你的应用程序和 CAN 协议栈。CAN 控制器负责处理 CAN 协议的底层细节——帧格式化、仲裁、错误检测、位时序管理。收发器负责把控制器输出的逻辑电平(一般是 3.3V 或 5V TTL)转换成 CAN 总线上的差分信号,反过来也一样。

一个 CAN 节点常见结构如下:

应用代码
  │
MCU / CPU
  │
CAN 控制器
  │  TXD/RXD
CAN 收发器
  │
CANH / CANL
  │
双绞线总线

CAN节点硬件组成:MCU、CAN控制器、CAN收发器、终端电阻四部分

2.2 CAN 控制器和 CAN 收发器的区别

很多人搞不清这俩的区别。我这么说吧:

CAN 控制器集成在 MCU 内部,属于数字逻辑模块,它实现的是 CAN 协议的数据链路层。它的工作全部是协议层面的。比如组装数据帧、校验 CRC、参与总线仲裁、统计错误计数器、切换节点状态。我们代码里初始化的 CAN 外设、配置波特率、收发报文,操作的都是控制器。

CAN 收发器是独立的外部芯片,属于模拟硬件模块,它实现的是物理层。它只做一件事,就是转换电平。把控制器的数字信号转换成总线上的差分信号,同时把总线的差分信号转回数字信号给控制器。

这关系就像 UART 控制器和 RS-232 电平转换芯片的关系。控制器产生的是 TTL 逻辑电平,但 CAN 总线不认 TTL,只认差分信号,所以中间得有个收发器来翻译。

现在很多 MCU(比如 STM32)都内置了 CAN 控制器,但你还需要外接一个收发器芯片,比如 TJA1050。

CAN控制器与CAN收发器的区别:协议层大脑 vs 物理层接口

2.3 CANH CANL 差分信号

CAN 总线用的是差分信号——两根线(CAN_H 和 CAN_L)的电压差来表示 0 和 1。

不是单独看 CANH 是高还是低,也不是单独看 CANL 是高还是低,而是看二者之间的电压差。

为啥要用差分?因为抗干扰强啊。外界噪声窜进来,单端信号比如串口,靠一根线的高低电平表示数据。外界有电磁干扰,电压轻微波动,数据就会出错。但差分信号依靠两根线的电压差值判断数据。外界干扰会同时作用在 CANH 和 CANL 上,差值始终保持不变,信号自然不会失真。

这也是 CAN 能在汽车、工业强干扰环境稳定工作的核心底气。

CANH/CANL差分信号原理:通过电压差表示0和1,抗共模干扰极强

2.4 显性电平和隐性电平

CAN 总线没有传统的高低电平说法,取而代之的是显性电平隐性电平,这个概念是 CAN 所有机制的基础。

  • 显性(Dominant) = 逻辑 0。此时 CAN_H 电压升高、CAN_L 电压降低,两根线产生明显压差,总线被信号占用。
  • 隐性(Recessive) = 逻辑 1。此时 CAN_H 和 CAN_L 电压基本相等,压差接近 0V,总线处于空闲状态。

记住一个关键规则:显性覆盖隐性(显性电平优先级高于隐性电平)。只要总线上有一个节点发显性(0),整条总线就是显性(0);只有所有节点都发隐性(1),总线才是隐性(1)。

这个“显性覆盖隐性”的规则,是整个 CAN 的仲裁机制和线与逻辑

显性电平与隐性电平概念:显性=逻辑0,隐性=逻辑1,显性覆盖隐性

2.5 线与逻辑

“线与”这个概念来自数字电路——多个输出接在一起,只要有一个输出低电平,整体就是低电平;所有输出都高电平,整体才是高电平。

CAN 总线的“显性覆盖隐性”本质上就是线与逻辑。多个节点同时驱动总线时,显性位总能赢过隐性位。这个特性让仲裁变得极其简单高效——不需要额外的仲裁信号线,总线本身就能“投票”。

节点A发送  节点B发送  总线结果
   1         1         1
   1         0         0
   0         1         0
   0         0         0

只要有 0,总线就是 0,因为显性 0 会压过隐性 1。

后面的仲裁、ACK、错误帧,全都和它有关。

CAN总线中的线与逻辑:显性覆盖隐性,多个节点同时驱动时总线自动投票

2.6 高速 CAN 和低速容错 CAN

CAN 有两个主要物理层标准:

高速 CAN(ISO 11898-2) :波特率最高 1Mbps,总线长度最长 40 米。需要终端电阻。这是最常见的类型,汽车动力系统、ADAS 这些对速度要求高的场景都用这个。

低速容错 CAN(ISO 11898-3) :波特率10~125Kbps,最长 1 公里。牛逼之处在于——一根线断了还能继续通信。车身控制、车窗、座椅这些对速度要求不高的地方用这个。

别看到 CAN 就以为都是同一种物理层。上层报文格式可以很像,但物理层、电气特性、拓扑要求可能不一样。

高速CAN与低速容错CAN对比:速度、布线、容错能力与应用场景

2.7 终端电阻

CAN 总线的传输线是双绞线,属于高频传输线路。高速 CAN 总线两端必须各接一个 120Ω 的终端电阻

为啥是 120Ω?

因为 CAN 双绞线的特性阻抗就是 120Ω。终端电阻的作用是消除信号反射——信号跑到总线尽头如果没有电阻吸收,会弹回来干扰后面的信号。就像你在一根管子里吹气,管子末端堵住了气会弹回来,开个口子让气出去就没事了。

终端电阻接在哪儿?

必须在总线的最两端的节点上。中间节点不需要接。如果接多了或者接少了,信号质量都会下降。实际工程中,经常有人忘记接终端电阻导致通信不稳定——这是排查 CAN 问题的第一件事。

CAN总线终端电阻:两端必须各接120Ω,消除信号反射

三、CAN 总线核心特性

这一章是 CAN 最精华的部分。理解了这些特性,你就理解了为什么 CAN 能吊打传统串口、I2C,成为工业和车载的王者!

3.1 多主机系统

传统的 I2C、SPI,必须分主机、从机,话语权完全掌握在主机手里。从机不能主动发数据,只能被动等待主机问询。

CAN 总线完全颠覆了这个模式。总线上所有节点,地位完全平等,都是主机。任何一个节点都可以在任何时刻主动向总线发消息。不需要等主机轮询,不需要申请总线使用权。

这个特性太适合复杂设备组网了。比如汽车的发动机、电池、刹车、灯光模块,各自独立工作,有故障随时上报,无需中央主机轮询,实时性直接拉满。

缺点是需要解决冲突——万一两个节点同时发怎么办?

这就是下一节要讲的仲裁机制。

不是随机退避,不是撞了重发,而是逐位比较优先级。赢的人继续发,输的人安静听着。这个机制很漂亮,有点东西。

CAN多主机系统:所有节点平等,可随时主动发送数据,区别于I2C/SPI主从模式

3.2 广播通信

CAN 报文发送出去后,总线上所有节点都能收到,这就是广播通信机制。

和点对点通信不同,发送方不需要指定接收设备地址。报文一发,全网可见。

你可能会问,这样不会造成数据干扰吗?

其实不会,因为有硬件过滤机制。每个节点会根据报文 ID,自主判断是否接收该帧数据。需要的就收下,不需要的直接丢弃,不占用系统资源。

这跟以太网的广播有点像,但 CAN 的广播是基于消息 ID 的,不是基于 MAC 地址或 IP 地址。

比如:

ID 0x100  电机转速
ID 0x101  电机温度
ID 0x200  电池电压
ID 0x201  电池电流
ID 0x300  仪表显示命令

电机控制器发 0x100,总线上所有节点都看到。仪表需要转速,就接收。BMS 不关心转速,就过滤掉。

当然,上层协议也可以规定某些 ID 范围代表某些节点,甚至做出类似源地址、目标地址的东西。比如 J1939 里就有源地址概念。但那是上层协议的事,不是经典 CAN 数据链路层本身的核心思想。

CAN广播通信:报文一发全网可见,节点按ID过滤各取所需

3.3 非破坏性逐位仲裁

这是 CAN 最核心、最牛逼的设计,大家多看几遍!!

当两个(或多个)节点同时开始发消息时,串口、I2C 直接数据错乱、通信失败。但 CAN 不会,它会逐位比较正在发送的 ID。每个节点在发送的同时监听总线——如果发现自己发的位跟总线上的不一致(说明有别的节点在发更高优先级的消息),这个节点立马退出,不再继续发。

赢了的节点继续发完整个消息,整个过程没有任何数据被破坏。优先级低的节点主动退出发送状态,转为接收状态。整个过程硬件自动完成,不会破坏已发送的有效数据,所以叫非破坏性逐位仲裁。

这就是“非破坏性”的含义——不是像以太网那样冲突了大家退避重传,而是在冲突发生的瞬间就决定了胜负,胜者继续,败者退出

非破坏性逐位仲裁过程:ID阶段逐位比较,优先级高的继续发,低的立即让路

3.4 ID 表示优先级,不表示设备地址

这是 CAN 跟 TCP/IP 最大的思维差异。

IP 协议里,IP 地址表示“”在通信。CAN 协议里,ID 表示“什么消息”——ID 越小,优先级越高

很多初学者默认 CAN ID 是设备地址,1号设备发 ID=0x001,2号设备发 ID=0x002,完全错了。

CAN ID 和设备地址没有任何关系。它只代表报文的优先级和数据类型。

举个例子:ID 0x100 是“刹车信号”,ID 0x200 是“车窗信号”。刹车信号的优先级比车窗高,因为 ID 更小。当刹车信号和车窗信号同时要发时,刹车信号胜出。

这种设计的好处是——添加或删除节点不影响其他节点。你往总线上加一个 ECU,只要它不发跟别人冲突的 ID,完全不影响现有通信。不像 I2C 那样每个设备得有唯一地址,加设备还得重新规划地址分配。

CAN ID标识符:表示优先级而非设备地址,这是与TCP/IP思维最大的区别

3.5 硬件过滤

既然 CAN 是全网广播,所有报文所有设备都能收到,那总线报文多了,MCU 岂不是要疯狂处理无效数据?

不用担心,CAN 控制器自带硬件过滤机制

我们可以提前配置过滤器、过滤掩码,只接收需要的报文 ID。不符合规则的报文,硬件直接自动丢弃,完全不进入内存、不触发中断、不占用 CPU 资源

这意味着——你不用在中断里写一堆 if-else 来判断要不要处理这个消息,过滤操作全硬件完成,无需软件干预。这也是 CAN 实时性高的关键原因。

CAN硬件过滤原理:控制器自动丢弃不符合规则的报文,不占CPU资源

3.6 错误检测与错误隔离

CAN 有5 种错误检测机制(位错误、填充错误、CRC 错误、格式错误、ACK 错误),后文会详细展开。

这里先提一句核心思想:

每个节点都在时刻监控总线和自己。发的时候监听自己发出去的对不对,收的时候检查收到的格式对不对、CRC 对不对。发现错误就发错误帧通知所有人。

更绝的是——如果一个节点频繁出错,CAN 控制器会逐渐把它从总线上隔离出去(Bus Off),不让它继续破坏总线通信。

CAN错误检测与错误隔离:5种错误检测机制,故障节点自动隔离

3.7 半双工通信

CAN 总线是半双工通信模式——同一时刻只能有一个节点发,其他节点都在听。发的时候不能收,收的时候不能发。

但 CAN 的厉害之处在于——发送的同时在监听。这跟普通的半双工不一样,普通的半双工(比如 RS-485)发的时候就是发,不知道自己发的跟别人有没有冲突。CAN 发的时候同时在听,能立刻发现冲突并启动仲裁。

很多人会觉得半双工是短板,其实不然。正是因为半双工+仲裁机制,才避免了总线冲突,保证了数据传输的可靠性。全双工的同时收发,只会带来更多数据错乱问题,完全不适合 CAN 的组网场景。

CAN半双工通信:同一时刻只能一方发送,配合仲裁机制保证可靠性

四、CAN 总线标准体系与 OSI 模型映射

4.1 CAN 在 OSI 七层模型中的位置

标准 OSI 七层模型分为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。

CAN 协议只定义了 OSI 七层模型中的两层物理层数据链路层

这意味着 CAN 只负责把数据从 A 点可靠地送到 B 点。至于数据是什么意思、怎么用——那是上层协议的事,CAN 不管。

这跟 TCP/IP 的 IP 层有点像——IP 只负责把包从源地址送到目的地址,不管包里装的是什么。

CAN在OSI七层模型中的位置:仅定义物理层和数据链路层

4.2 CAN 数据链路层里的 LLC 和 MAC

CAN 的数据链路层,又拆分为 LLC 逻辑链路控制子层和 MAC 媒体访问控制子层:

LLC(Logical Link Control,逻辑链路控制) :负责帧过滤过载通知恢复管理

MAC(Medium Access Control,介质访问控制) :负责帧封装(组帧)、仲裁错误检测错误标定应答

简单说——LLC 负责“要不要收这个帧”,MAC 负责“怎么把这个帧发出去和收进来”。

CAN数据链路层细分:LLC子层负责过滤与接收管理,MAC子层负责帧封装、仲裁与错误检测

4.3 CAN 物理层定义了什么?

物理层不只是“接两根线”。物理层定义了电平标准传输介质(双绞线)、连接器终端电阻等一堆东西

不同物理层标准(高速 CAN vs 低速 CAN)的区别就在这一层。数据链路层是通用的,不管物理层是高速还是低速,帧格式、仲裁机制、错误处理都一样。

CAN物理层定义:差分电平标准、显隐性电压范围、总线阻抗、终端电阻、传输介质、通信距离与波特率等硬件规则

4.4 ISO 标准化 CAN 的两大方向

CAN 主要有两个 ISO 标准:

ISO 11898高速 CAN 标准(最高 1Mbps),最常用,对应我们常用的工业、车载高速 CAN,波特率最高 1Mbps,差分信号传输,必须终端电阻匹配。

ISO 11519低速容错 CAN 标准(最高 125Kbps)。波特率低、容错性强,适用于车身辅助系统。

ISO 11898 后来又细分了多个部分:

  • ISO 11898-1:数据链路层
  • ISO 11898-2:高速 CAN 物理层
  • ISO 11898-3:低速容错 CAN 物理层

日常开发 99% 的场景,都是基于 ISO 11898 标准的高速 CAN。

你不需要一开始就把标准号背得滚瓜烂熟,但要知道:CAN 不是一个单文件协议,它有数据链路层,也有不同物理层标准。

很多资料说 CAN 2.0A、CAN 2.0B。

CAN 2.0A 支持 11 位标准 ID。

CAN 2.0B 支持 29 位扩展 ID。

这两个是经典 CAN 学习里最常见的概念。

ISO 11898高速CAN标准 与 ISO 11519低速容错CAN标准 对比

4.5 常见 CAN 上层协议

CAN 只管物理层和数据链路层,不定义数据含义、交互规则,想要实现设备间正常通信,必须依赖上层协议。

常见的上层协议有:

CANopen(CiA 301) :工业自动化领域最常用。面向对象设计,模块化好。它定义对象字典、PDO、SDO、NMT、心跳等机制。

SAE J1939:商用车、重工机械领域专用。针对柴油发动机预定义了大量的消息格式。定义 PGN、源地址等内容。

ISO 15765(UDS on CAN) :汽车诊断协议。就是 4S 店插 OBD 接口读故障码用的那个协议。跑在 CAN 上时,经常结合 ISO-TP 来传输超过 8 字节的数据。

ISO-TP 用来把长数据分片传到 CAN 上。经典 CAN 一帧最多 8 字节,诊断请求和响应经常超过 8 字节,所以需要分包、流控、重组。

很多初学者会把这些东西混在一起。

CAN 是总线和数据链路。

CANopen/J1939/UDS 是上层协议。

ISO-TP 是传输层分段机制。

SocketCAN 是 Linux 里的 CAN 网络栈接口。

CAN FD 是 CAN 的增强版本,能带更长数据段和更高数据阶段速率。

常见CAN上层协议:CANopen、SAE J1939、ISO 15765(UDS)、ISO-TP等

4.6 容易混淆的几个概念

CAN 协议 vs CAN 上层协议:CAN 协议只管怎么发,不管发什么。CANopen、J1939 这些是“用 CAN 来发什么”的规范。

CAN 2.0A vs CAN 2.0B:2.0A 用 11 位 ID(标准帧),2.0B 支持 11 位和 29 位 ID(扩展帧)。现在基本都是 2.0B。

经典 CAN vs CAN FD:经典 CAN 最高 1Mbps、最多 8 字节数据;CAN FD 数据段可以更快(最高 8Mbps)、数据最多 64 字节。后文会专门讲。

CAN 本身不支持重传。很多人以为 CAN 有错误重传机制,其实没有。CAN 只会检测错误、报错、隔离故障,重传逻辑需要上层软件协议自己实现。

总线空闲不代表没有数据。隐性电平是空闲状态,但总线广播特性会让所有节点实时监听,只是无有效数据传输而已。

CAN总线易混概念辨析:CAN协议 vs 上层协议、2.0A vs 2.0B、经典CAN vs CAN FD、重传与总线空闲监听

五、CAN 位时序 同步机制与波特率计算

这一章偏硬件,但程序员多少得懂一点。因为你配置 CAN 控制器的时候,得填一堆跟位时序相关的寄存器——填错了通信就建立不起来。

5.1 为什么 CAN 需要位时序

CAN 是异步串行通信,没有时钟线。所有节点靠约定好的位时序来同步。

每个节点用自己的时钟采样总线上的电平。如果大家的时钟不完全一样(事实上肯定不一样),时间长了就会累积误差,导致采样点偏移,最后收错数据。

位时序和同步机制,就是用来消除时钟偏差,保证全网设备时序一致:每个位分成若干小段,在固定位置采样,并且允许根据跳变沿微调采样点。

CAN位时序必要性:多节点组网需同步机制对齐时钟,避免采样错位

5.2 一个 CAN 位由哪些部分组成

CAN 协议把每一个 CAN 位时间,拆分为四个固定时间段

同步段(Sync_Seg) :1 Tq。用于同步,边沿跳变应该发生在这里。

传播段(Prop_Seg) :1~8 Tq。补偿信号在总线和节点内部的传播延迟。

相位缓冲段 1(Phase_Seg1) :1~8 Tq。采样点在这个段的末尾

相位缓冲段 2(Phase_Seg2) :1~8 Tq。采样点之后的时间,用于补偿。

同步段固定 1 个时间单元,作用是让所有节点时钟对齐,所有报文的第一个位必须在这里完成同步。

传播时间段用来补偿总线线路的传输延迟、收发器硬件延迟,保证信号传输时序准确。

两个相位缓冲段用来微调时钟偏差,预留容错空间,解决晶振偏移、时序错位问题。

一个CAN位的组成:同步段Sync_Seg、传播段Prop_Seg、相位缓冲段1/2与采样点

5.3 tq NBT 和波特率

先搞懂三个核心参数,所有波特率计算都基于它们。

tq(Time Quantum)最小时间单元,是 CAN 时序的基本刻度。由 MCU 总线时钟分频得到,时钟频率越高,tq 时间越短。

NBT(Nominal Bit Time) 是一个数据位包含的总 tq 数量,也就是同步段(Sync_Seg)、传播段(Prop_Seg)、相位段1(Phase_Seg1)、相位段2(Phase_Seg2)的 tq 总和。

波特率代表每秒传输的数据位数。波特率 = 时钟频率 / 分频系数 / NBT(波特率 = 1 / (NBT × tq))。

协议有硬性规范,NBT 总 tq 数量必须在 8~25 之间,不能随意配置。

tq、NBT与波特率的关系:三个核心参数决定CAN总线通信速度与稳定性

5.4 用一个例子计算 500kbps

假设 CAN 外设时钟是 80MHz,我们想配置 500kbps。

选择每 bit 16 个 tq。

那么需要 tq 频率:

500000 * 16 = 8MHz

80MHz 分频到 8MHz,需要分频 10。

于是:

CAN 时钟 = 80MHz
预分频 = 10
每 bit = 16 tq

波特率 = 80MHz / 10 / 16 = 500kbps

位段可以这样分:

Sync_Seg   = 1 tq
Prop_Seg   = 5 tq
Phase_Seg1 = 7 tq
Phase_Seg2 = 3 tq

总计 = 1 + 5 + 7 + 3 = 16 tq

采样点位置:

(1 + 5 + 7) / 16 = 81.25%

这个采样点在很多 500kbps 场景下比较常见。当然实际配置还要看总线长度、节点数量、收发器、控制器限制。

一个典型伪代码大概这样:

typedef struct {
    uint32_t prescaler;
    uint32_t sync_seg;
    uint32_t prop_seg;
    uint32_t phase_seg1;
    uint32_t phase_seg2;
    uint32_t sjw;
} can_bit_timing_t;

can_bit_timing_t timing = {
    .prescaler = 10,
    .sync_seg = 1,
    .prop_seg = 5,
    .phase_seg1 = 7,
    .phase_seg2 = 3,
    .sjw = 1,
};

真实项目别直接复制这个配置。你得结合 MCU 手册,不同厂商寄存器命名不一样。有的预分频寄存器写 9 实际代表除以 10,有的字段值要减 1。写代码时一定看手册。这里千万别凭直觉,凭直觉容易死得很有节奏。

5.5 采样点

采样点就是在一个位的时间里,读取总线电平的那个瞬间(判断数据是 0 还是 1 的时刻)。采样点在 Phase_Seg1 的末尾

一般来说,采样点越靠后(比如 75%~80% 的位置),抗干扰能力越强,但对时钟精度要求也越高。具体设多少,看 CAN 控制器的 datasheet 和总线的实际长度。

如果两个节点波特率名义一样,但采样点差很多,也可能通信不稳定。

这就是为什么有些工具软件让你选 500kbps,还会让你配置 sample point。不是软件闲着没事,它确实影响通信。

调试时,如果波特率确认没错,但错误帧很多,可以看看采样点配置。尤其是不同芯片、不同 CAN 工具混在一起时。

CAN采样点:决定CAN通信稳定性的关键参数,推荐75%~80%位置

5.6 SJW 再同步跳转宽度

SJW 是 Synchronization Jump Width,同步跳转宽度,很多人不知道这个参数的作用。

简单说,它是时钟偏差的最大修正范围。总线检测到时序偏差时,会自动微调相位段时长,最大修正幅度就是 SJW。

协议规定 SJW 最大值不能超过 4tq,且不能超过相位缓冲段1的时长。工程中一般配置为 1~2tq,适配绝大多数场景。

SJW再同步跳转宽度:时钟跑偏时的最大修正幅度,工程中常配1~2tq

5.7 硬同步和再同步

CAN 有两种同步机制,适用不同的通信场景。

硬同步发生在帧起始(SOF) 的下降沿。所有节点检测到 SOF 后,把自己的位时间重新对齐到总线的边沿。

再同步发生在帧的中间,每当检测到隐性到显性的跳变沿时,节点可以微调自己的位时间。

硬同步只在帧开始时做一次,再同步在帧中间持续做。两者配合,保证整个帧的每一位都被正确采样。

CAN硬同步与再同步:硬同步在SOF下降沿强制对齐,再同步在帧内持续微调

六、CAN 总线五大帧结构详解

CAN 总线上传输的有 5 种帧。这一章是 CAN 协议最细节的部分,是 CAN 协议的核心骨架,所有通信、仲裁、校验、报错,全部围绕帧结构展开。这一章是重中之重,面试必考、工程必用。

6.1 CAN 的 5 种帧

CAN 总线所有通信内容,只有五种帧类型:

数据帧:用来传输数据,最常用。

遥控帧:向某个节点请求数据。

错误帧:发现错误时通知所有人。

过载帧:接收方告诉发送方“我忙不过来,慢点”。

帧间空间:把前面的帧和后面的帧隔开。

CAN的5种帧类型:数据帧、遥控帧、错误帧、过载帧、帧间空间

6.2 标准帧和扩展帧

数据帧和遥控帧,分为标准帧扩展帧两种格式。

标准帧对应 CAN2.0A,只有 11 位 ID,帧结构更短,传输效率更高,适合常规设备。

标准 ID 范围:

0x000 ~ 0x7FF

扩展帧对应 CAN2.0B,有 29 位 ID,ID 资源更多,适合车载复杂组网、设备数量多的场景。

扩展 ID 范围:

0x00000000 ~ 0x1FFFFFFF

扩展帧不是简单把 ID 变长,它在仲裁段里引入了 SRR、IDE 等字段。标准帧和扩展帧在同一总线上仲裁时,会有特殊规则。

如果一个标准帧和扩展帧前 11 位 ID 相同,标准帧通常优先于扩展帧。因为标准帧在 RTR 位置发送显性位,而扩展帧对应位置是 SRR 隐性位。

这个细节别小看。报文规划时混用标准帧和扩展帧,要知道它会影响优先级。

标准帧与扩展帧对比:CAN 2.0A的11位ID vs CAN 2.0B的29位ID

6.3 数据帧整体结构

数据帧是结构最完整、使用最多的帧,从头到尾分为7个段:帧起始 → 仲裁段 → 控制段 → 数据段 → CRC 段 → ACK 段 → 帧结束

更展开一点:

SOF | 11-bit ID | RTR | IDE | r0 | DLC | Data | CRC | CRC Del | ACK Slot | ACK Del | EOF

扩展帧会更长,仲裁段包括 29 位 ID 相关字段。

应用代码看到的通常只是:

typedef struct {
    uint32_t id;
    uint8_t  is_extended;
    uint8_t  dlc;
    uint8_t  data[8];
} can_frame_t;

但总线上真正跑的东西远不止这些。控制器帮你把很多字段自动处理了

这也是 CAN 控制器存在的意义。让你每次手工拼 SOF、CRC、ACK?那程序员估计要原地辞职。

CAN数据帧整体结构:从SOF到EOF的7个段详解

6.3.1 SOF 帧起始

SOF 是 Start Of Frame,帧起始只有 1 位显性电平(逻辑0)

总线空闲时是隐性电平,一旦节点要发数据,先拉低总线发出 SOF。所有节点检测到下降沿,触发硬同步,准备接收报文。

SOF 很短,但地位很高,核心作用只有两个:标记报文开始触发全网时序同步

6.3.2 仲裁段

仲裁段是整帧的核心,包含报文 ID、帧类型标识。

标准帧仲裁段包含 11 位 ID + RTR 位(RTR 是 Remote Transmission Request)。扩展帧包含 29 位 ID + SRR、IDE、RTR 等位。

仲裁机制全程在这个阶段完成。多个节点同时发报文,逐位对比 ID,显性 0 优先,隐性 1 退让,直接决定总线使用权。

RTR 位用来区分帧类型,0 是数据帧,1 是遥控帧。

6.3.3 控制段

控制段里有 IDE、保留位、DLC 等字段。

DLC 表示数据长度。经典 CAN 支持 0 到 8 字节。

DLC 是 Data Length Code,不是数据本身。初学者有时看到 DLC 8,就以为数据段固定 8 字节。其实 DLC 可以是 0,很多控制命令可能只靠 ID 表达含义,数据段为空。

经典 CAN 的小数据包特点很明显。8 字节看起来少,但对控制系统够用。转速、电压、电流、状态位、故障码,很多都能塞进 8 字节。

不够怎么办?

上 ISO-TP,或者用 CAN FD,或者重新思考你是不是在 CAN 上塞了不该塞的东西。比如大文件传输就别硬上经典 CAN 了,大家都难受。

6.3.4 数据段

数据段就是我们真正要传输的有效数据,字节数由 DLC 决定。可以是 0 字节空帧,也可以是 1~8 字节有效数据。

经典 CAN 最多 8 字节。比如:

ID: 0x201
DLC: 8
Data: 10 27 00 00 34 12 01 00

这 8 字节代表什么,CAN 不管。

可能是小端编码的电压电流,也可能是状态位,也可能是计数器和校验和。具体含义由报文矩阵或上层协议定义。

工程里最怕没有报文矩阵

代码里到处写:

speed = frame.data[2] << 8 | frame.data[3];

三个月后没人知道这个 2 和 3 是什么。半年后协议改了,直接懵圈了。

所以 CAN 项目一定要维护报文定义。ID、周期、发送节点、接收节点、字节布局、缩放因子、偏移量、大小端、单位、范围、默认值、超时策略,全都要写清楚。

6.3.5 CRC 段

CRC 用来检测传输错误,CRC 段包含 15 位 CRC 校验码 + 1 位 CRC 界定符。

控制器硬件自动对帧起始到数据段的所有数据进行 CRC 运算,生成校验码。接收方接收后重新计算,对比校验码是否一致,判断数据是否传输出错。

这是 CAN 数据可靠性的核心保障,全程硬件自动校验,无需软件计算。

6.3.6 ACK 段

ACK 段是很多人调试报错的重灾区,包含 ACK 槽位和 ACK 界定符(隐性)。

发送节点在 ACK Slot 发送隐性位。任何正确接收到该帧的节点,会在 ACK Slot 发送显性位。

于是发送节点读到显性位,就知道:总线上至少有一个节点收到了这帧。

这里有个非常容易误解的点。

CAN ACK 不是业务确认。

它不代表目标设备处理了这个命令。

它甚至不代表某个特定设备收到了。

它只代表至少有一个节点从数据链路层认为这帧没问题,并给了 ACK。

如果总线上只有一个节点,它自己发帧没人 ACK,会出现 ACK 错误。很多人用单板测试 CAN,发现一直发送失败,就是因为没有第二个节点应答。

所以测试 CAN 时,最好至少两个节点,或者用 CAN 分析仪接入总线。

6.3.7 EOF 帧结束

帧结束由 7 位隐性电平组成,标记当前报文传输完毕。

同时告诉总线上所有节点,本轮传输结束,总线重新进入空闲状态,等待下一次通信。

这类字段平时应用代码看不到,但协议分析仪会显示。做底层调试时,理解它有用。

6.4 遥控帧

遥控帧和数据帧结构几乎一致,唯一区别是没有数据段RTR 位为 1

遥控帧用于请求某个数据帧。

它有 ID,有 DLC,但没有数据段。意思大概是:谁有这个 ID 对应的数据,发一下。

现在很多工程系统不太喜欢遥控帧,更常用周期发送或应用层请求响应。因为遥控帧会增加设计复杂度,也不是所有上层协议都推荐使用。

但经典 CAN 里它确实存在。看老系统或某些设备协议时,可能会遇到。

CAN遥控帧:没有数据段,用于请求指定ID的数据,RTR位为1

6.5 错误帧

节点检测到总线错误时,会立即发送错误帧,通知全网节点总线异常。

错误帧由错误标志(6 个显性位) + 错误界定符(8 个隐性位) 组成。发送后会破坏当前错误报文的传输,让全网丢弃错误数据,重新等待新报文。

错误帧分主动错误帧和被动错误帧,取决于节点错误状态。

Error Active 节点发送主动错误标志,使用显性位,影响力更强。

Error Passive 节点发送被动错误标志,使用隐性位,不会像主动错误标志那样强烈干扰总线。

这个设计和错误隔离有关。一个经常出错的节点,会逐步降低自己对总线的破坏能力。

CAN错误帧:检测到总线错误后发送的报警帧,中断当前错误报文

6.6 过载帧

过载帧的作用是延迟下一帧数据的接收

当节点接收缓冲区满、处理不过来的时候,发送过载帧,告诉总线暂缓发送新数据,给自己留出处理时间。正常稳定的总线通信,基本不会出现过载帧。一旦频繁出现,说明软件接收处理太慢,线程卡顿严重。

CAN过载帧:接收方忙不过来时发出的暂停信号,延迟下一帧接收

6.7 帧间空间

帧间空间是报文与报文之间的间隔,由隐性电平组成,用来分隔连续的报文。

CAN 不是连续无缝拼帧。哪怕总线负载很高,帧之间也有协议规定的间隔。

这也会影响总线负载计算。你不能只算数据字节,还要算帧开销、位填充、帧间隔。

它能避免多帧数据粘连,让控制器精准区分每一个独立报文,保证解析准确。

CAN帧间空间:报文之间的隐性电平间隔,用于分隔连续报文

七、CAN 仲裁机制 多个节点同时发送怎么办

理解了仲裁,你就理解了 CAN 的核心竞争力

7.1 为什么 CAN 需要仲裁

前面说过,CAN 是多主机总线,所有节点地位平等,都可以随时发数据。

两个节点都看到总线空闲,同时开始发,怎么办?

普通串口没这个能力。两个 TX 硬怼,数据就乱了。

CAN 的办法是利用显性位和隐性位的覆盖关系,在发送 ID 的过程中完成仲裁。

谁优先级高,谁继续。

谁优先级低,谁退出。

胜出的帧不被破坏。

这对实时系统很重要。高优先级消息不用等随机退避,它可以直接赢。

CAN为什么需要仲裁?多主机总线同时发送时靠仲裁维持秩序

7.2 逐位非破坏性仲裁过程

假设节点 A 和节点 B 同时发送标准帧。

节点A ID = 0x120
节点B ID = 0x100

把 ID 写成 11 位二进制:

0x120 = 001 0010 0000
0x100 = 001 0000 0000

逐位比较:

        bit10 bit9 bit8 bit7 bit6 bit5 bit4 ...
节点A     0    0     1     0     0     1     0
节点B     0    0     1     0     0     0     0
总线      0    0     1     0     0     0     0

到了 bit5,节点 A 发 1,节点 B 发 0。总线结果是 0。

节点 A 发现自己发 1 却读到 0,知道输了,于是停止发送,切换为接收。

节点 B 继续发送。

节点 A 不会把这当错误,因为仲裁阶段这种情况是正常的。

逐位非破坏性仲裁过程详解:发送隐性1读到显性0的节点立刻退出

7.3 CAN 优先级规则

CAN ID 数值越小,优先级越高。

这是因为 ID 从高位到低位发送,显性 0 压过隐性 1。

所以:

0x001 优先级高于 0x010
0x010 优先级高于 0x100
0x100 优先级高于 0x200

设计报文 ID 时,要把实时性强、紧急程度高的消息放在小 ID。

比如:

0x080 紧急故障
0x100 电机控制命令
0x180 电机状态
0x200 电池状态
0x500 诊断数据
0x700 心跳或低优先级管理消息

这只是示例,不是通用规范。

千万别因为某个节点编号是 1,就给它所有报文都分配 0x100 段。CAN ID 的第一目标是表达消息优先级和语义,而不是给节点排座位。

CAN优先级规则:ID数值越小优先级越高,显性0压倒隐性1

7.4 标准帧仲裁示例

三个节点同时发送:

节点A ID = 0x300
节点B ID = 0x180
节点C ID = 0x100

最终 ID 0x100 获胜。

因为它数值最小。

仲裁结束后,A 和 B 自动转为接收。它们会接收 C 发出的这帧。等总线空闲后,A 和 B 再尝试发送自己的帧。

这里没有应用层参与。不是你代码里写了 if 判断谁优先,是 CAN 控制器硬件自动完成。

这也是 CAN 适合实时控制的原因之一。

7.5 标准帧和扩展帧仲裁示例

假设一个标准帧和一个扩展帧,前 11 位基础 ID 相同。

标准帧在该位置发送 RTR 显性位。

扩展帧会发送 SRR 隐性位,并通过 IDE 表示扩展格式。

显性压过隐性,所以标准帧优先。

这就是为什么混用标准帧和扩展帧时,不能只看数值大小。你得理解帧格式字段本身也参与仲裁。

扩展帧 ID 长,能表达更多信息,但帧更长,开销更大,仲裁也更复杂。

别因为“29 位看起来更高级”就全用扩展帧。工程里没有高级不高级,只有合不合适。

八、CAN 错误检测 错误帧与 Bus Off 机制

CAN 之所以可靠,很大程度上靠的就是这套完善的错误处理机制

8.1 CAN 的五大错误类型

经典 CAN 常见五类错误:位错误、填充错误、CRC 错误、格式错误、ACK 错误

所有错误都会被控制器硬件实时检测,同时累加错误计数器,改变节点状态。

CAN的五大错误类型:位错误、填充错误、CRC错误、格式错误、ACK错误

8.2 位错误

发送节点会监测总线电平。

如果它发送一个位,却读回不同的位,就可能发生位错误。

但仲裁阶段和 ACK 阶段有特殊规则。比如仲裁时发送隐性读到显性,不算错误,而是仲裁失败。

正常数据段里如果出现发和读不一致,就要怀疑总线冲突、电气问题、收发器问题,或者某个节点乱发错误帧。

CAN位错误:发送电平与读回电平不一致,仲裁与ACK阶段有例外规则

8.3 填充错误

为了保证时序同步,CAN 有位填充规则。

在 SOF 到 CRC 序列这段区域内,如果连续出现 5 个相同极性的位,发送器会自动插入 1 个相反位。

接收器收到后会自动删除这个填充位。

如果接收器发现连续超过 5 个相同位,没有看到正确的填充位,就认为填充错误。

位填充的目的之一是保证总线上有足够边沿用于同步。

这个机制对程序员透明,但抓原始波形时要知道。否则你会疑惑:为什么总线上比我想象的多了一些位?

CAN填充错误:连续5个相同电平后未插入相反位即为填充错误

8.4 CRC 错误

接收节点计算 CRC,发现和帧里的 CRC 不一致,就触发 CRC 错误。

CRC 错误通常说明传输过程中有位被干扰、采样点不合适、波特率不准、线缆质量差、终端不对,或者总线负载和物理条件太糟。

单纯应用数据填错,一般不会导致 CAN 帧 CRC 错误。因为 CRC 是控制器根据实际发送的位自动算的。

CAN CRC错误:接收端重算CRC与帧内CRC不一致,通常说明传输链路或物理层问题

8.5 格式错误

CAN 某些固定字段必须是固定电平。

比如 CRC Delimiter、ACK Delimiter、EOF 等位置应该是隐性位。

如果这些位置出现不符合格式的电平,就会触发格式错误。

格式错误经常和严重物理层问题、错误节点干扰有关。

CAN格式错误:固定字段出现不符合协议格式的电平,帧结构被破坏

8.6 ACK 错误

发送节点在 ACK Slot 发送隐性位,期待其他节点用显性位应答。

如果没有任何节点应答,发送节点会检测到 ACK 错误。

这在单节点测试时特别常见。

你拿一个开发板发 CAN,旁边没有另一个正常节点,也没有 CAN 分析仪,结果发送失败。你看代码看半天,其实只是没人 ACK。

还有一种情况,对方波特率不一致,听不懂你的帧,也不会 ACK。

所以 ACK 错不一定表示线断了,也可能表示没有其他节点、波特率不一致、对方没开、过滤不会影响 ACK但控制器状态异常、收发器有问题。

这里说一句,过滤器通常不决定数据链路层 ACK。节点只要正确接收帧,即使软件过滤不交给应用,也可能参与 ACK。不同控制器模式下细节要看手册,但别简单理解成“没订阅 ID 就不 ACK”。

CAN ACK错误:ACK槽无人应答,常见于单节点测试或波特率不一致

8.7 错误帧发送时机

节点检测到错误后,会发送错误帧。

错误帧会让当前帧无效,其他节点也会知道这里出错了。

发送节点随后可能自动重发。

这就是为什么总线有错误时,你在分析仪里可能看到大量重复帧、错误帧、重发。不是软件循环发疯,而是 CAN 控制器按协议在重试。

当然,如果错误一直存在,错误计数器会上升,节点状态会变差,最后可能 Bus Off。

CAN错误帧发送时机:检测到错误立即打断当前帧,控制器按协议自动重试

8.8 错误计数器

CAN 节点内部有发送错误计数器 TEC接收错误计数器 REC

发送错误通常让 TEC 增加。

接收错误通常让 REC 增加。

成功发送或接收会让计数器下降。

这两个计数器决定节点错误状态。

你写驱动时,最好能读取并打印它们。现场调试时,“当前是 Error Passive,TEC=128” 比 “CAN 不通” 有用太多。

CAN错误计数器:TEC与REC监控节点状态,决定Error Active/Passive/Bus Off

8.9 节点错误状态

根据错误计数器的值,节点处于三种状态之一:

错误主动(Error Active) :TEC < 128 且 REC < 128。正常状态,发现错误就发主动错误帧(6 个显性位)。

错误被动(Error Passive) :TEC ≥ 128 或 REC ≥ 128。发现错误只能发被动错误帧(6 个隐性位)——不会破坏总线,但别人也听不到。

总线关闭(Bus Off)TEC > 255。节点彻底从总线断开,不发送也不接收。

CAN Bus Off机制:TEC≥256节点被强制隔离,终极保护总线稳定运行

8.10 Bus Off 机制

Bus Off 是 CAN 的终极保护机制,也是工程中最头疼的故障。

如果一个节点频繁出错(TEC 累积到 255 以上),节点直接进入 Bus Off 状态,彻底脱离总线,不再参与任何通信。这么做的目的是防止故障节点持续发送错误报文,拖垮整条总线的所有设备

恢复方法:

  1. 手动重新初始化 CAN 控制器
  2. 或者开启自动恢复——检测到 128 次 11 个连续隐性位后自动恢复

注意Bus Off 跟 REC 无关,只看 TEC。也就是说——节点 Bus Off 是因为自己发错了太多次,不是因为它收到了太多错帧。

九、程序员视角看 CAN 收发流程

前面讲了很多理论,这一章来点实战——从代码的角度看 CAN 通信。

9.1 MCU 中 CAN 外设通常如何初始化

主流 MCU 的 CAN 初始化流程基本一致,分为六大步骤,以 STM32 为例:

第一步,开启 CAN 外设时钟和 GPIO 时钟,初始化收发引脚模式。

第二步,配置 CAN 工作模式,正常模式、回环测试模式、静默模式。调试阶段常用回环模式自测,不用接外部设备,就能验证驱动是否正常。

第三步,配置位时序参数,根据晶振、目标波特率,配置分频、TQ、采样点、SJW,这是初始化最核心的一步。

第四步,配置过滤器和掩码,设置需要接收的报文 ID,过滤无效报文。

第五步,开启接收中断、FIFO 中断,保证报文及时接收、不丢包。

第六步,激活 CAN 外设,启动总线通信。

MCU中CAN外设初始化的六大步骤:以STM32为例

// 1. 使能 CAN 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);

// 2. 初始化 GPIO(CAN_RX 和 CAN_TX 引脚)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
// ... 配置 PA8 为 CAN_RX,PA9 为 CAN_TX

// 3. 配置 CAN 参数
CAN_InitStructure.CAN_TTCM = DISABLE;      // 时间触发模式
CAN_InitStructure.CAN_ABOM = ENABLE;       // 自动 Bus Off 管理
CAN_InitStructure.CAN_AWUM = DISABLE;      // 自动唤醒
CAN_InitStructure.CAN_NART = DISABLE;      // 自动重传
CAN_InitStructure.CAN_RFLM = DISABLE;      // 接收 FIFO 锁定
CAN_InitStructure.CAN_TXFP = DISABLE;      // 优先级由 ID 决定
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; // 正常模式
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;   // SJW = 1 Tq
CAN_InitStructure.CAN_BS1 = CAN_BS1_8tq;   // Phase_Seg1 = 8 Tq
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;   // Phase_Seg2 = 3 Tq
CAN_InitStructure.CAN_Prescaler = 4;       // 分频系数 = 4
CAN_Init(CAN1, &CAN_InitStructure);

// 4. 配置过滤器(只接收特定 ID)
CAN_FilterInitStructure.CAN_FilterNumber = 0;
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInit(&CAN_FilterInitStructure);

最关键的是 BS1、BS2、SJW 和 Prescaler——这四个参数决定了波特率。算错了通信就起不来。

9.2 发送一帧 CAN 报文的完整流程

// 1. 准备要发送的数据
CanTxMsg TxMessage;
TxMessage.ExtId = 0x123;      // 扩展 ID
TxMessage.IDE = CAN_Id_Extended;
TxMessage.RTR = CAN_RTR_Data; // 数据帧
TxMessage.DLC = 8;            // 8 字节数据
TxMessage.Data[0] = 0x01;
// ... 填充 Data[1]~Data[7]

// 2. 请求发送
uint8_t mailbox = CAN_Transmit(CAN1, &TxMessage);

// 3. 等待发送完成
while(CAN_TransmitStatus(CAN1, mailbox) != CAN_TxStatus_Ok);

就这么简单?对,从应用层看就是这么简单。但 CAN 控制器在底层做了很多事:

  • 组帧(加 SOF、CRC、ACK、EOF 等)
  • 位填充
  • 仲裁(如果冲突的话)
  • 错误检测
  • 自动重传(如果出错的话)

9.3 接收一帧 CAN 报文的完整流程

接收一般用中断方式:

void CAN1_RX0_IRQHandler(void)
{
    CanRxMsg RxMessage;
    CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);

    // 处理收到的数据
    if (RxMessage.ExtId == 0x123) {
        // 处理 ID 0x123 的消息
        process_data(RxMessage.Data, RxMessage.DLC);
    }
}

硬件过滤器已经帮你筛掉了不关心的 ID。你收到的都是你想要的。

9.4 驱动开发中需要关注什么?

如果你在写 CAN 驱动(而不是用现成的 HAL 库),需要关注:

  1. 波特率配置:BS1、BS2、SJW、Prescaler 的计算。
  2. 过滤器配置:掩码模式和列表模式的区别。
  3. 中断处理:发送完成中断、接收中断、错误中断。
  4. Bus Off 处理:监测 Bus Off 状态并处理恢复。
  5. 错误计数:读取 TEC/REC 做诊断。

9.5 Linux SocketCAN 简介

在 Linux 上,CAN 被抽象成了网络设备,可以用标准的 Socket API 来操作。

# 配置 CAN 接口
sudo ip link set can0 type can bitrate 500000
sudo ip link set up can0

# 发送数据
cansend can0 123#0102030405060708

# 接收数据
candump can0

用 C 代码操作:

int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
struct sockaddr_can addr;
addr.can_family = AF_CAN;
addr.can_ifindex = if_nametoindex("can0");
bind(s, (struct sockaddr *)&addr, sizeof(addr));

struct can_frame frame;
frame.can_id = 0x123;
frame.can_dlc = 8;
memcpy(frame.data, data, 8);
write(s, &frame, sizeof(frame));

是不是很熟悉?跟写 TCP/UDP 几乎一样。这就是 SocketCAN 的设计哲学——用你 already 熟悉的 API 来操作 CAN。

十、CAN 工程调试 总线不通怎么排查

这一章全是血泪经验

10.1 CAN 不通的常见原因

CAN 不通,别上来就改代码。

根据我的经验,CAN 通信起不来的原因,90% 是这几条

  1. 终端电阻没接或接错——这是最常见的原因。高速 CAN 必须两端各接 120Ω。
  2. 波特率不匹配——所有节点波特率必须一致。
  3. CAN_H 和 CAN_L 接反了——两根线 swapped 了。
  4. 忘记共地——CAN 虽然是差分信号,但收发器需要共地。
  5. 总线短路——CAN_H 对地、CAN_L 对地、CAN_H 对 CAN_L。

10.2 用万用表检查 CAN 总线

万用表是最基础的排查手段:

测终端电阻:断电状态下,在总线的任意位置测 CAN_H 和 CAN_L 之间的电阻。正常应该是 60Ω 左右(两个 120Ω 并联)。

  • 如果测到 120Ω ——只有一个终端电阻(或者有一个没接好)
  • 如果测到 ——一个终端电阻都没有
  • 如果测到 ——CAN_H 和 CAN_L 短路了

测电压:上电后,CAN_H 对地 2.5V 左右,CAN_L 对地 2.5V 左右(隐性状态)。通信时会有波动。

  • 如果 CAN_H 一直是 0V——对地短路
  • 如果 CAN_L 一直是 5V——对电源短路

10.3 用示波器检查 CAN 波形

示波器看 CANH、CANL 和差分波形,是排查 CAN 问题的终极武器

正常波形应该是这样的:

  • 隐性:CAN_H 和 CAN_L 都在 2.5V 左右重合
  • 显性:CAN_H 升到 3.5V,CAN_L 降到 1.5V,差分 2V

如果波形不对称——可能 CAN_H 和 CAN_L 的线路阻抗不匹配,或者某个节点的收发器有问题。

如果有毛刺——可能是干扰太大,检查双绞线和屏蔽。

如果根本没有波形——检查供电、终端电阻、波特率配置。

协议分析仪告诉你“我看懂了什么”,示波器告诉你“线上实际发生了什么”。

10.4 用逻辑分析仪或 CAN 工具抓包

逻辑分析仪可以看到协议层面的问题——帧 ID 对不对、数据对不对、有没有错误帧、总线负载率、是否有重发、时间戳间隔等等。

比如你认为某报文 10ms 发送一次,但抓包发现它 12ms、15ms、偶尔 30ms,这就要查调度、发送队列、仲裁延迟、任务优先级。

如果你认为对方没回,但抓包发现对方回了,只是你的过滤器没收,那就是软件接收路径问题。

如果抓包工具也收不到,但示波器有波形,那可能工具波特率不对,或者物理层信号质量差。

调试 CAN 要多维度看。只看软件日志,会瞎。

专业的 CAN 分析工具(比如 PCAN、CANoe、ZLG 的 CAN 分析仪)可以直接解析 CAN 帧,比逻辑分析仪好用得多。

10.5 典型故障案例

我讲几个项目里很常见的。

第一个是单节点 ACK 错误。

开发板自己发 CAN,没有接任何其他节点。发送函数一直失败。原因是没有节点 ACK。接上 USB-CAN 分析仪,立刻好了。

第二个是终端电阻缺失。

短线低速能跑,长线高速随机掉帧。示波器看到反射很明显。补齐两端 120Ω 后稳定。

第三个是波特率看似一致但时钟源不准。

一个节点内部 RC 时钟误差大,低温下更飘。常温能通信,环境温度变化后 CRC 错误变多。换外部晶振后解决。

第四个是过滤器配置错。

驱动只打开了标准帧过滤,对方发的是扩展帧。抓包工具能看到,应用层死活收不到。软件同事差点把协议栈重写一遍。

第五个是 Bus Off 后没有恢复策略。

现场干扰导致节点 Bus Off,之后它再也不上线。复位设备恢复。后来驱动增加 Bus Off 监控和安全恢复逻辑,问题才算有交代。

所以啊,CAN 调试不是玄学。它有套路。只是每个坑都长得像另一个坑。

十一、经典 CAN 与 CAN FD 的区别

经典CAN与CAN FD的区别:数据长度、速率、CRC等对比

11.1 为什么会出现 CAN FD

经典 CAN 很稳,但它也有俩个限制。

  1. 速度慢:最高 1Mbps。
  2. 数据少:一帧最多 8 字节。

现在汽车里的 ECU 越来越多,传感器数据越来越大(比如高清摄像头、雷达),8 字节根本不够用。如果要把大数据拆成多个 CAN 帧发,协议开销太大,总线负载扛不住。

CAN FD 出现就是为了解决这些问题。FD 是 Flexible Data-rate,灵活数据速率。

11.2 CAN FD 的主要变化

变化一:数据段速度翻倍

CAN FD 的仲裁段保持经典 CAN 的速度(最高 1Mbps),但数据段可以切换到更高速度(最高 8Mbps)。

变化二:数据长度扩展

从最多 8 字节扩展到最多 64 字节

DLC 做了扩展——DLC 值 9~15 映射到 12~64 字节。

变化三:CRC 加强

经典 CAN 是 15 位 CRC。CAN FD 用 17 位或 21 位 CRC。因为数据变长了,需要更强的校验。

变化四:取消了遥控帧

11.3 经典 CAN 和 CAN FD 是否兼容

这是一个很复杂的问题:

CAN FD 节点可以接收经典 CAN 帧——兼容。

经典 CAN 节点无法识别 CAN FD 帧——不兼容。经典 CAN 节点收到 CAN FD 帧(FDF 位为隐性)会认为是非法帧,发错误帧。

所以在混合网络里,经典 CAN 节点和 CAN FD 节点可以共存,但CAN FD 节点只能在仲裁段用经典 CAN 速度发帧,如果切换到高速数据段,经典 CAN 节点会报错。

实际工程中,大多数混合网络靠网桥/网关来隔离——经典 CAN 和 CAN FD 分段,中间用网关转发。

十二、用一张图串起 CAN 总线

我们把整篇文章串起来:

CAN 总线
  │
  ├── 物理层
  │     ├── CANH / CANL 差分信号
  │     ├── 显性 0 / 隐性 1
  │     ├── 线与逻辑
  │     ├── 高速 CAN / 低速容错 CAN
  │     └── 终端电阻与总线拓扑
  │
  ├── 数据链路层
  │     ├── 标准帧 11 位 ID
  │     ├── 扩展帧 29 位 ID
  │     ├── 数据帧 / 遥控帧 / 错误帧
  │     ├── CRC / ACK / EOF
  │     └── 位填充与帧间空间
  │
  ├── 核心机制
  │     ├── 多主机
  │     ├── 广播通信
  │     ├── 非破坏性逐位仲裁
  │     ├── ID 越小优先级越高
  │     ├── 硬件过滤
  │     └── 错误检测与错误隔离
  │
  ├── 位时序
  │     ├── tq
  │     ├── Sync_Seg
  │     ├── Prop_Seg
  │     ├── Phase_Seg1 / Phase_Seg2
  │     ├── 采样点
  │     └── SJW 与再同步
  │
  ├── 程序员关注点
  │     ├── 初始化 GPIO / 时钟 / 波特率
  │     ├── 配置过滤器
  │     ├── 发送邮箱与接收 FIFO
  │     ├── 中断处理
  │     ├── 错误计数器
  │     ├── Bus Off 恢复
  │     └── SocketCAN 工具链
  │
  └── 工程调试
        ├── 波特率
        ├── 终端电阻
        ├── CANH/CANL
        ├── 收发器模式
        ├── 示波器波形
        ├── 抓包分析
        └── 总线负载

CAN总线核心知识地图:从物理层到开发调试的全景图

这篇文章源自云栈社区的技术文档归档,希望能帮你在嵌入式的道路上少踩几个坑。真正搞懂 CAN,你会发现它对可靠性的执着,确实有点东西。




上一篇:Goroutine 暴增导致 OOM?Go 协程池设计与实战指南
下一篇:SpaceX股价续跌盘前跌破发行价,2万亿美元市值告急
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-6-25 05:21 , Processed in 0.751232 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表