🎀 文章作者:二土电子
🐸 期待大家一起学习交流!
一、FIFO简介
1.1 什么是FIFO
借用大家的常规描述,FIFO是英文First In First Out的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
博主理解的FIFO就像一个圆柱形收纳容器,或者说像一根水管。存放的东西从一端进入,另一端出来。从另一端拿取东西时,我们只能按照放入的顺序取出。第一个被放入的东西首先被取出,最后一件放入的东西最后被取出,当水管被填满后,再往里塞东西,就无法进入了,这就是FIFO的基本模型。下面简单画一下FIFO的示意图
1.2 FIFO的功能
- 对连续的数据流进行缓存,防止数据丢失。
- 可以减轻CPU负担。
博主在写到这里的时候在想,如果在单片机中用数组实现FIFO,能否起到减轻CPU负担的效果,或者换句话说,FIFO是如何减轻CPU负担的呢?
FIFO实际上是一个存储器,是一个硬件设备。以STM32F1系列单片机为例,在进行串口通信接收数据时,如果我们不使用FIFO存储器,那么每接收到一个字节的数据,CPU都需要来进行存储,如果说接收一串很长的信息,很可能造成信息丢失,而且每个字节的接收都需要CPU参与,极大的增加了CPU的负担。但是如果我们使用FIFO存储器来对接收数据进行缓存,当我们检测到数据传输完成后,产生一个中断,告诉CPU全部数据已经接收完成,可以进行处理,这时CPU再统一进行处理,这样一来,就减轻了CPU的负担。但是在利用数组实现FIFO时,本质上在接收数据时还是需要CPU的参与,因此在博主看来,并没有减轻CPU的负担。
在利用串口接收数据的时候,很多教程给出的例程都是只利用串口接收中断来实现的,实际使用时相信有些小伙伴会遇到数据丢失的情况。实际STM32有一个串口空闲中断,也叫做帧中断。在接收数据的时候,如果超过一个字节时间没有再接收到数据,会认为接收完成,产生空闲中断。而总线上会有一个字节时间内没有接收到数据的情况,通常是一帧数据发送完成,因此空闲中断也可以成为帧中断。对于利用串口空闲中断来接收数据的程序实现,可移步至博主STM32速成笔记专栏串口通信篇查看,关注文末公众号获取程序工程文件。
1.3 什么时候使用FIFO
- 不同时钟域之间的数据传输。
- 不同宽度的数据传输。
1.4 FIFO的分类
- 同步FIFO
同步FIFO是指读时钟和写时钟为同一个时钟。在时钟沿来临时同时发生读写操作。 - 异步FIFO
异步FIFO是指读写时钟不一致,读写时钟是互相独立的。
1.5 FIFO重要参数
通过对FIFO模型的通俗介绍,我们可以知道,FIFO可以想象成一个水管,先进先出,有一定的深度。我们在往FIFO里写入数据的时候,应该能够知道什么时候水管满了。在读取数据的时候,应该知道什么时候水管空了。下面我们就来看好一下FIFO的一些重要参数。
- FIFO的宽度
FIFO进行一次数据读写时数据的位数,比如与8位、16位、32位等等。 - FIFO的深度
FIFO能存储的数据个数。比如一个FIFO的宽度为8位,深度为N,那么他能存储N个8位数据。 - 满标志
FIFO已满或将满时发送的信号,提示CPU不要再对FIFO进行写操作,防止数据溢出。 - 空标志
FIFO已空或将空时发送的信号,提示CPU不要再对FIFO进行读操作,防止读出无效数据。 - 读时钟
读操作所遵循的时钟,每个时钟沿来临时读数据。 - 写时钟
写操作所遵循的时钟,每个时钟沿来临时写数据。
二、C语言数组实现FIFO
这里给出一个利用C语言数组实现FIFO的例子
#include <stdio.h>
#define FIFO_MAX_DEPTH 3 // FIFO深度
typedef struct
{
int data[FIFO_MAX_DEPTH];
int redIndex; // 读索引(读地址)
int writeIndex; // 写索引(写地址)
} FIFO_Queue;
// 初始化FIFO
// 最开始没有数据,写索引等于读索引
void Fifo_Init (FIFO_Queue *queue)
{
queue->redIndex = -1;
queue->writeIndex = -1;
}
// 检查FIFO是否已满
// 写索引的下一个位置是读索引,说明FIFO已满
// 实际也就是判断写索引的下一个位置是否等于FIFO深度
int isFull(FIFO_Queue *queue)
{
return (queue->writeIndex + 1) % FIFO_MAX_DEPTH == queue->redIndex;
// 也可以写成如下形式
// return queue->writeIndex + 1 == FIFO_MAX_DEPTH;
}
// 检查FIFO是否已空
// 判断写索引是否为-1
int isEmpty(FIFO_Queue *queue)
{
return queue->writeIndex == -1;
}
// 元素入队
void enqueue(FIFO_Queue *queue, int element)
{
// 判断FIFO是否已满
if (isFull(queue))
{
// 提示FIFO已满
printf("FIFO已满,无法继续写入数据!\n");
return;
}
// 判断FIFO是否为空
if (isEmpty(queue))
{
// 第一次存储数据需要更新一下读索引
queue->redIndex = 0;
}
// 存储数据,更新写索引
queue->writeIndex = (queue->writeIndex + 1) % FIFO_MAX_DEPTH;
queue->data[queue->writeIndex] = element;
printf ("数据写入成功\n");
}
// 元素出队
int dequeue(FIFO_Queue *queue)
{
// 判断FIFO是否为空
if (isEmpty(queue))
{
// 提示FIFO为空
printf("FIFO已空,无法继续读取数据!\n");
return -1;
}
// 读出数据
int element = queue->data[queue->redIndex];
// 判断是否读到最后一个数据
if (queue->redIndex == queue->writeIndex)
{
// 全部读取结束,重新初始化
queue->redIndex = -1;
queue->writeIndex = -1;
}
else
{
queue->redIndex = (queue->redIndex + 1) % FIFO_MAX_DEPTH;
}
return element;
}
int main()
{
FIFO_Queue queue;
Fifo_Init(&queue);
printf ("FIFO数据写入中……\n");
enqueue(&queue, 10);
enqueue(&queue, 20);
enqueue(&queue, 30);
enqueue(&queue, 40);
printf ("\n");
printf ("FIFO数据读取中……\n");
printf("Dequeued element: %d\n", dequeue(&queue));
printf("Dequeued element: %d\n", dequeue(&queue));
printf("Dequeued element: %d\n", dequeue(&queue));
printf("Dequeued element: %d\n", dequeue(&queue));
return 0;
}
输出结果如下