[嵌入式开发模块]环形缓冲区/循环队列 C语言实现

时间:2021-12-09 19:11:18

忙着毕设,很久没有写文章了,终于答辩完了,得了个校优秀毕业设计。毕设做的是个智能接口模块,用一周时间入门了,MC9S12XEP100的开发,又用一周时间入门了uC/OS-II嵌入式操作系统,在做毕设的过程中学到了很多,现在把一些工具发上来分享。


这里分享一个自己用纯C实现的环形缓冲区。环形缓冲区有很多作用,比如嵌入式中的通信可以用环形缓冲区作为信道,一个线程往里放字节,一个线程取字节进行处理,只要保证取的速度大于读的速度,就可以保证通信顺畅进行,不丢一个字节。
简要介绍:
环形缓冲区其实就是一个队列,里头的元素是先入先出的,但是因为其(逻辑上)是环形的,所以不需要像很多队列的实现那样在内部元素变动的时候需要移动内部剩下的元素。这样就使元素出队入队的时间复杂度只有O(1)。具体实现一般有链表和数组两种方法,当不能确定需要的缓冲区大小时使用链表较好,能确定时使用数组可以节省很多动态分配内存的开销。
在嵌入式开发中,一般不动态分配内存,而是使用静态分配的数组。所以这里我使用数组实现了环形缓冲区,为了能够在不同的程序中复用代码,使用结构体模拟了面向对象编程,这样就可以用一套代码管理不同的缓冲区了。
废话不多说,直接上代码。以下是.h 文件:

/*
*********************************************************************************************************
*
*
* RingQueueStruct
* 环形队列结构
*
* File : RingQueue.h
* By : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date : 2018/02/23
* version: V1.2
* NOTE(s): 这段程序用来对一个给定的缓冲区进行模拟环形队列的管理
* 程序本身不会自动分配缓冲区空间,用户需要自己负责分配空间,并且要保证不直接访问缓存区
* // 在某处分配内存空间
* RQTYPE buffer[BUFFER_SIZE];
* RING_QUEUE que,*ptr_que;
* unsigned char err;
* // 初始化
* ptr_que = RingQueueInit(&que,buffer,BUFFER_SIZE,&err);
* if(err == RQ_ERR_NONE ){
* // 初始化成功,使用其他函数
* }
* History : 2017/04/25 the original version of RingQueueStruct.
* 2017/10/16 put functions used frequently,RingQueueIn and RingQueueOut, in non-banked address;
* modify single line function RingQueueIsEmpty and RingQueueIsFull to marco function;
* to get better efficiency.
* 2018/02/23 1.add the marco(RQ_ARGUMENT_CHECK_EN) to controll argument check so user can save
* more code.
* 2.add the ADDRESSING MODE so the buffer can be defined in banked addressing area.
*********************************************************************************************************
*/



#ifndef RING_QUEUE_H
#define RING_QUEUE_H

/*
********************************************************************************************
* MISCELLANEOUS
********************************************************************************************
*/


#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE 1
#endif

/*
*********************************************************************************************************
* ADDRESSING MODE 寻址模式
*********************************************************************************************************
*/


// uncomment the corresponding line to select the addressing mode to the buffer of RingQueue module.
// if you don't understand. Just use the extended addressing mode
// 取消对应行的注释以选择环形缓冲区模块访问缓冲区时使用的寻址方式
// 如果你不知道这是什么意思的话,那就用扩展寻址就行了,这是默认的方式

// extended addressing mode 扩展区寻址(默认)
#define RQ_ADDRESSING_MODE
// banked RAM addressing mode RAM分页区寻址
//#define RQ_ADDRESSING_MODE __rptr
// global addressing mode 全局寻址
//#define RQ_ADDRESSING_MODE __far

/*
*********************************************************************************************************
* CONFIGURATION 配置
*********************************************************************************************************
*/


#define RQ_ARGUMENT_CHECK_EN TRUE // TRUE: arguments will be checked, however,this will
// cost a little code volume.

/*
*********************************************************************************************************
* CONSTANTS 常量
*********************************************************************************************************
*/

#define RQ_ERR_NONE 0u

#define RQ_ERR_POINTER_NULL 1u
#define RQ_ERR_SIZE_ZERO 2u

#define RQ_ERR_BUFFER_FULL 3u
#define RQ_ERR_BUFFER_EMPTY 4u

#define RQ_OPTION_WHEN_FULL_DISCARD_FIRST 0u // discard the first element when ring buffer is full
#define RQ_OPTION_WHEN_FULL_DONT_IN 1u // discard new element when ring buffer is full
/*
*********************************************************************************************************
* DATA TYPE 数据类型
*********************************************************************************************************
*/


