良许Linux教程网 干货合集 分享一款基于C语言实现的FIFO模块

分享一款基于C语言实现的FIFO模块

FIFO在嵌入式应用中被广泛使用,几乎在任何进行数据收发的地方都可以看到FIFO的存在。今天我要介绍的是一款基于C语言实现的FIFO模块:xqueue。

为什么我们需要FIFO呢?

FIFO是First-In First-Out(先进先出)的缩写,它是一种具有先入先出特点的缓冲区。

可以将其想象成一个大水池,水代表数据,注水速度代表数据输入的频率,放水速度代表数据处理的速度。当注水速度和放水速度相同时,我们无需使用水池来进行缓冲。但是,当注水速度大于放水速度,或者注水速度突然增大时(发生突发事件),为了保证水池不会溢出(数据不会丢失),我们就需要使用水池(缓冲区)来处理这种突发情况,并设置一个适当大小的水池空间(即FIFO的深度)。

image-20231031211740189
image-20231031211740189

或者为了降低CPU负担,提高数据处理效率,可以在积累到一定的数据量之后,再一次性处理。

在FPGA中,FIFO一般是使用RAM存储器作为缓冲区,可以分为同步FIFO或异步FIO,一般用于数据缓冲,或者不同时钟域之间的数据传递。

在单片机中,一般是基于一维数组和结构体实现的循环队列(Queue),或者叫环形队列。

image-20231031211807353
image-20231031211807353

FIFO的使用,既可以保证数据的完整性,还可以让数据被及时的处理。

本文介绍,基于C语言的循环队列缓冲区原理、设计与实现

2. FIFO的存取顺序

定义一个一维数组当作存储区,数组长度为6,再定义两个读写指针变量。

初始化时,FIFO为空,读写指针相等,并都置为0。

image-20231031211811706
image-20231031211811706

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

image-20231031211816875
image-20231031211816875

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

图片
图片

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

image-20231031211837372
image-20231031211837372

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

image-20231031211843361
image-20231031211843361

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

image-20231031211858637
image-20231031211858637

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

image-20231031211902611
image-20231031211902611

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

image-20231031211906588
image-20231031211906588

如果再存入一个数据之后,读写指针相等,此时可以判断是满状态吗?

显然是不能,因为当FIFO为空时,也是读写指针相等,所以这种情况就无法判断满和空。

这里就涉及到FIFO设计中,最重要的满和空的判断条件,需要遵循FIFO读写的两个规则:

  • FIFO为空时,不能执行读操作
  • FIFO为满时,不能执行写操作

为了避免这种情况发生,我们空出一个元素位置,写指针指向的位置永远为空,这样就会有两种满的情况:

  • rd
  • rd > wr
image-20231031211911388
image-20231031211911388

对于第一种情况,当(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

image-20231031211915379
image-20231031211915379

当wr

image-20231031211921181
image-20231031211921181

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\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 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;
}

运行结果:

image-20231031211929221
image-20231031211929221

循环队列元素的数据类型,可以根据需要指定,也可以是结构体类型。

以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

137e00002230ad9f26e78-265x300
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部