本篇文章是基于UCOSii的邮箱的使用,有一些小的细节还是值得注意的,方便日后再次使用时能及时注意到。
1、邮箱简介
邮箱简介可以参考正点原子的STM32F4开发手册里有,摘抄一下:在多任务操作系统中,常常需要在任务与任务之间通过传递一个数据(这种数据叫做“消息”)的方式来进行通信。为了达到这个目的,可以在内存中创建一个存储空间作为该数据的缓冲区。如果把这个缓冲区称之为消息缓冲区,这样在任务间传递数据(消息)的最简单办法就是传递消息缓冲区的指针。我们把用来传递消息缓冲区指针的数据结构叫做邮箱(消息邮箱)。
简单描述一下:使用邮箱可以在任务之间传递数据,例如传递一个变量的值。一方面可以做任务同步另一方面可以使用到这个数据来进行逻辑控制。
2、程序实现和分析
程序上很简单做个测试,由UCOSII创建三个任务,分别实现下述功能:
任务名称 | 优先级 | 功能 |
---|---|---|
led0_task | 中 | led0闪烁周期1s,每闪烁一次变量i累加1,当变量i累加到10后,发送一次邮箱,串口打印"led 0发送完成",发送后将i的值重新变为0,等待下一次发送。 |
led1_task | 低 | 无限期请求消息邮箱,若邮箱里有消息则打印出消息,然后led1闪烁1s后继续等待消息。 |
led2_task | 高 | 无限期请求消息邮箱,若邮箱里有消息则打印出消息,然后led2闪烁1s后继续等待消息。 |
LED0任务
void led0_task(void *pdata)
{
uint8_t i;
while(1){
LED3=0;
delay_ms(500);
LED3=1;
delay_ms(500);
if(i++==10) {
OSMboxPost(num_Mbox,&i);
i=0;
printf("led 0发送完成\r\n");
}
}
}
LED1任务
void led1_task(void *pdata)
{
uint8_t err;
uint8_t NUMmmm;
while(1)
{
NUMmmm=*((uint8_t *)OSMboxPend(num_Mbox,0,&err));
printf("led 1 task %d \r\n",NUMmmm);
LED1=0;
delay_ms(500);
LED1=1;
delay_ms(500);
}
}
LED2任务
void led2_task(void *pdata)
{
uint8_t err;
uint8_t *NUMmmm;
while(1)
{
NUMmmm=((uint8_t *)OSMboxPend(num_Mbox,0,&err)); //请求消息邮箱
printf("led 2 task %d \r\n",*NUMmmm);
LED2=0;
delay_ms(500);
LED2=1;
delay_ms(500);
}
}
创建了这三个任务后根据优先级关系可以知道程序会先执行LED2任务,但是LED2任务在死等邮箱中的消息所以会挂起LED2任务;LED3任务也是在死等消息邮箱,所以刚开始只有LED1任务是运行着的;LED1灯会先闪烁10次然后进入发送消息邮箱的程序。做实验使用之前先看如下三个函数:
2.1 OSMboxPost
这个函数只有两个参数,分别是要发送的邮箱名和要传递的数据,有几个作用和细节:
细节注意:使用这个函数后会往指定的邮箱中发布消息,同时函数内部会检查有无正在等待这个邮箱消息的任务,如果有正在等待消息的任务则判断当前使用OSMboxPost这个任务的优先级和等待着的任务优先级哪个高,如果等待着的优先级高则马上引起任务切换,若等待着任务的优先级低则不会引起任务切换。
2.2 OSMboxPend
这个函数是请求邮箱中的消息,如果设置第二个参数为0则表示会死等直到有消息了才会往下执行,在做数据接收时有几个细节:
数据接收的方法:
方法一:
uint8_t NUMmmm;
NUMmmm=*((uint8_t *)OSMboxPend(num_Mbox,0,&err));
OSMboxPend返回的是任意类型的指针,指向邮箱的消息地址,先把指针的类型转为u8,然后取地址的值。
方法二:
uint8_t *NUMmmm;
NUMmmm=((uint8_t *)OSMboxPend(num_Mbox,0,&err));
方法二只是用了指针的方式取值。
方法三:
u32 NUMmmm;
NUMmmm=(u32)OSMboxPend(num_Mbox,0,&err);
方法三因为OSMboxPend返回的数据是void *类型的,因此可以利用”void *可以强制转换为int来获得值“这个途径,网上说这个叫做万能指针,当你不知道自己需要一个什么样的数据类型的时候就可以用这个。
2.3 OSMboxPostOpt
这个函数有三个参数,相比于OSMboxPost而言多了最后一个参数,是用于广播的,意思是OSMboxPost只能往等待邮箱发一条消息,只有一个正在等待中的并且优先级最高的任务能获取到,其他等待该邮箱的任务不能获取到任然处于挂起状态,而OSMboxPostOpt可以使得所有正在等待这个邮箱消息的任务都能获取到消息。参数具体如下:
参数名称 | 含义 |
---|---|
OS_POST_OPT_NONE | 等同OSMboxPost,只向优先级别最高的等待任务发送 |
OS_POST_OPT_BROADCAST | 消息向所有等待任务广播 |
OS_POST_OPT_FRONT | 等同OSMboxPost,只向优先级别最高的等待任务发送 |
OS_POST_OPT_NO_SCHED | 只向优先级别最高的等待任务发送并且不会引起任务调度 |
2.4 实践出真知
测试1:LED0任务设置为OSMboxPost,LED1和LED2任务都为请求同一个邮箱消息OSMboxPend。
现象1:
分析1:由于是使用OSMboxPost,所以会向等待中的最高优先级任务发送消息,因为LED2优先级高于LED1,所以只有LED2任务会收到消息得到执行而LED1任务继续挂起。又由于LED0任务(使用OSMboxPost的任务)优先级比LED2优先级低,所以LED0调用OSMboxPost后马上引起任务切换,LED2任务马上执行,所以先打印出"LED 2 TASK 10"(10 代表i的值,此时是i累加到10后还没清除前的时候触发的消息邮箱,引起的任务调度),然后执行完LED2后再次回到LED0的OSMboxPost之后的代码将“LED 0发送完成”打印出来。
测试2:LED0任务设置为OSMboxPostOpt(num_Mbox,&i,OS_POST_OPT_BROADCAST),LED1和LED2任务都为请求同一个邮箱消息OSMboxPend。
现象2:
分析2:由于是使用OSMboxPostOpt并且是OS_POST_OPT_BROADCAST以广播的形式,所以LED2和LED1任务都会得到执行,使用OSMboxPostOpt函数,函数内部会引起任务调度,LED2任务优先级高于LED0所以LED2先执行此时i的值为10,LED2任务打印”LED 2 TASK 10“,然后执行完LED2后回到LED0任务,因为LED1任务优先级低于LED0所以LED0继续执行,打印"LED 0 发送完成"之后LED0会把i的值清0,LED0程序执行结束,此时LED1任务开始执行,所以LED1任务得到最后的消息i为0,打印”LED 1 TASK 0“。
测试3:LED0任务设置为OSMboxPostOpt(num_Mbox,&i,OS_POST_OPT_FRONT),LED1和LED2任务都为请求同一个邮箱消息OSMboxPend。
现象3:
分析3:虽然使用的是OSMboxPostOpt但是参数设置的是OS_POST_OPT_FRONT只会向优先级最高的任务发,所以现象和原因于测试1类似。
测试4:LED0任务设置为OSMboxPostOpt(num_Mbox,&i,OS_POST_OPT_NO_SCHED),LED1和LED2任务都为请求同一个邮箱消息OSMboxPend。
现象4:
分析4:虽然使用的是OSMboxPostOpt但是参数设置的是OS_POST_OPT_NO_SCHED只会向优先级最高的任务发并且任务不调度,所以当LED0发送消息邮箱完成后会把i的值清零,所以等LED0执行完后在执行LED2会出现这个问题。