// define the data type that stores in the RingQueue. 定义存在环形缓冲区内的数据的类型
typedef unsigned char RQTYPE;
typedef RQTYPE *RQ_ADDRESSING_MODE pRQTYPE;
typedef struct {
unsigned short RingBufCtr; /* Number of characters in the ring buffer */
unsigned short RingBufSize; /* Ring buffer Size */
pRQTYPE RingBufInPtr; /* Pointer to where next character will be inserted */
pRQTYPE RingBufOutPtr; /* Pointer from where next character will be extracted */
pRQTYPE RingBuf; /* Ring buffer array */
pRQTYPE RingBufEnd; /* Point to the end of the buffer */
} RING_QUEUE;

/*
*********************************************************************************************************
* FUNCTION PROTOTYPES 函数原型
*********************************************************************************************************
*/


RING_QUEUE *RingQueueInit(RING_QUEUE *pQueue,pRQTYPE pbuf,unsigned short bufSize,unsigned char *perr);
#pragma CODE_SEG __NEAR_SEG NON_BANKED
unsigned short RingQueueIn(RING_QUEUE *pQueue,RQTYPE data,unsigned char option,unsigned char *perr);
RQTYPE RingQueueOut(RING_QUEUE *pQueue,unsigned char *perr);
#pragma CODE_SEG DEFAULT
short RingQueueMatch(RING_QUEUE *pQueue,pRQTYPE pbuf,unsigned short len);
void RingQueueClear(RING_QUEUE *pQueue);

/*
*********************************************************************************************************
* RingQueueIsEmpty()
*
* Description : whether the RingQueue is empty. 环形队列是否为空
*
* Arguments : pQueue pointer to the ring queue control block; 指向环形队列控制块的指针
*
* Return : TRUE the RingQueue is empty.
* FALSE the RingQueue is not empty.
* Note(s) :
*********************************************************************************************************
*/


#define RingQueueIsEmpty(pQueue) ((pQueue)->RingBufCtr == 0)

/*
*********************************************************************************************************
* RingQueueIsFull()
*
* Description : whether the RingQueue is full. 环形队列是否为空
*
* Arguments : pQueue pointer to the ring queue control block; 指向环形队列控制块的指针
*
* Return : TRUE the RingQueue is full.
* FALSE the RingQueue is not full.
* Note(s) :
*********************************************************************************************************
*/


#define RingQueueIsFull(pQueue) ((pQueue)->RingBufCtr >= (pQueue)->RingBufSize)

#endif

然后下面是.c文件。

/*
*********************************************************************************************************
*
*
* RingQueueStruct
* 环形队列结构
*
* File : RingQueue.c
* By : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date : 2018/02/23
* version: V1.2
* NOTE(s):
*
* History : 2017/04/25 the original version of RingQueueStruct.
* 2017/10/16 put functions used frequently,RingQueueIn and RingQueueOut, in non-banked address;
* modify single line function RingQueueIsEmpty and RingQueueIsFull to marco function;
* to get better efficiency.
* 2018/02/23 1.add the marco(RQ_ARGUMENT_CHECK_EN) to controll argument check so user can save
* more code.
* 2.add the ADDRESSING MODE so the buffer can be defined in banked addressing area.
*********************************************************************************************************
*/


/*
*********************************************************************************************************
* INCLUDES
*********************************************************************************************************
*/

#include "RingQueue.h"

/*
*********************************************************************************************************
* LOCAL FUNCTION DECLARATION
*********************************************************************************************************
*/


#if(RQ_ARGUMENT_CHECK_EN == TRUE)
#define argCheck(cond,err,rVal) if(cond) { *perr = (err); return (rVal); }
#else
#define argCheck(cond,err,rVal)
#endif // of (SPI_ARGUMENT_CHECK_EN == TRUE)



/*
*********************************************************************************************************
* LOCAL FUNCTION DECLARE
*********************************************************************************************************
*/

#pragma CODE_SEG __NEAR_SEG NON_BANKED
// 内部使用,给定将给定指针在环形缓冲区内向前移动一步(到尾了会移回头)
static void _forwardPointer(RING_QUEUE *pQueue,pRQTYPE* pPointer);
#pragma CODE_SEG DEFAULT
/*
*********************************************************************************************************
* RingQueueInit()
*
* Description : To initialize the ring queue. 初始化环形队列
*
* Arguments : pQueue pointer to the ring queue control block; 指向环形队列控制块的指针
* pbuf pointer to the buffer(an array); 指向自定义的缓冲区(实际就是个数组)
* bufSize the Size of the buffer; 缓冲区的大小;
* perr a pointer to a variable containing an error message which will be set by this
* function to either:
*
* RQ_ERR_NONE
* RQ_ERR_SIZE_ZERO
* RQ_ERR_POINTER_NULL
*
* Return : the pointer to the ring queue control block; 返回指向环形队列控制块的指针
* 0x00 if any error; 如果出错了则返回NULL
*
*Note(s):
*********************************************************************************************************
*/


