常用单片机编程思想及例程5——循环队列(fifo+串口)

目录

前言

循环队列原理

功能实现

例程(串口收发)

写在最后 


前言

        说起队列相信大家或多或少都使用过,像常用的一些操作系统内部都是有集成的,用户可以直接使用,但是如果让我们自己去实现一个队列,应该怎么去做呢?毕竟并不是所有的场景都可以有操作系统。

        首先,只是单纯的初始化一个队列是没有任何意义的,它必须搭配相应的使用场景,例如常见的串口收发、DMA数据搬运、消息邮箱等。

        为什么要使用队列呢?直接从串口寄存器读取数据保存到全局缓冲区(已usart为例),然后再发送出去,效果不是一样的吗?关于这个问题,浅谈一下个人对队列的理解:

优点:

  1. 提高程序执行效率;
  2. 提高代码的可追溯性,便于排查问题;
  3. 提高系统稳定性(功能解耦、异步处理)

缺点:

  1. 系统复杂度提高;
  2. 系统可用性降低

循环队列原理

        关于循环队列大家应该知道是怎么回事,用户需要初始化一个数据缓冲区和一个读写位置,每次把需要保存到缓冲的数据压栈(保存到队列),需要用到的时候再从队列里边取出来,如果写入的数据(压栈数据)超过了最大容量,则再从头开始写,读操作与写操作类似(偷了一个图,嘿嘿)。

功能实现

话不多说,直接上代码,这样写的好处是用户可以自定义队列数据类型(类似于邮箱)

// 环形缓冲函数版.c
/**
 * @file CircleBuffer.c
 * @brief 环形缓冲
 * @author ChenXJ
 * @version 1.1
 * @date 2024-02-22
 */

#include "string.h"
#include "CircleBuffer.h"

/**
 * @brief 初始化环形缓冲
 *
 * @param p        被初始化的环形队列的指针
 * @param itemSize 队列中1个元素的大小,单位:字节
 * @param base     队列基地址
 * @param size     队列最大元素个数
 * @return         true - 创建成功, false - 创建失败
 */
bool BufferInit(tCircleBuffer *p, unsigned char itemSize, void *base, unsigned int size)
{
	if(p == NULL || base == NULL || itemSize == 0 || size == 0)
		return false;
	
	p->size = size;
	p->rd = 0;
	p->wr = 0;
	p->pbuf = base;
	p->itemSize = itemSize;

	return true;
}

/**
 * @brief 清空缓冲
 *
 * @param pdata p 环形缓冲的指针
 *
 * @return 1为空,0为非空
 */
bool BufferClear(tCircleBuffer *p)
{
	if (p == 0)
		return 0;

	p->rd = p->wr = 0;

	return 1;
}

/**
 * @brief 检查环形缓冲是否为空
 *
 * @param pdata p 环形缓冲的指针
 *
 * @return 1为空,0为非空
 */
bool IsBufferEmpty(tCircleBuffer *p)
{
	return p->wr == p->rd;
}

/**
 * @brief 检查环形缓冲是否已满
 *
 * @param pdata p 环形缓冲的指针
 *
 * @return 1为满,0为未满
 */
bool IsBufferFull(tCircleBuffer *p)
{
	return (p->wr + 1) % p->size == p->rd;
}

/**
 * @brief 获取环形缓冲容量
 *
 * @param pdata p 环形缓冲的指针
 *
 * @return 环形缓冲容量
 */
unsigned int BufferCapacity(tCircleBuffer *p)
{
	return p->size;
}

/**
 * @brief 获取环形缓冲读取数据个数
 *
 * @param pdata p 环形缓冲的指针
 *
 * @return 环形缓冲读取个数
 */
unsigned int BufferNumToRead(tCircleBuffer *p)
{
	unsigned int num = 0;

	if(p == 0)
		return 0;

//	if(p->wr >= p->rd)
//	{
		num = (p->wr + p->size - p->rd) % p->size;
//	}
//	else
//	{
//		num = (p->wr + p->size - p->rd) / p->itemSize;
//	}

	return num;
}

/**
 * @brief 从环形缓冲中读取1个数据
 *
 * @param pdata p 环形缓冲的指针
 * @param out 指向读取的数据
 *
 * @return 1表示读取成功  0表示缓冲为空,读取失败
 */
bool BufferReadOne(tCircleBuffer *p, void *out)
{
	if ((p == 0) || (out == 0))
	{
		return 0;
	}

	if (IsBufferEmpty(p))
	{
		return 0;
	}

	memcpy(out, (unsigned char *)p->pbuf + p->rd * p->itemSize, p->itemSize);
	p->rd = (p->rd + 1) % p->size;
	
	return 1;
}

