UCOS操作系统——消息传递(十二)

UCOS操作系统


前言

一个任务或者中断服务程序有时候需要和另一个任务交流信息,这个就是消息传递的过程就叫做任务间通信,任务间的消息传递可以通过2种途径:一是通过全局变量,二是通过发布消息。使用全局变量的时候每个任务或者中断服务程序都必须保证其对全局变量的独占访问。消息也可以通过消息队列作为中介发布给任务。
UCOS中的消息传递可以对比FreeRTOS的消息队列来学习。

一、什么是消息

消息包含一下几个部分:指向数据的指针,数据的长度和记录消息发布时刻的时间戳,指针指向的可以是一块数据区域或者甚至是一个函数。 消息的内容必须一直保持可见性,可见性是指代表消息的变量必须在接收消息的任务代码范围内有效。这是因为发布的数据采用的是指针传递,也就是引用传递,并不是值传递。也就是说,发布的消息本身并不产生拷贝,我们可以使用动态内存分配的方式来给消息分配一个内存块,或者,也可以传递一个指向全局变量、全局数据结构、全局数组或者函数的指针。

二、消息队列

在这里插入图片描述
我们常用的关于消息队列的函数其实只有三个,创建消息队列函数 OSQCreate(),向消息队列发送消息函数 OSQPost()和等待消息队列函数 OSQPend()。

1.OSQCreate()函数创建消息队列

OSQCreate()函数用来创建一个消息队列,消息队列使得任务或者中断服务程序可以向一个或者多个任务发送消息,函数原型如下。

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

p_q: 指向一个消息队列,消息队列的存储空间必须由应用程序分配,我们采用如下的
语句定义一个消息队列。
OS_Q Msg_Que;
p_name: 消息队列的名字。
max_qty: 指定消息队列的长度,必须大于 0。当然,如果 OS_MSGs 缓冲池中没有足够多的 OS_MSGs 可用,那么发送消息将会失败,并且返回相应的错误码,指明当前没有可用的 OS_MSGs
p_err: 保存调用此函数后返回的错误码
在这里插入图片描述
在这里插入图片描述

2.OSQPend()函数等待消息队列

当一个任务想要从消息队列中接收一个消息的话就需要使用函数 OSQPend()。当任务调用这个函数的时候,如果消息队列中有至少一个消息时,这些消息就会返回给函数调用者。函数原型如下:

