STM32 bxCAN 外设
解剖 STM32F4 的 bxCAN(Basic Extended CAN)控制器:发送邮箱、接收 FIFO、过滤器组的硬件结构,工作模式切换,以及位时序寄存器的精确配置。
解剖 STM32F4 的 bxCAN(Basic Extended CAN)控制器:发送邮箱、接收 FIFO、过滤器组的硬件结构,工作模式切换,以及位时序寄存器的精确配置。
RCC_APB1ENR 中的 CAN1EN 时钟。CAN_RF0R/CAN_RF1R 释放邮箱。Instance 指向 CAN1/CAN2 寄存器基址,Init 子结构包含预分频、模式、时序段、自动重传/唤醒/离线恢复等配置。配套结构体有 CAN_FilterTypeDef(过滤器)、CAN_TxHeaderTypeDef(发送帧头)、CAN_RxHeaderTypeDef(接收帧头)。理解 bxCAN 的数据流向,是正确配置收发的前提。下图展示了控制内核、发送邮箱、接收 FIFO 与过滤器组之间的连接:
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 | 只收不发,只送隐性位 | 总线监听/嗅探 |
CAN_MCR.TTCM 位使能后,bxCAN 会在每帧的 SOF 时刻捕获一个 16 位时间戳,写入报文的最后两个数据字节(DLC 需为 8)。这用于时间触发的 CAN(TTCAN)网络,让所有节点基于全局时基调度报文。普通应用一般关闭此功能。
波特率完全由 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%。
| 位[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) | |||||
下面是一段可直接编译的 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); } }
过滤器决定哪些报文能进入 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); }
bxCAN 的过滤器寄存器把 32 位映射按 CAN 帧的仲裁场排布:[31:21] 是标准 ID(STID[10:0]),[20:3] 是扩展 ID,bit2 是 IDE,bit1 是 RTR。所以在 16 位标度下,一个 11 位标准 ID 要左移 5 位才能对齐到高字节的 STID 位置。忘记移位是过滤器“配了却收不到”的高频原因,务必对照参考手册的“过滤器位映射”图。
调用 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 位对齐——这是初学最易踩的坑。