/**
 * @brief 从环形缓冲中读取多个数据
 *
 * @param pdata p 环形缓冲的指针
 * @param out 指向读取的数据
 * @param len 读取个数
 *
 * @return 1表示读取成功  0表示缓冲为空,读取失败
 */
bool BufferRead(tCircleBuffer *p, void *out,unsigned short len)
{
	unsigned int remain_len;
	if ((p == 0) || (out == 0))
	{
		return 0;
	}

	if (IsBufferEmpty(p))
	{
		return 0;
	}

	if(len > p->size)
	{
		return 0;
	}

	remain_len = p->size - p->rd;
	if(remain_len > len)
	{
		memcpy(out, (unsigned char *)p->pbuf + p->rd *p->itemSize, len * p->itemSize);
		p->rd = (p->rd + len) % p->size;
	}
	else
	{
		memcpy(out,(unsigned char *)p->pbuf + p->rd * p->itemSize,remain_len * p->itemSize);
		memcpy((unsigned char *)out+(remain_len * p->itemSize),p->pbuf,(len - remain_len) * p->itemSize);
		p->rd = (len - remain_len) % p->size;
	}

	return 1;
}

/**
 * @brief 向环形缓冲写入1个数据
 *
 * @param pdata p 环形缓冲的指针
 * @param in 指向写入的数据
 *
 * @return 1表示写入成功  0表示缓冲已满,写入失败
 */
bool BufferWriteOne(tCircleBuffer *p, void *in)
{
	if ((p == 0) || (in == 0))
	{
		return 0;
	}

	if (IsBufferFull(p))
	{
		return 0;
	}

	memcpy((unsigned char *)p->pbuf + p->wr * p->itemSize, in, p->itemSize);
	p->wr = (p->wr + 1) % p->size;
	
	return 1;
}

/**
 * @brief 向环形缓冲写入多个数据
 *
 * @param pdata p 环形缓冲的指针
 * @param in 指向写入的数据
 * @param len 写入个数
 *
 * @return 1表示写入成功  0表示缓冲已满,写入失败
 */
bool BufferWrite(tCircleBuffer *p, void *in,unsigned short len)
{
	unsigned int remain_len;
	if ((p == 0) || (in == 0))
	{
		return 0;
	}

	if (IsBufferFull(p))
	{
		return 0;
	}

	if(len > p->size)
	{
		return 0;
	}

	remain_len = p->size - p->wr;
	if(remain_len > len)
	{
		memcpy((unsigned char *)p->pbuf + p->wr * p->itemSize, in, (len * p->itemSize));
		p->wr = (p->wr + len) % p->size;
	}
	else
	{
		memcpy((unsigned char *)p->pbuf + p->wr * p->itemSize,in,(remain_len * p->itemSize));
		memcpy(p->pbuf,(unsigned char *)in + (remain_len * p->itemSize),(len - remain_len) * p->itemSize);
		p->wr = (len - remain_len) % p->size;
	}

	return 1;
}

 队列类型如下:

//环形缓冲.h
/**
 * @file CircleBuffer.h
 * @brief 环形缓冲
 * @author ChenXJ
 * @version 1.1
 * @date 2024-02-22
 */
 
#ifndef _CIRCLE_BUFFER_H_
#define _CIRCLE_BUFFER_H_
 
#include "stdbool.h"
#include "stdint.h"
 
/// 环形缓冲结构体
typedef struct _cyclebuffer
{
	unsigned int size;			///< 环形缓冲大小,为环形缓冲能存储数据的个数
	unsigned int rd;			///< 从环形缓冲读取数据的位置
	unsigned int wr;			///< 向环形缓冲写数据的位置
	void *pbuf;			        ///< 指向环形缓冲区的指针
	unsigned char itemSize;		///< 1个数据的大小,单位:字节
}tCircleBuffer;
 
/**
 * @brief 初始化环形缓冲
 *
 * @param p 被初始化的环形缓冲的指针
 * @param itemSize 缓冲中1个数据的大小,单位:字节
 * @param buf 环形缓冲的缓冲区
 * @param bufSize 缓冲区的大小,单位:字节
 */
bool BufferInit(tCircleBuffer *p, unsigned char itemSize, void *buf, unsigned int bufSize);
 
/**
 * @brief 清空缓冲
 *
 * @param pdata p 环形缓冲的指针
 *
 * @return 1为空,0为非空
 */
bool BufferClear(tCircleBuffer *p);

/**
 * @brief 检查环形缓冲是否为空,如果缓冲的大小为0,认为缓冲为空
 *
 * @param pdata p 环形缓冲的指针
 * 
 * @return 1为空,0为非空
 */
