1. 为什么FDCAN的滤波器配置值得单独拿出来讲
搞过STM32经典bxCAN的人,第一次上手STM32G474的FDCAN外设,大概率会在滤波器这块卡住。原因很简单:FDCAN的滤波器架构和bxCAN完全不是一回事。bxCAN时代,你配个14组滤波器,每组两个32位寄存器,标准帧和扩展帧各有一套掩码/列表模式,思路很直白。到了FDCAN,滤波器变成了**标准帧滤波器组(SIDFC)和扩展帧滤波器组(XIDFC)**两套独立配置,每组滤波器只处理一个ID,元素数量、FIFO分配、全局配置寄存器全都不一样。
我最近在一个电机控制项目里用STM32G474做CAN总线通信,需要同时接收6个不同节点的报文,ID从0x100到0x105,还要区分标准帧和扩展帧。一开始按bxCAN的思路去配,结果一个ID都收不到,调试了整整一个下午才把FDCAN的滤波器逻辑理清楚。这篇文章就把我踩过的坑和最终跑通的方案完整记录下来,包括滤波器组怎么分配、FIFO怎么绑定、多ID接收怎么组织代码,以及几个容易忽略的细节。
内容适合已经会用CubeMX配置FDCAN基本参数、但对滤波器配置还不太有把握的嵌入式开发者。如果你正在用STM32G474做多节点CAN通信,或者从bxCAN迁移到FDCAN,这篇应该能帮你省下不少调试时间。
2. FDCAN滤波器架构拆解与方案选型
2.1 FDCAN滤波器和bxCAN的本质区别
先把这个事情说清楚,不然后面配寄存器全是懵的。bxCAN的滤波器是一个滤波器组可以覆盖一段ID范围,你配一个32位掩码,就能过滤出一批ID。FDCAN不是这样,FDCAN的每个滤波器元素只对应一个具体的ID值,或者一组通过掩码匹配的ID。听起来好像差不多,但实际配置时的粒度完全不同。
FDCAN内部有两套滤波器路径:
- 标准帧滤波器(Standard ID Filter):处理11位ID的标准帧,配置在
FDCAN_SIDFC寄存器指定的消息RAM区域。 - 扩展帧滤波器(Extended ID Filter):处理29位ID的扩展帧,配置在
FDCAN_XIDFC寄存器指定的消息RAM区域。
每套滤波器由若干个滤波器元素(Filter Element)组成,每个元素占一个32位字。标准帧滤波器元素格式是SFT[1:0] + SFEC[2:0] + SFID1[10:0] + SFID2[10:0],扩展帧类似但ID是29位,需要两个32位字。
关键点:FDCAN的滤波器元素数量是有限的,而且必须在消息RAM里预先分配好空间。STM32G474的FDCAN消息RAM总共是212个字(32位),要同时容纳滤波器元素、接收FIFO、发送FIFO、发送事件FIFO。你分给滤波器的空间多了,FIFO就少了,这个平衡得提前算好。
2.2 消息RAM的空间分配逻辑
STM32G474的FDCAN消息RAM布局是这样的(参考RM0440手册):
| 区域 | 起始偏移 | 说明 |
|---|---|---|
| SIDFC | 可配置 | 标准帧滤波器列表 |
| XIDFC | 可配置 | 扩展帧滤波器列表 |
| RXF0C | 可配置 | 接收FIFO 0 |
| RXF1C | 可配置 | 接收FIFO 1 |
| TXEFC | 可配置 | 发送事件FIFO |
| TXBC | 可配置 | 发送缓冲区 |
每个区域的起始地址通过对应的配置寄存器设置,单位是32位字。比如你把SIDFC的起始地址设为0,FLSA字段设为0,那标准帧滤波器就从消息RAM的第0个字开始放。
我当时的分配方案是这样的:
- 标准帧滤波器:6个元素,每个1字,占6字
- 扩展帧滤波器:2个元素,每个2字,占4字
- RX FIFO 0:16个元素,每个4字(数据场最大8字节),占64字
- RX FIFO 1:不用,占0字
- TX FIFO:8个元素,每个4字,占32字
- TX Event FIFO:8个元素,每个2字,占16字
总共6+4+64+0+32+16 = 122字,远小于212字,空间很充裕。但如果你要接收大量不同ID,滤波器元素数量会迅速膨胀,这时候就得精打细算了。
注意:消息RAM的分配必须在FDCAN进入正常工作模式之前完成,而且一旦配置好,运行中不能动态改滤波器数量。要改就得先进入Init模式重新配置。
2.3 滤波器模式选择:范围匹配 vs 精确匹配
FDCAN的每个滤波器元素支持几种模式,通过SFT(Standard Filter Type)或EFT(Extended Filter Type)字段设置:
- 范围滤波器(Range Filter):
SFT=00,匹配SFID1到SFID2之间的所有ID。比如SFID1=0x100,SFID2=0x105,那0x100到0x105都能通过。 - 双ID滤波器(Dual ID Filter):
SFT=01,同时匹配SFID1和SFID2两个具体ID。 - 经典滤波器(Classic Filter):
SFT=10,用SFID1作为ID,SFID2作为掩码,类似bxCAN的掩码模式。 - 范围无掩码滤波器(Range No Mask):
SFT=11,和范围滤波器类似但行为略有不同。
我一开始想用范围滤波器一把覆盖0x100到0x105,但实际项目里这6个ID对应的报文长度和优先级不一样,需要分别放到不同的FIFO里处理。所以最终选了双ID滤波器+精确匹配的组合方案:用3个滤波器元素,每个元素配两个ID,刚好覆盖6个ID。
这里有个细节:双ID滤波器模式下,两个ID必须属于同一个FIFO。如果你想把0x100放FIFO0、0x101放FIFO1,那就不能用双ID滤波器,得拆成两个独立的滤波器元素。
2.4 FIFO分配策略
FDCAN有两个接收FIFO:RX FIFO 0和RX FIFO 1。每个滤波器元素通过SFEC(Standard Filter Element Configuration)字段决定匹配后的动作:
SFEC=000:禁用该滤波器元素SFEC=001:匹配后存入RX FIFO 0SFEC=010:匹配后存入RX FIFO 1SFEC=011:匹配后拒绝(用于屏蔽特定ID)SFEC=100:匹配后设置高优先级中断SFEC=101:匹配后设置低优先级中断SFEC=111:匹配后不存储但设置中断标志
我的方案是把6个ID分成两组:高优先级的3个ID(0x100、0x102、0x104)放FIFO0,低优先级的3个ID(0x101、0x103、0x105)放FIFO1。这样中断处理时可以优先处理FIFO0的报文,保证关键数据的实时性。
3. 手把手配置FDCAN滤波器与多ID接收
3.1 CubeMX基础配置
先在CubeMX里把FDCAN的基本参数配好。我用的配置如下:
- Frame Format:FD mode with Bit Rate Switching(如果你的节点都支持CAN FD)
- Mode:Normal
- Nominal Bit Rate:500 kbps(仲裁段)
- Data Bit Rate:2 Mbps(数据段)
- Sample Point:75%(仲裁段),80%(数据段)
- Auto Retransmission:Enabled
- Transmit Pause:Disabled
- Protocol Exception:Disabled
时钟配置很关键。STM32G474的FDCAN时钟源可以选PLL1Q、PLL2Q或HSE。我用的是PLL1Q,配置到160MHz,然后通过FDCAN的时钟分频器得到所需的位时钟。
提示:FDCAN的位时序计算和bxCAN不一样,它用的是
NBTP和DBTP两个寄存器分别控制仲裁段和数据段的位时序。CubeMX会自动算,但你要知道它算的对不对。500kbps在160MHz时钟下,预分频器设20,TSEG1=13,TSEG2=4,SJW=4,这样采样点正好是(1+13)/(1+13+4)=77.8%,接近75%。
3.2 消息RAM分配代码实现
CubeMX生成的代码里,FDCAN的初始化部分会调用HAL_FDCAN_Init(),但滤波器配置和消息RAM分配需要自己写。我是在MX_FDCAN1_Init()之后手动加的配置代码。
先定义消息RAM的偏移地址:
/* 消息RAM分配,单位:32位字 */ #define SIDFC_OFFSET 0 /* 标准帧滤波器起始 */ #define SIDFC_NUM 6 /* 标准帧滤波器元素数 */ #define XIDFC_OFFSET 6 /* 扩展帧滤波器起始 */ #define XIDFC_NUM 2 /* 扩展帧滤波器元素数 */ #define RXF0_OFFSET 10 /* RX FIFO 0起始 */ #define RXF0_NUM 16 /* RX FIFO 0元素数 */ #define RXF1_OFFSET 74 /* RX FIFO 1起始 */ #define RXF1_NUM 16 /* RX FIFO 1元素数 */ #define TXFIFO_OFFSET 138 /* TX FIFO起始 */ #define TXFIFO_NUM 8 /* TX FIFO元素数 */ #define TXEVENT_OFFSET 170 /* TX Event FIFO起始 */ #define TXEVENT_NUM 8 /* TX Event FIFO元素数 */然后在HAL_FDCAN_Init()之前,通过hfdcan1.Instance->SIDFC等寄存器设置这些偏移:
/* 配置消息RAM分配 */ hfdcan1.Instance->SIDFC = (SIDFC_OFFSET << FDCAN_SIDFC_FLSSA_Pos) | (SIDFC_NUM << FDCAN_SIDFC_LSS_Pos); hfdcan1.Instance->XIDFC = (XIDFC_OFFSET << FDCAN_XIDFC_FLESA_Pos) | (XIDFC_NUM << FDCAN_XIDFC_LSE_Pos); hfdcan1.Instance->RXF0C = (RXF0_OFFSET << FDCAN_RXF0C_F0SA_Pos) | (RXF0_NUM << FDCAN_RXF0C_F0S_Pos); hfdcan1.Instance->RXF1C = (RXF1_OFFSET << FDCAN_RXF1C_F1SA_Pos) | (RXF1_NUM << FDCAN_RXF1C_F1S_Pos); hfdcan1.Instance->TXBC = (TXFIFO_OFFSET << FDCAN_TXBC_TBSA_Pos) | (TXFIFO_NUM << FDCAN_TXBC_TFQS_Pos); hfdcan1.Instance->TXEFC = (TXEVENT_OFFSET << FDCAN_TXEFC_EFSA_Pos) | (TXEVENT_NUM << FDCAN_TXEFC_EFS_Pos);这段代码必须在HAL_FDCAN_Init()之前执行,因为Init函数会检查这些配置并锁定消息RAM。
3.3 标准帧滤波器配置
接下来配置6个标准帧滤波器元素。每个元素是一个32位字,格式如下:
bit 31-30: SFT[1:0] 滤波器类型 bit 29-27: SFEC[2:0] 滤波器配置 bit 26-16: SFID1[10:0] 第一个ID bit 15-0: SFID2[10:0] 第二个ID(或掩码)我用双ID滤波器模式,3个元素覆盖6个ID:
/* 标准帧滤波器配置 */ /* 元素0:ID 0x100 和 0x101,存入FIFO0 */ hfdcan1.Instance->SIDFC = ...; /* 先设置好偏移 */ /* 通过消息RAM写入滤波器元素 */ uint32_t *msg_ram = (uint32_t *)&hfdcan1.Instance->RAM[0]; /* 注意:实际访问消息RAM需要通过FDCAN的RAM窗口,不同系列地址不同 */这里有个坑:STM32G474的消息RAM不能直接通过指针访问,需要通过FDCAN_IR寄存器的RAM_ACCESS位或者用HAL库提供的HAL_FDCAN_ConfigFilter()函数。我一开始想直接写内存,结果发现访问不到,后来改用HAL库的API才搞定。
用HAL库配置标准帧滤波器的代码如下:
FDCAN_FilterTypeDef sFilterConfig; /* 滤波器0:双ID模式,匹配0x100和0x101,存入FIFO0 */ sFilterConfig.IdType = FDCAN_STANDARD_ID; sFilterConfig.FilterIndex = 0; sFilterConfig.FilterType = FDCAN_FILTER_DUAL; sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; sFilterConfig.FilterID1 = 0x100; sFilterConfig.FilterID2 = 0x101; if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK) { Error_Handler(); } /* 滤波器1:双ID模式,匹配0x102和0x103,存入FIFO0 */ sFilterConfig.FilterIndex = 1; sFilterConfig.FilterID1 = 0x102; sFilterConfig.FilterID2 = 0x103; if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK) { Error_Handler(); } /* 滤波器2:双ID模式,匹配0x104和0x105,存入FIFO1 */ sFilterConfig.FilterIndex = 2; sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1; sFilterConfig.FilterID1 = 0x104; sFilterConfig.FilterID2 = 0x105; if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK) { Error_Handler(); }注意FilterIndex是从0开始的,对应消息RAM里SIDFC区域的第几个元素。我分配了6个元素的空间,但只用了3个,剩下的3个保持默认(禁用状态)。
3.4 扩展帧滤波器配置
扩展帧滤波器元素占两个32位字,格式更复杂一些。第一个字包含EFT、EFEC、EFID1,第二个字包含EFID2。我用HAL库配置:
/* 扩展帧滤波器0:匹配0x18FF0001,存入FIFO0 */ sFilterConfig.IdType = FDCAN_EXTENDED_ID; sFilterConfig.FilterIndex = 0; sFilterConfig.FilterType = FDCAN_FILTER_MASK; sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; sFilterConfig.FilterID1 = 0x18FF0001; sFilterConfig.FilterID2 = 0x1FFFFFFF; /* 掩码,全匹配 */ if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK) { Error_Handler(); }扩展帧的掩码模式比较实用,FilterID2作为掩码,某位为1表示该位必须匹配,为0表示忽略。比如你想接收所有0x18FFxxxx的报文,就把FilterID1设为0x18FF0000,FilterID2设为0x1FFF0000。
3.5 全局滤波器配置与中断使能
滤波器元素配好后,还要设置全局滤波器规则。FDCAN有两个全局配置:
- 拒绝非匹配帧:
HAL_FDCAN_ConfigGlobalFilter()的NonMatchingStd和NonMatchingExt参数设为FDCAN_REJECT,这样不匹配任何滤波器的报文会被丢弃。 - 拒绝远程帧:
RejectRemoteStd和RejectRemoteExt设为FDCAN_REJECT_REMOTE。
if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, /* 非匹配标准帧拒绝 */ FDCAN_REJECT, /* 非匹配扩展帧拒绝 */ FDCAN_REJECT_REMOTE, /* 拒绝标准远程帧 */ FDCAN_REJECT_REMOTE /* 拒绝扩展远程帧 */ ) != HAL_OK) { Error_Handler(); }然后使能FIFO0和FIFO1的新消息中断:
if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE | FDCAN_IT_RX_FIFO1_NEW_MESSAGE | FDCAN_IT_BUS_OFF | FDCAN_IT_ERROR_PASSIVE | FDCAN_IT_ERROR_WARNING, 0) != HAL_OK) { Error_Handler(); }最后启动FDCAN:
if (HAL_FDCAN_Start(&hfdcan1) != HAL_OK) { Error_Handler(); }3.6 中断回调与多ID分发处理
FIFO收到新消息后会触发中断,在回调函数里读取报文并根据ID分发:
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { FDCAN_RxHeaderTypeDef rxHeader; uint8_t rxData[8]; if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != 0) { if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &rxHeader, rxData) == HAL_OK) { switch (rxHeader.Identifier) { case 0x100: /* 处理电机状态报文 */ ProcessMotorStatus(rxData, rxHeader.DataLength); break; case 0x101: /* 处理温度报文 */ ProcessTemperature(rxData, rxHeader.DataLength); break; case 0x102: /* 处理电流报文 */ ProcessCurrent(rxData, rxHeader.DataLength); break; default: break; } } } }FIFO1的回调类似,只是用HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO1, ...)。
注意:
HAL_FDCAN_GetRxMessage()每次只取一条报文,如果FIFO里有多条,需要循环读取直到FIFO空。可以通过HAL_FDCAN_GetRxFifoFillLevel()查询FIFO里还有多少条。
4. 调试过程中踩过的坑与排查实录
4.1 滤波器配了但收不到任何报文
这是我最开始遇到的问题。现象是:FDCAN初始化成功,总线上的波形也正常,但中断死活不触发。排查步骤:
确认FDCAN是否真的进入了Normal模式。读
FDCAN_CCCR寄存器的INIT和CCE位,如果INIT=1说明还在Init模式,根本没开始通信。我一开始忘了调HAL_FDCAN_Start(),卡在这里。检查消息RAM分配是否冲突。如果SIDFC和RXF0C的起始地址重叠了,滤波器元素会覆盖FIFO空间,导致行为异常。我写了个小函数把各区域的起始地址和长度打印出来核对。
确认滤波器元素的
SFEC字段不是000。SFEC=000表示禁用该滤波器,匹配了也不会存FIFO。我一开始用CubeMX生成的默认配置,SFEC就是000,改了半天才发现。检查全局滤波器配置。如果
NonMatchingStd设成了FDCAN_ACCEPT_IN_RX_FIFO0,那不匹配的报文也会进FIFO0,看起来像是滤波器没生效。我建议调试阶段先设成FDCAN_REJECT,确认滤波器工作正常后再改。
4.2 双ID滤波器只收到一个ID
用双ID滤波器模式时,如果SFID1和SFID2配好了但只收到一个ID的报文,大概率是FIFO满了没及时读。FDCAN的RX FIFO默认是阻塞模式,满了之后新报文会覆盖最旧的或者被丢弃,取决于RXF0C寄存器的F0OM位。我设的是覆盖模式,结果旧的报文被冲掉了,看起来像是只收到了一个ID。
解决办法:要么加大FIFO深度,要么在中断里循环读取直到FIFO空。我后来把FIFO0和FIFO1都设成了16个元素,并且在回调里加了循环读取:
while (HAL_FDCAN_GetRxFifoFillLevel(hfdcan, FDCAN_RX_FIFO0) > 0) { HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &rxHeader, rxData); /* 分发处理 */ }4.3 扩展帧滤波器掩码模式匹配范围不对
扩展帧的掩码模式里,FilterID2的每一位对应FilterID1的对应位。为1表示必须匹配,为0表示忽略。我一开始把FilterID2设成了0x1FFFFFFF,以为这样能匹配所有扩展帧,结果发现只能匹配FilterID1完全相同的ID。
正确的做法是:如果你想匹配0x18FF0000到0x18FFFFFF这个范围,FilterID1设为0x18FF0000,FilterID2设为0x1FFF0000。这样高13位必须匹配,低16位任意。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 完全收不到报文 | FDCAN未启动或仍在Init模式 | 读CCCR寄存器的INIT位 |
| 收不到但总线有波形 | 滤波器SFEC=000或全局滤波器拒绝 | 检查滤波器配置和全局过滤器 |
| 只收到部分ID | FIFO满或滤波器元素不够 | 查FIFO填充级别,增加滤波器元素 |
| 中断不触发 | 中断未使能或NVIC未配置 | 检查HAL_FDCAN_ActivateNotification和NVIC |
| 收到的数据错位 | 消息RAM分配重叠 | 核对各区域起始地址和长度 |
| 扩展帧匹配范围不对 | 掩码设置错误 | 确认FilterID2的掩码位含义 |
4.5 几个实测有效的调试技巧
技巧一:用回环模式先验证滤波器逻辑。把FDCAN设成Loopback模式,自己发自己收,不依赖外部节点。这样能快速确认滤波器和FIFO配置是否正确。我每次改滤波器配置都会先在回环模式下跑一遍。
技巧二:把消息RAM的分配打印出来。写个函数读取SIDFC、XIDFC、RXF0C等寄存器的值,解析出起始地址和元素数量,通过串口打印。这样一眼就能看出有没有重叠。
技巧三:用HAL_FDCAN_GetErrorCounters()监控错误。如果总线上有错误帧,这个函数能读出TEC和REC的值。TEC持续增长说明发送有问题,REC增长说明接收有问题。
技巧四:滤波器元素数量宁多勿少。消息RAM有212个字,标准帧滤波器每个元素才1个字,多分配几个不心疼。我一开始只分了4个,后来加到6个,再后来加到8个,留点余量方便后期扩展。
5. 多ID接收的代码组织与性能优化
5.1 用查表法替代switch-case
当ID数量多起来之后,switch-case会变得很长,而且每次都要从头比较。我改用了查表法,把ID和处理函数的对应关系放在一个数组里:
typedef void (*CanHandlerFunc)(uint8_t *data, uint32_t len); typedef struct { uint32_t id; CanHandlerFunc handler; } CanIdHandler; const CanIdHandler canHandlerTable[] = { {0x100, ProcessMotorStatus}, {0x101, ProcessTemperature}, {0x102, ProcessCurrent}, {0x103, ProcessVoltage}, {0x104, ProcessSpeed}, {0x105, ProcessPosition}, }; void DispatchCanMessage(FDCAN_RxHeaderTypeDef *header, uint8_t *data) { for (int i = 0; i < sizeof(canHandlerTable)/sizeof(canHandlerTable[0]); i++) { if (canHandlerTable[i].id == header->Identifier) { canHandlerTable[i].handler(data, header->DataLength); return; } } }这样新增ID只需要在表里加一行,不用改分发逻辑。如果ID数量超过20个,可以考虑用二分查找或者哈希表进一步优化。
5.2 FIFO深度与中断频率的平衡
FIFO深度设多大合适?这个要看总线的负载率和你的处理速度。我实测下来,500kbps总线、6个节点、每个节点10ms发一帧的情况下,FIFO深度设8就够用了。但如果总线负载率超过50%,建议设16甚至32。
中断频率也要考虑。如果每个报文都触发一次中断,CPU开销会比较大。FDCAN支持FIFO水位中断,可以设置当FIFO里的报文数量达到某个阈值时才触发中断。通过HAL_FDCAN_ConfigInterruptLines()和HAL_FDCAN_ActivateNotification()配合使用。
我目前的方案是FIFO0用新消息中断(每条都触发),FIFO1用水位中断(攒够4条再触发)。这样高优先级的报文能及时处理,低优先级的批量处理减少中断次数。
5.3 消息RAM的优化分配
如果项目里标准帧和扩展帧都要处理,消息RAM的分配需要仔细规划。我的经验是:
- 标准帧滤波器元素:按实际ID数量分配,留2-4个余量
- 扩展帧滤波器元素:每个占2字,按实际数量分配
- RX FIFO 0:高优先级报文,深度8-16
- RX FIFO 1:低优先级报文,深度8-16
- TX FIFO:按发送频率分配,深度4-8
- TX Event FIFO:如果不用发送时间戳,可以设0
总用量控制在180字以内,留30字左右的余量给后期调整。
5.4 实际项目中的配置参数汇总
把我最终跑通的配置参数整理成表格,方便对照:
| 参数 | 值 | 说明 |
|---|---|---|
| 时钟源 | PLL1Q @ 160MHz | FDCAN内核时钟 |
| 仲裁段波特率 | 500 kbps | 预分频20,TSEG1=13,TSEG2=4 |
| 数据段波特率 | 2 Mbps | 预分频5,TSEG1=13,TSEG2=4 |
| 标准帧滤波器数量 | 6 | 实际使用3个双ID元素 |
| 扩展帧滤波器数量 | 2 | 实际使用1个掩码元素 |
| RX FIFO 0深度 | 16 | 高优先级报文 |
| RX FIFO 1深度 | 16 | 低优先级报文 |
| TX FIFO深度 | 8 | 发送缓冲区 |
| TX Event FIFO深度 | 8 | 发送事件记录 |
| 消息RAM总用量 | 122字 | 剩余90字余量 |
这套配置在72小时连续运行测试中没有出现丢帧或FIFO溢出,总线负载率约35%。
5.5 从bxCAN迁移到FDCAN的注意事项
如果你之前用的是STM32F1或F4系列的bxCAN,迁移到G474的FDCAN时要注意这几点:
第一,滤波器数量变少了但更灵活。bxCAN有14组滤波器,FDCAN的标准帧滤波器元素数量取决于你分配多少消息RAM,理论上可以很多,但每个元素只能覆盖1-2个ID。如果你的项目需要过滤大量ID,要重新规划。
第二,FIFO变成了两个独立的。bxCAN有FIFO0和FIFO1,每个3级深度。FDCAN的FIFO深度可配置,但两个FIFO的用途需要重新设计。
第三,中断处理方式不同。bxCAN的中断标志在CAN_RI0R等寄存器里,FDCAN的中断标志在FDCAN_IR寄存器里,而且中断线映射需要通过HAL_FDCAN_ConfigInterruptLines()配置。
第四,位时序计算方式不同。bxCAN用CAN_BTR一个寄存器,FDCAN用NBTP和DBTP两个寄存器分别控制仲裁段和数据段。CAN FD的数据段波特率可以比仲裁段高很多,这是bxCAN不具备的能力。
6. 几个容易被忽略的细节
6.1 消息RAM的访问权限
FDCAN的消息RAM在FDCAN处于Init模式时才能写入,进入Normal模式后只能读。所以滤波器配置必须在HAL_FDCAN_Start()之前完成。如果你在运行中想改滤波器,必须先调HAL_FDCAN_Stop()回到Init模式,改完再Start。
6.2 滤波器元素的默认状态
消息RAM上电后的初始值是不确定的,未使用的滤波器元素必须显式禁用(SFEC=000),否则可能匹配到意外的ID。我习惯在初始化时把所有滤波器元素都设成禁用,然后再逐个配置需要的。
6.3 中断优先级配置
FDCAN的中断优先级要在NVIC里配好。如果系统里还有别的中断(比如定时器、ADC),FDCAN的中断优先级不能太低,否则FIFO可能溢出。我一般把FDCAN的RX中断设成中等优先级(比如优先级3),比定时器中断低但比串口中断高。
6.4 总线终端电阻
这个和滤波器无关但很容易被忽略。CAN总线两端必须各接一个120欧姆的终端电阻,否则通信距离短的时候可能正常,距离一长就各种错误帧。我调试时用示波器量了总线波形,发现没有终端电阻时信号反射很严重,加上之后波形干净多了。
6.5 冷启动时的总线同步
FDCAN在总线空闲时会自动同步,但如果总线上其他节点已经在通信,FDCAN启动时可能需要等待一段时间才能同步上。如果启动后立即发送报文,可能会因为未同步而失败。我的做法是启动后延时100ms再开始发送,或者监听总线直到收到一帧有效报文再开始发送。
这套FDCAN滤波器配置方案在我目前的项目里已经稳定运行了三个多月,中间经历过总线负载突增、节点热插拔等场景,没有出现滤波器失效或FIFO溢出。如果你在配置过程中遇到其他问题,可以重点检查消息RAM分配和滤波器元素的SFEC字段,这两个地方是最容易出错的。