笔记 / 嵌入式CAN总线通信

引言

UART 只能点对点,I2C 和 SPI 只能由一个主机带一圈从机(见 嵌入式通信协议UART、I2C、SPI对比详解)。要在一台设备里挂几十个节点、允许任意节点主动发送、还得在电机旁边稳定工作,这三种都做不到。CAN 用一对差分线同时解决了三件事:多主竞争有仲裁,节点出错有隔离,短帧自带 CRC。汽车上几十个 ECU 挂在同一对线上,工业和电机控制也大量采用。

下面按四层展开:电气层(差分与线与)、仲裁层(谁先发送)、报文层(帧的字段)、时序层(采样点位置),最后是 bxCAN 外设的配置。

一、拓扑与电气

CAN 网络拓扑(RM0008 Figure 222):多个节点(应用 MCU + CAN 控制器 + CAN 收发器)共用一对 CAN High / CAN Low 差分总线

每个节点由三部分组成:MCU 内的 CAN 控制器(bxCAN,负责协议)、收发器(TJA1050、SN65HVD230 等,负责电平转换)、以及连接 CAN_H/CAN_L 的两根总线。控制器输出 TX/RX 逻辑电平,收发器把它转换成总线上的差分电压。

CAN 没有时钟线,也没有主从,只靠两个电平约定工作:

总线状态CAN_HCAN_L差分电压逻辑值
显性(dominant)≈ 3.5 V≈ 1.5 V≈ 2 V0
隐性(recessive)≈ 2.5 V≈ 2.5 V≈ 0 V1

关键在于显性电平压过隐性电平:只要有一个节点输出 0,总线就是 0。可以把总线理解为一根被所有节点并联下拉的线,任何一个节点把它拉到低电平,整根线就是低电平。多个节点同时发送时,总线电平等于所有输出的线与。这个物理特性是下一节仲裁的基础。

差分带来的好处是共模抑制:两根线绞在一起,干扰同时作用于两根线,接收端取差值后共模噪声被抵消。这也是 CAN 线要求绞接、并且能延伸到几十米到一公里的原因(1 Mbps 约 40 m,降到 40 kbps 约 1 km,硬约束是信号往返延迟)。

信号沿电缆传播到末端时,如果负载阻抗与电缆的特性阻抗不匹配,一部分能量会反射回来,叠加在后续的位电平上。手册的拓扑图没有画终端电阻,实物中必须有:总线两端各接一只 120 Ω 电阻,跨接在 CAN_H 与 CAN_L 之间,让末端呈现电缆期望的阻抗,从而匹配特性阻抗、吸收反射。两端都接好后,万用表测得总线电阻约 60 Ω(两只 120 Ω 并联),这是上电调试前的例行检查。漏接终端电阻会出现信号振铃和偶发 CRC 错误;中间多接一只则负载加重、显性电平变弱,整条总线都不稳定。

二、仲裁:ID 小者优先

多主意味着可能发生冲突:两个节点同时发送怎么办。CAN 的规则只有一条:节点在发送隐性位(1)的同时回读总线,如果读到显性位(0),说明有其它节点在抢占,于是立即退出发送并转为接收。

由于总线是线与,ID 数值小的帧必然在某个位上先发出 0,把对方压成”读到 0”。因此:

  • ID 越小优先级越高(0x001 胜过 0x100)
  • 仲裁失败的节点不会损坏数据,也不重发垃圾帧;它自动转为接收,等总线空闲后再次尝试,获胜的那一帧无损通过。这称为非破坏性仲裁
  • 工程上把电机电流环报文的 ID 排在诊断报文之前,实时性就由仲裁结果决定

代价是优先级设计没有回旋余地:高优先级节点持续发送会饿死低优先级节点。CAN 没有公平调度机制,优先级分配本身就是系统设计的一部分。

三、报文格式

CAN 帧格式总览(RM0008 Figure 235):数据帧(标准/扩展 ID)、遥控帧、错误帧、过载帧及帧间空间的位场划分,右侧 Notes 栏解释 SOF/ID/RTR/IDE/DLC/CRC/ACK/EOF 各字段含义

以标准数据帧为主干,逐字段说明:

字段位数作用
SOF1显性位,硬同步,帧起始
ID11仲裁 + 报文身份(标识”什么报文”,不是”发给谁”)
RTR10=数据帧,1=遥控帧
IDE10=标准格式,1=后面还有 18 位 ID
r0/r11保留,显性
DLC4数据长度 0–8 字节
Data0–64有效载荷
CRC15校验序列 + 1 位界定符
ACK2发送方发隐性,任何一个正确接收的节点把它改成显性——没有 ACK 位被拉低,发送方就知道无人接收
EOF7全隐性,帧结束

