ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32 CAN过滤器寄存器配置详解:掩码计算与实验验证

STM32 CAN过滤器寄存器配置详解:掩码计算与实验验证 做嵌入式这些年我见过不少同事被STM32的CAN过滤器折腾到怀疑人生。配置好过滤器之后想收的收不到不想收的满屏都是最后只能在中断里再加一层if判断去筛ID白白浪费了硬件过滤器的价值。其实STM32的bxCAN过滤器寄存器一点也不复杂只是手册里的位定义和实际填充顺序第一次看确实容易懵。这篇文章我就用寄存器设置的方式把CAN通讯过滤器配置从头到尾讲清楚并且用CAN分析仪做了实验验证直接拿数据说话。整篇文章基于STM32F1/F4通用的bxCAN外设寄存器差异处我会单独标注适合刚接触CAN或者想脱离CubeMX写底层驱动的人。1. 为什么你的CAN报文“收发正常”但就是等不到想要的ID1.1 CAN总线本来就是“一呼百应”的广播网络CAN总线本质上是一个多点广播网络没有“点对点”这个概念。任何节点发送一帧报文总线上所有节点都能收到。你接不接这根线硬件都已经把这帧报文放进接收邮箱了。如果总线上一共有10个节点每个节点每秒发200帧报文那你的MCU每秒要处理2000帧其中可能真正归你的只有50帧。所以我一直觉得STM32的bxCAN外设做得很聪明的一点就是它把“过滤”这件工作放到了硬件层面。过滤器寄存器一旦配置好不匹配的帧直接在接收路径上被丢弃根本不会进入FIFO也不会触发接收中断。这个机制对降低CPU负载、提高系统实时性非常关键。很多刚开始做CAN开发的人容易犯一个错误觉得过滤器就是“多此一举”反正我可以在接收中断里做判断。一旦总线负载上来你会发现中断频率高到主循环几乎跑不动甚至丢帧。硬件过滤器的意义就在于把“处理”和“查看”分开CPU只处理自己关心的报文。1.2 软件过滤与硬件过滤的成本差异我做过一个500kbps波特率的CAN实验一帧8字节的标准数据帧最极限情况下总线大约能承载8000帧/秒。就算打个对折4000帧/秒的中断频率对任何MCU来说都不是小数目。如果每个中断里都先接收再判断ID最后决定要不要处理那主循环就别想干别的了。硬件过滤器不一样。它在报文进入接收FIFO之前就开始比对不匹配就直接扔。整个过程不占用CPU周期也不产生中断。等CPU去读FIFO的时候收到的每一帧都是“有效”的。这就是为什么你配置好过滤器之后哪怕总线报文再多中断频率也完全可控。1.3 为什么寄存器设置这么容易出问题滤波器的寄存器设置本身不复杂真正坑人的地方在于配置顺序。STM32的过滤器寄存器不是“想改就能改”的你要先通过CAN_FMR寄存器里的FINIT位让过滤器进入初始化模式然后才能修改其他配置寄存器最后还要退出初始化模式配置才会真正生效。很多人一上来直接往CAN_FM1R、CAN_FS1R这些寄存器里写值写完之后一看配置没生效开始怀疑芯片是不是坏了。其实只是没有碰FMR这个总开关。还有一类问题出在掩码计算上ID值左移几位、掩码填什么搞错了之后过滤器要么啥都收要么啥都收不到。所以这篇文章我会把配置顺序和掩码计算作为重点先把原理讲透再给实际代码最后用实验数据验证。2. 过滤器寄存器家族谁控制模式谁决定生死2.1 FMR、FM1R、FS1R、FFA1R、FA1R逐个说明STM32的bxCAN最多有28个过滤器组我们常用的过滤器0到过滤器27。每个过滤器组都由一组寄存器控制但很多初学者会被这几个缩写搞混。我先讲清楚它们各自管什么。寄存器全称作用FMRFilter Master Register过滤器主寄存器FINIT位是过滤器配置的总开关FM1RFilter Mode Register每个过滤器是列表模式还是掩码模式FS1RFilter Scale Register每个过滤器是32位位宽还是16位位宽FFA1RFilter FIFO Assignment Register过滤器匹配成功后报文进FIFO0还是FIFO1FA1RFilter Active Register过滤器是否激活1为激活0为关闭这5个寄存器都是每一位对应一个过滤器。比如FM1R的第0位控制过滤器0是“列表模式”还是“掩码模式”FA1R的第0位控制过滤器0有没有被激活。看起来简单但5个寄存器加起来有100多个位一旦混淆配置出来的行为就会非常奇怪。2.2 FR1/FR2才是真正“装ID”的地方FM1R、FS1R这些寄存器只决定“行为规则”真正装匹配ID的是CAN_FiR1和CAN_FiR2也就是我们常说的FR1和FR2。每个过滤器组都有两个这样32位宽的寄存器具体怎么填取决于你在FS1R里设置的是16位还是32位。在32位掩码模式下FR1用来放“匹配的ID”FR2用来放“掩码”。FR1的高11位放标准ID最低几位还要放IDE位和RTR位。FR2也一样对应位置放掩码。很多人只关心ID不关心IDE位和RTR位结果标准帧、扩展帧、远程帧混在一起进中断这是后话。在32位列表模式下FR1和FR2分别放两个完全匹配的ID只要报文ID和其中一个相等就放行。在16位模式下FR1和FR2会被拆成4个16位区域能装更多ID或者更多组掩码但逻辑上更绕。2.3 推荐的配置顺序我习惯的配置顺序是固定的一套基本不会出错设置CAN_FMR的FINIT位让过滤器进入初始化模式。配置FS1R决定过滤器是32位还是16位。配置FM1R决定过滤器是掩码模式还是列表模式。配置FFA1R决定匹配成功后报文进哪个FIFO。先把FA1R对应位清零关掉过滤器防止配置过程中意外生效。往FR1、FR2里填ID和掩码。把FA1R对应位置1激活过滤器。清除CAN_FMR的FINIT位退出初始化模式。第5步很多人会漏掉。虽然大多数情况下你配置完再激活不会出问题但在正式产品里如果过滤器还开着但你正在改配置恰好这时候总线上来了一帧报文匹配逻辑用的是新旧混合的状态可能就会产生一次“脏匹配”。所以我的习惯永远是“先关再改改完再开”。3. 过滤器模式与掩码计算把寄存器里的位说成人话3.1 掩码模式1代表必须匹配0代表爱咋咋地掩码这个词听起来高大上其实就是一张“比对清单”。在掩码模式中如果掩码位为1那么ID对应位必须完全一致如果掩码位为0这一位不需要比较直接通过。我给你举几个例子。假设我们只关心标准帧ID 0x321那配置就是ID寄存器填0x321掩码寄存器填0x7FF。0x7FF是11位全1意味着11位ID每一位都要匹配差一位都不行。如果你想收0x320到0x32F这一组ID就可以把ID寄存器填0x320掩码填0x7F0。0x7F0的二进制是11111110000低4位不参与比较高7位必须匹配0x320的高7位。这样一来0x320到0x32F都会被放行而0x330、0x300这些都不会进FIFO。需求ID寄存器掩码寄存器效果只收0x3210x3210x7FF只有完全等于0x321的帧通过收0x320~0x32F0x3200x7F0高7位匹配低4位任意收0x100~0x1FF0x1000x700高4位匹配低7位任意所有标准帧0x0000x000全部放行最后一行是“全通”配置很多人调试时会用它来确认自己的CAN收发链路是通的。如果你发现“明明没配置过滤器为什么能收到所有报文”先看看是不是过滤器初始化代码里掩码被填成了0。3.2 列表模式点名不在名单里不搭理列表模式比掩码模式更简单粗暴。在32位列表模式下FR1放一个IDFR2放另一个ID只有报文ID和这两个ID完全相等时才会通过。没有掩码没有模糊匹配就是点名。列表模式适合那种“我明确知道我要哪几个固定ID”的场景。比如我需要同时接收0x111和0x222那就在FR1里填0x111FR2里填0x222其他ID一律不要。这种模式下不需要纠结掩码计算是最不容易出错的过滤方式。但它也有局限性。一个32位列表模式过滤器只能放2个ID而你配置多个过滤器的话每个过滤器是独立工作、逻辑或的关系也就是说多组列表规则是可以叠加的。如果按ID号建你可以用多个过滤器组拼一个“短名单”。3.3 16位还是32位按实际需求选没有谁更好FS1R里选择32位还是16位核心区别是“一个过滤器能装多少条规则”。32位模式下FR1和FR2都是32位整体使用一个过滤器在掩码模式下可以配置“1个ID1个掩码”在列表模式下可以配置“2个ID”。16位模式下FR1和FR2会被拆成4个16位区域每个区域都能放独立的ID或者掩码所以一个过滤器在掩码模式下能配“2组ID2组掩码”在列表模式下能配“4个ID”。从使用体验来说32位模式更直观适合标准ID和扩展ID混用的场景。16位模式虽然能装更多ID但位段切割比较复杂容易写错我一般只在明确需要“一个过滤器塞4个固定ID”的时候才用。3.4 IDE和RTR不是装饰品过滤器的匹配寄存器里除了ID之外还有IDE位和RTR位。IDE位区分标准帧和扩展帧RTR位区分数据帧和远程帧。如果掩码里不对这两个位做匹配过滤器会认为IDE位和RTR位“不参与比较”那么即使你过滤了ID扩展帧、远程帧也可能混进来。我第一次做CAN过滤时就遇到过这个问题。配置过滤器只收标准ID 0x321但没管IDE位和RTR位结果总线上有一帧扩展帧扩展ID的低11位恰好也是0x321它竟然也成功进入了FIFO。后来我把FR2掩码寄存器里的IDE位和RTR位都置1才真正把“标准数据帧0x321”和“其他一切”严格分开。这个细节手册里写得隐晦实际调试时却非常致命。4. 手把手配置“只收0x321”的过滤器寄存器写法和验证方法4.1 完整代码寄存器方式这里我给出一个最小化的寄存器配置代码目标是让CAN1的过滤器0只接收标准数据帧0x321其他帧全部丢弃。代码基于STM32F1/F4的bxCAN标准外设寄存器定义。/** * brief CAN过滤器配置过滤器032位掩码模式 * 只接收标准数据帧 ID 0x321 */ void CAN_Filter_Only_0x321(void) { /* 1. 进入过滤器初始化模式这是总开关 */ CAN1-FMR | CAN_FMR_FINIT; /* 2. 选择过滤器032位位宽掩码模式挂在FIFO0上 */ CAN1-FS1R | (1UL 0); /* 1 32位位宽 */ CAN1-FM1R ~(1UL 0); /* 0 掩码模式 */ CAN1-FFA1R ~(1UL 0); /* 0 FIFO0 */ /* 3. 先关闭过滤器0配置完成后再打开 */ CAN1-FA1R ~(1UL 0); /* 4. 填充ID和掩码 FR1: 标准ID 0x321 左移21位放到ID位段 FR2: ID掩码 0x7FF 左移21位再把IDE掩码和RTR掩码都置1 IDE掩码位 bit3RTR掩码位 bit2 */ CAN1-sFilterRegister[0].FR1 (0x321UL 21); CAN1-sFilterRegister[0].FR2 (0x7FFUL 21) | (1UL 3) /* IDE掩码置1要求IDE匹配 */ | (1UL 2); /* RTR掩码置1要求RTR匹配 */ /* 5. 激活过滤器0 */ CAN1-FA1R | (1UL 0); /* 6. 退出过滤器初始化模式 */ CAN1-FMR ~CAN_FMR_FINIT; }这段代码里有几个容易踩坑的地方我再说一遍。FR1里的ID为什么要左移21位因为32位模式下标准ID被放在寄存器的[31:21]位段也就是高11位。0x321左移21位刚好落到这个位置。FR2里的掩码也一样0x7FF左移21位表示11位ID全部参与匹配。而IDE掩码和RTR掩码的位置不是在高位而是在低位的bit3和bit2。如果你只填了ID和掩码的高11位没有把这两个掩码位置1过滤器实际上也会匹配到扩展帧和远程帧。我建议在正式代码里把这两个位写死明确“我只要标准数据帧”这样行为最可预测。4.2 与库函数写法对拍确认没有任何逻辑遗漏如果你用的是STM32标准外设库上面这段寄存器操作等价于下面这段代码。很多老工程师喜欢用标准库因为逻辑清晰但如果你能看懂寄存器版再回头看库函数版会发现它就是把我们刚才做的几步封装了一下。CAN_FilterInitTypeDef filter; filter.CAN_FilterNumber 0; filter.CAN_FilterMode CAN_FilterMode_IdMask; filter.CAN_FilterScale CAN_FilterScale_32bit; filter.CAN_FilterIdHigh 0x321 5; filter.CAN_FilterIdLow 0x0000; filter.CAN_FilterMaskIdHigh 0x7FF 5; filter.CAN_FilterMaskIdLow (1UL 3) | (1UL 2); filter.CAN_FilterFIFOAssignment CAN_FIFO0; filter.CAN_FilterActivation ENABLE; CAN_FilterInit(filter);注意一下库函数里把ID左移5位是因为它先把这个值写入FilterIdHigh而FilterIdHigh是FR1的高16位。一个16位值左移5位后再放入高16位等效于整个32位左移21位。所以库函数版的0x321 5和我寄存器版的0x321 21是同一个意思底层完全一致。很多人在网上看到这两种写法以为有什么差别其实没有。你只需要选择一种自己习惯的然后记住位切换的逻辑别在两边来回换算的时候出岔子。4.3 列表模式和16位模式的参考写法最后再给一个32位列表模式的例子用来接收标准数据帧0x111和0x222。CAN1-FMR | CAN_FMR_FINIT; CAN1-FS1R | (1UL 1); /* 过滤器132位位宽 */ CAN1-FM1R | (1UL 1); /* 1 列表模式 */ CAN1-FFA1R ~(1UL 1); /* FIFO0 */ CAN1-FA1R ~(1UL 1); CAN1-sFilterRegister[1].FR1 (0x111UL 21); CAN1-sFilterRegister[1].FR2 (0x222UL 21); CAN1-FA1R | (1UL 1); CAN1-FMR ~CAN_FMR_FINIT;列表模式下FR2里不再放掩码而是放第二个ID。如果只放一个ID可以把FR2填成和FR1一样或者干脆不用这个过滤器用另一个过滤器来放行第二个ID。逻辑上多个过滤器之间是或关系任何一个过滤器匹配成功报文都会进入接收FIFO。16位模式我没在这段代码里展开因为实际项目里用32位模式已经覆盖了绝大多数需求。如果你真的需要用16位模式塞更多ID建议对着数据手册的寄存器布局慢慢填并且做好注释这类代码隔一个月再看很容易懵。5. 实验验证用CAN分析仪把过滤器“打回原形”5.1 实验环境与接线光说寄存器还不够不实测一下心里不踏实。我这次实验用的硬件很简单STM32F103C8T6核心板一块USB-CAN分析仪一个CAN收发器芯片核心板自带或外接的TJA1050120Ω终端电阻两个USB转TTL串口模块一个接线就是STM32的CAN_TX、CAN_RX分别连到CAN收发器然后CANH、CANL并接USB-CAN分析仪和另一个120Ω终端电阻。STM32的CAN1引脚配置为标准复用推挽输出波特率设为500kbps。在STM32端我写了接收中断回调每收到一帧就判断ID如果ID是0x321计数变量加1。同时这帧也会被CAN分析仪软件记录方便两边对拍。5.2 测试流程与逐项记录我通过USB-CAN分析仪软件按顺序发送不同ID和不同帧类型的报文每组发送200帧等STM32端接收完毕再读计数变量。所有测试都在同一个过滤器配置下进行配置就是上面那段“只收标准数据帧0x321”。测试用例过滤器配置发送ID帧类型发送帧数STM32实际接收帧数结论A1掩码0x7FFID 0x3210x321标准数据帧200200放行A2掩码0x7FFID 0x3210x322标准数据帧2000拦截A3掩码0x7FFID 0x3210x321扩展数据帧2000IDE位掩码生效A4掩码0x7FFID 0x3210x321远程帧2000RTR位掩码生效A5掩码0x7FFID 0x3210x321标准数据帧200200重复确认稳定B1掩码0x000ID 0x3210x111标准数据帧200200全通C1列表ID 0x111/0x2220x111标准数据帧200200列表模式放行C2列表ID 0x111/0x2220x333标准数据帧2000列表模式拦截A1和A2是一组对照验证ID掩码本身是否生效。A3和A4是我前面专门强调的IDE位和RTR位实测下来确实拦截了扩展帧和远程帧这也说明FR2里掩码bit3和bit2位必须处理否则这两组用例结果会完全相反。B1是“掩码为0”的全通验证。我把ID寄存器还填0x321但掩码寄存器改成0结果发送0x111居然也能进FIFO。这其实是一个非常容易犯的错误很多人在代码里把FR2填漏了或者把掩码填成0导致过滤器形同虚设。我特意把这组数据加进来就是提醒大家写完掩码后一定要检查FR2是不是真的非0。C1和C2切换成了列表模式验证效果简洁明了。列表模式只要配置准确就不会出现“掩码算错”的问题对新手来说更友好。5.3 如何确认不是“凑巧收对”看到实验“成功”先别急着开心因为有时候过滤“成功”是因为总线负载太低错误帧没来得及出现而已。我验证过滤器有一个固定的流程稳得很。第一步先配置成全通模式也就是掩码填0让STM32所有报文都能收用CAN分析仪在总线上发各种ID、各种帧类型确认物理层、波特率、中断回调都没问题。第二步再配置成严格的单ID过滤并用计数变量和CAN分析仪软件同时记录。两边数据一对照如果STM32计数和发送帧数一致说明过滤器放行的报文都被正确接收如果不一致要么是过滤逻辑写错了要么是接收邮箱溢出丢了帧。第三步一定要把“不应该收到的帧”也加进测试序列。比如上面表格里的A2、A3、A4、C2光测“该收的能收到”还不够必须测“不该收的收不到”否则你根本不知道掩码是不是算错了。5.4 不使用USB-CAN分析仪时的替代方案如果手头暂时没有USB-CAN分析仪也可以先把CAN外设配置成回环模式自己发自己收先把过滤器的配置逻辑验证一遍。回环模式下发送邮箱发出的报文会直接在内部被接收路径捕获绕过了CAN收发器所以调试起来很方便。但要注意回环模式测的是“MCU内部路径”不是总线物理通信。等回环模式验证通过后再接上两个真正有CAN收发器的节点做总线测试尽量减少排查范围。6. 常见问题与排错技巧这些坑我基本都踩过6.1 配置了过滤器但所有报文都能进中断出现这个现象第一怀疑对象是FR2掩码寄存器。你把CAN1-sFilterRegister[0].FR2打印出来看看是不是0。如果掩码是0那过滤器就等于全通不管ID寄存器填什么都没用。还有一种情况就是你写的是列表模式但你往FR2里填的是掩码值而不是第二个ID也会出现“看似过滤实则全通”的诡异现象。另外如果你的代码里激活了多个过滤器比如过滤器0配置成掩码模式过滤器1配置成全通模式那么因为过滤器之间是或的关系全通过滤器会把所有报文都放进来。所以排查时要看整个FA1R寄存器确认到底哪些过滤器被激活了。6.2 配置了过滤器反而一个都收不到这种情况比“全通”更常见。首先看是不是没有把FA1R对应位置1。很多人在FMR里进入初始化模式填好FR1、FR2然后直接退出初始化模式但忘了激活过滤器结果寄存器看起来都对实际不起作用。还有一种可能是CAN_RX引脚配置错了。有的开发板引脚被其他外设复用比如I2C或者串口导致CAN接收信号根本没进到MCU内部。这时候配置再对也没用建议先用逻辑分析仪或者示波器看CAN_RX引脚上有没有波形。最后别忘了检查FIFO溢出标志。如果总线上报文特别多FIFO0满了之后即使后续报文匹配了过滤器也不会再进FIFO如果中断里没有及时读取CAN_RFR寄存器并清掉溢出标志看起来就是“过滤器坏了什么也收不到”。6.3 严格模式突然失效扩展帧或远程帧混了进来这个问题十有八九出在IDE位和RTR位的掩码上。如果你在FR2里没有对bit3和bit2置1IDE位和RTR位就不参与匹配那么标准帧和扩展帧、数据帧和远程帧之间会互相穿透。我建议在所有固定ID过滤的应用里都把IDE掩码和RTR掩码打开。哪怕你现在只跑标准数据帧也不确定以后总线上会不会出现扩展帧或远程帧。把这两个位掩码置1等于告诉硬件“我只要特定帧类型的特定ID”而不是“我只要ID别的我不管”。6.4 F103、F407等不同系列的寄存器兼容问题STM32F1、F2、F4这几代产品的bxCAN过滤器寄存器布局基本一致上面的代码我直接在F407上也跑过不需要改动。但如果你用的是STM32F0系列那就要特别小心它的CAN外设和F1/F4不是同一个体系滤波器配置方式差异很大。F0的CAN过滤器更简陋不支持那么多模式配置前务必打开对应型号的参考手册不要盲套F1的代码。另外标准外设库和HAL库对过滤器组的管理方式也不同。HAL库里的HAL_CAN_ConfigFilter会把过滤器初始化细节封装得比较好但底层寄存器还是我们讲的这些。如果你在HAL库工程里直接操作寄存器记得要处理好HAL库内部的初始化状态否则可能会被HAL函数再次覆盖。6.5 一个实用的排错方法直接读寄存器调试过滤器问题最快的路径不是一遍遍烧录代码而是在IDE的寄存器窗口里直接看CAN1-FMR、FM1R、FS1R、FA1R、sFilterRegister[0].FR1、FR2这些值。Keil和IAR都有外设寄存器查看功能断点停在配置完成后观察这几个寄存器是否符合预期。如果FMR的FINIT位还是1说明你配置完没退出初始化模式如果FA1R对应位是0说明过滤器没激活如果FR1是0、FR2是0说明你的ID和掩码根本没写进去。这些信息比猜来猜去快得多。我还习惯在串口里加一行调试打印把关键寄存器值打出来产品调试现场没有IDE环境时这招特别管用。7. 写在最后的几点经验过滤器寄存器这个知识点说实话不大但卡住人的时候特别要命。我自己第一次做CAN多节点通信时就是因为没搞懂IDE位和RTR位的掩码逻辑整整排查了一天最后才发现是远程帧穿透了过滤。那之后我就养成了两个习惯一是所有过滤器配置里都把IDE和RTR掩码写死二是每个过滤器至少做一次“不匹配帧”测试绝不死盯着“匹配帧”看。如果你刚开始接触这块建议先跑通一个最简单场景过滤器只收0x321然后用USB-CAN分析仪分别发0x321、0x322、标准帧、扩展帧、远程帧看结果是否完全符合预期。这个实验做完你对STM32 CAN过滤器的理解会比看十遍手册都扎实。最后再啰嗦一句代码里的寄存器注释一定要写清楚尤其是位段偏移和掩码含义。这类代码三个月后回头读注释就是你自己的救星。
返回列表