3、CAN过滤器配置:标识符列表模式、掩码模式、32位/16位过滤器、FIFO分配策略
各位同学,咱们今天聊CAN过滤器。说实话,这玩意儿刚接触时我也觉得有点绕。但你要知道,在车载项目里,CAN总线上的报文多得像菜市场里的吆喝声。你不设过滤器,CPU就得一个个去读,那效率,啧啧,惨不忍睹。
我最早做的一个车身控制项目,总线上一秒跑上千帧报文。没配过滤器的时候,MCU光处理CAN中断就占了70%的CPU时间。后来我把过滤器一配,CPU占用直接降到15%。你看,这就是配置的价值。
3.1 为什么需要CAN过滤器?
说白了,CAN总线是个广播网络。你发一帧,所有人都能收到。但你的MCU只关心其中几帧,比如车速、发动机转速、油门位置。其他的,比如车窗状态、空调温度,跟你这个ECU没关系。
没有过滤器,MCU就得把所有报文都收进来,然后软件里一个个判断ID。这就像你每天收1000封邮件,每封都得打开看一眼才知道是不是你的。累不累?
CAN硬件过滤器的作用,就是在报文进入FIFO之前,帮你做一次预筛选。符合条件的放行,不符合的直接丢弃。硬件干这事儿,一个时钟周期就搞定,比软件快几个数量级。
核心原则:能用硬件过滤的,绝不用软件过滤。这是嵌入式优化的第一铁律。
3.2 两种过滤模式:列表模式 vs 掩码模式
STM32的CAN过滤器支持两种工作模式。我习惯这么理解:
- 列表模式:像机场VIP名单。只有名单上的人才能进。精确匹配,一个萝卜一个坑。
- 掩码模式:像小区门禁卡。只要卡号符合某个范围就能进。模糊匹配,一放放一片。
3.2.1 标识符列表模式
列表模式就是精确匹配。你告诉硬件:「我只收ID为0x123、0x456、0x789这三帧,其他的统统不要。」
这种模式的好处是精准,没有误收。坏处是能匹配的ID数量有限。一个32位过滤器,在列表模式下只能配2个ID。16位过滤器能配4个。
我在做OBD诊断仪时用过这个模式。诊断请求的ID是固定的,就那么几个。用列表模式,干净利落。
// 列表模式配置示例 - 只接收ID为0x321和0x123的报文
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x3210; // 第一个ID: 0x321
CAN_FilterInitStructure.CAN_FilterIdLow = 0x1230; // 第二个ID: 0x123
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xFFFF; // 列表模式下,掩码寄存器存的是第二个ID
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0xFFFF;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
小技巧:列表模式下,掩码寄存器其实被用来存放第二个ID。所以FilterIdHigh/Low放第一个ID,FilterMaskIdHigh/Low放第二个ID。别搞反了,我刚开始就犯过这错。
3.2.2 掩码模式
掩码模式就灵活多了。你定义一个基准ID,再定义一个掩码。掩码为1的位必须精确匹配,掩码为0的位可以忽略。
举个例子:基准ID是0x100,掩码是0x7F0。这意味着ID的高7位必须匹配0x100的高7位,低4位随意。所以0x100到0x10F这16个ID都会被接收。
你想想看,这在车载项目里多有用。比如某个传感器系列,ID范围是0x200到0x20F。你一个掩码就全收了,不用一个个配。
// 掩码模式配置示例 - 接收ID范围0x200-0x20F的所有报文
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x2000; // 基准ID: 0x200
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x7F0; // 掩码: 高7位匹配,低4位忽略
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
注意:掩码模式下,掩码位为0表示「不关心」,为1表示「必须匹配」。这和有些人习惯的思维正好相反。我有个同事就搞反了,排查了半天才发现是掩码配反了。
3.3 32位过滤器 vs 16位过滤器
STM32的每个CAN过滤器组,可以配置成32位模式或16位模式。这影响的是你能同时过滤多少个ID。
| 过滤器尺度 | 列表模式 | 掩码模式 |
|---|---|---|
| 32位 | 2个ID | 1个ID + 1个掩码 |
| 16位 | 4个ID | 2个ID + 2个掩码 |
32位模式处理的是标准帧(11位ID)或扩展帧(29位ID)。16位模式只能处理标准帧。
我个人习惯是:如果项目里只用标准帧,优先用16位模式。因为同样的硬件资源,能过滤更多ID。但如果涉及扩展帧,那就只能用32位模式了。
嗯,这里要注意一点:32位模式下,ID和掩码的排列方式有点讲究。标准帧的11位ID放在高11位,扩展帧的29位ID占满32位。配置时别搞混了。
// 16位过滤器配置 - 掩码模式,可过滤2组ID
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_16bit;
// 第一组: ID=0x100, 掩码=0x7F0 (接收0x100-0x10F)
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x1000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x7F0;
// 第二组: ID=0x200, 掩码=0x7F0 (接收0x200-0x20F)
CAN_FilterInitStructure.CAN_FilterIdLow = 0x2000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x7F0;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
3.4 FIFO分配策略
STM32的CAN外设有两个FIFO:FIFO0和FIFO1。每个FIFO有3个邮箱,可以缓存3帧报文。
过滤器配置的最后一步,就是指定过滤到的报文进哪个FIFO。这看起来简单,但分配策略其实挺讲究。
我一般这么分:
- FIFO0:放高优先级报文。比如车速、刹车、转向这些实时性要求高的。
- FIFO1:放低优先级报文。比如温度、里程、诊断信息这些可以稍微延迟的。
为什么这么分?因为FIFO0的中断优先级可以设得更高。高优先级报文来了,CPU立刻响应。低优先级报文排队等着,不影响关键任务。
我曾经在一个项目中,把所有报文都塞进FIFO0。结果高优先级中断频繁触发,低优先级的报文反而被堵死了。后来改成FIFO0放关键报文,FIFO1放普通报文,问题就解决了。
// FIFO分配示例 - 高优先级进FIFO0,低优先级进FIFO1
// 过滤器0: 接收ID=0x100-0x10F (高优先级,进FIFO0)
CAN_FilterInitStructure.CAN_FilterNumber = 0;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;
CAN_FilterInit(&CAN_FilterInitStructure);
// 过滤器1: 接收ID=0x200-0x20F (低优先级,进FIFO1)
CAN_FilterInitStructure.CAN_FilterNumber = 1;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO1;
CAN_FilterInit(&CAN_FilterInitStructure);
实战建议:FIFO0和FIFO1的中断优先级要拉开差距。我一般FIFO0用最高优先级,FIFO1用中等优先级。这样即使总线繁忙,关键报文也不会丢。
3.5 避坑指南
做CAN过滤器配置,有几个坑我踩过,分享给大家:
- 过滤器编号别搞错:STM32有14个或28个过滤器组(看型号)。编号从0开始。你配了过滤器0,结果报文进了过滤器1,那就白配了。
- 扩展帧的掩码要小心:29位ID的掩码配置比标准帧复杂。我建议用工具生成配置代码,手写容易出错。
- 别忘了使能过滤器:
CAN_FilterActivation = ENABLE这行代码很容易漏掉。不使能,过滤器不工作,所有报文都进不来。 - FIFO溢出处理:如果FIFO满了,新报文会被丢弃。所以中断服务程序里要尽快读取报文,别在中断里做耗时操作。
好了,CAN过滤器配置就讲这么多。说白了就是两件事:选模式(列表还是掩码),定尺度(32位还是16位),然后分FIFO。配好了,你的CAN通信效率能翻倍。配不好,CPU就在那傻等中断。
下一节咱们聊CAN中断处理与DMA传输优化,到时候我会分享一个实际项目中的优化案例,很有意思。