uCOS软件定时器相关函数理解

软件定时器控制块(结构体)

struct  os_tmr {

OS_OBJ_TYPE   Type;  //内核对象类型为OS_OBJ_TYPE_TMR

    CPU_CHAR    *NamePtr;   //软件定时器名字 

    OS_TMR_CALLBACK_PTR  CallbackPtr;  //指向回调函数的指针

    void       *CallbackPtrArg;  //指向回调函数参数的指针   

    OS_TMR     *NextPtr;   //指向下一个定时器

    OS_TMR     *PrevPtr;   //指向上一个定时器

    OS_TICK      Remain;   //距定时器定时时间到达还有多少个时基                       

    OS_TICK      Dly;   //定时器初次定时值

    OS_TICK     Period;   //定时器定时周期,以定时器时基为最小单位

    OS_OPT    Opt;  //定时器选项

    OS_STATE    State;  //定时器状态

    OS_TMR     *DbgPrevPtr;  //指向定时器调试列表中的上一个定时器

    OS_TMR     *DbgNextPtr;  //指向定时器调试列表中的下一个定时器

};

这里定时器选项就是两个:OS_OPT_TMR_ONE_SHOT(单次模式,只定时一次不重复),OS_OPT_TMR_PERIODIC(周期模式)。

软件定时器任务