RING_QUEUE* RingQueueInit(RING_QUEUE *pQueue,pRQTYPE pbuf,unsigned short bufSize,unsigned char *perr){
argCheck(pQueue == 0x00 || pbuf == 0x00,RQ_ERR_POINTER_NULL,0x00);
argCheck(bufSize == 0,RQ_ERR_SIZE_ZERO,0x00);
pQueue->RingBufCtr = 0;
pQueue->RingBuf = pbuf;
pQueue->RingBufInPtr = pbuf;
pQueue->RingBufOutPtr = pbuf;
pQueue->RingBufSize = bufSize;
pQueue->RingBufEnd = pbuf + bufSize;
*perr = RQ_ERR_NONE;
return pQueue;
}

/*
*********************************************************************************************************
* RingQueueIn()
*
* Description : Enqueue an element. 入队一个元素
*
* Arguments : pQueue pointer to the ring queue control block; 指向环形队列控制块的指针
* data the data to enqueue; 要入队的数据
* option option when queue is full ,you can choose: 当队列满的时候的选项,你可以选择:
* RQ_OPTION_WHEN_FULL_DISCARD_FIRST 抛弃队头的元素来填进去新的元素
* RQ_OPTION_WHEN_FULL_DONT_IN 不入队新给的元素
* perr a pointer to a variable containing an error message which will be set by this
* function to either:
*
* RQ_ERR_NONE if no err happen
* RQ_ERR_POINTER_NULL if pointer is 0x00
* RQ_ERR_BUFFER_FULL if buffer is full
*
* Return : the Elements Count after enqueue the element
* 调用函数后队列中的元素个数
*Note(s) :
*********************************************************************************************************
*/

#pragma CODE_SEG __NEAR_SEG NON_BANKED
unsigned short RingQueueIn(RING_QUEUE *pQueue,RQTYPE data,unsigned char option,unsigned char *perr){
argCheck(pQueue == 0x00,RQ_ERR_POINTER_NULL,0x00);
if(pQueue->RingBufCtr >= pQueue->RingBufSize){
*perr = RQ_ERR_BUFFER_FULL;
if(option == RQ_OPTION_WHEN_FULL_DISCARD_FIRST){
_forwardPointer(pQueue,&pQueue->RingBufOutPtr); /* Wrap OUT pointer */
}else{ // option == RQ_OPTION_WHEN_FULL_DONT_IN
return pQueue->RingBufCtr;
}
}else{
pQueue->RingBufCtr++; /* No, increment character count */
*perr = RQ_ERR_NONE;
}
*pQueue->RingBufInPtr = data; /* Put character into buffer */
_forwardPointer(pQueue,&pQueue->RingBufInPtr); /* Wrap IN pointer */
return pQueue->RingBufCtr;
}
/*
*********************************************************************************************************
* RingQueueOut()
*
* Description : Dequeue an element. 出队一个元素
*
* Arguments : pQueue pointer to the ring queue control block; 指向环形队列控制块的指针
* perr a pointer to a variable containing an error message which will be set by this
* function to either:
*
* RQ_ERR_NONE if no err happen
* RQ_ERR_POINTER_NULL if pointer is 0x00
* RQ_ERR_BUFFER_EMPTY if buffer is empty
*
* Return : 0 if any error or the data is 0;
* others the data
*
*Note(s):
*********************************************************************************************************
*/

RQTYPE RingQueueOut(RING_QUEUE *pQueue,unsigned char *perr){
RQTYPE data;
argCheck(pQueue == 0x00,RQ_ERR_POINTER_NULL,0x00);
if(pQueue->RingBufCtr == 0){
*perr = RQ_ERR_BUFFER_EMPTY;
return 0;
}
pQueue->RingBufCtr--; /* decrement character count */
data = *pQueue->RingBufOutPtr; /* Get character from buffer */
_forwardPointer(pQueue,&pQueue->RingBufOutPtr); /* Wrap OUT pointer */
*perr = RQ_ERR_NONE;
return data;
}
#pragma CODE_SEG DEFAULT
/*
*********************************************************************************************************
* RingQueueMatch()
*
* Description : Match the given buffer in RingQueue 在环形队列中匹配给定缓冲区
*
* Arguments : pQueue pointer to the ring queue control block; 指向环形队列控制块的指针
* pbuf pointer to the chars need to match;
* len the length of the chars
* Return : -1 Don't match -1 则没有匹配到
* >= 0 match >= 0 则匹配到了
*
*Note(s):
*********************************************************************************************************
*/


