遇到问题
最近在进行数据的接收时,单纯的使用数组接收,已经满足不了我的需求。所以在网上了解到可以使用队列进行对数据的存储,使用也很简单,代码量也不大,并且都贴有运行代码,我也尝试着执行了几个粘贴来的代码,确实是符合要求的,但有个问题,并没有被考虑进去,那就是在单片机中使用队列,出队或者入队是是会被中断打断的,此时若是使用指针进行数据的操作,效果可想而知,直接卡死,你若是能看到我这篇文章,很显然你也遇到了同样的问题,所以在想,不行就用数组对付接收,然后清除buf。简单而实用,但遇到问题怎可轻言放弃,接下来说一下我的思路,速度有点快,快上车
如何解决
在网上你可能会看到无冲突的案例,是使用了关闭中断的方式,使入队、出队很安全,毫无风险,但同时,也可能出现丢包的情况出现,但为了入队、出队的安全,毕竟道路千万条,安全第一条,做出点牺牲是应该的嘛。 那有没有更加好的办法呢,有,我这里提供一种解决方法,不一定是最好的,但一定符合你的要求,既不用关中断,也保证了入队、出队的安全性
队列入队、出队思路
在一般的场合下,都是在中断中进行入队的操作,而在主循环中出队操作,所以问题就在入队的时候,不去干扰到出队,换句话说,入队与出队是两件事,各干各的,互不打扰,但又互相影响,可以想象成量子纠缠,在量子力学中,对入列与出列在彼此的相互作用后,系统总体容量依然不变。(别听我瞎说)
既然是互不打扰,那就各干各的,入队一个入队buf,出队一个出队buf,需要出队就将出队buf内的数据读出,需要入队就将数据写入入队buf,很好,思路是没问题的,出队时会将数据读空的,入队时会将数据塞满的,那怎么办,转换一下呗,那什么时候转换呢? 出队时:出队buf为空,入队buf不为空时,把出队buf改为入队buf,入队buf改为出队buf,这样下次读时可以读出数据,写入时也非溢出的写入进去 入队时:入队buf已满,出队buf不管是满是空,将把出队buf改为入队buf,入队buf改为出队buf,为什么要这么做,根据队列的性质,先进先出,当一个buf满时,说明出现了栈溢出,也就是队列溢出,需要将最先进入的数据清除,所以在一个队列溢出时,另一个队列全部都会被清除 入队的情况:当出队数据一个没有被读出,出队队列一直为满队列,入队队列在入队一个数据时,队列的长度会增加1,并不满足队列的性质,所以在入队队列加入数据时,要将出队队列手动取出一个数据,以保证队列的长度小于等于队列的大小。 基于以上思路,做一下总结,定义两个数组,一个为入队数组,一个为出队数组,入队数组会控制出队数组,出队数组会控制入队数组,控制的权限就是保证队列性质,保证量子纠缠态。
代码实战
首先在这里我先贴出我写的代码
#include "queue.h"
void Message_QueueMode(Queue4 *Queue,unsigned char mode)
{
Queue->mode= mode;
}
void Message_QueueEmptyOne(Queue4 *Queue,unsigned char **Head, unsigned char **Tail, unsigned char (*Buff)[4],unsigned char lock)
{
*(Head +lock) = Buff[lock];
*(Tail +lock) = Buff[lock];
Queue->full[lock] = 0;
}
void Message_QueueEmpty(Queue4 *Queue,unsigned char **Head, unsigned char **Tail, unsigned char (*Buff)[4])
{
for(int i = 0; i < QUEUE_BUFFER_SUM; i++)
{
*(Head +i) = Buff[i];
*(Tail +i) = Buff[i];
Queue->full[i] = 0;
}
Queue->lock = 0;
Queue->queue_idle = 0;
}
void Message_QueueLenUpdate(Queue4 *Queue,unsigned char **Head, unsigned char **Tail, unsigned short Len)
{
for(int i = 0; i < QUEUE_BUFFER_SUM; i++)
{
if(*(Tail+i) > *(Head+i)) { Queue->length[i] = *(Tail+i)-*(Head+i); }
if(*(Tail+i) < *(Head+i)) { Queue->length[i] = *(Tail+i)+Len-*(Head+i); }
if(*(Tail+i) == *(Head+i) && Queue->full[i]) { Queue->length[i] = Len;}
if(*(Tail+i) == *(Head+i) && !Queue->full[i]) { Queue->length[i] = 0; }
}
}
void Message_QueueDataIn(Queue4 *Queue,unsigned char **Head, unsigned char **Tail, unsigned char (*Buff)[4], unsigned short Len, unsigned char *Data, unsigned short DataLen)
{
if(Queue->mode){
if(Queue->queue_idle == 0){ MessageQueueEmptyOne( (*Queue),!Queue->lock); }
MessageQueueEmptyOne( (*Queue),Queue->lock);
}
for(int num = 0; num < DataLen; num++, Data++)
{
if(!Queue->mode){
MessageQueueLenUpdate( (*Queue) );
if( (Queue->length[0] + Queue->length[1]) >= 4 && !Queue->queue_idle)
{
if(Queue->length[!Queue->lock] == 0) {
Queue->lock = !Queue->lock;
Queue->full[!Queue->lock] = 0;
}
for(int i = 0; i <= ((Queue->length[Queue->lock] + Queue->length[!Queue->lock]) - 4);i++)
{
if(++(*(Head+!Queue->lock)) == Buff[!Queue->lock]+Len) { *(Head+!Queue->lock) = Buff[!Queue->lock]; }
if(*(Tail+!Queue->lock) == *(Head+!Queue->lock) && Queue->full[!Queue->lock] == 0){ break; }
}
}
}
MessageQueueLenUpdate( (*Queue) );
**(Tail+Queue->lock) = *Data;
if(Queue->full[Queue->lock] == 1)
{
if(++(*(Head+Queue->lock)) == Buff[Queue->lock]+Len) { *(Head+Queue->lock) = Buff[Queue->lock]; }
if(++(*(Tail+Queue->lock)) == Buff[Queue->lock]+Len) { *(Tail+Queue->lock) = Buff[Queue->lock]; }
}
else
{
if(++(*(Tail+Queue->lock)) == Buff[Queue->lock]+Len) { *(Tail+Queue->lock) = Buff[Queue->lock]; }
if(*(Tail+Queue->lock) == *(Head+Queue->lock) ) { Queue->full[Queue->lock] = 1; }
}
}
MessageQueueLenUpdate( (*Queue) );
}
void Message_QueueDataOut(Queue4 *Queue,unsigned char **Head, unsigned char **Tail, unsigned char (*Buff)[4], unsigned short Len, unsigned char *Data)
{
Queue->queue_idle = 1; Queue->ret = 0;
if( !(*(Tail+!Queue->lock) == *(Head+!Queue->lock) && Queue->full[!Queue->lock] == 0 ))
{
*Data = **(Head+!Queue->lock);
if(++(*(Head+!Queue->lock)) == Buff[!Queue->lock]+Len) { *(Head+!Queue->lock) = Buff[!Queue->lock];}
Queue->full[!Queue->lock] = 0; Queue->ret = 1;
}
MessageQueueLenUpdate( (*Queue) );
if(Queue->length[Queue->lock] != 0 && Queue->length[!Queue->lock] == 0)
{
Queue->lock = !Queue->lock;
if(Queue->ret == 0){ MessageQueueDataOut((*Queue),Data); }
}
Queue->queue_idle = 0;
}
#ifndef __QUEUE_H
#define __QUEUE_H
typedef enum
{
DATEQUEUE = 0,
TASKQUEUE = 1,
}queuemode_e;
typedef struct
{
#define QUEUE_BUFFER_SUM 2
unsigned char full[QUEUE_BUFFER_SUM];
unsigned char length[QUEUE_BUFFER_SUM];
unsigned char ret,lock,queue_idle,mode;
unsigned char *Head[QUEUE_BUFFER_SUM];
unsigned char *Tail[QUEUE_BUFFER_SUM];
unsigned char Buff[QUEUE_BUFFER_SUM][4];
}Queue4;
void Message_QueueEmpty (Queue4 *Queue,unsigned char **Head, unsigned char **Tail, unsigned char (*Buff)[4]);
void Message_QueueDataLen (Queue4 *Queue,unsigned char **Head, unsigned char **Tail, unsigned short Len);
void Message_QueueDataIn (Queue4 *Queue,unsigned char **Head, unsigned char **Tail, unsigned char (*Buff)[4], unsigned short Len, unsigned char *Data, unsigned short DataLen);
void Message_QueueDataOut (Queue4 *Queue,unsigned char **Head, unsigned char **Tail, unsigned char (*Buff)[4], unsigned short Len, unsigned char *Data);
void Message_QueueMode (Queue4 *Queue,unsigned char mode);
#define MessageQueueLenUpdate(x) Message_QueueLenUpdate(&x,x.Head,x.Tail, sizeof((x).Buff[0]))
#define MessageQueueEmpty(x) Message_QueueEmpty (&x,x.Head,x.Tail, x.Buff)
#define MessageQueueEmptyOne(x,y) Message_QueueEmptyOne (&x,x.Head,x.Tail, x.Buff,(y))
#define MessageQueueDataIn(x,y,z) Message_QueueDataIn (&x,x.Head,x.Tail, x.Buff,sizeof((x).Buff[0]),(y),(z))
#define MessageQueueDataOut(x,y) Message_QueueDataOut(&x,x.Head,x.Tail, x.Buff,sizeof((x).Buff[0]),(y))
#define MessageQueueMode(x,y) Message_QueueMode(&x,(y));
#endif
一共有100行代码左右,相比较,确实比普通的队列更加繁琐,但确实实现了该有的效果,成功总要是付出点代码的嘛
这里我觉得口头叙述效果可能并不好,说到后面前面又忘记了,所以我画了一张图,入队、出队的逻辑思维图,在结合代码看,一定会有意想不到的惊喜
这就是数据结构可视化的结构,循环队列 下图代表入队和出队出现的情况与处理机制,如果有不明白的地方可以在下方留言
难以信服,实战演示
我使用的单片机是stm32F103C8T6
这里不做 先入列在出列,或者 先出列在入列 的基本操作,演示成功也没有什么特别的,直接演示最头疼的部分
使用过单片机就会知道,单片机有个优先级很低,但很好用的定时器,滴答定时器,HAL用来封装了延时函数,这里我使用标准库来演示,在滴答定时器进行入队操作,入队个数随意,100,1000都可以,因为机制不允许越界,只会存储最后存入的,删除最先存入的。
static unsigned char varible = 0;
unsigned char send[4];
void SysTick_Handler(void)
{
for(int i = 0; i <3; i++)
{
send[i] = varible++;
}
MessageQueueDataIn(KeyMsg,send,3);
}
unsigned char echo;
Queue4 KeyMsg;
int main(void)
{
MessageQueueEmpty(KeyMsg);
MessageQueueMode(KeyMsg,TASKQUEUE);
SysTick_Init();
LED_GPIO_Config();
USART1_Init(115200);
CMD_LIST_Init();
while(1)
{
MessageQueueDataOut(KeyMsg,&echo);
if(KeyMsg.ret == 1)
{
printf("varible = %d\r\n",echo);
}
}
}
中断中入队,循环中出队,下面是串口直观打印图,当前为 任务队列模式
下面打印结果是 消息队列模式 打印情况,若出现丢包,是因为入队快而出队慢 下面我贴出硬件连接图,过于简单
代码总结
最后的代码是符合我的预期的,而且长时间的测试并没有出现问题,逻辑上的框图也可以体现出,避免指针越界或数据位错乱问题。总体上满足了使用要求,如果有需要测试平台的源码及完整使用教程,可以评论或私信@我。
最后贴出代码
链接:可中断式非阻塞循环队列代码 如有任何问题,都可私信或评论解答疑问
|