【C语言】一个好用的循环队列与使用示例(以EC200/600为例的AT框架)

目录

1.前言

上一篇:https://blog.csdn.net/ylc0919/article/details/111050124
自从之前说要发二代框架,不知不觉竟然已经鸽了十个月了,难的有时间总结这段时间的收获。写完第一代AT队列之后,我又有许多设想,比如,能否用一个AT队列管理多个AT模块;比如,AT框架的循环队列加循环缓冲占用了很大空间,能够只将数据的相应序号写入队列,读队列的时候临时填充数据帧;又比如,代码中很多地方用到队列,能否写一个队列框架,每次用到的时候直接复制代码,或者用指针来实现多个队列公用一个队列框架……

经过这么长时间以来的实践验证,其中大部分猜想有了结果。

2.结论

依照惯例,先上结论:
1.一个AT队列控制多个AT模块并不实用,因为其中一个AT模块卡住会导致所有模块都动不了,就EC600来说,信号差的时候一条注网指令需要十几秒的反应时间是很正常的,这时候队列处理不了其他指令。
2.取消循环缓冲,队列只存命令序号,须分情况考虑。如果需要处理的数据和时间相关性不大,且格式固定,队列里只存放命令序号是非常好的办法。如果数据一直在变化,那么这种方法并不太好用,但是也不是不能用,可以在flash中开另一个队列,先将当前数据写入到flash,这样既做到了掉电保持,也做到了节省内存。
3.用指针来实现多个队列公用一个队列框架,理论上没有问题,但我没有找到一个简单好用的办法,写出来的代码都不如直接复制队列框架改个名…
4.整理出一个队列框架,用的时候直接复制修改,能够灵活使用,正是本文要写的。

3.循环队列

下面放上循环队列各部分代码,重要地方都做了注释。

3.1写队列到队列头

队列头在前面跑,如果追上了队列尾,就吃掉一个。

void write_to_queue(MY_INFO_t info)
{
	//队列写满了,就覆盖掉最先写入的数据(也可以直接返回,忽略掉新添加的数据)
	if(my_queue.rear==((my_queue.front+1)%MY_QUEUE_MAXNUM))	{
		my_queue.rear=(my_queue.rear+1)%MY_QUEUE_MAXNUM;
	}
	//写入队列
	my_queue.info_queue[my_queue.front]=info;
	//队列头进一格
	my_queue.front=(my_queue.front+1)%MY_QUEUE_MAXNUM;			
}

3.2从尾部读读队列

队列尾在后面追,如果追上了队列头,说明队列无数据。
如果跑了系统,可以直接开一个线程放进去。
如果裸机跑,可以放到10ms事件中。

//每次调用读队列为一个tick,超时时间为n ticks
void read_from_queue(void)
{
	if(my_queue.flags.busy==0){	//队列空闲
		if(my_queue.rear!=my_queue.front){
			my_queue.flags.busy=1;

			my_queue.info		=my_queue.info_queue[my_queue.rear];
			my_queue.otime		=0;
			my_queue.retryNum	=0;
			my_queue.rear		=(my_queue.rear+1)%MY_QUEUE_MAXNUM;		//队列尾追一格			
			
			//数据处理
			
		}
	}
	else{						//队列忙碌										
		my_queue.otime++;								//ticks+1
		if(my_queue.otime>my_queue.info.oTime){			//计时时间到
			my_queue.otime=0;
			if(my_queue.retryNum<my_queue.info.retryNum){//如果没有达到重试次数上限
				my_queue.retryNum++;
				//重试

			}
			else{										//异常,重试达到上限,调用错误处理
				queue_error();							//错误处理
				my_queue.retryNum=0;
				my_queue.flags.busy=0;
			}
		}
	}
}

3.3获取当前队列内数据数量

主要用于调试

u16 get_queue_data_num(void)
{
	u16 ret=0;
	if(my_queue.front>my_queue.rear)
		ret=my_queue.front-my_queue.rear;
	else if(my_queue.front<my_queue.rear)
		ret=MY_QUEUE_MAXNUM-my_queue.rear+my_queue.front;
	else if(my_queue.front==my_queue.rear)
		ret=0;
	return ret;
}

3.4清空队列

直接清空队列结构即可

void clear_my_queue(void)
{
	memset((void*)&my_queue,0,sizeof(MY_QUEUE_t));
}

3.5两个重要结构体

这是要填入队列的每一条数据包含的内容,可以直接在这里新增变量

typedef struct 
{
	u8 retryNum;		//重试次数上限
	u16 cmd;			//信息id
	u16 oTime;			//超时时间 单位ticks
}MY_INFO_t;

这是队列控制结构体,

typedef struct
{
	struct{
		u8 busy:1;							
	}flags;						
	u8 retryNum;							//当前重试次数
	u8 rstNum;								//复位次数,多次失败特殊处理(暂未使用)
	MY_INFO_t info_queue[MY_QUEUE_MAXNUM];	//全部指令环形队列
	MY_INFO_t info;							//当前取出的指令信息
	u16 otime;								//当前命令等待时间
	u16 front;								//队列头数组下标
	u16 rear;								//队列尾数组下标
}MY_QUEUE_t;

4.效果与示例

代码太多,只选择部分截图展示。

4.1三个读队列线程

LiteOS下,我直接给每个读队列单独开了线程。
【C语言】一个好用的循环队列与使用示例(以EC200/600为例的AT框架)

4.2 AT框架写队列与EC200初始化

为了简化初始化代码,我没有使用结构体作为写队列入参。
【C语言】一个好用的循环队列与使用示例(以EC200/600为例的AT框架)
相比在写队列函数里面用strlen获得长度,我还是更喜欢把他当做参数传入进去。
【C语言】一个好用的循环队列与使用示例(以EC200/600为例的AT框架)

4.3 AT框架读队列

读队列,没啥好说的
【C语言】一个好用的循环队列与使用示例(以EC200/600为例的AT框架)

4.4 EC200维持TCP长连接

这些就照着AT指令手册写就行了
【C语言】一个好用的循环队列与使用示例(以EC200/600为例的AT框架)

5.下载

5.1 循环队列

https://download.csdn.net/download/ylc0919/44922216

5.2 AT框架+EC200的TCP长连接(与EC600通用)

https://download.csdn.net/download/ylc0919/44922750
需要对照EC200At指令手册。

上一篇:2021年全国甲卷理科数学真题


下一篇:浮动