bool IsBufferEmpty(tCircleBuffer *p);
 
/**
 * @brief 检查环形缓冲是否已满,如果缓冲的大小为0,认为缓冲为满
 *
 * @param pdata p 环形缓冲的指针
 * 
 * @return 1为满,0为未满
 */
bool IsBufferFull(tCircleBuffer *p);
 
/**
 * @brief 获取环形缓冲容量
 *
 * @param pdata p 环形缓冲的指针
 * 
 * @return 环形缓冲容量
 */
unsigned int BufferCapacity(tCircleBuffer *p);
 
/**
 * @brief 获取环形缓冲读取数据个数
 *
 * @param pdata p 环形缓冲的指针
 *
 * @return 环形缓冲读取个数,单位:p->itemSize
 */
unsigned int BufferNumToRead(tCircleBuffer *p);

/**
 * @brief 从环形缓冲中读取一个数据
 *
 * @param pdata p 环形缓冲的指针
 * @param out 指向读取的数据
 * 
 * @return 1表示读取成功  0表示缓冲为空,读取失败
 */
bool BufferReadOne(tCircleBuffer *p, void *out);

/**
 * @brief 从环形缓冲中读取多个数据
 *
 * @param pdata p 环形缓冲的指针
 * @param out 指向读取的数据
 * @param len 读取数据个数
 *
 * @return 1表示读取成功  0表示缓冲为空,读取失败
 */
bool BufferRead(tCircleBuffer *p, void *out,unsigned short len);

/**
 * @brief 向环形缓冲写入一个数据
 *
 * @param pdata p 环形缓冲的指针
 * @param in 指向写入的数据
 * 
 * @return 1表示写入成功  0表示缓冲已满,写入失败
 */
bool BufferWriteOne(tCircleBuffer *p, void *in);
 
/**
 * @brief 向环形缓冲写入多个数据
 *
 * @param pdata p 环形缓冲的指针
 * @param in 指向写入的数据
 * @param len 写入数据个数
 *
 * @return 1表示写入成功  0表示缓冲已满,写入失败
 */
bool BufferWrite(tCircleBuffer *p, void *in,unsigned short len);

#endif

 代码亲测可用,大家可自行参考,如有不足之处还请提出,不胜感激!

例程(串口收发)

         关于程序例程在这里不再赘述,主要以伪代码的方式说明循环队列的使用。

        先说串口接收,正常情况下,串口接收大都是通过接收完成中断取一个字节,然后把数据放到接收缓冲区里,等到空闲中断(如果有)到来时再做数据解析。这是理想情况下,这么做确实没问题。但作为软件工程师应该考虑各种极端情况,如果串口接收了一半数据掉电了怎么办?串口从机程序阻塞,一包数据不是连续发送且不定长的话怎么办?(当然,如果是这种情况,这里还需要进行“拼包”),每次从中断中读完数据,处理完之后再清缓冲,再去读,当数据吞吐量足够大的时候,这也是一笔不小的时间开销。如果使用队列进行处理,我们只需要在接收到数据的时候压栈,当空闲中断(如果有)到来的时候从队列里读取数据解析(因为是循环队列,并不需要清理缓冲),哪怕有一次或几次空闲中断没有进入也没有关系(有溢出风险!),只要接收中断可以正常接收数据,当我从队列读数据时,一样可以把之前的数据全部读出来,并且可以通过队列缓冲更加直观的看到数据接收情况,便于排查问题。(如果没有空闲中断,也可以通过定时查询的方式读取队列)

        串口发送有查询发送和中断发送两种方式,一般情况下,当数据吞吐量较小的时候两种发送区别不大,但是当有大量数据处理或者系统实时性要求较为严格时,中断发送还是很有必要的,毕竟中断调度的优先级要高于代码的优先级。查询式发送需要等待发送完成标志(虽然这段时间很快,但却是绝对阻塞的),中断发送却可以避免这个问题,发送完成标志通过中断处理,不需要用户的代码中存在阻塞的情况(同样的功能实现,执行效率更高的代码就是更好的代码,在面对功能需求的时候,实现功能是基础,在此基础上写出更高质量的代码才应该是我们的追求),那么中断发送时如何和队列结合起来?stm32标准库里边中断发送每次都是一个字节,怎么发送多字节呢?这个在hal库里边其实是有实现方式的,如果你看过hal库的发送接口,就不难发现其实它就是通过队列实现的。大概思路就是:先初始化一个队列,当用户需要发送数据的时候先压栈,同时打开发送中断,在中断服务中从队列中读数据,直到写入的数据发送完成,再关闭中断。试想一下这样实现的好处是什么?这样做的好处就是做到了发送和接收异步处理!!!这个很重要,这两个人线程互不干扰,发送和接收都是通过队列实现,省去了清缓存等一系列操作。用户只管把要发送的数据丢给发送队列,剩下的发送任务交给中断(功能解耦是模块化的前提,在多线程任务中尤为重要)

 使用例程r如下:

