STM32开发项目:FIFO数据模型库

日期作者版本说明
2020.09.23TaoV1.0发布了第一版库函数
2023.06.27TaoV1.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;
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全能骑士涛锅锅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值