## 一、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 等高层协议,但扎实的底层基础永远是第一步。