四个变体中,有三点需要区分:

  • 扩展帧:29 位 ID,仲裁段拆成 11 位基 ID + SRR + IDE + 18 位扩展 ID,前段按标准帧规则仲裁——同 ID 时标准帧优先
  • 遥控帧:RTR=1、无数据。用于向网络请求”谁有 0x201 的数据,发一份”
  • 错误帧:任何节点检测到错误就发 6 个连续显性位——这故意违反位填充规则,全网立刻知道这帧作废

位填充(bit stuffing)贯穿 SOF 到 CRC:连续 5 个同电平位后自动插入一个反相位。NRZ 编码在电平不变时没有任何边沿,接收端无法校正自己的采样时钟——UART 靠每个字符的起始位重新对齐,位填充起的是同样的作用。它同时让错误帧的 6 个连续显性位成为明确的违规信号。计算帧长时不能忽略填充位,最高速率下它会占用约 20% 的标称带宽。

CAN 是广播总线,帧里没有源地址和目标地址,ID 标识的是报文内容而不是接收方,是否接收由各节点的过滤器决定,见第五节。

四、位时序

CAN 位时序(RM0008 Figure 234):一个标称位时间分为同步段 SYNC_SEG(1 tq)、位段 1 BS1、位段 2 BS2,采样点位于 BS1 与 BS2 交界,发送点位于位末尾

UART 只需要收发双方约定相同的波特率,CAN 的要求更严:仲裁要求全网节点在同一个位时间内的同一时刻采样,时钟误差会直接影响仲裁和 ACK 的正确性。每个位被切成三段:

段长度作用
SYNC_SEG固定 1 tq同步缓冲,预期跳变落在这里
BS11–16 tq跳变后传播 + 相位缓存,采样点在 BS1 结束处
BS21–8 tq采样后的缓冲,也给重同步留调整空间

时间量子 tq = (BRP + 1) × tPCLK,于是波特率 = 1 / [(1 + BS1 + BS2) × tq]。采样点位置 = (1 + BS1) / (1 + BS1 + BS2),工程惯例放在 75%–87.5%,给信号传播和晶振误差留余量。

以 500 kbps 为例(PCLK1 = 36 MHz,即 时钟树与RCC配置详解 里的 72/36/72 配置):

  • 位时间 = 2 µs;取 tq = 250 ns → BRP+1 = 9,每位 8 tq
  • 切分 1 + 5 + 2:采样点 = 6/8 = 75%
  • 寄存器:Prescaler=9, TS1=4, TS2=1(段长减一写进寄存器)

SJW(重同步宽度,1–4 tq)是最后一个参数:接收方发现跳变没落在 SYNC_SEG,就把 BS1 拉长或 BS2 缩短最多 SJW 个 tq 追回去。SJW 不能超过 BS1/BS2 的最小值,通常配 1 个 tq。

节点间晶振误差的预算大约 ±1.4%(容忍度取决于采样点位置),因此 CAN 节点应当使用晶体。HSI 的 ±1% 精度在温度变化后不足以覆盖这个预算。

五、bxCAN:邮箱、FIFO 与过滤器

F1 的 bxCAN 把协议处理硬件化,CPU 需要操作的只有三部分:

  • 发送:3 个发送邮箱,装好帧后写请求寄存器即可,硬件负责排队、仲裁、重发
  • 接收:2 条 FIFO、各 3 帧深,收到的帧先落 FIFO;应用层按需读取
  • 过滤:28 组过滤器(双 CAN 时一分为二),每组可选 32 位掩码/列表或 16 位掩码/列表模式

过滤器是 CAN 驱动的关键:总线上同时存在大量 ID,若全部触发中断,CPU 无法承受。掩码模式回答”这一类我都要”(ID + mask,mask 的 0 位是无关位);列表模式回答”精确这几个”(每组精确匹配 2 个 32 位 ID)。过滤器命中的帧进入 FIFO0 或 FIFO1,映射关系可以自由安排,例如电机报文进 FIFO0 走较高优先级中断、诊断报文进 FIFO1 慢慢取。

错误管理同样由硬件完成。每个节点维护发送/接收错误计数器(TEC/REC):

状态条件行为
Error Active计数 < 128正常收发,出错发主动错误帧
Error PassiveTEC 或 REC > 127受限:出错只发被动错误标志,且发完要等 8 个隐性位
Bus OffTEC > 255物理上离线,控制器不再收发

Bus-off 并不意味着节点损坏,而是被总线隔离了:它干扰总线的次数超过阈值。恢复条件是检测到 128 次连续 11 个隐性位(也可以由软件发出退出恢复模式的指令来加速)。产品中必须监控 bus-off 状态:现场出现接线短路或终端电阻缺失时,设备静默通常就是这个原因。

六、HAL 配置示例

