日期 | 作者 | 版本 | 说明 |
---|---|---|---|
2020.09.23 | Tao | V1.0 | 发布了第一版库函数 |
2023.06.27 | Tao | V1.1 | 根据读者建议,为避免误解,将库源码与应用示例代码中的宏定义FIFO_DATA_NUM 的值统一为2,保持一致 |
背景说明
先进先出队列(简称队列)是一种基于先进先出(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 2
#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],
},
{
.BufferSize = FIFO_DATA_SIZE,
.Buffer = FIFO_DataBuffer[1],
},
};
/**
* @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;