嵌入式系统与单片机|技术阅读
登录|注册

您现在的位置是:嵌入式系统与单片机 > 技术阅读 > 超级精简系列之十九:超级精简的循环FIFO池,C实现

超级精简系列之十九:超级精简的循环FIFO池,C实现

一. 前言

前面我们实现了循环缓冲区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_POOLFIFO池。

二. 实现

数据结构还是和原来的环形FIFO一样,但是存储空间分为固定大小的分块,不再和原来一样可以任意大小,这样每一次inout的数据块都是连续的,并且可以配置为固定对齐,这样硬件DMA等可以直接使用,不需要搬运到buffer中。

然后还有一个大的不同是状态更新和空间使用时间维度分离了,原来是inout搬运完数据,同时就更新完相关状态(in,out指针,有效数据量等)。现在是获取到当前inout的位置,然后使用这一片空间,使用完后再更新相关状态。

2.1数据结构

和原来的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;

2.2接口

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);

2.3代码

#include <string.h>#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可以减少图中红色部分,即两个BUFFERBUFFERFIFO之间的拷贝,即直接使用FIFO_POOL中的空间而无需BUFFER中转。

使用FIFO_POOL更详细的一个应用场景如下

四. 总结

注意以上仅仅实现FIFO本身,实际应用在多线程,或者前后台(中断和主循环)中访问FIFO,需要考虑临界段保护,这一点要特别小心,可以根据具体环境具体处理。

从实际应用来看,我们设计的FIFO_POOL满足了我们的应用需求,效率高且方便,可减少内存的拷贝,移植性也很好可以方便的移植到不同场景使用。