分享一款基于C语言实现的FIFO模块
来源:strongerHuang发布时间:2023-04-231351浏览
询问 AI
作者 | strongerHuang
微信公众号|strongerHuang
1. 为什么需要FIFO
FIFO 是First-In First-Out的缩写,它是一个具有先入先出特点的缓冲区。
可以理解成一个大的水池,水对应数据,注水速度对应数据输入的频率,放水速度对应数据处理的速度,当注水速度和放水速度相同时,我们不需要使用水池来缓冲,但是当注水速度大于放水速度,或者注水速度突然变大时(突发),为了保证水池不溢出(数据不丢失),就需要水池(缓冲区)来处理这种突发情况,并设置合理大小的水池空间(FIFO的深度)。

或者为了降低CPU负担,提高数据处理效率,可以在积累到一定的数据量之后,再一次性处理。
在FPGA中,FIFO一般是使用RAM存储器作为缓冲区,可以分为同步FIFO或异步FIO,一般用于数据缓冲,或者不同时钟域之间的数据传递。
在单片机中,一般是基于一维数组和结构体实现的循环队列(Queue),或者叫环形队列。

FIFO的使用,既可以保证数据的完整性,还可以让数据被及时的处理。
本文介绍,基于C语言的循环队列缓冲区原理、设计与实现。
2. FIFO的存取顺序
定义一个一维数组当作存储区,数组长度为6,再定义两个读写指针变量。
初始化时,FIFO为空,读写指针相等,并都置为0。

写入一个数据1之后,写指针递增,读指针不变:

再写两个数据2和3,写指针递增,读指针不变:

写了三个数据之后,我们读出一个数据1,写指针不变,读指针递增:

读出一个数据2,再写两个数据4和5,读写指针变化:

再写一个数据6,此时超过数组长度,但是数组头部还有空间,所以写指针回到数组起始地址0:

再写一个数据7,此时判断FIFO满:

可能会有朋友疑惑,不是还有一个空位置可以存放数据吗?

如果再存入一个数据之后,读写指针相等,此时可以判断是满状态吗?
显然是不能,因为当FIFO为空时,也是读写指针相等,所以这种情况就无法判断满和空。
这里就涉及到FIFO设计中,最重要的满和空的判断条件,需要遵循FIFO读写的两个规则:
FIFO为空时,不能执行读操作
FIFO为满时,不能执行写操作
为了避免这种情况发生,我们空出一个元素位置,写指针指向的位置永远为空,这样就会有两种满的情况:
rd < wr
rd > wr

对于第一种情况,当(wr + 1) % FIFO_SIZE == rd时,可以认为FIFO满,FIFO_SIZE是指数组长度;
对于第二种情况,当wr + 1 == rd时,可以认为FIFO满。
以上两种情况可以合并为一种,即(wr + 1) % FIFO_SIZE == rd时,判断FIFO满。
所以这种判断方式,会牺牲一个存储位置,实际可以存储的元素个数为FIFO_SIZE-1。
同理,获取当前FIFO内元素的个数,也可以分为两种情况:
当wr > rd时, count = wr - rd

当wr < rd时,count = wr + FIFO_SIZE - rd

