Chapter 06 · CAN 总线 / 汽车电子

STM32 bxCAN 外设

解剖 STM32F4 的 bxCAN(Basic Extended CAN)控制器:发送邮箱、接收 FIFO、过滤器组的硬件结构,工作模式切换,以及位时序寄存器的精确配置。

课程进度38%

关键概念

bxCAN (Basic Extended CAN)
STM32F1/F4 系列内置的 CAN 控制器外设,符合 CAN 2.0A/2.0B 规范,支持标准帧(11 位 ID)和扩展帧(29 位 ID),最高波特率 1 Mbps。它不是 CAN FD 控制器——那是后续 STM32G4/H7 上的 FDCAN。bxCAN 挂在 APB1 总线上,使用前必须开启 RCC_APB1ENR 中的 CAN1EN 时钟。
发送邮箱 (Tx Mailbox ×3)
bxCAN 有 3 个发送邮箱,构成一个发送 FIFO。软件把待发报文(ID + DLC + 数据)填入空闲邮箱并置请求位,硬件按优先级(默认按 ID 大小,ID 越小优先级越高)自动仲裁发送。3 个邮箱允许连续排队 3 帧,避免频繁等待。
接收 FIFO (Rx FIFO0 / FIFO1)
bxCAN 有两个接收 FIFO,每个深度为 3 帧。通过过滤器可以把不同 ID 的报文分流到 FIFO0 或 FIFO1,实现硬件级分类。FIFO 满后再来报文会触发溢出(Overrun),可选择丢弃最新帧或覆盖最旧帧。读取时通过 CAN_RF0R/CAN_RF1R 释放邮箱。
过滤器组 (Filter Bank ×28)
bxCAN 在单 CAN 型号上提供 14 组、双 CAN 型号上共 28 组硬件过滤器。每组可配置为“标识符列表模式”(精确匹配特定 ID)或“掩码模式”(匹配一段 ID 范围),位宽可选 16 位或 32 位。硬件过滤在报文接收阶段就丢弃不关心的帧,极大减轻 CPU 负担。
工作模式:初始化 / 正常 / 睡眠
bxCAN 有三种操作模式。初始化模式(INRQ=1):配置位时序和过滤器时必须进入,此时不参与总线活动。正常模式:清 INRQ 并等待硬件同步到总线(连续检测到 11 个隐性位)后即可收发。睡眠模式(SLEEP=1):低功耗,可由总线活动或软件唤醒。此外还有回环、静默等测试模式。
位时序 (Bit Timing) 与采样点
CAN 每个位被切分为若干时间份额(Time Quanta, Tq):同步段(SYNC_SEG,固定 1 Tq)+ 传播/相位段 1(BS1)+ 相位段 2(BS2)。采样点位于 BS1 末尾,推荐落在整位的 75%~87.5% 处。波特率 = APB1 时钟 / (预分频 × (1 + BS1 + BS2))。所有节点采样点必须一致,否则无法正确采样总线电平。
SJW (Synchronization Jump Width)
再同步跳转宽度。由于各节点晶振存在微小误差,接收方需在隐性→显性跳变时微调采样时刻,SJW 规定了单次可调整的最大 Tq 数(1~4)。SJW 太小抗抖动能力弱,太大易误采样,一般取 1 Tq 即可满足绝大多数场景。
CAN_HandleTypeDef (HAL 句柄)
HAL 库描述一个 bxCAN 实例的结构体,成员 Instance 指向 CAN1/CAN2 寄存器基址,Init 子结构包含预分频、模式、时序段、自动重传/唤醒/离线恢复等配置。配套结构体有 CAN_FilterTypeDef(过滤器)、CAN_TxHeaderTypeDef(发送帧头)、CAN_RxHeaderTypeDef(接收帧头)。

bxCAN 内部框图

理解 bxCAN 的数据流向,是正确配置收发的前提。下图展示了控制内核、发送邮箱、接收 FIFO 与过滤器组之间的连接:

