【组件】通用环形缓冲区模块

写在前面

环形缓冲区是嵌入式应用(医疗电子、消费电子、工控)中常用的数据结构模型,如音视频流、通信总线数据收发等。一些操作系统、第三方库都提供现成的环形缓冲区接口API,性能和安全性都有保障,多线程访问互斥机制,空内存访问保护等。

1.基本原则

先进先出(FIFO),重复使用。

2.实现原理

环形缓冲区,顾名思义就是一段循环使用的一段内存。通过写指针向“空白内存”(未写入过或者已经被读出的内存)写入数据并记录,读指针从已写的内存读取数据并记录,当指针访问到内存最后一个位置时,再折回第一个内存位置,达到循环效果。

3.注意事项

1)环形缓冲区本质是“生产者消费者”模型,必须先有生产(写)后有消费(读),不可提前消费(读);

2)注意程序效率问题,如果读效率过低,导致“生产”过剩,从而覆盖未读出的数据,导致出错,此时需增加环形缓冲区内存大小或者优化代码效率;

3)单一线程访问时是安全的,多线程访问时,为保证数据安全性必须加互斥锁机制;

4.应用场合

处理串口(RS232)接收数据命令:

串口中断--->数据接收--->写入环形缓冲区--->数据处理线程读取环形缓冲区--->处理有效数据。

5.实现方式

头文件


#ifndef _FIFO_H_
#define _FIFO_H_

#include <stdbool.h>
#include <stdint.h>

typedef struct
{
	uint8_t *buf;      	/* 缓冲区 */
	uint32_t buf_size;    	/* 缓冲区大小 */
	uint8_t *pwrite;      	/* 写指针 */
	uint8_t *pread;       	/* 读指针 */
	uint8_t	write_cover;    /* 读覆盖标识 */
	uint8_t read_cover;	/* 写覆盖标识 */
	void (*lock)(void);	/* 互斥上锁 */
	void (*unlock)(void);	/* 互斥解锁 */
}_fifo_t;


extern void fifo_register(_fifo_t *pfifo, uint8_t *pfifo_buf, uint32_t size);
extern void fifo_release(_fifo_t *pfifo);
extern uint32_t fifo_write(_fifo_t *pfifo, const uint8_t *pbuf, uint32_t size);
extern uint32_t fifo_read(_fifo_t *pfifo, uint8_t *pbuf, uint32_t size);
extern uint32_t fifo_get_total_size(_fifo_t *pfifo);
extern uint32_t fifo_get_free_size(_fifo_t *pfifo);
extern uint32_t fifo_get_occupy_size(_fifo_t *pfifo);

#endif

源文件

#include <stddef.h>
#include "fifo.h"


/**
  * @brief  注册一个fifo
  * @param  pfifo: fifo结构体指针
		    pfifo_buf: fifo内存块
		    size: 长度
  * @retval none
*/
void fifo_register(_fifo_t *pfifo, uint8_t *pfifo_buf, uint32_t size)
{
	pfifo->buf_size = size;
	pfifo->buf 	= pfifo_buf;
	pfifo->pwrite = pfifo->buf;
	pfifo->pread  = pfifo->buf;
	pfifo->write_cover = 0;
	pfifo->read_cover  = 0;
}

/**
  * @brief  释放fifo
  * @param  pfifo: fifo结构体指针
  * @retval none
*/
void fifo_release(_fifo_t *pfifo)
{
	pfifo->buf_size = 0;
	pfifo->buf 	= NULL;
	pfifo->pwrite = 0;
	pfifo->pread  = 0;
	pfifo->write_cover = 0;
	pfifo->read_cover  = 0;
}

/**
  * @brief  往fifo写数据
  * @param  pfifo: fifo结构体指针
		    pbuf: 待写数据
		    size: 待写数据大小
  * @retval 实际写大小
*/
uint32_t fifo_write(_fifo_t *pfifo, const uint8_t *pbuf, uint32_t size)
{
	uint32_t w_size= 0,free_size = 0;
	
	if ((size==0) || (pfifo==NULL) || (pbuf==NULL))
	{
		return 0;
	}

        free_size = fifo_get_free_size(pfifo);
        if(free_size == 0)
        {
            return 0;
        }

        if(free_size < size)
        {
            size = free_size;
        }

	w_size = size;
	while(w_size-- > 0)
	{
		*pfifo->pwrite++ = *pbuf++;
		if (pfifo->pwrite >= &(pfifo->buf[pfifo->buf_size])) 
		{
			pfifo->pwrite = pfifo->buf;
			pfifo->write_cover = ~pfifo->write_cover;
		}
	}
	
	return size;
}