void *OSQPend (OS_Q *p_q,
 OS_TICK timeout,
 OS_OPT opt,
 OS_MSG_SIZE *p_msg_size,
 CPU_TS *p_ts,
 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: 用来保存调用此函数后返回的错误码。

在这里插入图片描述

3.OSQPost()向消息队列发送消息

可以通过函数 OSQPost()向消息队列发送消息,如果消息队列是满的,则函数 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: 指向实际发送的内容,p_void 是一个执行 void 类型的指针,其具体含义由用户程序的决定。
msg_size: 设定消息的大小,单位为字节数。
opt: 用来选择消息发送操作的类型,基本的类型可以有下面四种。
OS_OPT_POST_ALL 将消息发送给所有等待该消息队列的任务,需要和选项 OS_OPT_POST_FIFO 或者
OS_OPT_POST_LIFO 配合使用。
OS_OPT_POST_FIFO 待发送消息保存在消息队列的末尾
OS_OPT_POST_LIFO 待发送的消息保存在消息队列的开头
OS_OPT_POST_NO_SCHED 禁止在本函数内执行任务调度。
我们可以使用上面四种基本类型来组合出其他几种类型
p_err: 用来保存调用此函数后返回的错误码。

如果有多个任务在等待消息队列的话,那么优先级最高的任务将获得这个消息。如果等待消息的任务优先级比发送消息的任务优先级高,则系统会执行任务调度,等待消息的任务立即恢复运行,而发送消息的任务被挂起。可以通过 opt 设置消息队列是 FIFO 还是 LIFO。

如果有多个任务在等待消息队列的消息,则 OSQPost()函数可以设置仅将消息发送给等待任务中优先级最高的任务(opt 设置为 OS_OPT_POST_FIF 或者 OS_OPT_POST_LIFO),也可以将 消 息 发 送 给 所 有 等 待 的 任 务 (opt 设 置 为 OS_OPT_POST_ALL) 。 如 果 opt 设 置 为OS_OPT_POST_NO_SCHED,则在发送完消息后,会进行任务调度。

三、消息队列实验

设计一个应用程序,该程序有 4 任务、两个消息队列和一个定时器。任务 start_task 用于创建其他 3 个任务。main_task 任务为主任务,用于检测按键,并且将按键的值通过消息队列KEY_Msg 发送给任务 Keyprocess_task,main_task 任务还用于检测消息队列 DATA_Msg的总大小和剩余空间大小,并且控制 LED0 的闪烁。
首先应该定义消息队列

消息队列//
#define KEYMSG_Q_NUM 1 //按键消息队列的数量
#define DATAMSG_Q_NUM 4 //发送数据的消息队列的数量
OS_Q KEY_Msg; //定义一个消息队列,用于按键消息传递,模拟消息邮箱
OS_Q DATA_Msg; //定义一个消息队列,用于发送数据
定时器
u8 tmr1sta=0; //标记定时器的工作状态
OS_TMRtmr1; //定义一个定时器
//查询 DATA_Msg 消息队列中的总队列数量和剩余队列数量
void check_msg_queue(u8 *p)
{
    
    
 CPU_SR_ALLOC();
u8 msgq_remain_size; //消息队列剩余大小
msgq_remain_size = DATA_Msg.MsgQ.NbrEntriesSize-DATA_Msg.MsgQ.NbrEntries;
p = mymalloc(SRAMIN,20); //申请内存
//显示 DATA_Msg 消息队列总的大小
sprintf((char*)p,"Total Size:%d",DATA_Msg.MsgQ.NbrEntriesSize);
LCD_ShowString(10,190,100,16,16,p);
sprintf((char*)p,"Remain Size:%d",msgq_remain_size); //显示 DATA_Msg 剩余大小
LCD_ShowString(10,230,100,16,16,p);
myfree(SRAMIN,p); //释放内存
OS_CRITICAL_EXIT(); //退出临界段
}

前面虽然定义了2个消息队列和1个定时器,但是此时还不能用,我们需要调用OSQCreate()
和 OSTmrCreate()这两个函数来创建消息队列和定时器,在任务 start_task 中我们来创建这两个
消息队列和定时器,代码如下。

//开始任务函数
void start_task(void *p_arg)
{
    
    
OS_ERR err;
CPU_SR_ALLOC();
p_arg = p_arg;
CPU_Init();
#if OS_CFG_STAT_TASK_EN > 0u
 OSStatTaskCPUUsageInit(&err); //统计任务 
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了测量中断关闭时间
 CPU_IntDisMeasMaxCurReset();
#endif
#if OS_CFG_SCHED_ROUND_ROBIN_EN //当使用时间片轮转的时候
//使能时间片轮转调度功能,时间片长度为 1 个系统时钟节拍,既 1*5=5ms
OSSchedRoundRobinCfg(DEF_ENABLED,1,&err); 
#endif
OS_CRITICAL_ENTER(); //进入临界区
//创建消息队列 KEY_Msg
OSQCreate ( (OS_Q* )&KEY_Msg,//消息队列 (1)
 (CPU_CHAR* )"KEY Msg",//消息队列名称
 (OS_MSG_QTY )KEYMSG_Q_NUM, //消息队列长度,这里设置为 1
 (OS_ERR* )&err); //错误码
//创建消息队列 DATA_Msg
OSQCreate ( (OS_Q* )&DATA_Msg, (2)
 (CPU_CHAR* )"DATA Msg",
 (OS_MSG_QTY )DATAMSG_Q_NUM,
 (OS_ERR* )&err);
//创建定时器 1
OSTmrCreate((OS_TMR* )&tmr1, //定时器 1 (3)
 (CPU_CHAR* )"tmr1", //定时器名字
 (OS_TICK )0, //0ms
 (OS_TICK )50, //50*10=500ms
 (OS_OPT )OS_OPT_TMR_PERIODIC, //周期模式
 (OS_TMR_CALLBACK_PTR)tmr1_callback,//定时器 1 回调函数
 (void* )0, //参数为 0
 (OS_ERR* )&err); //返回的错误码
······
//为了省篇幅,此处省略掉了创建任务的代码。
······
OS_CRITICAL_EXIT(); //退出临界区
OSTaskDel((OS_TCB*)0,&err); //删除 start_task 任务自身
}

(1) 调用函数 OSCreate()创建一个消息队列 KEY_Msg,KEY_Msg 队列长度为 1,我们用来模拟 UCOSII 中的消息邮箱。
(2) 调用函数 OSCreate()创建一个消息队列 DATA_Msg,队列长度为 4。
(3) 调用函数OSTmrCreate()创建一个定时器tmr1,tmr1为周期定时器,定时周期为500ms

然后写四个函数

//定时器 1 的回调函数
void tmr1_callback(void *p_tmr,void *p_arg)
{
    
    
u8 *pbuf;
static u8 msg_num;
OS_ERR err;
pbuf = mymalloc(SRAMIN,10); //申请 10 个字节
if(pbuf) //申请内存成功
{
    
    
msg_num++;
sprintf((char*)pbuf,"ALIENTEK %d",msg_num);
//发送消息
OSQPost((OS_Q *)&DATA_Msg,
(void* )pbuf,
(OS_MSG_SIZE )10,
(OS_OPT )OS_OPT_POST_FIFO,
(OS_ERR *)&err);
if(err != OS_ERR_NONE)
{
    
    
myfree(SRAMIN,pbuf); //释放内存
OSTmrStop(&tmr1,OS_OPT_TMR_NONE,0,&err); //停止定时器 1
tmr1sta = !tmr1sta;
LCD_ShowString(10,150,100,16,16,"TMR1 STOP! ");
} } }
//主任务的任务函数
void main_task(void *p_arg)
{
    
    
u8 key,num;
OS_ERR err;
u8 *p;
while(1)
{
    
    
key = KEY_Scan(0); //扫描按键
if(key)
{
    
    
//发送消息
OSQPost((OS_Q* )&KEY_Msg,
(void* )&key,
(OS_MSG_SIZE )1,
(OS_OPT )OS_OPT_POST_FIFO,
(OS_ERR* &err);
}
num++;
if(num%10==0) check_msg_queue(p);//检查 DATA_Msg 消息队列的容量
if(num==50)
{
    
    
num=0;
LED0 = ~LED0;
}
OSTimeDlyHMSM(0,0,0,10,OS_OPT_TIME_PERIODIC,&err); //延时 10ms
} }
//按键处理任务的任务函数
void Keyprocess_task(void *p_arg)
{
    
    
u8 num;
u8 *key;
OS_MSG_SIZE size;
OS_ERR err;
while(1)
{
    
    
//请求消息 KEY_Msg
key=OSQPend((OS_Q* )&KEY_Msg, 
(OS_TICK )0,
 (OS_OPT )OS_OPT_PEND_BLOCKING,
 (OS_MSG_SIZE* )&size,
 (CPU_TS* )0,
 (OS_ERR* )&err);
switch(*key)
{
    
    
case WKUP_PRES: //KEY_UP 控制 LED1
LED1 = ~LED1;
break;
case KEY2_PRES: //KEY2 控制蜂鸣器
BEEP = ~BEEP;
break;
case KEY0_PRES: //KEY0 刷新 LCD 背景
num++;
LCD_Fill(126,111,233,313,lcd_discolor[num%14]);
break;
case KEY1_PRES: //KEY1 控制定时器 1
tmr1sta = !tmr1sta;
if(tmr1sta) 
{
    
    
OSTmrStart(&tmr1,&err);
LCD_ShowString(10,150,100,16,16,"TMR1 START!");
}
else
{
    
    
OSTmrStop(&tmr1,OS_OPT_TMR_NONE,0,&err); //停止定时器 1
LCD_ShowString(10,150,100,16,16,"TMR1 STOP! ");
}
break;
} } }
//显示消息队列中的消息
void msgdis_task(void *p_arg)
{
    
    
u8 *p;
OS_MSG_SIZE size;
OS_ERR err; 
while(1)
{
    
    
//请求消息
p=OSQPend((OS_Q* )&DATA_Msg, 
 (OS_TICK )0,
 (OS_OPT )OS_OPT_PEND_BLOCKING,
 (OS_MSG_SIZE* )&size;
 (CPU_TS* )0,
 (OS_ERR* )&err);
LCD_ShowString(5,270,100,16,16,p);
myfree(SRAMIN,p); //释放内存
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时 1s

tmr1_callback()函数是定时器 1 的回调函数,在 start_task 任务中我们是创建了一个定时器tmr1 的,tmr1 是一个周期定时器,定时周期为 500ms。在 tmr1 的回调函数 tmr1_callback()中通过函数 OSQPost()向消息队列 DATA_Msg 发送消息,这里向消息队列发送数据采用的是 FIFO方式,当发送失败的话就释放相应的内存并关闭定时器。main_task()函数为主任务的任务函数,在这个函数中我们不断的扫描按键的键值,然后将键值发到消息队列 KEY_Msg 中,这里向消息队列发送数据采用的是 FIFO 方式。

四、实验现象

在这里插入图片描述
可以看出 DATA_Msg 的总大小为 4,这个和我们创建 DATA_Msg 消息队列时设置的一样。由于此时定时器 1 并没有启动,所以消息队列 DATA_Msg 的剩余大小也为 4,右下部分的方框部分的 LCD 背景为白色,当我们按下 KEY0 键的时候就会刷新右下方框中的背景
在这里插入图片描述
可以看出当按下 KEY0 以后右下部分的 LCD 背景就会被刷新为其他颜色(这
里的黄色为多次按下 KEY0 后的效果)。按下 KEY1 键开启定时器 1,那么定时器 1 的回调函数
就会每隔 500ms 向消息队列 DATA_Msg 中发送一条消息
在这里插入图片描述
此时消息队列 DATA_Msg 还剩下 1 个可用空间,定时器 1 还会一直向DATA_Msg 发送消息,直到消息队列 DATA_Msg 满了,就会关闭定时器 1,停止发送
在这里插入图片描述
可以看出此时消息队列 DATA_Msg 剩余空间为 0,那么定时器 1 回调函数中再次调用函数 OSQPost()向消息队列 DATA_Msg 中发送数据的话就会发送失败,此时 err 就为OS_ERR_MSG_POOL_EMPTY,提示消息队列空了,err 不等于 OS_ERR_NONE,那么就会关闭定时器 1,停止向 DATA_Msg 中发送数据,除非再次手动开启定时器 1,也就是按下 KEY1键。大家可能会注意到,消息队列 DATA_Msg 中剩余空间为 0,定时器 1 关闭了,但是此时任务 msgdis_task 还是能够接收到消息,并且在 LCD 上显示,而且消息队列 DATA_Msg 的剩余空间大小会增大,直到等于 DATA_Msg 总的大小才会停止
在这里插入图片描述
这是因为虽然定时器 1 关闭了,没有数据发送到消息队列 DATA_Msg 中,但是此时DATA_Msg 中还有没有处理掉的数据,因此 msgdis_task 任务还会一直运行,直到处理完DATA_Msg 中的所有数据,每处理掉一个数据,DATA_Msg 的剩余大小就会加 1,当处理完所有的数据,DATA_Msg 剩余大小肯定就会等于总大小了。

总结

本章比较长,UCOS中的消息传递与FreeROTS的消息队列虽然概念一样,但还是有些地方是不同的。

猜你喜欢

转载自blog.csdn.net/qq_51963216/article/details/123931421