STM32F4 bxCAN 内部结构 ═══════════════════════════════════════════════════════════════ ┌───────────────────────────────────────────┐ 软件写入 │ CAN 控制内核 (Control/Status/Config)│ ────────▶│ CAN_MCR / CAN_MSR / CAN_BTR 位时序 │ └───────┬───────────────────────┬─────────────┘ │ │ 发送路径接收路径 ═════════════ │ ══════════════ ┌────────────────┐ │ ┌──────────────────┐ │ Tx Mailbox 0 │ │ │ 28 组过滤器组 │ │ Tx Mailbox 1 │──┐ │ │ 列表/掩码 模式 │ │ Tx Mailbox 2 │ │ 优先级 │ │ 16/32 位宽 │ └────────────────┘ │ 仲裁 │ └────────┬─────────┘ │ ▼ │ │ 命中分流 │ ┌─────────┐ │ ┌─────────┴────────┐ └────▶│ 发送调度 │ │ ▼ ▼ └────┬────┘ │ ┌─────────┐ ┌─────────┐ │ │ │Rx FIFO0 │ │Rx FIFO1 │ │ │ │ 深度 3 │ │ 深度 3 │ ▼ │ └────┬────┘ └────┬────┘ ┌─────────────────▼───┐ │ │ │ CAN_TX / CAN_RX 引脚 │◀───┴───────────────┘ │ (需外接 CAN 收发器)│ └──────────────────────┘ │ CAN_H / CAN_L 差分总线
bxCAN 只是控制器,不是收发器

STM32 的 CAN_TX / CAN_RX 是 逻辑电平(3.3V TTL)信号,无法直接驱动 CAN_H / CAN_L 差分总线。必须外接一颗 CAN 收发器芯片(如 TJA1050、SN65HVD230、MCP2551),把 TTL 电平转换成差分电平。收发器的 CANH/CANL 才是真正连到双绞线总线上的物理层。

三种工作模式的状态转换

模式进入方式总线行为典型用途
初始化 (Init)CAN_MCR.INRQ=1不收不发,脱离总线配置时序/过滤器
正常 (Normal)清 INRQ 并等同步正常收发,参与仲裁运行时收发报文
睡眠 (Sleep)CAN_MCR.SLEEP=1时钟停止,低功耗省电待机
回环 (Loopback)BTR.LBKM=1自发自收,不上总线无外设自测
静默 (Silent)BTR.SILM=1只收不发,只送隐性位总线监听/嗅探
时间触发通信模式 (TTCM)

CAN_MCR.TTCM 位使能后,bxCAN 会在每帧的 SOF 时刻捕获一个 16 位时间戳,写入报文的最后两个数据字节(DLC 需为 8)。这用于时间触发的 CAN(TTCAN)网络,让所有节点基于全局时基调度报文。普通应用一般关闭此功能。

位时序寄存器 CAN_BTR

波特率完全由 CAN_BTR 决定。假设 APB1 时钟为 42 MHz,要得到 500 kbps,可取预分频 6、BS1=13、BS2=2,则每位 = 1+13+2 = 16 Tq,波特率 = 42M / (6×16) = 437.5k……为得到精确 500k 常用 APB1=42M、Prescaler=6、BS1=11、BS2=2(共 14 Tq,42M/(6×14)=500k),采样点 (1+11)/14 ≈ 85.7%。

CAN_BTR 寄存器(偏移 0x1C)— 位时序配置

位[31]位[30]位[25:24]位[22:20]位[19:16]位[9:0]
SILM LBKM SJW TS2 (BS2) TS1 (BS1) BRP 预分频
SILM=静默 · LBKM=回环 · SJW=再同步跳转(值+1 Tq) · TS2/TS1=相位段(值+1 Tq) · BRP=预分频(值+1)
CAN 一个位的时间构成(采样点 ≈ 85%) │◀───────────── 1 个位时间 = 14 Tq ─────────────▶│ ┌────┬───────────────────────────────┬───────────┐ │SYNCBS1 (TS1)BS2 │ │ 1 │ 11 Tq │ 2 Tq │ └────┴───────────────────────────────┴───────────┘ ▲ ▲ 重同步起点 采样点 (Sample Point) 在此读取总线电平 波特率 = fAPB1 / (BRP × (1 + BS1 + BS2)) = 42MHz / (6 × 14) = 500 kbps

