前言
在单片机数据处理的时候,如果在中断里添加太多函数,可能会影响整个程序的运行。这时利用数据结构队列,先将缓存放入队列,等主程序空闲时再处理,可以变相提高代码稳定性。本文介绍队列的原理以及单片机C语言实现。
一、队列的概念
队列(Queue)是限定只能在表的两端分别进行插入或删除操作的线性表。在队列结构中,数据元素只能从一端(表尾)插入,从另一端(表头)删除。允许插入的一端称为队尾(Rear),允许删除的一端称为队头(Front)。当队列中没有包含数据元素时,称为空队。向一个队列插入新的元素称为人队(Enqueue),此时,插入的元素成为新的队尾元素;从队列中删除一个元素时,只能删除当前的队头元素,称为出队(Dequeue)。基于列的这种“先进先出”的结构特点,因而也称为先进先出(First In First Out,FIFO)线性表。
二、队列结构
队列的表示与实现也可以采用顺序存储结构和链式存储结构描述
2.1 顺序队列
采用顺序存储结构表示队列时,可以用C语言中的一维数组进行描述。由于对于队列的操作只在队头和队尾进行,因此,需要设置两个指针 front和rear分别指示队头和队尾的位置,并可约定队尾指针rear指向当前队尾元素后的下一个位置,队头指针front指向当前的队头元素。顺序队列的表示形式如下,
#define MAXSIZE 100 //队列的最大容量
typedef struct SqQueue
{
ElmType data[MAXSIZE]; //队列的存储空间
int rear,front; //对头队尾指针
}SqQueue;
(1)初始化队列。队尾指针和队头指针均指向下标为0的单元,令front=rear=0。
(2)入队操作。新元素将存放到当前队尾指针所指的单元,并使队尾指针rear增1指示下一次插入的位置,则rear=rear+1。
(3)出队操作。将当前队头指针所指单元的值返回,并将队头指针front增1即可,则 front=front+1。
入队和出队操作的过程如上图所示。可以看出,队列为空,不能做出队操作,因此判断队列为空的条件是front==rear;经过多次插入和删除操作以后,队列的队尾指针和队头指针将逐步向后移动,最终出现当队尾指针已指向所定义空间中的最后单元时,即rear=MAXSIZE-1时,队列满,无法再入队。虽然此时队头指针所指位置之前可能存在若干单元空闲,却无法进行入队操作的问题。这种现象称为“假溢出”,可以借助两种方法解决顺序队列的“假溢出”问题:
方法一:采用“移动队列”的方法,即每当执行一次出队操作,则依次将队头和队尾指针向数组的起始位置移动,始终保持队头在数组的起始位置。这种方法的代价是产生大量的元素移动,显然不是一种好方法。
方法二:采用循环队列,将一维数组的最后一个单元和第一个单元连接起来构成循环数组,此时称为“循环队列”。当队尾指针已指向数组的最后时,在进行人队操作过程中,可将队尾指针rear 移至数组的起始位置,表示下一次入队操作时的队尾。(居指针移动的方式是rear=(rear+1)%MAXSIZE,队头指针的移动也一样front=(front+1)%MAXSIZE,如图所示:
循环队列初始化空队时,front=rear=0;当队列经过多次出队入队操作后,会出现队头指针front 和队尾指针rear 再次指向同一单元的情况,此时有可能队列空也有可能队列满。为了区分队满和队空的情况,规定当队空时,front== rear;队满时,队尾指针指向队头指针的前一个位置即为满Jrear+1)%MAXSIZE==front,实际循环队列队满时,所容纳的元素个数为 MAXSIZE—T;通过空留一个存储单元的方式区分循环队列队满和队空的情况。下面第三小节详细说明循环队列的存储表示和基本操作的实现方法。
2.2 链队
采用链式存储结构表示队列,称为链队。显然,利用与线性链表类似的方法可以很容易实现链队结构,并分别设置队尾和队头指针指示链队的位置。在链队结构中增加一个附加的头结点,并使队头指针front指向它。此时,判断空队的标志是,链队的队尾指针rear和队头指针 front均指向头结点。带头结点的链队结构示意图如图所示:
三、C语言实现
在单片机中,由于单片机RAM空间比较小,通常只有几十KB到击败KB大小,此时采用链队的方式并不方便内存管理,所以本节主讲顺序队列的实现
3.1 ringbuffer.h头文件
包含基本数据的结构体,以及函数声明
#ifndef __RINGBUFFER_H
#define __RINGBUFFER_H
#ifdef __cplusplus
extern "C" {
#endif
#include "stm32f4xx.h"
#include <stdio.h>
typedef struct {
uint8_t* pBuff; //保存缓存数据
uint8_t* pEnd; // pBuff + legnth
uint8_t* wp; // Write Point
uint8_t* rp; // Read Point
uint16_t length;
uint8_t flagOverflow; // set when buffer overflowed
} RingBuffer;
void rbInitialize(RingBuffer* pRingBuff, uint8_t* buff, uint16_t length);
void rbClear(RingBuffer* pRingBuff);
void rbPush(RingBuffer* pRingBuff, uint8_t value);
uint8_t rbPop(RingBuffer* pRingBuff);
uint16_t rbGetCount(const RingBuffer* pRingBuff);
int8_t rbIsEmpty(const RingBuffer* pRingBuff);
int8_t rbIsFull(const RingBuffer* pRingBuff);
#ifdef __cplusplus
}
#endif
#endif
3.2 ringbuffer.c源文件
#include "ringbuffer.h"
//初始化队列结构体
void rbInitialize(RingBuffer* pRingBuff, uint8_t* buff, uint16_t length)
{
pRingBuff->pBuff = buff;
pRingBuff->pEnd = buff + length;
pRingBuff->wp = buff;
pRingBuff->rp = buff;
pRingBuff->length = length;
pRingBuff->flagOverflow = 0;
}
//清除缓存溢出标志位
//正常情况下不会过流,只有在IAP情况下可能会出现虚假过流
void rbClear(RingBuffer* pRingBuff)
{
pRingBuff->wp = pRingBuff->pBuff;
pRingBuff->rp = pRingBuff->pBuff;
pRingBuff->flagOverflow = 0;
}
//入队
//首先判断队列有没有满,满就退回一段数据,未满就写数据
void rbPush(RingBuffer* pRingBuff, uint8_t value)
{
uint8_t* wp_next = pRingBuff->wp + 1;
if( wp_next == pRingBuff->pEnd ) {
wp_next -= pRingBuff->length; // Rewind pointer when exceeds bound
}
if( wp_next != pRingBuff->rp ) {
*pRingBuff->wp = value;
pRingBuff->wp = wp_next;
} else {
pRingBuff->flagOverflow = 1;
}
}
//出队,空队返回,否则读取数据
uint8_t rbPop(RingBuffer* pRingBuff)
{
if( pRingBuff->rp == pRingBuff->wp ) return 0; // empty
uint8_t ret = *(pRingBuff->rp++);
if( pRingBuff->rp == pRingBuff->pEnd ) {
pRingBuff->rp -= pRingBuff->length; // Rewind pointer when exceeds bound
}
return ret;
}
//返回未读数据长度
uint16_t rbGetCount(const RingBuffer* pRingBuff)
{
return (pRingBuff->wp - pRingBuff->rp + pRingBuff->length) % pRingBuff->length;
}
//判断队列数据是否为空
//1:空;0:非空
int8_t rbIsEmpty(const RingBuffer* pRingBuff)
{
return pRingBuff->wp == pRingBuff->rp;
}
//判断队列空间是否满,没啥意义,可以不用
int8_t rbIsFull(const RingBuffer* pRingBuff)
{
return (pRingBuff->rp - pRingBuff->wp + pRingBuff->length - 1) % pRingBuff->length == 0;
}
3.3 函数调用
- 申请缓存
#define RX_BUFF_SIZE 4096
static RingBuffer RX_RingBuff;
//初始化
void RcvFrom_Init()
{
static uint8_t RX_Buff[RX_BUFF_SIZE];
rbInitialize(&RX_RingBuff, RX_Buff, sizeof(RX_Buff));
}
//入队
void Push_RcvData(u8 dat)
{
rbPush(&RX_RingBuff, dat);
}
- 中断压入队列,以串口中断为例
void USART1_IRQHandler(void)
{
u8 rcvdata;
if (USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)
{
USART_ClearFlag(USART1,USART_FLAG_ORE);
USART_ReceiveData(USART1);
//The RXNE flag can also be cleared by a read operation to the USART_DR register(USART_ReceiveData()).
}
if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)
{
rcvdata = USART_ReceiveData(USART1);
Push_RcvData(rcvdata);
}
}
3.主函数里处理
u8 cur;
void main(void)
{
/*初始化*/
RcvFrom_Init();
while(1)
{
cur = rbPop(&RX_RingBuff);
/*具体处理*/
}
}