背景说明
先进先出队列(简称队列)是一种基于先进先出(FIFO)策略的集合类型。对于数据传输速度不同步的应用场景里,采用FIFO队列作为数据缓冲是十分有必要的。在单片机开发的项目中,笔者遇见应用FIFO缓冲的两个典型场景分别是串口数据收发与ADC高速采样。
FIFO队列最重要的指标之一就是队列长度问题,因为队列长度是有限的,有可能被填满,这就涉及到该机制的丢弃原则。常见的一个丢弃原则叫做Tail Drop机制,简单地说就是该队列如果已经满了,那么后续进入的报文被丢弃。还有一种原则是覆盖机制,简单的说就是如果队列已经满了,那么后续进入的数据将覆盖最前面的数据,队列中的数据始终保持着最新。
两种机制有各自的应用场景,但当队列已满时,都有不可避免的数据丢失。因此,在硬件资源足够的前提下,可以适当增大队列的深度,提高读操作的速度,以尽量避免队列满的情况。本文设计的FIFO队列模型采用了覆盖机制,读者可以根据需要将它改成Tail Drop机制。
库源码
头文件
- /*
- * fifo.h
- *
- * Created on: 2020年9月18日
- * Author: Tao
- */
- #ifndef SOURCE_ALWHALESLIB_SYSEXTEND_INC_FIFO_H_
- #define SOURCE_ALWHALESLIB_SYSEXTEND_INC_FIFO_H_
- #include "stm32f4xx.h"
- #include "stm32f4xx_conf.h"
- /**
- * 考虑到FIFO数据成员可能会是不同类型的数据,
- * 因此在此处重命名一个数据类型,
- * 并将此数据类型作为FIFO数据结构库的成员类型。
- * 这么做的好处是可以方便的修改FIFO的成员数据类型。
- */
- typedef uint16_t FIFO_DataType;
- typedef struct
- {
- uint32_t BufferSize; //数据缓存容量
- FIFO_DataType *Buffer; //数据缓存
- FIFO_DataType *Read_P; //读指针
- FIFO_DataType *Write_P; //写指针
- uint32_t DataCount; //剩余数据量
- } FIFO_Struct;
- #define FIFO_DATA_NUM 1
- #define FIFO_DATA_SIZE 10000
- extern FIFO_Struct FIFO_Data[FIFO_DATA_NUM];
- void FIFO_Init(FIFO_Struct *FIFO_Data);
- void FIFO_WriteData(FIFO_Struct *FIFO_Data,FIFO_DataType *Data, uint32_t length);
- void FIFO_WriteOneData(FIFO_Struct *FIFO_Data,FIFO_DataType Data);
- void FIFO_ReadData(FIFO_Struct *FIFO_Data,FIFO_DataType *Data, uint32_t length);
- uint16_t FIFO_ReadOneData(FIFO_Struct *FIFO_Data);
- uint8_t FIFO_IsDataFull(FIFO_Struct *FIFO_Data);
- uint8_t FIFO_IsDataEmpty(FIFO_Struct *FIFO_Data);
- uint32_t FIFO_GetDataCount(FIFO_Struct *FIFO_Data);
- #endif /* SOURCE_ALWHALESLIB_SYSEXTEND_INC_FIFO_H_ */
复制代码
源文件
- /*
- * fifo.c
- *
- * Created on: 2020年9月18日
- * Author: Tao
- */
- #include "fifo.h"
- static FIFO_DataType FIFO_DataBuffer[FIFO_DATA_NUM][FIFO_DATA_SIZE];
- FIFO_Struct FIFO_Data[FIFO_DATA_NUM] =
- {
- {
- .BufferSize = FIFO_DATA_SIZE,
- .Buffer = FIFO_DataBuffer[0],
- },
- };
- /**
- * @brief 初始化FIFO队列数据:清空数据、复位读写指针
- */
- void FIFO_Init(FIFO_Struct *FIFO_Data)
- {
- FIFO_Data->DataCount = 0;
- FIFO_Data->Read_P = FIFO_Data->Buffer;
- FIFO_Data->Write_P = FIFO_Data->Buffer;
- }
- /**
- * @brief 将数据写入FIFO队列,当数据超出缓冲区的大小时,停止写入数据
- * @param FIFO_Data: 需要操作的FIFO队列数据指针
- * @param Data: 需要写入的数据指针
- * @param length: 需要写入的数据长度
- */
- void FIFO_WriteData(FIFO_Struct *FIFO_Data,FIFO_DataType *Data, uint32_t length)
- {
- //将数据依次写入到队列缓冲区
- for(uint32_t index = 0; index < length; index++)
- {
- //如果计数器大于等于缓冲容量
- if(FIFO_Data->DataCount > FIFO_Data->BufferSize -1)
- {
- //退出写数据
- return;
- }
- //写入一个数据
- *FIFO_Data->Write_P = *Data;
- //写指针移动一位
- FIFO_Data->Write_P++;
- //数据缓存指针移动一位
- Data++;
- //计数器自增1
- FIFO_Data->DataCount++;
- //如果写指针已经到达缓冲区边界
- if(FIFO_Data->Write_P >= FIFO_Data->Buffer + FIFO_Data->BufferSize)
- {
- //使写指针回到缓冲区起点
- FIFO_Data->Write_P = FIFO_Data->Buffer;
- }
- }
- }
- /**
- * @brief 向FIFO队列中写入一个数据
- * @param FIFO_Data: 需要操作的FIFO队列数据指针
- * @param Data: 要写入的数据
- */
- void FIFO_WriteOneData(FIFO_Struct *FIFO_Data,FIFO_DataType Data)
- {
- FIFO_WriteData(FIFO_Data, &Data, 1);
- }
- /**
- * @brief 将数据从FIFO队列中读出,当缓冲区为空时,停止读出数据
- * @param FIFO_Data: 需要操作的FIFO队列数据指针
- * @param Data: 用来存放读出数据的指针
- * @param length: 需要读出的数据长度
- */
- void FIFO_ReadData(FIFO_Struct *FIFO_Data,FIFO_DataType *Data, uint32_t length)
- {
- //将缓冲区数据依次读出到Data数组中
- for(uint32_t index = 0; index < length; index++)
- {
- //缓冲区数据计数器为0时
- if(FIFO_Data->DataCount == 0)
- {
- //退出读数据
- return;
- }
- //如果数据计数器大于写指针减去缓冲区起始位置(说明写入的数据已经到达过缓冲区边界)
- if(FIFO_Data->Write_P - FIFO_Data->Buffer < FIFO_Data->DataCount)
- {
- //确定数据初始位置,并传递给读指针
- FIFO_Data->Read_P = FIFO_Data->BufferSize - FIFO_Data->DataCount + FIFO_Data->Write_P;
- }
- //写入的数据还未到达过缓冲区边界
- else
- {
- //确定数据初始位置,并传递给读指针
- FIFO_Data->Read_P = FIFO_Data->Write_P - FIFO_Data->DataCount;
- }
- //读出一个数据
- *Data = *FIFO_Data->Read_P;
- //读指针移动一位
- FIFO_Data->Read_P++;
- //数据缓存指针移动一位
- Data++;
- //计数器自减1
- FIFO_Data->DataCount--;
- //如果读指针已经到达缓冲区边界
- if(FIFO_Data->Read_P >= FIFO_Data->Buffer + FIFO_Data->BufferSize)
- {
- //使读指针回到缓冲区起点
- FIFO_Data->Read_P = FIFO_Data->Buffer;
- }
- }
- }
- /**
- * @brief 从FIFO队列中读出一个数据
- * @param FIFO_Data: 需要操作的FIFO队列数据指针
- * @retval 读出的数据
- */
- FIFO_DataType FIFO_ReadOneData(FIFO_Struct *FIFO_Data)
- {
- FIFO_DataType tempData;
- FIFO_ReadData(FIFO_Data, &tempData, 1);
- return tempData;
- }
- /**
- * @brief 判断队列数据是否已满
- * @param FIFO_Data: 需要操作的FIFO队列数据指针
- * @retval 指示队列是否已满
- * @arg 0: 队列未满
- * @arg 1: 队列已满
- */
- uint8_t FIFO_IsDataFull(FIFO_Struct *FIFO_Data)
- {
- if(FIFO_Data->DataCount >= FIFO_Data->BufferSize)
- {
- return 1;
- }
- else
- {
- return 0;
- }
- }
- /**
- * @brief 判断队列数据是否为空
- * @param FIFO_Data: 需要操作的FIFO队列数据指针
- * @retval 指示队列是否为空
- * @arg 0: 队列不空
- * @arg 1: 队列为空
- */
- uint8_t FIFO_IsDataEmpty(FIFO_Struct *FIFO_Data)
- {
- if(FIFO_Data->DataCount == 0)
- {
- return 1;
- }
- else
- {
- return 0;
- }
- }
- /**
- * @brief 获取队列数据的数量
- * @param FIFO_Data: 需要操作的FIFO队列数据指针
- * @retval 队列数据的数量
- */
- uint32_t FIFO_GetDataCount(FIFO_Struct *FIFO_Data)
- {
- return FIFO_Data->DataCount;
- }
复制代码
应用指南
声明并初始化FIFO队列数据
根据项目的需要可以选择声明一个FIFO数据类型的变量或者数组,并初始化。可以依次为每一个成员赋值以完成初始化,或者是为FIFO结构体类型变量分配缓存区后,通过void FIFO_Init(FIFO_Struct *FIFO_Data)来完成其他成员的初始化。
- #define FIFO_DATA_NUM 2
- #define FIFO_DATA_SIZE 10000
- extern FIFO_Struct FIFO_Data[FIFO_DATA_NUM];
- FIFO_Struct FIFO_Data[FIFO_DATA_NUM] =
- {
- {
- .BufferSize = FIFO_DATA_SIZE,
- .Buffer = FIFO_DataBuffer[0],
- },
- {
- .BufferSize = FIFO_DATA_SIZE,
- .Buffer = FIFO_DataBuffer[1],
- },
- };
- void User_Init()
- {
- /*省略无关代码*/
- FIFO_Init(&FIFO_Data[0]);
- FIFO_Init(&FIFO_Data[1]);
- /*省略无关代码*/
- }
复制代码
在需要写入数据的地方
根据写入数据的数量,可以选择调用void FIFO_WriteData(FIFO_Struct *FIFO_Data,FIFO_DataType *Data, uint32_t length)或者void FIFO_WriteOneData(FIFO_Struct *FIFO_Data,FIFO_DataType Data)。例如:
- /**
- * @brief 在外部中断14的服务函数中周期性的写入AD7606的高速采样数据
- */
- void EXTI14Triggered()
- {
- if(User_StartDAQ != 0)
- {
- AD7606_RefreshData();
- FIFO_WriteOneData(&FIFO_Data[0], AD7606_RawData[0]);
- }
- }
复制代码
在需要读出数据的地方
根据读出数据的数量,可以选择调用void FIFO_ReadData(FIFO_Struct *FIFO_Data,FIFO_DataType *Data, uint32_t length)或者uint16_t FIFO_ReadOneData(FIFO_Struct *FIFO_Data)。例如:
- /**
- * @brief This function is called by timer update interrupt handler, which will be executed periodically.
- */
- void User_RefreshData()
- {
- //注意SensorData数组顺序与ADS1115数据通道顺序不一致
- SensorData[0].value = ADS1115_Data[1];
- SensorData[1].value = ADS1115_Data[0];
- //注意SensorData数组顺序与AD7606数据通道顺序不一致
- SensorData[2].value = (int16_t)AD7606_RawData[1]/32768.0*5000;
- SensorData[3].value = (int16_t)FIFO_ReadOneData(&FIFO_Data[0])/32768.0*5000;
- User_SetOutputCurrent();
- User_RefreshSingalSwitch();
- }
复制代码
FIFO队列的数据类型
由于C语言不支持模板特性,因此采用typedef关键字重命名数据类型,并将此别名用作FIFO队列的数据成员类型。根据不同项目的不同需求,可以更改数据类型:
- typedef float FIFO_DataType;
复制代码
|