STM32 CAN 总线通信:收发配置与过滤器设置实战
👁 5 阅读 · 2026-08-14 · STM32 CAN
CAN 总线凭借高可靠性和实时性,成为汽车与工业控制的主流通信方式。STM32 内置的 bxCAN 模块支持标准帧与扩展帧,并提供强大的硬件过滤器。本文从硬件连接、波特率计算到报文收发与过滤器配置,结合完整代码示例,助你快速掌握 STM32 CAN 通信的核心要点,避开常见坑点。
## 一、CAN 总线与 STM32 bxCAN 简介
CAN(Controller Area Network)是一种多主串行总线,采用差分信号传输,抗干扰能力强。STM32 的 bxCAN 控制器支持 CAN 协议 2.0A 和 2.0B,具备 3 个发送邮箱、2 个接收 FIFO(每个深度 3 帧),以及 14 个(部分系列 28 个)硬件过滤器。
过滤器的作用是**硬件级筛选**接收到的报文,只有通过过滤的数据才会存入 FIFO,极大减轻 CPU 负担。理解过滤器的工作模式,是 CAN 开发的关键。
## 二、硬件连接与时钟使能
以 STM32F103 为例,CAN1 的引脚通常复用为 PA11(RX)和 PA12(TX)。接线时注意:
- CAN 收发器(如 TJA1050)的 TXD 接 STM32_TX(PA12),RXD 接 STM32_RX(PA11)。
- 总线两端需各接 120Ω 终端电阻。
- 共地,否则通信异常。
使能时钟时,除了 GPIO 和 CAN1,**必须同时使能 AFIO 时钟**,并将引脚重映射配置为默认(或所需映射)。
```c
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
```
## 三、CAN 初始化配置
CAN 初始化主要包括 GPIO 配置、CAN 工作模式、波特率设置。波特率由预分频器(Prescaler)和位时间(BS1、BS2)决定:
- 位时钟 = APB1 时钟 / Prescaler
- 波特率 = 位时钟 / (1 + BS1 + BS2)
例如 APB1 为 36MHz,目标 500kbps,可选 Prescaler=4,BS1=9,BS2=6(采样点位置约 62.5%)。
```c
void CAN_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // RX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // TX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void CAN_Init_Default(void)
{
CAN_InitTypeDef CAN_InitStructure;
CAN_GPIO_Config();
CAN_DeInit(CAN1);
CAN_StructInit(&CAN_InitStructure);
CAN_InitStructure.CAN_TTCM = DISABLE; // 时间触发模式
CAN_InitStructure.CAN_ABOM = ENABLE; // 自动离线管理
CAN_InitStructure.CAN_AWUM = ENABLE; // 自动唤醒
CAN_InitStructure.CAN_NART = DISABLE; // 自动重传
CAN_InitStructure.CAN_RFLM = DISABLE; // 接收 FIFO 锁定
CAN_InitStructure.CAN_TXFP = DISABLE; // 发送优先级由报文 ID 决定
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; // 正常模式,调试时可设为 LoopBack
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_9tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_6tq;
CAN_InitStructure.CAN_Prescaler = 4; // 500kbps @ 36MHz APB1
CAN_Init(CAN1, &CAN_InitStructure);
}
```
## 四、报文发送与接收
发送流程:填写 `CanTxMsg` 结构体 → 选择发送邮箱 → `CAN_Transmit()`。需检查发送状态,返回 `CAN_TxStatus_Ok` 表示已放入邮箱。
```c
uint8_t CAN_Send_Msg(uint32_t id, uint8_t *data, uint8_t len)
{
CanTxMsg TxMessage;
uint8_t i;
TxMessage.StdId = id; // 标准 ID,如需扩展帧使用 ExtId + IDE
TxMessage.IDE = CAN_Id_Standard;
TxMessage.RTR = CAN_RTR_Data;
TxMessage.DLC = len;
for (i = 0; i < len; i++)
TxMessage.Data[i] = data[i];
return CAN_Transmit(CAN1, &TxMessage); // 返回状态
}
```
接收有两种方式:查询 FIFO 或中断。初始化时通过 `CAN_ITConfig()` 开启 `CAN_IT_FMP0` 中断,并在中断服务函数中读取 `CanRxMsg`。
```c
void USB_LP_CAN1_RX0_IRQHandler(void) // 中断向量名因系列而异
{
CanRxMsg RxMessage;
if (CAN_GetITStatus(CAN1, CAN_IT_FMP0))
{
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
// 处理 RxMessage.StdId、RxMessage.Data、RxMessage.DLC 等
CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
}
}
```
## 五、过滤器配置详解
过滤器可配置为**列表模式**或**掩码模式**,每个过滤器有两个 32 位寄存器:过滤器寄存器 CAN_FxR0 和 CAN_FxR1。
- **掩码模式**:R0 为 ID 掩码,R1 为 ID。对应位为 1 则必须匹配,为 0 则忽略。适合匹配一组 ID。
- **列表模式**:R0 和 R1 分别存放两个独立的 ID,匹配任意一个即通过。适合精确匹配少量 ID。
**单掩码示例**:只接收 ID 为 0x100 或 0x101(标准帧,扩展帧需设置 CAN_ID_EXT)。
```c
void CAN_Filter_Config(void)
{
CAN_FilterInitTypeDef CAN_FilterInitStructure;
CAN_FilterInitStructure.CAN_FilterNumber = 0;
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; // 掩码模式
// ID 掩码:高 16 位对应标准 ID(左移 21 位),只比较 bit[3:0] 为标准 ID 低 4 位
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xFFF0; // 标准 ID 高 11 位,低 4 位忽略
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterIdHigh = (0x100 << 5); // 标准 ID 左移 5 位填充
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
}
```
对于**列表模式**,设置 `CAN_FilterMode_IdList`,此时 R0 和 R1 分别存储两个 ID。例如只接收 0x100 和 0x200:
```c
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList;
CAN_FilterInitStructure.CAN_FilterIdHigh = (0x100 << 5); // 第一个 ID
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = (0x200 << 5); // 第二个 ID
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
```
注意:过滤器寄存器的位布局与 CAN 帧 ID 的对应关系非常容易出错,调试时建议使用逻辑分析仪或官方示例验证。
## 六、完整代码示例
下面是一个初始化 + 发送 + 接收并回显的整合示例(省略具体回调):
```c
// main.c 关键部分
int main(void)
{
uint8_t msg[] = {0x01, 0x02, 0x03};
NVIC_Config(); // 配置 CAN RX0 中断优先级
CAN_Init_Default();
CAN_Filter_Config();
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
while (1)
{
CAN_Send_Msg(0x100, msg, 3);
Delay_ms(100);
}
}
void USB_LP_CAN1_RX0_IRQHandler(void)
{
CanRxMsg RxMsg;
if (CAN_GetITStatus(CAN1, CAN_IT_FMP0))
{
CAN_Receive(CAN1, CAN_FIFO0, &RxMsg);
// 收到后回显相同 ID 和内容
CAN_Send_Msg(RxMsg.StdId, RxMsg.Data, RxMsg.DLC);
CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
}
}
```
## 七、注意事项
- **波特率必须一致**:总线所有节点需相同波特率和采样点,否则通信不稳定。
- **引脚复用**:确认所使用系列引脚的默认复用功能,避免和调试口冲突。
- **过滤器初始化顺序**:必须在 `CAN_Init()` 之后,否则过滤器可能无效。
- **中断服务函数名**:不同型号的 IRQHandler 名称可能不同(如 `CAN1_RX0_IRQHandler`),请查阅对应启动文件。
- **发送状态检查**:`CAN_Transmit()` 返回值需检查,邮箱满时可能失败。
- **总线离线处理**:启用 ABOM(自动离线管理)可自动恢复,否则需手动恢复。
- **调试模式建议**:先用 `CAN_Mode_LoopBack` 自测,排除硬件问题后再联机。
掌握以上要点,你已能独立完成 STM32 CAN 的基本收发和过滤配置。实际项目中可进一步探索 CANopen、J1939 等高层协议,但扎实的底层基础永远是第一步。