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数据的类型,可以是结构体类型?*/ #define?qdata_t?uint8_t /*?FIFO长度,实际存放的数据=FIFO_SIZE-1?*/ #define?FIFO_SIZE?6 typedef?enum?{ ????QUEUE_OK, ????QUEUE_FULL, ????QUEUE_EMPTY }qstatus_t; typedef?struct?{ ????uint16_t?addr_wr;????????/*?写地址?*/ ????uint16_t?addr_rd;????????/*?读地址?*/ ????uint16_t?length;?????????/*?FIFO长度,实际存放的数据=length-1?*/ ????qdata_t?fifo[FIFO_SIZE]; }queue_t; qstatus_t?queue_reset(queue_t?*q); qstatus_t?queue_read(queue_t?*q,?qdata_t?*pdata); qstatus_t?queue_write(queue_t?*q,?qdata_t?data); int?queue_isFull(queue_t?*q); int?queue_isEmpty(queue_t?*q); int?queue_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_t?queue_reset(queue_t?*q) { ???
?int?i?=?0; ????q->addr_wr?=?0; ????
q->addr_rd?=?0; ??
??q->length?=?FIFO_SIZE; ????for(i?=?0;?i?length;?i++)
????????q->fifo[i]?=?0;
????return?QUEUE_OK; } /*?FIFO写入数据?*/ qstatus_t?queue_write(queue_t?*q,?qdata_t?data) { ??
??if(queue_isFull(q)) ????{ ????????
printf("Write?failed(%d),?queue?is?full ",?data); ?
???????return?QUEUE_FULL; ??
??} ????q->fifo[q->addr_wr]?=?data; ????q->addr_wr?=?(q->addr_wr?+?1)?%?q->length; ??
??printf("write?success:?%02d ",?data); ????
queue_print(q); ?
???return?QUEUE_OK; } /*?FIFO读出数据?*/ qstatus_t?queue_read(queue_t?*q,?qdata_t?*pdata) { ??
??if(queue_isEmpty(q)) ????{ ????
????printf("Read?failed,?queue?is?empty "); ????
????return?QUEUE_EMPTY; ??
??} ????*pdata?=?q->fifo[q->addr_rd]; ????q->addr_rd?=?(q->addr_rd?+?1)?%?q->length; ????printf("read?success:?%02d ",?*pdata); ??
??queue_print(q); ????return?QUEUE_OK; } /*?FIFO是否为空?*/ int?queue_isEmpty(queue_t?*q) {
????return?(q->addr_wr?==?q->addr_rd); } /*?FIFO是否为满?*/ int?queue_isFull(queue_t?*q) { ??
??return?((q->addr_wr?+?1)?%?q->length?==?q->addr_rd); } /*?FIFO内数据的个数?*/ int?queue_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内的数据和读写指针的位置?*/ int?queue_print(queue_t?*q) { ?
???int?i?=?0; ????int?j?=?0; ????for(i?=?0;?i?addr_rd;?i++) ?????
???printf("?????"); ????printf("rd=%d",?q->a
ddr_rd); ????printf(" "); ?
???for(i?=?0;?i?length;?i++) ????{ ????????if(q->addr_wr?>?q->addr_rd) ?????
???{ ????????????if(i?>=?q->addr_rd?&&?i?addr_wr) ??????????
??????printf("[%02d]?",?q->fifo[i]); ????????????else ?????????????
???printf("[??]?"); ????????} ???
?????else//addr_rd?>?addr_wr ?????
???{ ????????????if(i?addr_wr?||?i?>=?q->addr_rd) ????
???
?????????printf("[%02d]?",?q->fifo[i]); ?????
???????else ????????????????printf("[??]?"); ??????
??} ????} ????printf("------count?=?%d ",?queue_count(q)); ???
?for(i?=?0;?i?addr_wr;?i++) ???????
?printf("?????"); ????printf("wr=%d",?q->addr_wr); ??
??printf(" "); ????return?QUEUE_OK; }
实际应用:
/* ?*?Copyright(C),?2010-2023,?CSDN?@?whik1194 ?*?Time?????
??:?2023年4月9日 ?*?Author?????:?https://blog.csdn.net/whik1194 ?*?GitHub?????:?https://github.com/whik/xqueue ?*/ #include?
#include? #include?"xqueue.h" int?main(int?argc,?char?*argv[]) { ??
??queue_t?queue; ????qdata_t?data; ?
???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"); ????return?0; }
运行结果:
循环队列元素的数据类型,可以根据需要指定,也可以是结构体类型。
编辑:黄飞
?
评论