3、STM32 CAN外设:bxCAN控制器介绍、CAN模式配置、过滤器配置、中断配置

好,咱们今天来啃一块硬骨头——STM32的bxCAN外设。

说实话,CAN总线这块,我当年刚入行时也踩过不少坑。尤其是那个过滤器配置,看着寄存器一堆,心里直发毛。但后来做多了发现,其实就那么几件事:模式选对、过滤器设好、中断配通。今天我就把这些经验掰开了揉碎了讲给你听。

3.1 bxCAN控制器核心架构

STM32的CAN外设叫bxCAN,全称是Basic Extended CAN。它支持CAN 2.0A和2.0B协议,最高速率1Mbps。

我习惯把bxCAN分成三块来看:

  • 核心层:负责CAN协议的收发、位定时、错误管理
  • 过滤器层:14个(或28个,看型号)可配置的标识符过滤器
  • 中断层:3个专用中断向量,分别处理发送、接收和错误

你想想看,这三层配合好了,整个CAN通信就稳了。我在一个车载项目中,就因为过滤器没配好,导致ECU收到了大量无关报文,CPU负载直接飙到80%。后来把过滤器一改,负载降到15%。嗯,这就是配置的力量。

3.2 CAN模式配置

bxCAN支持三种工作模式:

模式 说明 典型应用
正常模式 正常收发报文 UDS诊断通信
静默模式 只收不发,总线无影响 总线监听、调试
环回模式 自发自收,不经过总线 自测试、开发调试

配置模式其实很简单,就是操作CAN_MCR寄存器的INRQ和SLEEP位。我一般这样写初始化代码:

// 进入初始化模式
CAN1->MCR |= CAN_MCR_INRQ;
while((CAN1->MSR & CAN_MSR_INAK) != CAN_MSR_INAK); // 等待确认

// 配置位时序(这里以500kbps为例,APB1=42MHz)
CAN1->BTR = (CAN_BTR_TS1_2 | CAN_BTR_TS1_1 | CAN_BTR_TS1_0) | // TSEG1=7
             (CAN_BTR_TS2_2 | CAN_BTR_TS2_1) |                 // TSEG2=6
             CAN_BTR_SJW_0 |                                   // SJW=1
             5;                                                // BRP=5

// 退出初始化模式,进入正常模式
CAN1->MCR &= ~CAN_MCR_INRQ;
while((CAN1->MSR & CAN_MSR_INAK) == CAN_MSR_INAK);
我的小技巧:配置位时序时,我习惯先算好Tq。公式是:Tq = (BRP+1) / APB1时钟。比如APB1=42MHz,BRP=5,那Tq就是6/42M≈143ns。然后TSEG1+TSEG2+1=16个Tq,采样点在87.5%左右,这个值对大多数CAN总线都很友好。

3.3 过滤器配置——避坑指南

过滤器这块,我当年真是被它折磨得不轻。说白了,过滤器就是硬件层面的报文筛选器。你设好了,CPU只处理你关心的报文;没设好,要么收不到,要么收一堆垃圾。

bxCAN的过滤器有两种模式:

  • 标识符列表模式:精确匹配,只收指定ID的报文
  • 标识符掩码模式:范围匹配,收一组ID的报文

每个过滤器组可以配置为:

配置 模式 可过滤ID数量
2个32位 掩码或列表 2个标准ID或2个扩展ID
4个16位 掩码或列表 4个标准ID
1个32位+2个16位 混合 灵活配置

我曾经在一个UDS项目中,需要同时过滤0x7DF(诊断请求)和0x7E0~0x7E7(功能寻址)。当时我用了掩码模式,32位配置:

// 配置过滤器组0:掩码模式,32位
CAN1->FMR |= CAN_FMR_FINIT;  // 进入过滤器初始化模式

// 设置过滤器组0为32位掩码模式
CAN1->FM1R = 0;  // 位0=0表示掩码模式
CAN1->FS1R |= CAN_FS1R_FSC0;  // 32位宽度

// 配置ID和掩码
// 我们想过滤0x7DF和0x7E0~0x7E7
// 标准ID左移21位存入寄存器
CAN1->sFilterRegister[0].FR1 = (0x7DF << 21);  // ID
CAN1->sFilterRegister[0].FR2 = (0x7FF << 21);  // 掩码:只关心ID的低11位

// 使能过滤器组0
CAN1->FA1R |= CAN_FA1R_FACT0;

CAN1->FMR &= ~CAN_FMR_FINIT;  // 退出初始化模式
注意!配置过滤器时,一定要先置位CAN_FMR的FINIT位,进入初始化模式。我见过有人忘了这一步,直接写寄存器,结果过滤器死活不工作。调试了一整天,最后发现是这行代码漏了。嗯,血的教训。

3.4 中断配置

bxCAN有三个中断向量:

  • USB低优先级/CAN1_RX0:接收FIFO0中断
  • CAN1_RX1:接收FIFO1中断
  • CAN1_SCE:发送完成、错误、唤醒等状态中断

我一般这样配中断:

// 使能CAN1时钟
RCC->APB1ENR |= RCC_APB1ENR_CAN1EN;

// 配置NVIC
NVIC_SetPriority(CAN1_RX0_IRQn, 1);
NVIC_EnableIRQ(CAN1_RX0_IRQn);

// 使能CAN接收中断
CAN1->IER |= CAN_IER_FMPIE0;  // FIFO0消息挂起中断

// 中断服务函数
void CAN1_RX0_IRQHandler(void)
{
    CanRxMsg rxMsg;
    
    // 检查是否有待处理消息
    if(CAN1->RF0R & CAN_RF0R_FMP0)
    {
        // 读取消息
        rxMsg.StdId = CAN1->sFIFOMailBox[0].RIR >> 21;
        rxMsg.DLC = CAN1->sFIFOMailBox[0].RDTR & 0x0F;
        // ... 读取数据
        
        // 释放FIFO
        CAN1->RF0R |= CAN_RF0R_RFOM0;
        
        // 处理报文(比如解析UDS请求)
        ProcessUDSMessage(&rxMsg);
    }
}
我的建议:中断服务函数里尽量少做事。我一般只做三件事:读报文、释放FIFO、设一个标志位。真正的协议解析放到主循环里做。否则中断嵌套一多,系统就卡死了。你想想看,CAN总线上一秒可能来几千帧报文,中断里处理太多肯定扛不住。

3.5 实战经验总结

最后,我把自己这些年用bxCAN的心得总结一下:

  1. 初始化顺序很重要:先配时钟,再配GPIO(复用功能),然后配CAN模式,最后配过滤器和中断。顺序乱了,可能就起不来。
  2. 过滤器要精准:UDS诊断中,一般只关心0x7DF和0x7E0~0x7E7这几个ID。别偷懒用全通模式,否则CPU会被无关报文淹没。
  3. 中断优先级别设太高:CAN中断我一般设1或2,留给更紧急的任务(比如系统滴答定时器)更高的优先级。
  4. 调试时多用环回模式:我刚开始做CAN驱动时,总喜欢直接挂总线。后来发现环回模式真香——自己发自己收,不用接CAN收发器,调试效率翻倍。

好了,bxCAN这部分就讲到这里。下一节咱们聊聊UDS诊断协议的核心——服务请求与响应的实现。到时候我会结合今天讲的CAN配置,手把手带你写一个完整的诊断通信模块。