3. FIFO的代码实现
根据以上FIFO存取逻辑,我们可以使用一维数组来构造一个环形缓冲区,读写地址循环递增,分别实现FIFO初始化、读写操作、判断空满、获取元素个数等函数,并封装成模块。
xqueue.h
/*
*Copyright(C),2010-2023,CSDN@whik1194
*Time:2023年4月9日
*Author:https://blog.csdn.net/whik1194
*GitHub:https://gitee.com/whik/xqueue
*/
#ifndef__XQUEUE_H__
#define__XQUEUE_H__
#include"stdint.h"
/*FIFO数据的类型,可以是结构体类型*/
#defineqdata_tuint8_t
/*FIFO长度,实际存放的数据=FIFO_SIZE-1*/
#defineFIFO_SIZE6
typedefenum{
QUEUE_OK,
QUEUE_FULL,
QUEUE_EMPTY
}qstatus_t;
typedefstruct{
uint16_taddr_wr;/*写地址*/
uint16_taddr_rd;/*读地址*/
uint16_tlength;/*FIFO长度,实际存放的数据=length-1*/
qdata_tfifo[FIFO_SIZE];
}queue_t;
qstatus_tqueue_reset(queue_t*q);
qstatus_tqueue_read(queue_t*q,qdata_t*pdata);
qstatus_tqueue_write(queue_t*q,qdata_tdata);
intqueue_isFull(queue_t*q);
intqueue_isEmpty(queue_t*q);
intqueue_print(queue_t*q);
#endif
xqueue.c文件
/*
*Copyright(C),2010-2023,CSDN@whik1194
*Time:2023年4月9日
*Author:https://blog.csdn.net/whik1194
*GitHub:https://gitee.com/whik/xqueue
*/
#include"xqueue.h"
#include"stdio.h"
/*FIFO复位*/
qstatus_tqueue_reset(queue_t*q)
{
inti=0;
q->addr_wr=0;
q->addr_rd=0;
q->length=FIFO_SIZE;
for(i=0;i<q->length;i++)
q->fifo[i]=0;
returnQUEUE_OK;
}
/*FIFO写入数据*/
qstatus_tqueue_write(queue_t*q,qdata_tdata)
{
if(queue_isFull(q))
{
printf("Writefailed(%d),queueisfull\n",data);
returnQUEUE_FULL;
}
q->fifo[q->addr_wr]=data;
q->addr_wr=(q->addr_wr+1)%q->length;
printf("writesuccess:%02d\n",data);
queue_print(q);
returnQUEUE_OK;
}
/*FIFO读出数据*/
qstatus_tqueue_read(queue_t*q,qdata_t*pdata)
{
if(queue_isEmpty(q))
{
printf("Readfailed,queueisempty\n");
returnQUEUE_EMPTY;
}
*pdata=q->fifo[q->addr_rd];
q->addr_rd=(q->addr_rd+1)%q->length;
printf("readsuccess:%02d\n",*pdata);
queue_print(q);
returnQUEUE_OK;
}
/*FIFO是否为空*/
intqueue_isEmpty(queue_t*q)
{
return(q->addr_wr==q->addr_rd);
}
/*FIFO是否为满*/
intqueue_isFull(queue_t*q)
{
return((q->addr_wr+1)%q->length==q->addr_rd);
}
/*FIFO内数据的个数*/
intqueue_count(queue_t*q)
{
if(q->addr_rd<=q->addr_wr)
return(q->addr_wr-q->addr_rd);
//addr_rd>addr_wr;
return(q->length+q->addr_wr-q->addr_rd);
}
/*打印当前FIFO内的数据和读写指针的位置*/
intqueue_print(queue_t*q)
{
inti=0;
intj=0;
for(i=0;i<q->addr_rd;i++)
printf("");
printf("rd=%d",q->addr_rd);
printf("\n");
for(i=0;i<q->length;i++)
{
if(q->addr_wr>q->addr_rd)
{
if(i>=q->addr_rdi<q->addr_wr)
printf("[%02d]",q->fifo[i]);
else
printf("[]");
}
else//addr_rd>addr_wr
{
if(i<q->addr_wr||i>=q->addr_rd)
printf("[%02d]",q->fifo[i]);
else
printf("[]");
}
}
printf("------count=%d\n",queue_count(q));
for(i=0;i<q->addr_wr;i++)
printf("");
printf("wr=%d",q->addr_wr);
printf("\n");
returnQUEUE_OK;
}
实际应用:
/*
*Copyright(C),2010-2023,CSDN@whik1194
*Time:2023年4月9日
*Author:https://blog.csdn.net/whik1194
*GitHub:https://github.com/whik/xqueue
*/
#include<stdio.h>
#include<stdlib.h>
#include"xqueue.h"
intmain(intargc,char*argv[])
{
queue_tqueue;
qdata_tdata;
queue_reset(queue);
queue_write(queue,1);
queue_write(queue,2);
queue_write(queue,3);
queue_read(queue,data);
queue_read(queue,data);
queue_write(queue,4);
queue_write(queue,5);
queue_write(queue,6);
queue_write(queue,7);
queue_read(queue,data);
queue_read(queue,data);
queue_read(queue,data);
queue_write(queue,8);
queue_write(queue,9);
queue_write(queue,10);
queue_read(queue,data);
system("pause");
return0;
}
运行结果:

循环队列元素的数据类型,可以根据需要指定,也可以是结构体类型。
后台回复【xqueue】,获取下载地址,或者到以下开源地址获取资料。
4. 开源地址
xqueue
https://gitee.com/whik/xqueue


新闻来源:strongerHuang,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。


