一、概述
1、任务间的通信:
一个任务或者中断服务程序有时候需要和另一个任务交流信息,这个就是消息传递的过程就叫做任务间通信。
任务间的消息传递可以通过2种途径:一是通过全局变量,二是通过发布消息。
使用全局变量的时候每个任务或者中断服务程序都必须保证其对全局变量的独占访问。消息也可以通过消息队列作为中介发布给任务。
2、消息构成
- 指向数据的指针
- 数据长度
- 记录消息发布时刻的时间戳。
3、消息队列的使用
- 指针指向的可以是一块数据区域或者甚至是一个函数。
- 消息的内容必须一直保持可见性,可见性是指代表消息的变量必须在接收消息的任务代码范围内有效。这是因为发布的数据采用的是指针传递,也就是引用传递,并不是值传递。也就是说,发布的消息本身并不产生内存拷贝,可以使用动态内存分配的方式来给消息分配一个内存块,或者,也可以传递一个指向全局变量、全局数据结构、全局数组或者函数的指针。
- 消息队列的相关操作,ISR只能发布消息,不能等待。
- 消息发布方发布消息时可以指定是先入先出模式(FIFO)还是设置为后入先出模式(LIFO),例如ISR发布的消息可以使用LIFO这样可以绕开其他消息最先传递到任务。
二、函数接口
1、创建消息队列
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()。当任务调用这个函数的时候,如果消息队列中有至少一个消息时,这些消息就会返回给函数调用者。函数原型如下:
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:用来保存调用此函数后返回的错误码。
如果消息队列中没有任何消息,并且参数opt为OS OPT PEND NON BLOCKING时,那么调用OSQPend()函数的任务就会被挂起,直到接收到消息或者超时。如果有消息发送给消息队列,但是同时有多个任务在等待这个消息,那么UCOSIII将恢复等待中的最高优先级的任务。
3、向消息队列发送消息
可以通过函数OSQPostO向消息队列发送消息,如果消息队列是满的,则函数OSQPost()就会立刻返回,并且返回一个特定的错误代码,函数原型如下:
void OSQPost (OS_Q *p_q,
void *p_void,
OS_MSG_SIZE msg_size,
OS_OPT opt,
OS_ERR *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,则在发送完消息后,会进行任务调度。
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 禁止在本函数内执行任务调度。
我们可以使用上面四种基本类型来组合出其他几种类型,如下:
OS_OPT_POST_FIFO + OS_OPT_POST_ALL
OS_OPT_POST_LIFO + OS_OPT_POST_ALL
OS_OPT_POST_FIFO + OS_OPT_POST_NO_SCHED
OS_OPT_POST_LIFO + OS_OPT_POST_NO_SCHED
OS_OPT_POST_FIFO + OS_OPT_POST_ALL + OS_OPT_POST_NO_SCHED
OS_OPT_POST_LIFO + OS_OPT_POST_ALL+ OS_OPT_POST_NO_SCHED
p_ err:用来保存调用此函数后返回的错误码。
四、实例
任务一通过消息队列发消息给任务二。
#include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "includes.h" //任务1控制块 OS_TCB Task1_TCB; void task1(void *parg); CPU_STK task1_stk[128]; //任务1的任务堆栈,大小为128字,也就是512字节 //任务2控制块 OS_TCB Task2_TCB; void task2(void *parg); CPU_STK task2_stk[128]; //任务2的任务堆栈,大小为128字,也就是512字节 OS_Q g_queue; //主函数 int main(void) { OS_ERR err; systick_init(); //时钟初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组配置 usart_init(9600); //串口初始化 LED_Init(); //LED初始化 //OS初始化,它是第一个运行的函数,初始化各种的全局变量,例如中断嵌套计数器、优先级、存储器 OSInit(&err); //创建任务1 OSTaskCreate( (OS_TCB *)&Task1_TCB, //任务控制块,等同于线程id (CPU_CHAR *)"Task1", //任务的名字,名字可以自定义的 (OS_TASK_PTR)task1, //任务函数,等同于线程函数 (void *)0, //传递参数,等同于线程的传递参数 (OS_PRIO)6, //任务的优先级6 (CPU_STK *)task1_stk, //任务堆栈基地址 (CPU_STK_SIZE)128/10, //任务堆栈深度限位,用到这个位置,任务不能再继续使用 (CPU_STK_SIZE)128, //任务堆栈大小 (OS_MSG_QTY)0, //禁止任务消息队列 (OS_TICK)0, //默认时间片长度 (void *)0, //不需要补充用户存储区 (OS_OPT)OS_OPT_TASK_NONE, //没有任何选项 &err //返回的错误码 ); if(err!=OS_ERR_NONE) { printf("task 1 create fail "); while(1); } //创建任务2 OSTaskCreate( (OS_TCB *)&Task2_TCB, //任务控制块 (CPU_CHAR *)"Task2", //任务的名字 (OS_TASK_PTR)task2, //任务函数 (void *)0, //传递参数 (OS_PRIO)6, //任务的优先级7 (CPU_STK *)task2_stk, //任务堆栈基地址 (CPU_STK_SIZE)128/10, //任务堆栈深度限位,用到这个位置,任务不能再继续使用 (CPU_STK_SIZE)128, //任务堆栈大小 (OS_MSG_QTY)0, //禁止任务消息队列 (OS_TICK)0, //默认时间片长度 (void *)0, //不需要补充用户存储区 (OS_OPT)OS_OPT_TASK_NONE, //没有任何选项 &err //返回的错误码 ); //创建消息队列,支持16条消息(实际上能够存储16个指针) OSQCreate(&g_queue,"g_queue",16,&err); //启动OS,进行任务调度 OSStart(&err); printf("....... "); while(1); } void task1(void *parg) { OS_ERR err; char *p=NULL; OS_MSG_SIZE msg_size=0; printf("task1 is create ok "); while(1) { //等待消息队列 //0:一直等待 //OS_OPT_PEND_BLOCKING 阻塞形式等待信号量,若等不了信号量,则让出CPU使用权给其他任务 //不需要时间戳(时间标记):用于记录等待信号量花了多长时间,默认写NULL,不记录 p = OSQPend(&g_queue,0,OS_OPT_PEND_BLOCKING ,&msg_size,NULL,&err); if(err != OS_ERR_NONE) { printf("OSQPend g_queue error code=%d ",err); continue; } //以下就是临界区代码一定要赶紧完成,否则会影响到中断延迟 printf("msg[%s] len[%d] ",p,msg_size); memset(p,0,msg_size); //!!注意消息发送完之后,要清空消息指针 } } //给task1发送消息"hello task1" void task2(void *parg) { OS_ERR err; printf("task2 is create ok "); while(1) { OSQPost(&g_queue,"hell o task1",11,OS_OPT_POST_FIFO,&err); delay_ms(1000); } }