软件 FIFO-串口数据接收缓冲的得力助手

3 阅读6分钟

目录

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.h

/* 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 源程序及用例

  • fifo源程序在工程 TestPrjFifo/Core/fifo 目录下;
  • 将 fifo 移植到其他工程,按上述代码讲解操作即可使用.