關注+星標公眾號,不錯過精彩內容

作者 | strongerHuang
微信公眾號?|?strongerHuang
FIFO 是First-In First-Out的縮寫,它是一個具有先入先出特點的緩沖區。
可以理解成一個大的水池,水對應數據,注水速度對應數據輸入的頻率,放水速度對應數據處理的速度,當注水速度和放水速度相同時,我們不需要使用水池來緩沖,但是當注水速度大于放水速度,或者注水速度突然變大時(突發),為了保證水池不溢出(數據不丟失),就需要水池(緩沖區)來處理這種突發情況,并設置合理大小的水池空間(FIFO的深度)。

或者為了降低CPU負擔,提高數據處理效率,可以在積累到一定的數據量之后,再一次性處理。
在FPGA中,FIFO一般是使用RAM存儲器作為緩沖區,可以分為同步FIFO或異步FIO,一般用于數據緩沖,或者不同時鐘域之間的數據傳遞。
在單片機中,一般是基于一維數組和結構體實現的循環隊列(Queue),或者叫環形隊列。

FIFO的使用,既可以保證數據的完整性,還可以讓數據被及時的處理。
本文介紹,基于C語言的循環隊列緩沖區原理、設計與實現。
定義一個一維數組當作存儲區,數組長度為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

根據以上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\n",?data);
????????return?QUEUE_FULL;
????}
????q->fifo[q->addr_wr]?=?data;
????q->addr_wr?=?(q->addr_wr?+?1)?%?q->length;
????printf("write?success:?%02d\n",?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\n");
????????return?QUEUE_EMPTY;
????}
????*pdata?=?q->fifo[q->addr_rd];
????q->addr_rd?=?(q->addr_rd?+?1)?%?q->length;
????printf("read?success:?%02d\n",?*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->addr_rd);
????printf("\n");
????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\n",?queue_count(q));
????for(i?=?0;?i?addr_wr;?i++)
????????printf("?????");
????printf("wr=%d",?q->addr_wr);
????printf("\n");
????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;
}
運行結果:

循環隊列元素的數據類型,可以根據需要指定,也可以是結構體類型。
后臺回復【xqueue】,獲取下載地址,或者到以下開源地址獲取資料。
xqueue
https://gitee.com/whik/xqueue
------------?END?------------

●專欄《嵌入式工具》
●專欄《嵌入式開發》
●專欄《Keil教程》
●嵌入式專欄精選教程
關注公眾號回復“加群”按規則加入技術交流群,回復“1024”查看更多內容。

