目录
1. FIFO概述
2. 软件FIFO的基本原理
3. 软件FIFO程序讲解
4. 完整项目工程代码
1. FIFO概述
在嵌入式系统开发中,串口通信是一种广泛应用的数据传输方式。然而,由于串口数据接收的异步性和不确定性,如何有效地管理接收到的数据成为一个关键问题。软件 FIFO(First - In - First - Out,先进先出队列)作为一种数据结构,其核心原则是:最先进入队列的元素将最先被处理或移除。这种机制为串口数据接收缓冲提供了高效、可靠的解决方案。
2. 软件FIFO的基本原理
软件 FIFO 本质上是一种线性数据结构,遵循先进先出的原则。它就像一个管道,数据从一端进入(入队),从另一端取出(出队)。在实现上,通常使用数组或链表来存储数据。 以数组实现为例,我们需要两个指针:读指针(read pointer)和写指针(write pointer)。当有数据到达时,写指针负责将数据写入数组的相应位置,然后写指针向后移动;当需要读取数据时,读指针从数组中取出数据,并同样向后移动。当读指针和写指针相遇时,表示 FIFO 为空;当写指针追上读指针(且两者之间的距离达到数组大小)时,表示 FIFO 已满。
串口通信时,数据以字节为单位逐位传输。由于 CPU 处理速度和串口接收速度的差异,可能会出现数据丢失的情况。软件 FIFO 作为串口数据接收缓冲,可以在数据接收时将其暂存起来,CPU 可以根据自身的处理节奏从 FIFO 中读取数据,从而避免数据丢失,确保数据的完整性。
3. 软件FIFO程序讲解
3.1 通用接口定义
/* FIFO 结构定义,
用于存放FIFO缓冲区信息及操作的指针信息
*/
typedef struct {
volatile unsigned int indexRead; // 读指针位置
volatile unsigned int indexWrite; // 写指针位置
unsigned int size; // 大小
unsigned int free; // 剩余容量
unsigned char *buf; // buf- 缓冲区地址
}FIFO_T;
unsigned char FifoInit(FIFO_T *fifo, unsigned char *buf, unsigned int bufLen); // FIFO 初始化函数
unsigned char FifoPush(FIFO_T *fifo, unsigned char data); // FIFO 入队单个字节
unsigned int FifoPushMult(FIFO_T *fifo, unsigned char* data, unsigned int len); // FIFO 入队多个字节
int FifoPop(FIFO_T *fifo); // FIFO 出队单个字节
int FifoPopMult(FIFO_T *fifo, unsigned char *buf, unsigned int len); // FIFO 出队多个字节
unsigned int FifoGetCount(FIFO_T *fifo); // 获取当前FIFO缓冲区已缓冲的数据长度
unsigned int FifoGetFreeCount(FIFO_T *fifo); // 获取当前FIFO缓冲区剩余空间大小
3.2 FifoInit()分配FIFO缓冲区及缓冲区大小
/*!
*
* @param[in] fifo : FIFO 结构指针
* @param[in] buf : FIFO 缓冲区指针,用于缓冲数据
* @param[in] bufLen : FIFO 缓冲区大小(该值必须为2的倍数)
*
* @param[out] none
* @return 0 ; 成功
* @return 1 ; 传入参数无效
* @return 其他值 ; 出错
*
* @brief fifo初始化.
*
*/
unsigned char FifoInit(FIFO_T *fifo, unsigned char *buf, unsigned int bufLen)
{
if((!fifo) ||
(!buf) ||
(bufLen % 2)) {
return 1;
}
fifo->buf = buf;
fifo->size = bufLen;
fifo->free = bufLen;
memset(fifo->buf, 0, fifo->size);
fifo->indexRead = 0;
fifo->indexWrite = 0;
return 0;
}
// 该函数实现FIFO缓冲区的分配和操作指针的初始化
FifoInit应用
#define DEBUG_USART_BUF_SIZE (2 * 512) // 缓冲区大小
// 定义FIFO缓冲区
static unsigned char s_DebugBuf[DEBUG_USART_BUF_SIZE];
static FIFO_T s_DebugFifo;
/*!
*
* @param[in] none
* @param[out] none
* @return none
*
* @brief 串口开启接收.
*
*/
void DebugUartStartReceive(void)
{
unsigned int temp = 0;
// 清除中断
temp = (DEBUG_HUART.Instance)->SR; // 清空闲中断
temp = (DEBUG_HUART.Instance)->DR;
__HAL_UART_CLEAR_FLAG(&DEBUG_HUART, DEBUG_CLEAR_FLAG);
__disable_irq(); // 关中断
FifoInit(&s_DebugFifo, s_DebugBuf, DEBUG_USART_BUF_SIZE); // 初始化FIFO缓冲区
s_debugRcv.len = 0;
s_debugRcv.timeCnt = 0;
__enable_irq(); // 开中断
__HAL_UART_ENABLE_IT(&DEBUG_HUART,DEBUG_UART_IT);
}
3.3 FifoPush()缓冲数据
/*!
*
* @param[in] fifo : FIFO 结构指针
* @param[in] data : 要入队的数据
*
* @param[out] none
* @return 0 ; 成功
* @return 1 ; FIFO 已满
* @return 其他值 ; 出错
*
* @brief fifo 入队单个字节.
*
*/
unsigned char FifoPush(FIFO_T *fifo, unsigned char data)
{
if(!fifo) {
return 2;
}
if(0 == fifo->free) { // 队列已满
return 1;
}
fifo->buf[fifo->indexWrite++] = data;
if(fifo->indexWrite == fifo->size) {
fifo->indexWrite = 0;
}
fifo->free--;
return 0;
}
// 该函数可在串口中断中进行调用,缓冲接受到的数据
串口中断入队接收到的数据
/*!
*
* @param[in] none
* @param[out] none
* @return none
*
* @brief 中断处理函数.
*
*/
static void uartIrqCallback(UART_HandleTypeDef* uartHandle)
{
unsigned int flagTmp = (uartHandle->Instance)->SR;
if(!uartHandle) return ;
if(UART_FLAG_RXNE == (flagTmp & UART_FLAG_RXNE)) {
FifoPush(&s_DebugFifo, (uartHandle->Instance)->DR);
}
__HAL_UART_CLEAR_OREFLAG(uartHandle);
HAL_UART_IRQHandler(uartHandle);
}
3.4 FifoPopMult() 读取数据
/*!
*
* @param[in] fifo : FIFO 结构指针
* @param[in] buf : 缓冲区指针,存放读取的数据
* @param[in] len : 缓冲区长度
*
* @param[out] none
* @return >=0 ; 实际读取到的数据长度
* @return 其他值 ; 出错
*
* @brief fifo 出队多个字节.
*
*/
int FifoPopMult(FIFO_T *fifo, unsigned char *buf, unsigned int len)
{
int rLen = 0;
unsigned int val = 0;
if(!fifo ||
(!buf)) {
return -1;
}
rLen = fifo->size - fifo->free;
if(rLen <= len) {
len = rLen;
}
rLen = 0;
while(len) {
val = fifo->size - fifo->indexRead;
if(val >= len) { // 数据连续,直接读出
val = len;
}
memcpy(&buf[rLen], &fifo->buf[fifo->indexRead], val);
len -= val;
rLen += val;
fifo->free += val;
fifo->indexRead = (fifo->indexRead + val) & (fifo->size - 1);
}
return rLen;
}
// 若数据接受完成后( 使用函数 FifoGetCount() 判断数据是否有增加 ),
// 使用该函数一次性从FIFO缓冲区里读出数据
/*!
*
* @param[in] buf : 存放数据的缓冲区的地址 (若 *buf 为 NULL, 就返回原始缓冲器地址)
* @param[in] buf : bufLen,缓冲区长度(单位:字节)
* @param[out] none
* @return 实际读取到的数据大小(单位:字节)
*
* @brief 读取接收到的数据.
*
*/
unsigned int DebugUartRead(char** buf, unsigned int bufLen)
{
#if(1 == DEBUG_ENABLE)
// char* bufTmp = NULL;
unsigned int rxLen = 0;
if(0 == debugUartIsRceved(debugUartGetRxLen, DebugUartStartReceive)) return 0;
rxLen = debugUartGetRxLen();
if(rxLen <= bufLen) bufLen = rxLen;
// 拷贝数据到缓冲区
// 若 (*buf) 为 NULL ,就返回 缓冲器地址
if(NULL == (*buf) ) {
return 0; // 此操作未使用,直接返回0
}
else {
bufLen = FifoPopMult(&s_DebugFifo, (void*)(*buf), bufLen);
}
#else
bufLen = 0;
#endif
return bufLen;
}
4. 完整项目工程代码
- fifo源程序在工程 TestPrjFifo/Core/fifo 目录下;
- 将 fifo 移植到其他工程,按上述代码讲解操作即可使用.