short RingQueueMatch(RING_QUEUE *pQueue,pRQTYPE pbuf,unsigned short len){
pRQTYPE pPosQ,pCurQ,pCurB,pEndB;
unsigned short rLen,Cnt;
if(len > pQueue->RingBufCtr)
return -1;
pPosQ = pQueue->RingBufOutPtr;
pEndB = pbuf + len;
Cnt = 0;
rLen = pQueue ->RingBufCtr;
while(rLen-- >= len){ // if remian length of queue bigger than buffer. continue
pCurQ = pPosQ;
pCurB = pbuf;
while(pCurB != pEndB && *pCurQ == *pCurB) { // compare one by one,until match all(pCurB==pEndB) or some one don't match
_forwardPointer(pQueue,&pCurQ);
pCurB++;
}
if(pCurB == pEndB) // if match all
return Cnt;
Cnt++;
_forwardPointer(pQueue,&pPosQ);
}
return -1;
}

/*
*********************************************************************************************************
* RingQueueClear()
*
* Description: Clear the RingQueue. 清空环形队列
*
* Arguments : pQueue pointer to the ring queue control block; 指向环形队列控制块的指针
*
* Return:
*
* Note(s):
*********************************************************************************************************
*/


void RingQueueClear(RING_QUEUE *pQueue){
#if(RQ_ARGUMENT_CHECK_EN == TRUE)
if(pQueue == 0x00)
return;
#endif
pQueue->RingBufCtr = 0;
pQueue->RingBufInPtr = pQueue -> RingBufOutPtr;
}

/*
*********************************************************************************************************
* LOCAL FUNCTION
*********************************************************************************************************
*/


#pragma CODE_SEG __NEAR_SEG NON_BANKED
static void _forwardPointer(RING_QUEUE *pQueue,pRQTYPE* pPointer){
if (++*pPointer == pQueue->RingBufEnd)
*pPointer = pQueue->RingBuf; /* Wrap OUT pointer */
}
#pragma CODE_SEG DEFAULT

简单解释下。在.h文件中定义了一个环形缓冲区的控制块,当然也可以当其为一个环形缓冲区对象,用户需要为每个环形缓冲区分配一个控制块和其缓冲区(也就是一个数组)。理想情况下,虽然用户知道控制块的结构,但也不应该直接访问内部字段,而应该通过提供的函数来访问。
队列中默认的元素是无符号字符,如果要改成缓存其他类型的话改下.h文件中的
typedef unsigned char RQTYPE;
这行就行了。
使用示例:

#include "RingQueue.h"
#define RX_BUF_MAX_SIZE 200 // 定义缓冲区的最大大小为200
static unsigned char RxBuffer[RX_BUF_MAX_SIZE]; // 定义缓冲区
static RING_QUEUE RxRingQ; // 定义环形缓冲区的控制块
void main(){
unsigned char err;
// 初始化缓冲区
RingQueueInit(&RxRingQ,RxBuffer,RX_BUF_MAX_SIZE,&err);
if(err != RQ_ERR_NONE){
//初始化缓冲区失败的处理
}
……
}

然后调用所有方法都需要传递环形缓冲区控制块的指针。如入队就像:

// 往RxRingQ缓冲区内入队一个元素c,如果满的话丢弃第一个元素
RingQueueIn(&RxRingQ,c,RQ_OPTION_WHEN_FULL_DISCARD_FIRST,&err);

出队就像:

// 从RxRingQ缓冲区内提取一个字符
c = RingQueueOut(&RxRingQ,&err);

其他就不一 一举例了。要特别说明下的是RingQueueMatch()这个方法并不是队列应该有的方法,这是为了比如我需要在缓冲区中匹配到某一串字符后做某些事情而特别加上的,不需要的话删掉即可。比如我需要一旦出现“abc”就做某些事情,那我代码可以类似这么写:

static const unsigned char* StringsWait = "abc";
……
while(true){
//比如从某处获得了下一个字符c
……
// 将字符c入队
RingQueueIn(&RxRingQ,c,RQ_OPTION_WHEN_FULL_DISCARD_FIRST,&err);
if(RingQueueMatch(&RxRingQ,StringsWait,3) >= 0){ // 如果在缓冲区内找到"abc"
// RingQueueClear(&RxRingQ); // 可能需要清空缓冲区
// 做想要做的事
……
}
}

有什么建议或意见请留言,谢谢!


更改历史:

2017/10/27 更新到 V1.1
2018/02/23 更新到 V1.2