bxCAN 初始化:MX_CAN_Init

下面是一段可直接编译的 HAL 初始化代码,配置 CAN1 为 500 kbps 正常模式,并配置一组掩码过滤器(此处先接收所有帧,后续章节再收窄)。引脚使用 PA11(CAN_RX) / PA12(CAN_TX),复用功能 AF9

/* can.c — STM32F4 bxCAN 初始化,500kbps */
#include "stm32f4xx_hal.h"

CAN_HandleTypeDef hcan1;

void MX_CAN1_Init(void) {
  hcan1.Instance = CAN1;              /* 指向 CAN1 寄存器基址 */
  hcan1.Init.Prescaler = 6;         /* BRP:42MHz / 6 = 7MHz */
  hcan1.Init.Mode = CAN_MODE_NORMAL; /* 正常模式 */
  hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;  /* SJW = 1 Tq */
  hcan1.Init.TimeSeg1 = CAN_BS1_11TQ;     /* BS1 = 11 Tq */
  hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;      /* BS2 = 2 Tq → 7M/14 = 500k */
  hcan1.Init.TimeTriggeredMode    = DISABLE;
  hcan1.Init.AutoBusOff           = ENABLE;  /* 离线自动恢复 */
  hcan1.Init.AutoWakeUp           = DISABLE;
  hcan1.Init.AutoRetransmission   = ENABLE;  /* 出错自动重传 */
  hcan1.Init.ReceiveFifoLocked    = DISABLE; /* FIFO 满时覆盖旧帧 */
  hcan1.Init.TransmitFifoPriority = DISABLE; /* 按 ID 优先级发送 */

  if (HAL_CAN_Init(&hcan1) != HAL_OK) {
    Error_Handler();
  }
}

/* HAL 底层回调:开时钟 + 配置引脚,由 HAL_CAN_Init 自动调用 */
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan) {
  GPIO_InitTypeDef g = {0};
  if (hcan->Instance == CAN1) {
    __HAL_RCC_CAN1_CLK_ENABLE();      /* ① 开 CAN1 时钟(APB1)*/
    __HAL_RCC_GPIOA_CLK_ENABLE();     /* ② 开 GPIOA 时钟 */

    /* ③ PA11=CAN_RX, PA12=CAN_TX,复用推挽 AF9 */
    g.Pin = GPIO_PIN_11 | GPIO_PIN_12;
    g.Mode = GPIO_MODE_AF_PP;
    g.Pull = GPIO_PULLUP;
    g.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    g.Alternate = GPIO_AF9_CAN1;    /* 关键:CAN1 的复用号是 AF9 */
    HAL_GPIO_Init(GPIOA, &g);
  }
}

过滤器配置:列表模式 vs 掩码模式

过滤器决定哪些报文能进入 FIFO。掩码模式:ID 与掩码按位比较,掩码为 1 的位必须匹配、为 0 的位任意,适合“接收一段范围的 ID”。列表模式:把两个 ID 直接作为匹配值,只有完全相等才通过,适合“只收几个特定 ID”。

/* 32 位掩码模式:接收所有帧(掩码全 0 = 不比较任何位)*/
void CAN_Filter_AcceptAll(void) {
  CAN_FilterTypeDef f = {0};
  f.FilterBank = 0;                          /* 使用第 0 组过滤器 */
  f.FilterMode = CAN_FILTERMODE_IDMASK;      /* 掩码模式 */
  f.FilterScale = CAN_FILTERSCALE_32BIT;     /* 32 位 */
  f.FilterIdHigh     = 0x0000;
  f.FilterIdLow      = 0x0000;
  f.FilterMaskIdHigh = 0x0000;              /* 掩码 0 = 全部放行 */
  f.FilterMaskIdLow  = 0x0000;
  f.FilterFIFOAssignment = CAN_RX_FIFO0;     /* 命中帧进 FIFO0 */
  f.FilterActivation = ENABLE;
  f.SlaveStartFilterBank = 14;             /* 双 CAN 时 CAN2 起始组 */
  HAL_CAN_ConfigFilter(&hcan1, &f);
}

