在 STM32系列的 FDCAN 中,接收报文既可以进入Rx FIFO,也可以被定向写入Rx Buffer。本文用尽量简洁的方式说明二者的区别、为什么要同时存在,以及在 STM32H7 HAL 库下如何配置。
一、前言
如果你之前使用过 STM32 传统bxCAN,对接收路径的印象大多是,报文进入Rx FIFO0或Rx FIFO1,软件再从FIFO中取出并解析。而在 STM32H7或其它后推出的STM32系列的FDCAN中,接收机制更灵活,报文命中过滤器后,除了进入 Rx FIFO,还可以直接进入Rx Buffer。这意味着 FDCAN 不再只有“统一排队接收”这一种模式,而是同时支持顺序接收和定向接收。这也是 FDCAN 相比传统 CAN 外设更灵活的一个地方。
二、FIFO和Buffer的特性
Rx FIFO是顺序接收队列,Rx Buffer是固定接收存储单元集合。FIFO方式适合接收大量普通报文,Buffer方式更适合接收少量关键报文。Rx Buffer 不是队列,而是 Message RAM 中一组按索引划分的固定接收存储位置。
例如:Rx Buffer 0、Rx Buffer 1、Rx Buffer 2。每个 Buffer 元素都对应一个固定位置。过滤器命中后,报文会被硬件直接写入指定索引对应的存储单元,软件再按 Buffer 索引读取对应的报文。
三、为什么既有FIFO,又有Buffer
如果只有 FIFO,那么所有报文都必须先进入统一队列,再由软件依序取出并按 ID 分类。
这样会带来几个问题:关键报文和普通报文混在一起;软件必须做额外分发;在流量较大时,关键报文的处理路径不够直接;反过来,如果只有 Buffer,也不合理。因为大量普通报文并不适合一个个固定映射到指定位置,配置啰嗦,通用性差。FDCAN 同时提供这两种机制,本质上是为了兼顾两类需求:
- FIFO 解决大量普通报文的顺序接收问题。报文须再次识别分发。Buffer 解决关键报文的定向接收问题。报文直达。
这样的话,普通报文走队列,处理自然,关键报文直达固定位置,路径明确,降低软件二次分拣成本,让接收架构更灵活。简单说,FIFO管“通用流量”,Buffer管“关键流量”。
四、基于STM32H7 HAL的过滤器配置及验证
下面我将FDCAN配置在传统经典内部回环模式,配置了3个过滤器,其中2个过滤器只接收特定ID的消息,过滤后的消息存放到指定的BUFFER,另外1个过滤器针对一定范围的ID的消息进行接收,接收到的消息将存放到FIFO0。FDCAN的基本配置如下:
三个过滤器的具体配置是这样的:第一个,允许通过的消息ID范围是0x300~0x3FF,消息存放于Rx FIFO0。第二个,允许通过的消息ID固定是0x111,消息存放于到Rx Buffer0。第三个,允许通过的消息ID固定是0x222,消息存放于到Rx Buffer1。
三个过滤器的参考配置代码如下:
// 0x300~0x3FF -> Rx FIFO 0 (FIFO接收模式)static void FDCAN1_Filter0_ToFifo0_Config(void){FDCAN_FilterTypeDef sFilterConfig = {0};/* 标准帧滤波器0:范围模式,接收 0x300~0x3FF */sFilterConfig.IdType = FDCAN_STANDARD_ID; /* 标准ID */sFilterConfig.FilterIndex = 0; /* Index */sFilterConfig.FilterType = FDCAN_FILTER_RANGE; /* 范围模式 */sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; /* 命中后送入FIFO0 */sFilterConfig.FilterID1 = 0x300; /* 范围起始 */sFilterConfig.FilterID2 = 0x3FF; /* 范围结束 */if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK){Error_Handler();}}// 0x111 -> Rx Buffer0 (BUFFER 接收模式)static void FDCAN1_Filter1_ToRxBuffer0_Config(void){FDCAN_FilterTypeDef sFilterConfig = {0};sFilterConfig.IdType = FDCAN_STANDARD_ID;sFilterConfig.FilterIndex = 1; /* Index 1:TO_RXBUFFER 0*/sFilterConfig.FilterType = FDCAN_FILTER_MASK;sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXBUFFER;sFilterConfig.FilterID1 = 0x111;sFilterConfig.FilterID2 = 0x7FF; // 精确匹配sFilterConfig.RxBufferIndex = 0; /* 命中后送入BUFFER 0 */if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK){Error_Handler();}}// 0x222 -> Rx Buffer1 (BUFFER 接收模式)static void FDCAN1_Filter2_ToRxBuffer1_Config(void){FDCAN_FilterTypeDef sFilterConfig = {0};sFilterConfig.IdType = FDCAN_STANDARD_ID;sFilterConfig.FilterIndex = 2; /* Index 2:TO_RXBUFFER 1*/sFilterConfig.FilterType = FDCAN_FILTER_MASK;sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXBUFFER;sFilterConfig.FilterID1 = 0x222;sFilterConfig.FilterID2 = 0x7FF; // 精确匹配sFilterConfig.RxBufferIndex = 1; /* 命中后送入BUFFER 1 */if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK){Error_Handler();}}
启动基于FIFO和BUFFER接收的新消息CAN中断。【注:这里没启用FIFO满和丢消息中断】
if (HAL_FDCAN_ActivateNotification(&hfdcan1,FDCAN_IT_RX_FIFO0_NEW_MESSAGE|FDCAN_IT_RX_BUFFER_NEW_MESSAGE,0) != HAL_OK){Error_Handler();}
下面是用于测试的CAN发送函数,每次一次性发送3帧数据。
void FDCAN1_Send3FramesOnce(void){static uint8_t cnt = 0;static uint16_t id_forFIFO = 0x300;HAL_StatusTypeDef status;uint8_t data1[8] = {0x11, cnt, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};uint8_t data2[8] = {0x22, cnt, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};uint8_t data3[8] = {0x33, cnt, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27};if (id_forFIFO++ > 0x3FF){id_forFIFO = 0x300;}status = FDCAN1_SendStdData(id_forFIFO, data1);uart3_log("Message 0x%03X %srn", id_forFIFO, (status == HAL_OK) ? "TX Successfully!" : "TX failed");status = FDCAN1_SendStdData(0x111, data2);uart3_log("Message 0x%03X %srn", 0x111, (status == HAL_OK) ? "TX Successfully!" : "TX failed");status = FDCAN1_SendStdData(0x222, data3);uart3_log("Message 0x%03X %srn", 0x222, (status == HAL_OK) ? "TX Successfully!" : "TX failed");uart3_log("---- This round TX done, cnt=%u ----rnrn", cnt);cnt++;}
在HAL_FDCAN_IRQHandler()中断服务函数里区分消息是来自FIFO还是BUFFER,再调用库准备的相关回调函数。下面是基于FIFO接收的回调处理函数:
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs){if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != 0U){if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &g_fifo0_rx_hdr,g_fifo0_rx_data) == HAL_OK){/* 略 */}}}
下面是基于Buffer接收的回调处理函数:
void HAL_FDCAN_RxBufferNewMessageCallback(FDCAN_HandleTypeDef *hfdcan){//**************/* 检查 Rx Buffer 0 */if (HAL_FDCAN_IsRxBufferMessageAvailable(hfdcan, 0) != 0U){if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_BUFFER0, &g_rxbuf0_hdr,g_rxbuf0_data) == HAL_OK){/* 略 */}}/* 检查 Rx Buffer 1 */if (HAL_FDCAN_IsRxBufferMessageAvailable(hfdcan, 1) != 0U){if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_BUFFER1, &g_rxbuf1_hdr,g_rxbuf1_data) == HAL_OK){/* 略 */}}}
下面是演示过程中某时刻的打印输出结果。循环发送与接收,每几秒钟发送3帧数据。图中红色框内容是发送完成后输出提示,绿色方框的内容是接收到消息后的输出显示。三帧数据中,其中1帧经滤波后存放在FIFO,另外2帧经滤波后存放在CAN接收BUFFER。
OK,今天的话题就分享到这里,算是抛砖引玉。下次再聊~!
245