• 正文
  • 相关推荐
申请入驻 产业图谱

STM32 FDCAN的Rx Buffer与Rx FIFO的差别及配置演示

7小时前
245
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

在 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,今天的话题就分享到这里,算是抛砖引玉。下次再聊~!

相关推荐