ARTICLE DETAIL

资讯详情

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

STM32 CAN通讯过滤器寄存器详解:从配置到实战验证

STM32 CAN通讯过滤器寄存器详解:从配置到实战验证 做STM32的CAN通讯十有八九会在过滤器这儿卡一下。我第一次调F103的bxCAN时收发器、波特率、中断都配好了发端数据照发收端就是进不了接收中断。把过滤器关掉之后又反过来总线上所有节点的帧全都涌进FIFO程序大部分时间都在处理垃圾ID。后来把CAN_FxR1、CAN_FxR2、FMR、FS1R、FFA1R、FA1R这几个寄存器逐个手动配置又用两块板子在真实总线上连着验证了一晚上才算把STM32 CAN通讯过滤器寄存器的行为彻底摸清。这篇就从寄存器角度把这些配置逻辑和验证结果完整记录下来给正在调过滤器的朋友一份可以直接照着做的参考。1. 为什么明明一行配置能解决的问题我还是要翻开寄存器手册很多教程里过滤器就一句话调HAL_CAN_ConfigFilter参数抄一下完事。但实际项目里一旦出现“过滤器配了好像没配”“只收到一部分帧”“CAN1和CAN2互相抢过滤器”这类问题不搞懂寄存器就只能反复试错。1.1 过滤器在CAN接收链路里的位置bxCAN外设收到一帧报文后不是直接扔进FIFO等CPU读而是先过一次硬件过滤器。过滤器的作用就是根据标识符标准帧的11位ID或扩展帧的29位ID决定这帧要不要进FIFO、进FIFO0还是FIFO1。链路大致是这样总线电平 - CAN内核解析 - 验收过滤器 - FIFO0/FIFO1 - 中断或查询读取。过滤器位于内核和FIFO之间匹配不上的帧在硬件层面就被丢掉了CPU根本感知不到。这也正是过滤器的价值总线上几百个ID你只关心其中两三个如果全收进来再靠软件判断中断频率会高得离谱还可能因为FIFO溢出丢掉真正关心的帧。1.2 只有寄存器能解释清楚的几个场景我用HAL或者标准库配过滤器遇到过三类问题每一类最后都得回到寄存器才能定位过滤器配置成功但某个bank就是不生效。回读FA1R才发现激活位没写进去或者FMR的FINIT位没有按正确时序操作。CAN1和CAN2同时用结果CAN2一个过滤器都收不到。因为CAN2使用的bank范围和CAN1重叠没有正确设置FMR里的CAN2SB。Bootloader和App都要重建过滤规则。直接调HAL封装函数当然能初始化但切换过程中寄存器处于什么状态、哪些bank被占必须看得懂FMR/FA1R。这类问题只看库函数的参数列表永远找不到答案。寄存器才是底层唯一的事实标准。1.3 本文的实验方法后面的内容都基于STM32F103C8T6的bxCAN外设两块板子通过TJA1050收发器实际对接普通模式500kbps。F105、F407这些带bxCAN的芯片逻辑一致使用FDCAN的G系列/H7系列不要直接照抄FDCAN的过滤器组织形式完全不同。2. 从两组32位寄存器到六个控制寄存器过滤器组的完整地图STM32F103的bxCAN里有28个过滤器组编号0到27。每个过滤器组内部有两个32位寄存器CAN_FxR1和CAN_FxR2x是组号在标准外设库里对应CANx-sFilterRegister[bank].FR1和.FR2。这两个寄存器装的就是ID或掩码而它工作在什么模式、什么位宽、挂哪个FIFO、有没有激活由另外五个控制寄存器决定。2.1 一个过滤器组内部CAN_FxR1和CAN_FxR2的分工一个过滤器组本质上就是一个8字节的存储单元可以按两种位宽来用32位位宽两个寄存器合起来表示完整的一条过滤规则。列表模式时FR1和FR2各存一个ID两个槽位掩码模式时FR1存期望IDFR2存掩码。16位位宽FR1的高16位、低16位FR2的高16位、低16位一共四个16位槽位。列表模式可以放四条ID掩码模式可以放两组“ID掩码”。一句话概括资源一个组能装几条规则取决于你是列表模式还是掩码模式以及有没有把寄存器拆成16位槽。2.2 五个控制寄存器各自管什么28个过滤器组在内存里连续排列每组固定占用8字节。第一组的FR1偏移是0x40FR2是0x44下一组直接加8所以第n组的FR1偏移就是0x40 n*8FR2再加4。调试器里手工改寄存器时这个偏移关系很有用。过滤器组怎么工作由下面这几个寄存器控制寄存器英文全称每个bit对应一个过滤器组置1/置0的含义FMRFilter Master Registerbit0FINIT写保护bits13:8CAN2SBCAN2起始组号FM1RFilter Mode Register1列表模式0掩码模式FS1RFilter Scale Register132位位宽016位位宽FFA1RFilter FIFO Assignment Register1关联FIFO10关联FIFO0FA1RFilter Activation Register1激活该过滤器组0禁用也就是说你配置第7组过滤器时就是操作第7个bit。比如想让第7组工作在32位列表模式并挂FIFO1就要把FS1R的bit7置1、FM1R的bit7置1、FFA1R的bit7置1最后FA1R的bit7也置1让它生效。2.3 FINIT的写保护改配置前必须跨过的门槛FMR寄存器里最容易被忽略的就是bit0FINIT。这款CAN控制器对过滤器相关的寄存器有一道写保护只有FINIT等于1时FM1R、FS1R、FFA1R、FA1R、CAN_FxR1、CAN_FxR2这些寄存器才允许被修改。FINIT等于0时你往这些寄存器写值写进去的等于空气。标准库初始化函数内部会自动处理这个时序但如果你在调试器里直接改寄存器做实验或者自己写寄存器操作函数就必须手动控制。正常顺序只有两步先把FMR的FINIT置1再配置其他寄存器全部写完最后把FINIT清0配置才会在硬件层面真正生效。后面实验配置函数里我会把这个时序写进去。2.4 CAN1和CAN2之间怎么分bankF103的bxCAN比较特殊28个过滤器组是CAN1和CAN2共用的CAN1可以全用CAN2只能用其中一部分。划分依据就是FMR寄存器里的CAN2SB字段它指定CAN2从第几个组开始用。比如设置CAN2SB14那0到13号组归CAN114到27号组归CAN2。CAN2SB复位默认值是0这在只用一个CAN时没问题但如果你初始化完CAN2发现过滤器完全不工作第一反应就应该是检查CAN2SB有没有设置而不是去调掩码。3. 掩码模式、列表模式、标准帧、扩展帧把过滤逻辑拆成位运算过滤器寄存器里存的东西不神秘本质就是位比较。只是这个位比较有几个参数容易绕晕拆开看就清晰了。3.1 列表模式拿ID去和两个槽位挨个比列表模式下的规则可以理解成一张名单。报文进来后硬件把报文的标识符拿过来和FR1、FR2里存的值逐一比较只要完全等于其中一个就放行。这里说的“完全等于”是寄存器值的完全相等。标准帧ID是11位扩展帧ID是29位它们在寄存器里的摆放方式不同所以配置时要把ID先转换成对应的寄存器值。FR1和FR2在列表模式下都是ID槽位掩码概念不存在。比如我想只收两个标准帧ID0x123和0x456那FR1写标准帧0x123的寄存器值FR2写标准帧0x456的寄存器值。3.2 掩码模式用1表示必须匹配掩码模式把FR2从“第二个ID槽位”改成了“掩码”。掩码的每一位含义是1代表这一位必须与FR1里对应位相同0代表这一位无所谓。举个例子。FR1的ID填0x100FR2的掩码填0x7F8。0x7F8换算成二进制是bit10到bit3全为1bit2到bit0全为0。这意味着报文ID的bit10到bit3必须等于0x100的这些位而低3位不参与比较。所以0x100、0x103、0x107都能进0x0FF进不来因为它的高8位不对。这就是掩码模式做批量接收的基本思路。掩码计算是过滤器配置里最容易出错的地方我自己的习惯是先在纸上把二进制写出来别直接在十进制里心算。比如“收0x100到0x107”这类需求0x100到0x107的区别只在bit2到bit0掩码就是0x7F8。3.3 标准帧和扩展帧在过滤器寄存器里的不同摆法这是很多人栽跟头的核心原因。发送邮箱的ID寄存器里标准帧ID是左移21位放进高位的而过滤器寄存器里的标准帧ID是在低位对齐直接放在bit10到bit0扩展帧ID则是左移3位后存放在寄存器中。也就是说你发送时用的ID数值不能直接拿来填过滤器必须做一次换算。我在实验里用到的换算规则标准帧ID过滤器值 标准ID本身也就是(uint32_t)std_id 0x7FF。扩展帧ID过滤器值 ((uint32_t)ext_id 3) | 0x04其中左移3位是为29位ID腾出位置bit2置1表示这是扩展帧。对应的掩码标准帧常用掩码0x7FF表示11位ID全比较。扩展帧常用掩码0xFFFFFFFC表示29位ID和IDE位全部参与比较最低两位不关心。3.4 模式选择的决策建议实际项目里我一般按下面这张表来选使用场景推荐配置理由只收一个确定的ID32位列表模式FR1和FR2各放一个还能顺带收第二个收一段连续的ID范围32位掩码模式用一个掩码覆盖整段不用堆多个过滤器组同时收标准帧和扩展帧32位掩码模式同组放宽匹配掩码不覆盖帧类型位靠软件再判断过滤器组不够用16位位宽把一组拆成四个槽位但配置时要格外小心CAN1和CAN2都有接收需求先分bank再各配各的规则避免两组CAN互相覆盖4. 实验验证四种典型过滤配置在真实总线上跑一遍原理归原理最终还是要看实际效果。这一章的四个实验我都在两块STM32F103C8T6对接的真实总线上跑过配置函数和结果都记录下来。4.1 实验平台与基础初始化两块板子都带TJA1050电平转换芯片接线很简单CANH接CANHCANL接CANLGND共地。短距离测试两边都保留120Ω终端电阻也不会出问题。波特率统一配500kbps。先初始化GPIO和CAN控制器。下面这段是标准外设库的写法重点是APB1时钟36MHz下预分频4、BS19tq、BS28tq19818tq算出500kbpsvoid CAN1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; CAN_InitTypeDef CAN_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); GPIO_PinRemapConfig(GPIO_Remap1_CAN1, DISABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); CAN_InitStructure.CAN_TTCM DISABLE; CAN_InitStructure.CAN_ABOM ENABLE; CAN_InitStructure.CAN_AWUM DISABLE; CAN_InitStructure.CAN_NART DISABLE; CAN_InitStructure.CAN_RFLM DISABLE; CAN_InitStructure.CAN_TXFP DISABLE; CAN_InitStructure.CAN_Mode CAN_Mode_Normal; CAN_InitStructure.CAN_SJW CAN_SJW_1tq; CAN_InitStructure.CAN_BS1 CAN_BS1_9tq; CAN_InitStructure.CAN_BS2 CAN_BS2_8tq; CAN_InitStructure.CAN_Prescaler 4; CAN_Init(CAN1, CAN_InitStructure); }注意CAN控制器初始化完成后要先做一次自测发送一条报文再确认能收到确认收发通路没问题再开始配置过滤器。4.2 过滤配置函数直接操作寄存器版本我封了一个直接操作寄存器的函数后面实验全部调用它。mode传0表示掩码模式传1表示列表模式scale传0表示16位传1表示32位fifo传0或1表示挂FIFO0还是FIFO1void CAN_FilterRegConfig(CAN_TypeDef *CANx, uint32_t bank, uint32_t mode, uint32_t scale, uint32_t fifo, uint32_t fr1, uint32_t fr2) { CANx-FMR | CAN_FMR_FINIT; // 进入初始化模式解除写保护 if (mode 1) CANx-FM1R | (1UL bank); else CANx-FM1R ~(1UL bank); if (scale 1) CANx-FS1R | (1UL bank); else CANx-FS1R ~(1UL bank); if (fifo 1) CANx-FFA1R | (1UL bank); else CANx-FFA1R ~(1UL bank); CANx-sFilterRegister[bank].FR1 fr1; CANx-sFilterRegister[bank].FR2 fr2; CANx-FA1R | (1UL bank); // 激活该过滤器组 CANx-FMR ~CAN_FMR_FINIT; // 退出初始化模式配置生效 }这个函数必须先进入FINIT初始化模式再改寄存器最后激活并退出。如果觉得某次配置没生效第一步就回读这几个寄存器看看而不是去怀疑总线。发消息的辅助函数void SendStd(uint16_t id) { CanTxMsg TxMsg; TxMsg.StdId id; TxMsg.ExtId 0; TxMsg.IDE CAN_Id_Standard; TxMsg.RTR CAN_RTR_Data; TxMsg.DLC 1; TxMsg.Data[0] 0xAA; while (CAN_Transmit(CAN1, TxMsg) CAN_TxStatus_Failed); } void SendExt(uint32_t id) { CanTxMsg TxMsg; TxMsg.StdId 0; TxMsg.ExtId id; TxMsg.IDE CAN_Id_Extended; TxMsg.RTR CAN_RTR_Data; TxMsg.DLC 1; TxMsg.Data[0] 0xBB; while (CAN_Transmit(CAN1, TxMsg) CAN_TxStatus_Failed); }接收端用查询方式读FIFO打印收到的ID和来源FIFOCanRxMsg RxMsg; if (CAN_MessagePending(CAN1, CAN_FIFO0) 0) { CAN_Receive(CAN1, CAN_FIFO0, RxMsg); if (RxMsg.IDE CAN_Id_Standard) printf([FIFO0] Std 0x%03X\r\n, RxMsg.StdId); else printf([FIFO0] Ext 0x%08X\r\n, RxMsg.ExtId); } if (CAN_MessagePending(CAN1, CAN_FIFO1) 0) { CAN_Receive(CAN1, CAN_FIFO1, RxMsg); if (RxMsg.IDE CAN_Id_Standard) printf([FIFO1] Std 0x%03X\r\n, RxMsg.StdId); else printf([FIFO1] Ext 0x%08X\r\n, RxMsg.ExtId); }4.3 实验一列表模式只收标准帧0x123配置第0组过滤器32位列表模式挂FIFO0CAN_FilterRegConfig(CAN1, 0, 1, 1, 0, 0x0123, 0x0123);发送端依次发送标准帧ID0x123、0x124、0x100、0x456。每帧间隔100ms。接收端串口实际输出只有0x123其余三帧全部被硬件丢弃。这说明列表模式下FR1和FR2就是两个精确匹配槽位。实验记录发送ID期望结果实际结果0x123收到收到0x124丢弃丢弃0x100丢弃丢弃0x456丢弃丢弃4.4 实验二掩码模式收下0x100到0x107一组ID同样用第0组切换成32位掩码模式。FR1填0x100FR2填0x7F8。0x7F8的bit10到bit3是1低3位是0表示ID高8位必须等于0x100的高8位低3位任意CAN_FilterRegConfig(CAN1, 0, 0, 1, 0, 0x0100, 0x07F8);发送端依次发送0x0FF、0x100、0x103、0x107、0x108、0x7FF。接收端只收到0x100、0x103、0x107。0x108虽然只比0x107大1但bit3已经变了被掩码挡住这正是掩码按位比较的特点。发送ID期望结果实际结果0x0FF丢弃丢弃0x100收到收到0x103收到收到0x107收到收到0x108丢弃丢弃0x7FF丢弃丢弃4.5 实验三列表模式接收扩展帧ID 0x1234567扩展帧29位ID左移3位再参与过滤。先算好寄存器值0x1234567左移3位等于0x91A2B38再加上bit2的IDE标志位得到0x91A2B3Cuint32_t ext_mask_val (0x1234567UL 3) | 0x04UL; CAN_FilterRegConfig(CAN1, 0, 1, 1, 0, ext_mask_val, ext_mask_val);发送端发送扩展帧0x1234567、0x1234568、0x2234567。接收端只收到0x1234567。注意发送时接收打印要用RxMsg.ExtId不是StdId。发送扩展ID期望结果实际结果0x1234567收到收到0x1234568丢弃丢弃0x2234567丢弃丢弃4.6 实验四两个过滤器组分别绑定FIFO0和FIFO1第0组列表模式收标准帧0x222挂FIFO1第1组掩码模式收0x300到0x307挂FIFO0CAN_FilterRegConfig(CAN1, 0, 1, 1, 1, 0x0222, 0x0222); CAN_FilterRegConfig(CAN1, 1, 0, 1, 0, 0x0300, 0x07F8);发送端交替发送0x222、0x300、0x304。接收端串口输出分别带上了[FIFO1]和[FIFO0]前缀说明FFA1R的FIFO分配位工作正常。这个实验验证的不只是过滤还有多优先级场景下的FIFO分流能力比如高优先级报文进FIFO1、普通报文进FIFO0后续读邮箱时可以直接按FIFO处理。5. 实测中踩过的坑和排查思路配置过滤器本身不复杂真正复杂的是出错以后怎么定位。下面几个问题全是我在实验过程中真实碰到过的按坑的典型度排个序。5.1 坑1没置FINIT就把配置项当摆设有一次我在调试器里直接改FM1R想看看列表模式效果改了bit0为1然后读回FM1R发现还是0。一开始以为是硬件坏了后来才意识到FMR的FINIT还是0整个过滤器配置区是写保护的。这个坑在调试器里特别容易踩因为你写寄存器时不会报错寄存器也不变看起来就像没写一样。正确操作是先写CANx-FMR | CAN_FMR_FINIT;配置完再清掉。而且要注意配置过程和退出必须在同一个临界区内完成中间不要被中断打断去收发CAN帧否则可能出现配置写到一半就开始过滤的竞态。5.2 坑2配置都对了就是忘了FA1R激活第二个高频坑是FS1R、FM1R、FFA1R、FR1、FR2全填好了FIFO里就是没数据。排查到最后发现FA1R对应位还是0。FA1R的每个位是过滤器的总开关。这个寄存器太不起眼很容易在配置列表里漏掉。我的建议是把“激活”这一步写进配置函数里而不是在主程序里调用完再单独激活。上面封装的CAN_FilterRegConfig函数最后一行就是激活这样就不会漏。5.3 坑332位掩码模式下把第二个寄存器当成ID填列表模式用习惯了以后切到掩码模式会下意识在FR2里也填一个期望ID。但掩码模式下FR2是掩码不是ID槽位。如果你期望收两个特定ID然后只改模式不改FR2FR2就会变成另一个ID结果两个ID都收不到或者只收到一个非常迷惑。掩码模式下的FR2填的是位比较规则1表示这几位必须匹配0表示忽略。填值之前先把二进制在纸上写出来别用十进制估算。5.4 坑4CAN2过滤器组被CAN1占光只用CAN1的时候28个组随便用根本不会想到bank分配问题。一旦把CAN2也初始化了CAN2的过滤器却怎么配都不生效十有八九是FMR里CAN2SB没设。复位后CAN2SB默认是0意味着CAN2默认从第0组开始用这和CAN1完全重叠。正常做法是在初始化CAN2过滤器之前把CAN2SB设为14让CAN2用第14到第27组。代码里就是修改FMR寄存器的高位CANx-FMR (CANx-FMR ~(0x3FUL 8)) | (14UL 8);CAN2只占14个组CAN1用0到13分配完双方互不干扰。5.5 快速判断过滤器是否生效的三步法最后给一个我自己一直在用的排查套路。不管过滤器配置成什么样先别急着怀疑总线、波特率、中断按下面三步走第一步把过滤器临时配成全接收。掩码模式FR1填0FR2填0掩码全0意味着所有位都不比较任何帧都能进。这时如果还收不到问题就不在过滤器而是总线或者CAN初始化。第二步改成目标过滤规则发送一个期望ID看FIFO有没有数据。没有的话回读寄存器printf(FM1R%08X FS1R%08X FFA1R%08X FA1R%08X\r\n, CAN1-FM1R, CAN1-FS1R, CAN1-FFA1R, CAN1-FA1R); printf(FR1%08X FR2%08X\r\n, CAN1-sFilterRegister[0].FR1, CAN1-sFilterRegister[0].FR2);对比回读值和预期通常能立刻发现问题要么FINIT没退出要么FA1R没置位要么FR2算错。第三步发送一个不期望的邻近ID确认FIFO里的计数不增加。这一步很多人会忽略但“该收的收进来”和“不该收的挡在外面”是两件事只验证一半不算完。我自己现在配过滤器已经习惯先把估算好的寄存器值写在注释里再写配置代码。因为掩码这种东西隔一个晚上再看就会觉得当时算错了。把计算过程留档既能自检也方便同事接手。
返回列表