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

嵌入式技术

1343人已加入

描述

1. 为什么需要FIFO

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

可以理解成一个大的水池,水对应数据,注水速度对应数据输入的频率,放水速度对应数据处理的速度,当注水速度和放水速度相同时,我们不需要使用水池来缓冲,但是当注水速度大于放水速度,或者注水速度突然变大时(突发),为了保证水池不溢出(数据不丢失),就需要水池(缓冲区)来处理这种突发情况,并设置合理大小的水池空间(FIFO的深度)。

C语言

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

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

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

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

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

2. FIFO的存取顺序

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

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

C语言

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

C语言

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

C语言

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

C语言

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

C语言

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

C语言

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

C语言

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

C语言

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

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

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

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

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

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

rd < wr

rd > wr

C语言

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

C语言

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

C语言

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 < q->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 < q->addr_rd; i++)      
   printf("     ");     printf("rd=%d", q->a
ddr_rd);     printf(" ");  
   for(i = 0; i < q->length; i++)     {         if(q->addr_wr > q->addr_rd)      
   {             if(i >= q->addr_rd && i < 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 ", queue_count(q));    
 for(i = 0; i < q->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; }

运行结果:

C语言

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

编辑:黄飞

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分