uint8_t queue_rcv_buf[100];
uint8_t queue_snd_buf[100];
buffer_t p1,p2;

typedef struct {
    uint8_t byte;
    uint8_t flg;
}rcv_msg_t;

rcv_msg_t rcv_msg;

void it_handle(void)
{
    if(txe)
    {
        clear;
        uint32_t len = BufferNumToRead(&p2);
        if(len)   //有数据要发送
        {
            uint8_t byte = 0;
            BufferReadOne(&p2,&byte);
            usart->DR = byte;
        }
        else
        {
            disable_it_txe; //没有数据要发送的时候关闭发送中断
        }
            
    }
    else if(rxne) //接收中断
    {
        clear;
        rcv_msg.byte = usart->DR;
        BufferWriteOne(&p1,&rcv_msg.byte);
    }
    else if(idle) //空闲中断
    {
        clear;
        rcv_msg.flg = 1;
    }
}


void init(void)
{
    usart_init();
    BufferInit(&p1,1,queue_rcv_buf,100);
    BufferInit(&p2,1,queue_snd_buf,100);
}

//数据处理任务
void loop(void)
{
    uint8_t recvbuf[x] = {0}; //接收到的数据
    uint8_t sendbuf[x] = {0}; //要发送的数据
    uint16_t len1 = 0,len2 = 0;

#if 有空闲中断
    if(rcv_msg.flg)
    {
        rcv_msg.flg = 0;
        len1  = BufferNumToRead(&p1);  //获取queue长度
        BufferRead(&p1,recvbuf,len1);    //读数据
        handle(recvbuf,len1); //数据解析
    }
#else
    (每10ms执行一次)  //时间自定义
    {
        len1  = BufferNumToRead(&p1);  //获取queue长度
        BufferRead(&p,recvbuf,len1);    //读数据
        handle(recvbuf,len1); //数据解析
    }
#endif

    //需要通过串口发送数据
    BufferWrite(&p2,sendbuf,len2);
    enable_it_txe;  //开启发送中断
}

写在最后 

         文章皆自原创,如因此对各位有所启发,实属我幸;当然,如有不恰当的地方,也欢迎看官提出,不胜感激!

以下是用C语言实现循环队列的代码及使用例程: ```c #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 5 typedef struct { int front; // 队头指针 int rear; // 队尾指针 int data[MAX_SIZE]; // 数据存储数组 } Queue; // 初始化队列 void InitQueue(Queue *q) { q->front = 0; q->rear = 0; } // 判断队列是否为空 int IsEmpty(Queue q) { return q.front == q.rear; } // 判断队列是否已满 int IsFull(Queue q) { return (q.rear+1) % MAX_SIZE == q.front; } // 入队 void EnQueue(Queue *q, int x) { if (IsFull(*q)) { printf("Queue is full.\n"); return; } q->data[q->rear] = x; q->rear = (q->rear+1) % MAX_SIZE; } // 出队 int DeQueue(Queue *q) { if (IsEmpty(*q)) { printf("Queue is empty.\n"); return -1; } int x = q->data[q->front]; q->front = (q->front+1) % MAX_SIZE; return x; } // 遍历队列 void TraverseQueue(Queue q) { if (IsEmpty(q)) { printf("Queue is empty.\n"); return; } printf("Traversing queue: "); int i = q.front; while (i != q.rear) { printf("%d ", q.data[i]); i = (i+1) % MAX_SIZE; } printf("\n"); } int main() { Queue q; InitQueue(&q); EnQueue(&q, 1); EnQueue(&q, 2); EnQueue(&q, 3); TraverseQueue(q); int x = DeQueue(&q); printf("Dequeued element: %d\n", x); TraverseQueue(q); EnQueue(&q, 4); EnQueue(&q, 5); EnQueue(&q, 6); TraverseQueue(q); return 0; } ``` 使用例程: ```c InitQueue(&q); // 初始化队列 EnQueue(&q, 1); // 入队 EnQueue(&q, 2); EnQueue(&q, 3); int x = DeQueue(&q); // 出队 printf("Dequeued element: %d\n", x); TraverseQueue(q); // 遍历队列 ``` 输出: ``` Traversing queue: 1 2 3 Dequeued element: 1 Traversing queue: 2 3 Queue is full. Traversing queue: 2 3 4 5 6 ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈大本事er

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值