500 kbps、FIFO0 收一帧中断通知:

CAN_HandleTypeDef hcan;

void CAN_Config(void)
{
    __HAL_RCC_CAN1_CLK_ENABLE();               // 先开时钟

    hcan.Instance = CAN1;
    hcan.Init.Prescaler           = 9;         // tq = 250 ns @ PCLK1 36 MHz
    hcan.Init.Mode                = CAN_MODE_NORMAL;
    hcan.Init.SyncJumpWidth       = CAN_SJW_1TQ;
    hcan.Init.TimeSeg1            = CAN_BS1_5TQ;   // 1+5+2=8tq → 500 kbps
    hcan.Init.TimeSeg2            = CAN_BS2_2TQ;   // 采样点 75%
    hcan.Init.TimeTriggeredMode   = DISABLE;
    hcan.Init.AutoBusOff          = ENABLE;    // bus-off 后硬件自动恢复
    hcan.Init.AutoWakeUp          = ENABLE;
    hcan.Init.AutoRetransmission  = ENABLE;    // 仲裁输/出错自动重发
    hcan.Init.ReceiveFifoLocked   = DISABLE;   // FIFO 满时新帧覆盖旧帧
    hcan.Init.TransmitFifoPriority= DISABLE;   // 邮箱按 ID 优先级而不是请求顺序
    HAL_CAN_Init(&hcan);

    // 过滤器 0:32 位掩码,只收 ID=0x201 一类(掩码全 1 = 精确匹配)
    CAN_FilterTypeDef f = {0};
    f.FilterBank           = 0;
    f.FilterMode           = CAN_FILTERMODE_IDMASK;
    f.FilterScale          = CAN_FILTERSCALE_32BIT;
    f.FilterIdHigh         = 0x201 << 5;       // 32 位过滤寄存器:STD_ID 在 [31:21]
    f.FilterIdLow          = 0x0000;
    f.FilterMaskIdHigh     = 0x7FF << 5;       // 低 11 位全比对
    f.FilterMaskIdLow      = 0x0000;
    f.FilterFIFOAssignment = CAN_RX_FIFO0;
    f.FilterActivation     = ENABLE;
    HAL_CAN_ConfigFilter(&hcan, &f);

    HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
    HAL_CAN_Start(&hcan);
}

// FIFO0 有帧待取 → 回调(在 ISR 上下文,快取快走)
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan_)
{
    CAN_RxHeaderTypeDef hdr;
    uint8_t data[8];
    HAL_CAN_GetRxMessage(hcan_, CAN_RX_FIFO0, &hdr, data);
    // ISR 里不做业务:拷走数据,置信号量/队列,业务在任务里做
    // 中断到任务的交接方式见 [[freertos-semaphore-callback]]
}

两个工程习惯:AutoRetransmission 保持 ENABLE,失败自动重发是协议承诺的可靠性;发送用 HAL_CAN_AddTxMessage 前查 HAL_CAN_GetTxMailboxesFreeLevel,三只邮箱都满时等中断通知,不要轮询死等。

F1 的 CAN 没有 DMA 请求线(DMA1 的映射表里没有 CAN),所以数据搬运只能靠 FIFO 深度和中断的及时性。串口、SPI 这类有 DMA 通道的外设另见 DMA直接存储器访问详解。

七、常见问题与注意事项

问题原因解决
完全无 ACK、发送一直失败总线单节点 / 终端电阻缺失 / 收发器没焊万用表量总线 60 Ω,示波器看 CAN_H/L 摆幅
偶发 CRC 错误、误帧位时序参数错、节点间晶振偏差大统一 tq 划分,采样点 75–87.5%
只在高负载时丢帧FIFO 满溢出中断里快取;过滤器收窄;看 HAL_CAN_GetRxFifoFillLevel
通信偶发中断后自行恢复节点进入 bus-off 后自动恢复监控 ESR,查接线/终端/干扰源
点对点不通,更换节点后正常一端误配静默/环回模式检查 CAN_MODE_NORMAL,别用上次的 LOOPBACK
实验室通信正常,量产失败低负载时误差预算没用完按 87.5% 采样点 + 最坏晶振误差重算预算
需要发送超过 8 字节的数据经典 CAN 上限 8 字节拆帧 + 序号重组,或改用 CAN FD(单帧到 64 字节)

总结

CAN 的四层各负责一件事:差分电气层保证信号能到达,线与仲裁决定谁先发送,短帧加 CRC 与 ACK 保证数据正确,位时序保证全网同步采样。bxCAN 把这四层都做成了硬件,软件需要处理的只有三处——位时序参数计算、过滤器划分、中断到任务的数据交接。与 UART 的点对点、I2C/SPI 的主从编址相比,CAN 的总线模型没有主节点、没有地址,只有优先级和报文内容。