/* 列表模式:只精确接收 ID=0x123 与 0x456 两个标准帧 */
void CAN_Filter_TwoIDs(void) {
  CAN_FilterTypeDef f = {0};
  f.FilterBank = 1;
  f.FilterMode = CAN_FILTERMODE_IDLIST;      /* 列表模式 */
  f.FilterScale = CAN_FILTERSCALE_16BIT;     /* 16 位可放 4 个标准 ID */
  /* 标准 ID 左移 5 位对齐到 STID[10:0](因低 5 位是 IDE/RTR)*/
  f.FilterIdHigh     = (0x123 << 5);
  f.FilterIdLow      = (0x456 << 5);
  f.FilterMaskIdHigh = (0x123 << 5);       /* 16 位列表这两个字段也当作 ID */
  f.FilterMaskIdLow  = (0x456 << 5);
  f.FilterFIFOAssignment = CAN_RX_FIFO0;
  f.FilterActivation = ENABLE;
  HAL_CAN_ConfigFilter(&hcan1, &f);
}
标准 ID 为什么要左移 5 位?

bxCAN 的过滤器寄存器把 32 位映射按 CAN 帧的仲裁场排布:[31:21] 是标准 ID(STID[10:0]),[20:3] 是扩展 ID,bit2 是 IDE,bit1 是 RTR。所以在 16 位标度下,一个 11 位标准 ID 要左移 5 位才能对齐到高字节的 STID 位置。忘记移位是过滤器“配了却收不到”的高频原因,务必对照参考手册的“过滤器位映射”图。

掩码模式适合

  • 接收一段连续 ID 范围
  • 只关心 ID 高几位(如某个模块前缀)
  • 过滤器组数量有限时节省资源
  • 掩码=1 要匹配,掩码=0 任意

列表模式适合

  • 只关心零散的几个精确 ID
  • 不希望误收相邻 ID
  • 一组 32 位可存 2 个 ID,16 位可存 4 个
  • 匹配即“完全相等”,无范围概念
进不了正常模式?

调用 HAL_CAN_Init 后若 CAN 长时间卡在初始化模式,多半是 物理层没接好:CAN_H/CAN_L 未接收发器、总线两端缺少 120Ω 终端电阻、或收发器未上电。bxCAN 退出初始化模式需要在总线上检测到连续 11 个隐性位,若总线一直是显性(如短路),硬件永远无法同步,HAL_CAN_Start 会超时失败。

本章小结

bxCAN 是 STM32F4 内置的 CAN 2.0A/B 控制器,挂在 APB1 总线,核心资源为 3 个发送邮箱、2 个接收 FIFO(各深 3)、14/28 组硬件过滤器。它只输出 TTL 电平,必须外接 CAN 收发器芯片(TJA1050 等)才能驱动差分总线。工作模式分初始化(配置时序/过滤器)、正常(收发)、睡眠(低功耗),另有回环、静默测试模式。波特率由 CAN_BTR 决定:波特率 = APB1 / (BRP × (1+BS1+BS2)),采样点应落在 75%~87.5%,典型 42MHz→500kbps 用 BRP=6、BS1=11、BS2=2。HAL 用 CAN_HandleTypeDef 描述实例,HAL_CAN_MspInit 里开时钟并把 PA11/PA12 配为 GPIO_AF9_CAN1。过滤器有掩码模式(匹配 ID 范围)和列表模式(精确匹配),标准 ID 在过滤寄存器中需左移 5 位对齐——这是初学最易踩的坑。

⚒️ 配套开发者工具箱 · JSON / 正则 / 时间戳 / Cron 等 17 款免费在线工具