前面我们实现了循环缓冲区FIFO:https://mp.weixin.qq.com/s/MvL9eDesyuxD60fnbl1nag.
在串口驱动:https://mp.weixin.qq.com/s/vzjWu2LxpVGZw-msCooh8Q.
PWM音频采集与播放:https://mp.weixin.qq.com/s/nCSw743V5iZjGzrV1oQK4Q
等应用场景都有应用。
但是以上循环缓冲区FIFO还有一些应用场景并不能很方便和高效的使用。
比如在音频应用场景,除了”PWM音频采集与播放”可能还会涉及到算法处理,此时应用场景是”采集-算法-输出”, 多了算法处理的过程,此时上述FIFO就显得不是很适用。
我们来看这种应用场景对FIFO的需求:
1.数据链路加长,尽量避免数据拷贝带来的开销和占用系统总线带宽显得很重要。原来的FIFO实现都是从FIFO拷贝到用户存储再去使用,拷贝次数较多。
2.充分利用硬件DMA的特性,原来的FIFO方式,每次写入的位置不一定是对齐的,且可能绕回不是一片连续的空间不适合应用DMA等。
3.效率很重要,缓冲区大小一般是固定值,比如音频算法可能一次处理10ms的数据量,那么就可以以这个颗粒度进行采集,运算和输出。此时使用池比原来的环形FIFO更合适。
综合以上应用场景和需求,我们继续造一个满足不同应用场景的FIFO的轮子,我们就叫做FIFO_POOL,FIFO池。
数据结构还是和原来的环形FIFO一样,但是存储空间分为固定大小的分块,不再和原来一样可以任意大小,这样每一次in和out的数据块都是连续的,并且可以配置为固定对齐,这样硬件DMA等可以直接使用,不需要搬运到buffer中。
然后还有一个大的不同是状态更新和空间使用时间维度分离了,原来是in和out搬运完数据,同时就更新完相关状态(in,out指针,有效数据量等)。现在是获取到当前in和out的位置,然后使用这一片空间,使用完后再更新相关状态。
和原来的FIFO实现类似
/**
* \struct fifo_pool_st
* FIFO_POOL缓冲区结构.
*/
typedef struct
{
uint32_t in; /**< 写入索引 */
uint32_t out; /**< 读出索引 */
uint32_t pool_cnt; /**< 有效数据块数 */
uint32_t pool_len; /**< 每个块长度 */
uint32_t pool_num; /**< 最大块数 */
uint8_t* buffer; /**< 缓存,用户分配 */
} fifo_pool_st;
In相关接口
/**
* \fn fifo_pool_in
* 往fifo pool里写数据
* \param[in] dev \ref fifo_pool_st
* \param[in] buffer 待写入的数据
* \param[in] len 待写入的长度
* \retval 返回实际写入的数据量
*/
uint32_t fifo_pool_in(fifo_pool_st* dev, uint8_t* buffer, uint32_t len);
/**
* \fn fifo_pool_getinaddr
* 获取fifo pool当前可写入地址
* \param[in] dev \ref fifo_pool_st
* \retval 返回可写入地址
*/
uint8_t* fifo_pool_getinaddr(fifo_pool_st* dev);
/**
* \fn fifo_pool_incinaddr
* 递增写入指针
* \param[in] dev \ref fifo_pool_st
*/
void fifo_pool_incinaddr(fifo_pool_st* dev);
Out相关接口
/**
* \fn fifo_pool_out
* 从fifo pool读出数据
* \param[in] dev \ref fifo_pool_st
* \param[in] buffer 存读出的数据
* \param[in] len 需要读出的数据长度
* \retval 返回实际读出的数据量 返回0表示满
*/
uint32_t fifo_pool_out(fifo_pool_st* dev, uint8_t* buffer, uint32_t len);
/**
* \fn fifo_pool_getoutaddr
* 获取fifo pool当前可读出地址
* \param[in] dev \ref fifo_pool_st
* \retval 返回可读出地址, 返回0表示空
*/
uint8_t* fifo_pool_getoutaddr(fifo_pool_st* dev);
/**
* \fn fifo_pool_incoutaddr
* 递增读出指针
* \param[in] dev \ref fifo_pool_st
*/
void fifo_pool_incoutaddr(fifo_pool_st* dev);
获取当前有效数据块数
/**
* \fn fifo_pool_getpoolnum
* 获取有效的数据块数
* \param[in] dev \ref fifo_pool_st
*/
uint32_t fifo_pool_getpoolnum(fifo_pool_st* dev);
#include
#include "fifo_pool.h"
#define FIFO_POOL_PARAM_CHECK 0
/**
* 往fifo pool里写数据
*/
uint32_t fifo_pool_in(fifo_pool_st* dev, uint8_t* buffer, uint32_t len)
{
uint8_t* p;
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if((dev == 0) || (buffer == 0) || (len == 0))
{
return 0;
}
if(dev->buffer == 0)
{
return 0;
}
if(len > dev->pool_len)
{
return 0;
}
#endif
if(dev->pool_cnt >= dev->pool_num)
{
/* 满 */
return 0;
}
else
{
p = dev->buffer + dev->in * dev->pool_len;
memcpy(p,buffer,len);
dev->in++;
if(dev->in >= dev->pool_num) /* 用减法代替取余 */
{
dev->in -= dev->pool_num;
}
dev->pool_cnt++;
}
return len;
}
/**
* 获取fifo pool当前可写入地址
*/
uint8_t* fifo_pool_getinaddr(fifo_pool_st* dev)
{
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if(dev == 0)
{
return 0;
}
#endif
if(dev->pool_cnt >= dev->pool_num)
{
/* 满 */
return 0;
}
else
{
return (dev->buffer + dev->in * dev->pool_len);
}
}
/**
* 递增写入指针
*/
void fifo_pool_incinaddr(fifo_pool_st* dev)
{
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if(dev == 0)
{
return;
}
#endif
if(dev->pool_cnt >= dev->pool_num)
{
/* 满 */
}
else
{
dev->in++;
if(dev->in >= dev->pool_num) /* 用减法代替取余 */
{
dev->in -= dev->pool_num;
}
dev->pool_cnt++;
}
}
/**
* 从fifo pool读出数据
*/
uint32_t fifo_pool_out(fifo_pool_st* dev, uint8_t* buffer, uint32_t len)
{
uint8_t* p;
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if((dev == 0) || (buffer == 0) || (len == 0))
{
return 0;
}
if(dev->buffer == 0)
{
return 0;
}
if(len > dev->pool_len)
{
return 0;
}
#endif
if(dev->pool_cnt == 0)
{
/* 空 */
return 0;
}
else
{
p = dev->buffer + dev->out * dev->pool_len;
memcpy(buffer, p, len);
dev->out++;
if(dev->out >= dev->pool_num) /* 用减法代替取余 */
{
dev->out -= dev->pool_num;
}
dev->pool_cnt--;
}
return len;
}
/**
* 获取fifo pool当前可读出地址
*/
uint8_t* fifo_pool_getoutaddr(fifo_pool_st* dev)
{
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if(dev == 0)
{
return 0;
}
#endif
if(dev->pool_cnt == 0)
{
/* 空 */
return 0;
}
else
{
return (dev->buffer + dev->out * dev->pool_len);
}
}
/**
* 递增读出指针
*/
void fifo_pool_incoutaddr(fifo_pool_st* dev)
{
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if(dev == 0)
{
return;
}
#endif
if(dev->pool_cnt == 0)
{
/* 空 */
}
else
{
dev->out++;
if(dev->out >= dev->pool_num) /* 用减法代替取余 */
{
dev->out -= dev->pool_num;
}
dev->pool_cnt--;
}
}
下图是原来的”采集-发送”的应用场景,采集需要先采集到BUFFER然后拷贝到FIFO,发送需要先从FIFO拷贝出来到BUFFER才能发送。
如果是”采集-处理-发送”的应用场景,就需要两个FIFO,如下图
而我们使用FIFO_POOL可以减少图中红色部分,即两个BUFFER和BUFFER到FIFO之间的拷贝,即直接使用FIFO_POOL中的空间而无需BUFFER中转。
使用FIFO_POOL更详细的一个应用场景如下
注意以上仅仅实现FIFO本身,实际应用在多线程,或者前后台(中断和主循环)中访问FIFO,需要考虑临界段保护,这一点要特别小心,可以根据具体环境具体处理。
从实际应用来看,我们设计的FIFO_POOL满足了我们的应用需求,效率高且方便,可减少内存的拷贝,移植性也很好可以方便的移植到不同场景使用。