UCOSII

smallcracker 2021-05-26 00:00:00
Categories: Tags:

入门使用

任务设置

1
2
3
4
5
6
7
8
9
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
LED_Init(); //初始化与LED连接的硬件接口
OSInit();
OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO );//创建起始任务
OSStart();
}

值得介绍的函数

建立任务函数:OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INTU prio) 第一个参数是指向任务函数的函数指针,第二个参数是任务开始时,传递给任务的参数的指针,第三个是分配给任务的堆栈的栈顶指针,prio是分配给任务的优先级。

第二个参数有待研究。

事件控制块

同步与通信。为了把描述事件的数据结构统一起来,UCOSII 使用叫做事件控制块(ECB)的数据结构来描述诸如信号量、邮箱(消息邮箱)和消息队列这些事件。事件控制块中包含包括等待任务表在内的所有有关事件的数据,事件控制块结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
typedef struct
{
INT8U OSEventType; //事件的类型
INT16U OSEventCnt; //信号量计数器
void *OSEventPtr; //消息或消息队列的指针,因为每一种消息的存储都需要一定的数据结构,他有可能只是一个变量,也有可能是一个数组,甚至可能是一个其他什么复杂的数据结构,如果数据结构比较复杂的话,我们就是令行定义一种结构体来描述那种数据结构,然后让OSEventPtr来指向这个结构体。
INT8U OSEventGrp; //等待事件的任务组
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//任务等待表
#if OS_EVENT_NAME_EN > 0u
INT8U *OSEventName; //事件名
#endif
} OS_EVENT;

信号量

信号量是一类事件,使用信号量的最初目的,是为了给共享资源设立一个标志,该标志表示该共享资源的占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。

信号量可以分为两种:一种是二值型信号量,另外一种是N 值信号量。
二值型信号量好比家里的座机,任何时候,只能有一个人占用。而N 值信号量,则好比公共电话亭,可以同时有多个人(N 个)使用。

关于信号量,介绍这样几个函数:他们分别完成了创建,请求,释放,删除功能。

邮箱

在多任务操作系统中,常常需要在任务与任务之间通过传递一个数据(这种数据叫做“消息”)的方式来进行通信。为了达到这个目的,可以在内存中创建一个存储空间作为该数据的缓冲区。如果把这个缓冲区称之为消息缓冲区,这样在任务间传递数据(消息)的最简单办法就是传递消息缓冲区的指针。我们把用来传递消息缓冲区指针的数据结构叫做邮箱(消息邮箱)。
在UCOSII中,我们通过事件控制块的OSEventPrt来传递消息缓冲区指针,同时使事件控制块的成员OSEventType为常数OS_EVENT_TYPE_MBOX,则该事件控制块就叫做消息邮箱。

大概就和中转站差不多。

关于邮箱,介绍这样几个函数:它们分别完成了创建邮箱,发送信息,请求邮箱,查询邮箱状态,删除邮箱的功能。

void*很强大,但是一定要在合适的时候使用;同时强转很逆天,但是一定要注意前后的类型是否真的能正确转换。通俗地说void*:

消息队列

消息队列可以在多个任务之间传递多条消息。使用消息队列可以在任务之间传递多条消息。消息队列由三个部分组成:事件控制块、消息队列和消息。当把事件控制块成员 OSEventType的值置为 OS_EVENT_TYPE_Q时,该事件控制块描述的就是一个消息队列。

消息队列的实现是通过循环队列来实现的。我们定义一种结构体os_q,用于描述一个循环队列,其定义如下:

1
2
3
4
5
6
7
8
9
10
typedef struct os_q 
{
struct os_q *OSQPtr; //指向下一个空的队列控制块
void **OSQStart; //消息队列的起始地址
void **OSQEnd; //指向消息指针数组结束单元的下一个单元,
void **OSQIn; //指向插入下一条消息的位置,
void **OSQOut; //指向被取出的消息的位置
INT16U OSQSize; //数组的长度
INT16U OSQEntries; //已经存放的消息指针的元素数目
} OS_Q;

在系统初始化时,系统按照配置文件的设置定义OS_MAX_QS个队列控制块,并利用队列控制块中的指针OSQPTr来将所有队列控制块链接成链表(就是初始化了一个空的链表,链表中每一个元素都是一个空的消息队列)

关于消息队列,介绍这样几个函数:创建,请求,发送

信号量集

在实际运行的应用中,任务常常需要和多个时间保持同步。就是说,任务需要多个信号量同时满足或者部分满足时才能运行。我们当然可以设置一系列信号量通过他们的逻辑运算来实现这样的功能,但是UCOII提供了一种特殊的数据结构——信号量集来实现这样的功能。

信号量集管理的信号都是二值信号,使用标志组来控制,定义如下:

1
2
3
4
5
6
typedef struct
{
INT8U OSFlagType; //识别是否为信号量集的标志?
void *OSFlagWaitList; //指向等待任务链表的指针
OS_FLAGS OSFlagFlags; //所有信号列表
}OS_FLAG_GRP;

每一个信号量集都对应着一个等待任务链表,该链表是一个双向链表,每一个元素就是一个任务,其节点的定义如下:

1
2
3
4
5
6
7
8
9
typedef struct
{
void *OSFlagNodeNext; //指向下一个节点的指针
void *OSFlagNodePrev; //指向前一个节点的指针
void *OSFlagNodeTCB; //指向对应任务控制块的指针
void *OSFlagNodeFlagGrp; //反向指向信号量集的指针
OS_FLAGS OSFlagNodeFlags; //信号过滤器
INT8U OSFlagNodeWaitType; //定义逻辑运算关系的数据
} OS_FLAG_NODE;

其中,信号过滤器用于选择OSFlagFlags中部分或者全部位作为有效信号。OSFlagNodeWaitType可以设置触发条件(全0触发,部分0触发,全1触发,部分1触发)。这样,每一个任务就可以根据自己的需要选择信号量集中自己关心的那几个信号量,对他们进行逻辑运算来得出分析结果。

OS_FLAGS是一个宏定义,可以被定义成一个8位、16位或32位变量,表示我们这个信号量集中有多少个信号量。OSFlagFlags的每一位就是那个信号量。

信号量集的操作我们关注这样几个函数:创建、请求、发送

后续思考:信号量集用来实现按键检测挺合适的。

软件定时器

其实就是定时器复用。一个系统节拍作为母定时器,提供一个系统tick。然后定义一组软件定时器,每一个软件定时器都有名称、时间、链表中位置、使用状态、使用方法、回调函数等信息。

起初,我们最朴素的想法是,每发生一次系统节拍中断,我们就扫描一次所有定时器,看一下哪些定时器发生了中断。然而这样做可能会比较低效,所以UCOSII提供了一种巧妙的方法:他将所有定时器进行分组,每次时钟节拍到来时只对部分定时器进行判断。这样可以缩短每次处理的时间,但是需要动态地维护一个定时器组。定时器组的维护只是在每次定时器到时时才会发生,其移除和插入操作不需要排序。这是一种比较高效的方法,减少了维护所需的时间。

UCOSII软件定时器实现了三种链表的维护;

1
2
3
OS_EXT OS_TMR OSTmrTbl[OS_TMR_CFG_MAX]; //定时器控制块数组
OS_EXT OS_TMR *OSTmrFreeList; //空闲定时器控制块链表指针
OS_EXT OS_TMR_WHEEL OSTmrWheelTbl[OS_TMR_CFG_WHEEL_SIZE];//定时器轮

定时器控制块描述了软件定时器的名称,定时时间,链表中的位置,使用状态,使用方式和回调函数等基本信息。

创建软件定时器的函数: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