{

软件定时器任务的创建过程:软件定时器任务的创建是在系统初始化OSInit()时完成的。OSInit()在宏OS_CFG_TMR_EN使能的情况下,调用OS_TmrInit()初始化时钟管理模型。在OS_TmrInit()中会调用OSTaskCreate()创建软件定时器任务。

OS_TmrInit():初始化定时器管理模型

{

1个入口参数:返回的错误类型,如果在调用函数时错误*p_err != OS_ERR_NONE,那么就return返回。

函数过程:① 首先进行安全检查。然后判断软件定时器调试是否使能,使能就把指向定时器调试列表的全局指针变量OSTmrDbgListPtr赋值为NULL,因为目前还没有一个定时器创建,调试列表中自然也没有。② 让全局的指向定时器的指针OSTmrListPtr赋值为NULL,全局的统计定时器个数的全局变量赋值为0;这个就是定时器列表,它把所有的创建的定时器都串在一起,形成双向链表。(f103使用的哈希算法,真正弄了个列表出来。)③ 如果使能了互斥量,那么调用OSMutexCreate()创建一个保护定时器的互斥量。在定时器创建函数OSTmrCreate()中初始化我们定义的定时器结构体变量时会使用互斥量保护此临界段(可能是因为在任务运行中创建软件定时器,使用开关中断方法会造成中断延迟,所以f4做了一下改进)(这个互斥量在os.h中定义,具有全局属性,在进入临界段时获取该互斥量,以获得修改临界段值的权利;在退出临界段时释放。多个定时器可只用一个互斥量)。④ uCOS只是在该函数中调用OSTaskCreate()创建任务,自然和我们创建任务一样,早就定义了全局的任务堆栈,任务优先级,任务大小(os_cfg_app.c);也声明了软件定时器任务函数(os.h)。这些参数用户可以修改,所以OSTaskCreate()创建任务之前,检查了优先级是否超出范围,堆栈指针是否指向NULL等。⑤ 调用OSTaskCreate()创建软件定时器任务。

}

OS_TmrTask():定时器任务函数

{

函数思想:之前我们说过,之所以要有定时器任务,是因为要在这里面判断定时器定时时间是不是到了,如果到了就调用相应的回调函数。那定时器任务怎么判断定时时间到了?注意这是软件定时器,没有硬件定时器的寄存器等结构。硬件定时器定时时间到时,寄存器相应标志位会置1。那么对于软件定时器,我们就可以模拟硬件定时器的标志位。但是软件定时器个数不确定,我们不能使用确定的全局变量个数来做标记。回忆硬件定时器的定时过程:首先确定硬件定时器节拍,然后给硬件定时器赋予定时值(即定时节拍数),当节拍数数到0时,标志位置1。其实标志位的出现就是为了同步。所以软件定时器也按照这个流程:确定好软件定时器节拍,然后每个定时器定时值不同,为了在一个任务中把所有定时器都判断到,就把所有软件定时器串成定时器列表,而且需要在每个定时器节拍到来时遍历整个列表,判断是否有定时器定时时间结束为了同步软件定时器节拍,在软件定时器任务中获取信号量,只有当定时器节拍释放信号量时,该任务才获取到信号量,然后恢复为就绪态,表示一个节拍到来,判断是否有定时器定时时间结束。

函数过程:和其他的任务函数一样,定时器任务函数也是无返回无限循环的,入口参数只有一个,就是返回的错误类型。① 进入while(p_tmr != (OS_TMR *)0)循环,调用任务信号量等待函数OSTaskSemPend()。注意:等待信号量的函数如果没等到信号量,会进入阻塞状态,直到等待超时、中止或者等到了信号量。② 如果等到了信号量,遍历整个列表,看是否有定时器定时时间结束。进入临界段;这里它测量了软件定时器任务函数的运行时间,所以获取了当前时间戳;增加当前定时器时间记录,使全局变量 OSTmrTickCtr值加1;全局变量OSTmrListPtr就是指向该列表头部的时钟,通过while()循环,遍历列表中各个时钟。注意:这里遍历时钟列表时调用了OSSchedLock()锁住了调度器;也确实应该锁住调度器,防止其他高级任务打断该任务,加大定时的不准确度。③ 怎么判断定时时间到?因为时钟结构体有一个元素是Remain,表示距定时器定时时间到达还有多少个时基,并且在创建时已经初始化该参数。所以在while循环中只需要让Remain元素自减,并判断是否减为0,减为0即表示定时时间到。④ 如果定时时间到,根据时钟结构体元素opt判断该时钟是单次模式还是周期模式,周期模式的话,再次给Remain赋值为Period;如果是单次模式,调用函数OS_TmrUnlink()将定时器从列表中移除,并把定时器状态改为OS_TMR_STATE_COMPLETED,表示定时完成。不管是哪种模式,都需要判断时钟结构体中指向回调函数的指针CallbackPtr是否存在,存在就要调用回调函数;然后调用OS_TmrUnlock解锁调度器。(OS_TmrUnlink()函数就是进行删除双向链表结点的工作,因为有头指针OSTmrListPtr来管理该链表,所以删除第一个结点和其他结点不太一样。在该函数中,成功移除后,将定时器状态改为OS_TMR_STATE_STOPPED,并将列表定时器个数OSTmrListEntries减1)。⑤ 退出while()循环,再次获取时间戳,并与之前进入循环的时间戳作差,并调用OS_TmrUnlock()释放互斥量或者解锁调度器。如果全局变量定时器任务最大执行时间OSTmrTaskTimeMax小于这个差,则赋值为该差值。

补充---通过函数指针调用函数

{

一般我们调用函数都是函数名+函数参数。由于函数名被使用时总是由编译器把它转换为函数指针,所以我们可以通过函数指针获得函数名,那就是(*函数指针)+函数参数,这里调用回调函数就是使用的该形式。*函数指针 = 函数名

}

--释放定时器节拍的信号量

{

uCOS 采用 Tick 任务(OS_TickTask)管理系统的时间节拍,而我们定时器节拍是由系统节拍分频而来,那么其发送信号量的地方当然也是在 SysTick 中断服务函数中,OS_CPU_SysTickHandler()----OSTimeTick()。但是uCOS 支持采用中断延迟,如果使用了中断延迟,那么发送任务信号量的地方就会在中断发布任务中(OS_IntQTask)。

uCOS软件定时器相关函数理解_第1张图片 从上面的代码中可以看到:软件定时器是通过判断OSTmrUpdateCtr是否等于0来进行信号量的发送。因为每次进入systick中断就是一个tick时间结束,我们一般设置的是1ms为一个周期。这里每个tick周期到来,OSTmrUpdateCtr自减,当它减到0时再发送软件定时器信号量,相当于软件定时器时基是OSTmrUpdateCtr*tick周期。在初始化软件定时器函数OS_TmrInit()中,OSTmrUpdateCtr和OSTmrUpdateCnt都为100,意味着软件定时器时基是100ms(10Hz)。}

软件定时器相关函数

{

OSTmrCreate:定时器创建函数

{

注意内核对象的创建函数并不是创建内核对象的意思,内核对象首先需要在文件中被定义,内核对象的创建函数相当于初始化内核对象控制块。相当于任务一样,我们首先在文件中定义任务,为了控制管理任务,我们定义了一个任务控制块结构体,里面包含任务所有的内容,在创建任务函数中初始化TCB中内容

8个入口参数:指向定时器的指针;定时器的名字;初始定时值;周期定时值;选项(单次还是周期);指向回调函数的指针;指向回调函数的形参;返回的错误类型。

函数过程:① 进行一系列的检查,其中选项检查:如果是单次模式,需要检查dly参数是否为0,为0则返回错误“定时初始参数无效”。如果是周期模式,则检查参数period是否为0,为0返回错误“周期重重载参数无效”。② 内核对象类型为OS_OBJ_TYPE_TMR,定时器初始状态为OS_TMR_STATE_STOPPED,除了按照入口参数给软件定时器控制块中的元素赋值以外,其他都是0或者NULL。③ 调用OS_TmrLock(),这个函数是说,如果使能了互斥量,就调用OSMutexPend()获取一个互斥量,用互斥量保护该定时器,就不用锁调度器,就不会造成中断延迟的问题(其实就是尝试用互斥量保护定时器结构体初始化赋值操作,其他内核对象的创建是直接使用临界段保护)。如果没有使能互斥量,就老老实实调用OSSchedLock()锁住调度器。④ 常规步骤,调用OS_TmrDbgListAdd()将定时器插入到定时器的调试列表中。定时器创建成功,记录定时器个数的全局变量OSTmrQty加1。⑤ 调用OS_TmrUnlock(),这个函数是说如果使能了(默认使能)互斥量,就调用OSMutexPost()释放互斥量;否则调用OSSchedUnlock()解锁调度器。(注:f103中还是使用的开关中断保护临界段。f407中保护定时器的互斥量创建是在初始化时钟管理模型函数OS_TmrInit()中进行的

}

OSTmrStart():启动软件定时器函数 

{

主要目的:将已经创建的软件定时器添加到定时器列表中。f4就是把它加入到双向链表中。OSTmrListPtr就是该链表的头指针。经过定时器创建函数初始化好定时器后,定时器是停止状态

2个入口参数:指向软件定时器的指针;返回的错误类型

返回:0/1。0代表存在错误,启动定时器失败;1代表启动定时器成功。

函数过程:① 执行安全检查、中断中非法调用检查、参数检查、参数对象类型检查等。安全检查这里就是检查参数错误类型指针是否是空指针NULL,不能对空指针进行操作。② 根据定时器的状态分类操作,switch-case(发现没有,uCOS考虑的很全面,害怕用户随意启动定时器,进行了一系列检查,并且罗列了定时器所有状态,根据状态分类操作。并返回不同的错误信息,供用户调试)。③ 如果定时器正在运行,那就重新启动,重新把定时器结构元素Remain赋值为Dly(定时器初次定时值)。因为定时器在运行,说明已经被添加到定时器列表中,不需要再次插入定时器列表。④ 如果定时器是停止或者已经定时完成的状态,那么修改状态为运行,并根据Dly值是否为给Remain赋值,如果为0,则Remain =Period,如果不为0,Remain = Dly。然后就是进行双向链表插入工作,这里是进行的插入结点到头部的操作,主要判断该链表中是否没有其他结点;并修改定时器列表中定时器的值OSTmrListEntries。返回无错误和1。⑤ 如果定时器没有被创建(unused),返回没有被创建的错误,并返回0;其他状态返回非法错误类型,并return 0。

}

OSTmrStop:停止定时器函数

{

uCOS的停止定时器函数有两个作用:一是停止定时器不做任何操作。二是停止定时器并调用回调函数,并且可以选择回调函数的参数(是创建时参数还是新的参数,根据opt选项进行操作)。作用二相当于我想提前结束该定时器,让它赶快执行回调函数,得到我想要的数据或者实现一些功能。

返回:0,停止失败。1,停止成功。

函数过程:① 停止定时器,就是调用OS_TmrUnlink()将定时器从定时器列表中移除,在OS_TmrUnlink()函数中,除了进行双向链表删除结点的操作,还会把定时器的状态改为OS_TMR_STATE_STOPPED,并令列表中定时器个数OSTmrListEntries减1。② 然后根据定时器状态进行操作,只有在正在进行的状态下,才会进行①的步骤,并且根据选项opt看是否调用回调函数。如果是OS_TMR_STATE_COMPLETED和OS_TMR_STATE_STOPPED,表明已经完成一次定时和已经停止并未被启动,那就返回错误类型“OS_ERR_TMR_STOPPED”和1。其他状态就是定时器未被创建(可能是创建后被删除)或者非法状态,返回相应的错误类型和0即可。

}

OSTmrDel():删除定时器函数

{

删除和停止的区别:不同于停止定时器操作,停止定时器只是从定时器列表中移除,调用启动定时器函数即可重新启动。删除定时器意味着清空定时器里面的内容,如果想再次使用,需要先创建,再调用启动函数才行。

函数过程:因为定时器会加入定时器列表,通过定时器任务统一管理,所以需要根据定时器状态进行不同的操作。主要就是定时器是否是运行状态,是运行状态,就需要把它从定时器列表中移除。其他有效状态,比如“完成或者停止”状态,只需要调用OS_TmrClr()清空定时器结构体内容,在该函数中主要是把定时器状态改为“OS_TMR_STATE_UNUSED”;然后使定时器计数值OSTmrQty减1,返回无错误和1。

}

}

软件定时器的使用:① 首先定义一个定时器结构体。② 调用OSTmrCreate()对该定时器进行初始化(初始化之前需要先编写回调函数)。③ 调用OSTmrStart()启动定时器,实则就是把定时器加入到时钟列表中。④ 也可以调用删除、停止等操作。

总结软件定时器:在创建好软件定时器并启动后,定时器就被加入到定时器列表中,供软件定时器任务所管理,当os切换到软件定时器任务时,软件定时器任务会等待软件定时器信号量的到来。如果信号量不可用,说明一个软件定时器的时基还没结束,那么就进入等待列表。软件定时器信号量的发送是在systick中断中进行的,一个tick定时结束,就会进入该中断,在该中断中判断是否一个定时器时基也结束,如果结束则发送定时器信号量,发送信号量函数中如果等待列表中有任务的存在,则将任务移出等待列表,插入就绪列表。所以这里就把之前阻塞的定时器任务插入到就绪列表,等待os切换到该任务时,则判断是否有定时器的定时时间结束,有定时器定时时间结束就调用其回调函数。所以,如果要测量定时时间的准确性,在调用OSTmrStart()启动定时器后应该获取一个时间戳,因为此时定时器已经插入到定时器列表,在每个定时器时基到来时会判断其定时时间是否结束,就相当于定时器已经开始定时。然后在回调函数中再获取一个时间戳,回调函数是定时器定时时间结束后所调用好函数,所以在其中获取时间戳就相当于定时时间结束。两者相减,就是定时时间。(这里注意区别任务的优先级和中断优先级。说到底任务只是一个个无限循环的函数,任何中断都可以打断这些函数的运行。中断就和中断比优先级,任务就和任务比优先级

你可能感兴趣的:(嵌入式操作系统uCOS-III,单片机,链表,stm32)