入门使用
任务设置
设置任务优先级
1
设置任务堆栈大小
1
声明任务堆栈
1
OS_STK *****_TASK_STK[*****_STK_SIZE];
声明任务函数
1
void *****_task(void *pdata);
主函数
1 | int main(void) |
值得介绍的函数
建立任务函数:OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INTU prio)
第一个参数是指向任务函数的函数指针,第二个参数是任务开始时,传递给任务的参数的指针,第三个是分配给任务的堆栈的栈顶指针,prio是分配给任务的优先级。
第二个参数有待研究。
事件控制块
同步与通信。为了把描述事件的数据结构统一起来,UCOSII 使用叫做事件控制块(ECB)的数据结构来描述诸如信号量、邮箱(消息邮箱)和消息队列这些事件。事件控制块中包含包括等待任务表在内的所有有关事件的数据,事件控制块结构体定义如下:
1 | typedef struct |
信号量
信号量是一类事件,使用信号量的最初目的,是为了给共享资源设立一个标志,该标志表示该共享资源的占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。
信号量可以分为两种:一种是二值型信号量,另外一种是N 值信号量。
二值型信号量好比家里的座机,任何时候,只能有一个人占用。而N 值信号量,则好比公共电话亭,可以同时有多个人(N 个)使用。
关于信号量,介绍这样几个函数:他们分别完成了创建,请求,释放,删除功能。
- 创建
OS_EVENT *OSSemCreate (INT16U cnt);
- 请求
void OSSemPend ( OS_EVENT *pevent, INT16U timeout, INT8U *err);
第三个参数有待研究 - 释放
INT8U OSSemPost(OS_EVENT *pevent);
- 删除
OS_EVENT *OSSemDel (OS_EVENT *pevent,INT8U opt, INT8U *err);
邮箱
在多任务操作系统中,常常需要在任务与任务之间通过传递一个数据(这种数据叫做“消息”)的方式来进行通信。为了达到这个目的,可以在内存中创建一个存储空间作为该数据的缓冲区。如果把这个缓冲区称之为消息缓冲区,这样在任务间传递数据(消息)的最简单办法就是传递消息缓冲区的指针。我们把用来传递消息缓冲区指针的数据结构叫做邮箱(消息邮箱)。
在UCOSII中,我们通过事件控制块的OSEventPrt来传递消息缓冲区指针,同时使事件控制块的成员OSEventType为常数OS_EVENT_TYPE_MBOX,则该事件控制块就叫做消息邮箱。
大概就和中转站差不多。
关于邮箱,介绍这样几个函数:它们分别完成了创建邮箱,发送信息,请求邮箱,查询邮箱状态,删除邮箱的功能。
创建邮箱
OS_EVENT *OSMboxCreate (void *msg);
发送信息
INT8U OSMboxPost (OS_EVENT *pevent,void *msg);
示例:
OSMboxPost(msg_key,(void*)&key);//表示将key变量取地址,再转化成void\*类型指针,再发送出去。
请求邮箱
void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err);
示例:key=*(u32*)OSMboxPend(msg_key,10,&err);//表示将这个无符号型指针转化成指向u32的指针,然后再解引用。
查询邮箱状态
INT8U OSMboxQuery(OS_EVENT *pevent,OS_MBOX_DATA *pdata);
删除邮箱
OS_EVENT *OSMboxDel(OS_EVENT *pevent,INT8U opt,INT8U *err);
void*很强大,但是一定要在合适的时候使用;同时强转很逆天,但是一定要注意前后的类型是否真的能正确转换。通俗地说void*:
- 这里有一片内存数据,我也不知道什么类型,给你了,你自己想怎么用怎么用吧,不过要用对奥!
- 我这里什么类型都能处理,你给我一片内存数据就可以了
消息队列
消息队列可以在多个任务之间传递多条消息。使用消息队列可以在任务之间传递多条消息。消息队列由三个部分组成:事件控制块、消息队列和消息。当把事件控制块成员 OSEventType的值置为 OS_EVENT_TYPE_Q时,该事件控制块描述的就是一个消息队列。
消息队列的实现是通过循环队列来实现的。我们定义一种结构体os_q,用于描述一个循环队列,其定义如下:
1 | typedef struct os_q |
在系统初始化时,系统按照配置文件的设置定义OS_MAX_QS个队列控制块,并利用队列控制块中的指针OSQPTr来将所有队列控制块链接成链表(就是初始化了一个空的链表,链表中每一个元素都是一个空的消息队列)
关于消息队列,介绍这样几个函数:创建,请求,发送
- 创建消息队列
OS_EVENT *OSQCreate(void**start,INT16U size);
- 请求消息队列
void*OSQPend(OS_EVENT*pevent,INT16U timeout,INT8U *err);
- 发送消息队列(FIFO)
INT8U OSQPost(OS_EVENT *pevent,void *msg);
信号量集
在实际运行的应用中,任务常常需要和多个时间保持同步。就是说,任务需要多个信号量同时满足或者部分满足时才能运行。我们当然可以设置一系列信号量通过他们的逻辑运算来实现这样的功能,但是UCOII提供了一种特殊的数据结构——信号量集来实现这样的功能。
信号量集管理的信号都是二值信号,使用标志组来控制,定义如下:
1 | typedef struct |
每一个信号量集都对应着一个等待任务链表,该链表是一个双向链表,每一个元素就是一个任务,其节点的定义如下:
1 | typedef struct |
其中,信号过滤器用于选择OSFlagFlags中部分或者全部位作为有效信号。OSFlagNodeWaitType可以设置触发条件(全0触发,部分0触发,全1触发,部分1触发)。这样,每一个任务就可以根据自己的需要选择信号量集中自己关心的那几个信号量,对他们进行逻辑运算来得出分析结果。
OS_FLAGS是一个宏定义,可以被定义成一个8位、16位或32位变量,表示我们这个信号量集中有多少个信号量。OSFlagFlags的每一位就是那个信号量。
信号量集的操作我们关注这样几个函数:创建、请求、发送
- 创建新的信号量集
OS_FLAG_GRP *OSFlagCreate (OS_FLAGS flags,INT8U *err );//其中flags是信号量集的信号列表的初值
- 请求信号量集
OS_FLAGS OSFlagPend(OS_FLAG_GRP*pgrp, OS_FLAGS flags, INT8U wait_type, INT16U timeout, INT8U *err);
这个函数好像没用到OS_FLAG_NODE结构体?好像绕过了该结构体的功能就实现了选择并运算的功能? - 向信号量集发送信号函数
OS_FLAGS OSFlagPost (OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U opt, INT8U *err);
所谓任务向信号量集发信号,就是对信号量集标志组中的信号进行置“1”(置位)或置“0”(复位)的操作。至于对信号量集中的哪些信号进行操作,用函数中的参数flags来指定;对指定的信号是置“1”还是置“0”,用函数中的参数opt来指定(opt = OS_FLAG_SET为置“1”操作;opt = OS_FLAG_CLR为置“0”操作)。
后续思考:信号量集用来实现按键检测挺合适的。
软件定时器
其实就是定时器复用。一个系统节拍作为母定时器,提供一个系统tick。然后定义一组软件定时器,每一个软件定时器都有名称、时间、链表中位置、使用状态、使用方法、回调函数等信息。
起初,我们最朴素的想法是,每发生一次系统节拍中断,我们就扫描一次所有定时器,看一下哪些定时器发生了中断。然而这样做可能会比较低效,所以UCOSII提供了一种巧妙的方法:他将所有定时器进行分组,每次时钟节拍到来时只对部分定时器进行判断。这样可以缩短每次处理的时间,但是需要动态地维护一个定时器组。定时器组的维护只是在每次定时器到时时才会发生,其移除和插入操作不需要排序。这是一种比较高效的方法,减少了维护所需的时间。
UCOSII软件定时器实现了三种链表的维护;
1 | OS_EXT OS_TMR OSTmrTbl[OS_TMR_CFG_MAX]; //定时器控制块数组 |
定时器控制块描述了软件定时器的名称,定时时间,链表中的位置,使用状态,使用方式和回调函数等基本信息。
创建软件定时器的函数:OS_TMR *OSTmrCreate (INT32U dly, INT32U period, INT8U opt, OS_TMR_CALLBACK callback,void *callback_arg, INT8U *pname, INT8U *perr);
它的参数分别是初始化定时时间,周期(仅在周期模式下生效),模式选择,回调函数,回调函数参数,软件定时器的名字,错误信息。其中,软件定时器有固定格式:void (*OS_TMR_CALLBACK)(void *ptmr, void *parg);
,第一个参数是当前定时器的控制块指针,第二个是回调函数的参数,可以不用,但必须有。
开启软件定时器的参数:BOOLEAN OSTmrStart (OS_TMR *ptmr, INT8U *perr);
第一个参数是要开启的软件定时器指针,perr为错误信息。
停止软件定时器函数:BOOLEAN OSTmrStop (OS_TMR *ptmr,INT8U opt,void *callback_arg,INT8U *perr);
,其中第一个参数要停止的软件定时器指针。opt就是停止选项,可以实现直接停止,用初始化的参数执行一次回调函数再停止,或者是用新的参数执行一次回调函数再停止。
总结
我们学习了任务,事件和定时器三个功能。
关于任务:我们学习了如何创建一个任务。
关于事件:我们学习了信号量,邮箱,消息队列,信号量集。
关于定时器:我们学习了如何创建,开启,停止软件定时器。
参考资料
作者:守望
链接:https://www.zhihu.com/question/401308260/answer/1672966568