背景
-
在主程序里面做i++(i=10),在某个中断服务函数里面做i–,会出现主程序刚取出i的值得时候被中断打断,在中断里面做了i—(写回去的值为9),返回到断点后,主程会对i(已经取出的值)做加1操作,然后写回去(最终i=11;).------在主程序里面在对i操作之前先把中断关了,等操作完后再打开中断。 出现以上情况的原因是—打断。在Ucos中不可避免的会出现以上打断问题。因为ucos是以优先级作为调度原则,所以也存在打断问题。所以任务间间交换信息如果用全局变量,就存在以上问题。所以ucos任务间交换信息尽量的不要用全局变量。
-
Ucosii的任务间通信机制:信号量、互斥信号量、消息邮箱、消息队列、事件标志组
-
任务控制块:存放当前任务的相关信息(任务函数地址、任务优先级、任务栈、任务状态)
-
事件控制块:当成功创建一个事件(信号量、互斥信号量、消息邮箱、消息队列、事件标志组)后,系统就分配一段内存空间,这段空间就是事件控制块,存放这该事件的相关信息。
信号量
可以把信号量看成是一个计数器,表示当前资源的占用情况,当释放一个资源时信号量+1,如果占用一个资源,信号量-1。且信号量不可删除,而且只有任务才允许使用信号量,ISR是不允许的。
-
1.要想使用信号量,必须先创建一个信号量,并且可以对这个信号量赋予一个初始值。
函数原型:OS_EVENT *OSSemCreate(INT16U value);
参数为信号量计数器,
函数作用:建立并初始化一个信号量
参数说明:建立的信号量的初始值,可以取 0 到 65535 之间的任何值。
返回值:指向成功创建的信号量的事件控制块地址 -
2.释放信号量(发送),信号量就会+1
函数原型:INT8U OSSemPost(OS_EVENT *pevent);
函数作用:释放一个信号量,把信号值加 1 -
3.要想得到一个信号量,要先查看信号量是否为0,如果大于0表示当前可以去占用一个信号量,如果为 0表示当前信号量被用光了,可以死等其他任务释放信号量,也可以不等。
函数原型:void OSSemPend (OS_EVENT *pevent,INT32U timeout,INT8U *perr);
pevent 是被请求信号量的指针,
timeout 为等待时限(设置为0表示无限等待),
err 为错误信息。
函数作用:申请一个信号量(挂起任务等待信号量)。 -
之外有,
函数原型:void OSSemSet(OS_EVENT *pevent,INT16U cnt,INT8U *perr);
函数作用:改变当前信号量的计数值,应用于信号量集函数原型:INT16U OSSemAccept(OS_EVENT *pevent);
函数作用:无等待查看信号量是否为0。OS_EVENT *sem_led1=OSSemCreate(0); //创建信号量 void led_task() { While(1) { OSSemPend(sem_led1,0,&err);//请求信号 Led =1;//点亮 } } void main_task() { While(1) { if(confidition) OSSemPost(sem_led1);//发送信号 } } //这样就可以通过主任务函数来控制各个子任务了
消息邮箱
- 顾名思义,可以往邮箱里存放消息,这个消息的内容比信号量要更加丰富。
- 这个邮箱只能存放一则消息。
- 如果这个邮箱里存在消息没有被读取,然后再往里面存放消息,就会失败。
- 在消息邮箱里面,如果发送方能力比较强(发送速度快),接受方能力比较弱(接受得慢),那么就会丢失新的消息。
- 使用邮箱可以在多任务间传递数据,而不仅仅只是一个标志位。主要也是用到了三个函数
- 创建邮箱:
OS_EVENT *OSMboxCreate (void *msg);
参数为消息即数据指针,返回值是邮箱指针 - 请求邮箱:
void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err);
其中pevent 是邮箱指针,timeout 为等待时限(设置为0表示无限等待),err 为错误信息。 - 发送邮箱:
INT8U OSMboxPost (OS_EVENT *pevent,void *msg);
其中 pevent 为消息邮箱的指针,msg 为消息指针。
OS_EVENT * msg_key=OSMboxCreate((void*)0); //创建邮箱 Void key_task() { u8 key; while(1) { key=KEY_Scan(0); //获取按键值 if(key)OSMboxPost(msg_key,(void*)key);//发送消息 } } Void main_task() { u8 key; While(1) { //接收 key=(u32)OSMboxPend(msg_key,10,&err); } } //子任务可以发送不同的按键值给主任务
- 创建邮箱:
消息队列
- 消息邮箱只能存放一则消息,而消息队列则可以存放一队(多则)消息,相当于增强了接受方的能力消息队列存放消息的方式是先进先出
- 创建一个消息队列:
OS_EVENT *OSQCreate (void **start,INT16U size)
start:指向用户创建一个存储区(数组),这个存储空间(数组)存放的是消息的地址。成功创建了消息队列后,创建的这个存储区(数组)就交由UCOSII去管理。
size:消息内存区的大小 - 发送一则消息:
INT8U OSQPost (OS_EVENT *pevent,void *pmsg);
pmsg:消息的地址
发送多则不同消息需要发送不同的消息地址。 - 删除消息队列;
OS_EVENT *OSQDel(OS_EVENT *pevent, INT8U opt, INT8U *perr);
删除后会将pevent->OSEventType置为无使用类型,所以再用OSQPend和OSQPost操作消息队列都无效。
返回值,如果成功则返回0,如果失败则返回pevent;
pevent是消息队列句柄;
opt可以是两个参数,OS_DEL_NO_PEND表示没有任务在等待该消息时才会删除消息,OS_DEL_ALWAYS即表示不管有没有任务在等待,都删除消息队列,如果有任务在等待该消息,会强制让任务标志为已经接收到该消息,然后进入就绪态; - 刷新消息队列的内容;实际是复位消息队列,即把消息队列的内容清空;
INT8U OSQFlush(OS_EVENT *pevent);
- 等待消息;
void *OSQPend(OS_EVENT *pevent, INT32U timeout, INT8U *perr);
返回值,返回最先入队的消息的指针;消息的类型是自定义的,所以用的void型指针;
pevnet,消息队列句柄;
timeout,等待消息的超时时间,如果为0,则表示一直等待有消息为止,其它值则表示经过timeout个tick(一个tick就是一个systick的中断周期)后,就不在等待消息,往下执行;
perr,异常返回值;OSQCreate(&g_queue_usart1,"g_queue_usart1",16,&err);//创建消息队列 void Task1_task(void *parg) { OS_ERR err; u32 cnt =0; char buf[64]={0};//消息存储 while(1) { cnt++; sprintf(buf,"task1 run %d times\r\n",cnt); OSQPost(&g_queue_usart1,buf,strlen(buf),OS_OPT_POST_FIFO,&err);//发送消息buf printf("task1 is running ...\r\n"); delay_ms(1000); } } void Task2_task(void *parg) { OS_ERR err; OS_MSG_SIZE msg_size;//消息大小 u32 i =0; char *p = NULL; printf("task2 is create ok\r\n"); while(1){ p=OSQPend(&g_queue_usart1,0,OS_OPT_PEND_BLOCKING,&msg_size,NULL,&err);//消息请求接收 if(p && msg_size) { printf("OSQPend msg:"); for(i=0;i<msg_size;i++) { printf("%c",p[i]); } printf("\r\n"); } printf("task2 is running ...\r\n"); delay_ms(1000); } }
实际项目三者配合使用,可发挥最大功效。
参考链接:https://blog.csdn.net/qq_40860986/article/details/93669196
参考链接:https://blog.csdn.net/weixin_42542969/article/details/100042565
参考链接:https://blog.csdn.net/mangofu/article/details/123033290
参考链接:https://blog.csdn.net/qq_25355591/article/details/112300802