写在前面
环形缓冲区是嵌入式应用(医疗电子、消费电子、工控)中常用的数据结构模型,如音视频流、通信总线数据收发等。一些操作系统、第三方库都提供现成的环形缓冲区接口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;
}