当前位置: 首页 > news >正文

µCOS-III从入门到精通 第十章(µC/OS-III消息队列)

参考教程:【正点原子】手把手教你学UCOS-III实时操作系统_哔哩哔哩_bilibili

一、队列介绍

1、概述

(1)队列是任务到任务、中断到任务数据交流的一种机制,它不同于全局变量。假设有一个全局变量a,现有两个任务都在写这个变量a,如下所示,变量自增分为三个步骤,如果在任务1读数据以后、修改数据以前发生任务切换,这将导致任务2和任务1读取相同的数据,并且基于相同的数据做相同的修改,这显然是有问题的,而使用队列可以避免这种问题(指访问冲突)。

(2)µC/OS-III基于队列实现了多种功能,并且读写队列做好了保护(主要依靠临界区),防止多任务同时访问冲突,用户只需要直接调用API函数即可。(中断不可以调用队列接收函数,但是可以调用队列发送函数)

2、µC/OS-III队列的特点

(1)数据入队出队方式:队列通常采用“先进先出”(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取;µC/OS-III中也可以配置为“后进先出”(LIFO)方式。

(2)数据传递方式:µC/OS-III中的队列数据是一个“万能指针”(void*类型的指针),可以指向任何数据甚至是函数,所以发送方和接收方必须按照约定好的方式去发送和接收消息,这样才能正常解析接收到的消息。

(3)队列不属于某个任务,任何任务和中断都可以向队列发送消息,但是读取消息只能在任务中,不支持中断读取消息。

(4)当任务向一个队列读取消息时,可以指定一个阻塞时间。

        假设此时队列已空无法出队(出队阻塞):

        若阻塞时间为0,其它任务打算让数据出队时会死等(期间任务将会进入阻塞态),一直等到队列中有数据可以出队为止。

        若阻塞时间不为0,其它任务打算让数据出队时会等待设定的阻塞时间(期间任务将会进入阻塞态),若在该时间内还无数据可出队,超时后直接返回,不再等待。

(5)当任务向一个队列写入消息时,无论如何都不会引起阻塞。

二、队列相关API函数介绍

1、概述

(1)使用队列的主要流程:创建队列→写队列(发送消息到队列)→读队列(获取消息队列的数据)。

(2)队列相关API函数概览:

函数

描述

OSQCreate

创建一个消息队列

OSQDel

删除一个消息队列

OSQFlush

清空消息队列中的所有消息

OSQPend

获取消息队列中的消息

OSQPendAbort

终止任务挂起等待消息队列

OSQPost

发送消息到消息队列

2、函数定义概览

(1)OSQCreate函数定义:

void OSQCreate
(
    OS_Q* 			p_q,
	CPU_CHAR* 	p_name,
	OS_MSG_QTY 	max_qty,
	OS_ERR* 		p_err
) 

形参

描述

p_q 

指向消息队列结构体的指针

p_name 

指向作为消息队列名的 ASCII 字符串的指针

max_qty 

消息队列的大小

p_err 

指向接收错误代码变量的指针

(2)OSQPost函数定义:

void  OSQPost
(
	OS_Q*			p_q,
	void* 			p_void,
	OS_MSG_SIZE	msg_size,
	OS_OPT 		opt,
	OS_ERR* 		p_err
) 

形参

描述

p_q 

指向消息队列结构体的指针

p_void 

指向消息的指针

msg_size 

消息的大小,单位: 字节

opt 

OS_OPT_POST_FIFO :将发送的消息保存在队列的末尾
OS_OPT_POST_LIFO :将发送的消息保存在队列的开头
OS_OPT_POST_ALL :将消息发送给所有等待该消息的任务
OS_OPT_POST_NO_SCHED :禁止在本函数内执行任务调度
以上的几种类型可以进行组合

p_err 

指向接收错误代码变量的指针

(3)OSQPend函数定义:

void  OSQPost
(
	OS_Q*			p_q,
	void* 			p_void,
	OS_MSG_SIZE	msg_size,
	OS_OPT 		opt,
	OS_ERR* 		p_err
) 

形参

描述

p_q 

指向消息队列结构体的指针

timeout 

任务挂起等待消息队列的最大允许时间,当为0时,表示将一直等待,直到接收到消息

opt 

OS_OPT_PEND_BLOCKING:如无任何消息存在就阻塞任务OS_OPT_PEND_NON_BLOCKING:如无任何消息就直接返回

p_msg_size 

指向一个变量用来表示接收到的消息长度(字节数)

p_ts 

指向接收消息队列接收时的时间戳的变量的指针,为NULL,说明用户没有要求时间戳

p_err 

指向接收错误代码变量的指针

三、队列操作实验

1、原理图与实验目标

(1)原理图(未画出OLED屏,接法与stm32教程中的一致):

(2)实验目标:

①设计4个任务——start_task、task1、task2、task3:

[1]start_task:用于创建其它三个任务,然后删除自身。

[2]task1:当按键key0按下,将当前存储按键次数变量的地址拷贝到队列key_queue中;当按键key1按下,将传输大容量数据,拷贝大容量数据的地址到队列big_date_queue中。(入队)

[3]task2:读取队列key_queue中的消息,在OLED屏上打印出按键次数。(出队)

[4]task3:从队列big_date_queue读取大容量数据地址,通过地址访问大容量数据,并通过OLED显示。(出队)

②预期实验现象:

[1]程序下载到板子上后,暂时没有任何现象。

[2]按下相关按键,OLED屏会有相应变化。

2、实验步骤

(1)将“任务创建和删除实验”的工程文件夹复制一份,在拷贝版中进行实验。

(2)在UCOS_experiment.c文件中添加头文件OLED.h,并定义一个整型数组(全局变量,作为大容量数据)以及两个队列(分别为按键次数队列和大容量数据队列)。

#include "OLED.h"

int buffer[5] = {13, 32, 26, 114, 51};

OS_Q key_queue;        //按键次数队列
OS_Q big_data_queue;   //大容量数据队列

(3)在UCOS_Test函数中需要创建记录按键次数的队列和大容量数据队列,与它们的句柄一一对应。

void UCOS_Test(void)
{
	OS_ERR err;
	OSInit(&err);    //初始化μC/OS-III

	//创建Start Task
    OSTaskCreate (&start_task_tcb,
                  "start_task",
                  (OS_TASK_PTR)start_task,
                  NULL,
                  START_TASK_PRIO,
                  start_task_stack,
                  START_TASK_STACK_SIZE / 10,
                  START_TASK_STACK_SIZE,
                  0,
                  0,
                  0,
                  (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                  &err);
	
	OSQCreate(&key_queue, "key_queue", 1, &err);  //创建队列key_queue,长度为1
	OSQCreate(&big_data_queue, "big_data_queue", 1, &err);   //创建队列big_data_queue,长度为1
    
    OSStart(&err);
}

(4)更改task1、task2和task3函数的实现。

void task1(void)
{
	OS_ERR err;
	uint8_t key = 0;
	unsigned int num = 0;      //记录按键1按下次数
	int* buf = buffer;         //OSQPost中不能直接传&buffer
	while(1)
	{
		key = Key_GetNum();    //读取按键键值
		if(key == 1)
		{
			num++;             //修改计数值
			OSQPost(&key_queue, &num, sizeof(num), OS_OPT_POST_FIFO, &err);
		}
		else if(key == 2)
		{
			buffer[1]++;       //修改大容量数据本身的值
			OSQPost(&big_data_queue, &buf, sizeof(buffer), OS_OPT_POST_FIFO, &err);
		}
		OSTimeDly(10, OS_OPT_TIME_DLY, &err);      //自我阻塞10ms
	}
}

void task2(void)
{
	OS_ERR err;
	CPU_SR_ALLOC();
	OS_MSG_SIZE num_length = sizeof(unsigned int);
	int* buffer1;
	while(1)
	{
		buffer1 = OSQPend(&key_queue, 0, OS_OPT_PEND_BLOCKING, &num_length, NULL, &err);  //读不出来就死等
		CPU_CRITICAL_ENTER();    //要屏蔽中断,防止与OLED通信时产生差错
		OLED_ShowNum(1, 1, *buffer1, 5);
		CPU_CRITICAL_EXIT();
		OSTimeDly(500, OS_OPT_TIME_DLY, &err);      //自我阻塞500ms
	}
}

void task3(void)
{
	OS_ERR err;
	OS_MSG_SIZE big_data_length = sizeof(int*);
	int** buffer2;
	while(1)
	{
		buffer2 = OSQPend(&big_data_queue, 0, OS_OPT_PEND_BLOCKING, &big_data_length, NULL, &err);  //读不出来就死等
		OLED_ShowNum(2, 1, buffer[1], 5);
		OLED_ShowNum(3, 1, (*buffer2)[1], 5);
		OSTimeDly(500, OS_OPT_TIME_DLY, &err);      //自我阻塞500ms
	}
}

(5)在main.c文件中初始化OLED屏模块和按键模块。

int main(void)
{
	/*模块初始化*/
	OLED_Init();    //OLED初始化
	Key_Init();     //按键初始化
	
	UCOS_Test();
	
	while (1)
	{
		
	}
}

(6)程序完善好后点击“编译”,然后将程序下载到开发板上,根据程序注释进行调试。

3、程序执行流程

(1)main函数全流程:

①初始化OLED模块、按键模块。

②调用UCOS_Test函数。

(2)测试函数全流程:

①创建记录按键次数的队列和大容量数据队列(下图未示出)。

②创建任务start_task。

(3)多任务调度执行阶段(发生在开启任务调度器以后):

①在前面的实验中已对任务调度方面做了多次详细解释,从本实验开始简单的任务调度将不再详解。

②按下按键1,task1函数中记录的按键1按下次数自增,并将存储按键次数变量的地址写进队列key_queue中;紧接着,原本task2函数中的OSQPend函数因为key_queue为空读不到数据而进入无限阻塞,现在key_queue有数据,OSQPend函数则将key_queue中的数据,也就是存储按键次数变量的地址取出,并解引用获取最新的按键次数,显示在OLED屏上;以此往复,每次按下按键1,task1就会往key_queue中写一次存储按键次数变量的地址,而task2会将它马上取出并解引用获取最新的按键次数。

③按下按键2,task1函数先修改大容量数据本身的值,然后将大容量数据的地址写进队列big_data_queue中;紧接着,原本task3函数中的OSQPend函数因为big_data_queue为空读不到数据而进入无限阻塞,现在big_data_queue有数据,OSQPend函数则将big_data_queue中的数据,也就是大容量数据的地址取出,并通过这个地址访问大容量数据,显示一部分在OLED屏上;以此往复,每次按下按键2,task1就会往big_data_queue中写一个数据的地址,而task3会将它马上取出,然后可以通过地址访问数据。

相关文章:

  • “国产AI之光”Manus,会成为下一个DeepSeek吗?
  • RHCE9.0版本笔记5:防火墙的本地/远程登录方式
  • linux查看python版本
  • conda 配置新环境时package will be install 和 package will be download 的区别
  • [Lc7_分治-快排] 快速选择排序 | 数组中的第K个最大元素 | 库存管理 III
  • p5.js:模拟 n个彩色小球在一个3D大球体内部弹跳
  • MySQL如何给其他账号分配权限?
  • Java基础系列:深入理解八大基本数据类型及避坑指南
  • python 程序一次启动有两个进程的问题(flask)
  • Jetpack Compose — 入门实践
  • 完全日期(日期枚举问题)--- 数学性质题型
  • Linux系统重置密码
  • Kubernetes中的微服务
  • 报表DSL优化,享元模式优化过程,优化效果怎么样?
  • SpringCloud——Consul服务注册与发现
  • powershell@宝塔面板批量建站脚本@批量设置@批量部署伪静态设置
  • 【AD】5-12 Object元素的隐藏与显示
  • c语言操作符
  • 前端项目中export和import的作用
  • 网安知识点
  • 商城网站建设清单/百度一下你就知道官网下载安装
  • 中山网站建设公司/软媒win7优化大师
  • 网站域名有了_网站如何建设/深圳外贸网站制作
  • 淘客网站备案/谷歌海外推广怎么做
  • 宁波电商平台网站建设/定制营销型网站建设
  • 淘宝客做的好的几个网站/百度关键词seo公司