/**
  * @brief  从fifo读数据
  * @param  pfifo: fifo结构体指针
		    pbuf: 待读数据缓存
		    size: 待读数据大小
  * @retval 实际读大小
*/
uint32_t fifo_read(_fifo_t *pfifo, uint8_t *pbuf, uint32_t size)
{
	uint32_t r_size = 0,occupy_size = 0;
	
	if ((size==0) || (pfifo==NULL) || (pbuf==NULL))
	{
		return 0;
	}
    
        occupy_size = fifo_get_occupy_size(pfifo);
        if(occupy_size == 0)
        {
            return 0;
        }
    
        if(occupy_size < size)
        {
            size = occupy_size;
        }

	r_size = size;
	while(r_size-- > 0)
	{
		*pbuf++ = *pfifo->pread++;
		if (pfifo->pread >= &(pfifo->buf[pfifo->buf_size]))
		{
			pfifo->pread = pfifo->buf;
			pfifo->read_cover = ~pfifo->read_cover;
		}
	}
	
	return size;
}

/**
  * @brief  获取fifo空间大小
  * @param  pfifo: fifo结构体指针
  * @retval fifo大小
*/
uint32_t fifo_get_total_size(_fifo_t *pfifo)
{
	if (pfifo==NULL)
		return 0;
	
	return pfifo->buf_size;
}

/**
  * @brief  获取fifo空闲空间大小
  * @param  pfifo: fifo结构体指针
  * @retval 空闲空间大小
*/
uint32_t fifo_get_free_size(_fifo_t *pfifo)
{
	uint32_t size;

	if (pfifo==NULL)
		return 0;
	
	if (pfifo->pwrite == pfifo->pread)
	{
		if(pfifo->write_cover == pfifo->read_cover)
			size = pfifo->buf_size;
		else
			size = 0;
	}
	else
	{
		if (pfifo->pwrite > pfifo->pread)
			size = (uint32_t)&(pfifo->buf[pfifo->buf_size]) - (uint32_t)&(pfifo->buf[0]) - (uint32_t)(pfifo->pwrite) + (uint32_t)(pfifo->pread);
		else
			size = pfifo->pread - pfifo->pwrite;
	}
	return size;
}

/**
  * @brief  获取fifo已用空间大小
  * @param  pfifo: fifo结构体指针
  * @retval fifo已用大小
*/
uint32_t fifo_get_occupy_size(_fifo_t *pfifo)
{
	uint32_t size;

	if (pfifo==NULL)
		return 0;
	
	size = pfifo->buf_size - fifo_get_free_size(pfifo);
	return size;
}

其中,注意点是环形缓冲区“满”与“空”状态处理,这里使用了读、写指针的“翻转”标识,即当读、写指针访问到缓冲区最后地址时,访问地址折回初始地址,此时做一个翻转标识;当读地址和写地址相等时,如果读、写翻转标识相等,说明此时缓冲区为“空状态”,否则缓冲区为“满”状态。

5.应用例子

注册一个512字节大小环形缓冲区,然后往缓冲区写入数据,接着读出来。

#include "fifo.h"

static uint8_t	buf_0[512];
static _fifo_t  fifo_0;

int main(void)
{
	uint8_t w_data[] = {0x01,0x02,0x03};
	uint8_t	r_data[10];
	
	fifo_register(&fifo_0, buf_0, sizeof(buf_0));
	fifo_write(&fifo_0, (const uint8_t*)w_data, sizeof(data));
	fifo_read(&fifo_0, r_data, 1);
	fifo_read(&fifo_0, &r_data[1], 2);

	return 0;
}
原创文章 128 获赞 147 访问量 36万+

猜你喜欢

转载自blog.csdn.net/qq_20553613/article/details/78902689