转载出处:http://www.openedv.com/posts/list/15061.htm
UCOSII从V2.83版本以后,加入了软件定时器,这使得UCOSII的功能更加完善,在其上的应用程序开发与移植也更加方便。在实时操作系统中一个好的软件定时器实现要求有较高的精度、较小的处理器开销,且占用较少的存储器资源。
通过前面的学习,我们知道UCOSII通过OSTimTick函数对时钟节拍进行加1操作,同时遍历任务控制块,以判断任务延时是否到时。软件定时器同样由OSTimTick提供时钟,但是软件定时器的时钟还受OS_TMR_CFG_TICKS_PER_SEC设置的控制,也就是在UCOSII的时钟节拍上面再做了一次“分频”,软件定时器的最快时钟节拍就等于UCOSII的系统时钟节拍。这也决定了软件定时器的精度。
软件定时器定义了一个单独的计数器OSTmrTime,用于软件定时器的计时,UCOSII并不在OSTimTick中进行软件定时器的到时判断与处理,而是创建了一个高于应用程序中所有其他任务优先级的定时器管理任务OSTmr_Task,在这个任务中进行定时器的到时判断和处理。时钟节拍函数通过信号量给这个高优先级任务发信号。这种方法缩短了中断服务程序的执行时间,但也使得定时器到时处理函数的响应受到中断退出时恢复现场和任务切换的影响。软件定时器功能实现代码存放在tmr.c文件中,移植时需只需在os_cfg.h文件中使能定时器和设定定时器的相关参数。
UCOSII中软件定时器的实现方法是,将定时器按定时时间分组,使得每次时钟节拍到来时只对部分定时器进行比较操作,缩短了每次处理的时间。但这就需要动态地维护一个定时器组。定时器组的维护只是在每次定时器到时时才发生,而且定时器从组中移除和再插入操作不需要排序。这是一种比较高效的算法,减少了维护所需的操作时间。
UCOSII软件定时器实现了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为定时器控制块,定时器控制块是软件定时器管理的基本单元,包含软件定时器的名称、定时时间、在链表中的位置、使用状态、使用方式,以及到时回调函数及其参数等基本信息。
OSTmrTbl[OS_TMR_CFG_MAX];:以数组的形式静态分配定时器控制块所需的RAM空间,并存储所有已建立的定时器控制块,OS_TMR_CFG_MAX为最大软件定时器的个数。
OSTmrFreeLiSt:为空闲定时器控制块链表头指针。空闲态的定时器控制块(OS_TMR)中,OSTmrnext和OSTmrPrev两个指针分别指向空闲控制块的前一个和后一个,组织了空闲控制块双向链表。建立定时器时,从这个链表中搜索空闲定时器控制块。
OSTmrWheelTbl[OS_TMR_CFG_WHEEL_SIZE]:该数组的每个元素都是已开启定时器的一个分组,元素中记录了指向该分组中第一个定时器控制块的指针,以及定时器控制块的个数。运行态的定时器控制块(OS_TMR)中,OSTmrnext和OSTmrPrev两个指针同样也组织了所在分组中定时器控制块的双向链表。软件定时器管理所需的数据结构示意图如图
OS_TMR_CFG_WHEEL_SIZE定义了OSTmrWheelTbl的大小,同时这个值也是定时器分组的依据。按照定时器到时值与OS_TMR_CFG_WHEEL_SIZE相除的余数进行分组:不同余数的定时器放在不同分组中;相同余数的定时器处在同一组中,由双向链表连接。这样,余数值为0~OS_TMR_CFG_WHEEL_SIZE-1的不同定时器控制块,正好分别对应了数组元素OSTmr-WheelTbl[0]~OSTmrWheelTbl[OS_TMR_CFGWHEEL_SIZE-1]的不同分组。每次时钟节拍到来时,时钟数OSTmrTime值加1,然后也进行求余操作,只有余数相同的那组定时器才有可能到时,所以只对该组定时器进行判断。这种方法比循环判断所有定时器更高效。随着时钟数的累加,处理的分组也由0~OS_TMR_CFG_WHE EL_SIZE-1循环。这里,我们推荐OS_TMR_CFG_WHEEL_SIZE的取值为2的N次方,以便采用移位操作计算余数,缩短处理时间。
信号量唤醒定时器管理任务,计算出当前所要处理的分组后,程序遍历该分组中的所有控制块,将当前OSTmrTime值与定时器控制块中的到时值(OSTmrMatch)相比较。若相等(即到时),则调用该定时器到时回调函数;若不相等,则判断该组中下一个定时器控制块。如此操作,直到该分组链表的结尾。软件定时器管理任务的流程如图
当运行完软件定时器的到时处理函数之后,需要进行该定时器控制块在链表中的移除和再插入操作。插入前需要重新计算定时器下次到时时所处的分组。计算公式如下:
定时器下次到时的OSTmrTime值(OSTmrMatch)=定时器定时值+当前OSTmrTime值
新分组=定时器下次到时的OSTmrTime值(OSTmrMatch)%OS_TMR_CFG_WHEEL_SIZE
接下来我们看看在UCOSII中,与软件定时器相关的几个函数。
1) 创建软件定时器函数
创建软件定时器通过函数OSTmrCreate实现,该函数原型为:OS_TMR *OSTmrCreate (INT32U dly, INT32U period, INT8U opt, OS_TMR_CALLBACK callback,void *callback_arg, INT8U *pname, INT8U *perr)。
dly,用于初始化定时时间,对单次定时(ONE-SHOT模式)的软件定时器来说,这就是该定时器的定时时间,而对于周期定时(PERIODIC模式)的软件定时器来说,这是该定时器第一次定时的时间,从第二次开始定时时间变为period。
period,在周期定时(PERIODIC模式),该值为软件定时器的周期溢出时间。
opt,用于设置软件定时器工作模式。可以设置的值为:OS_TMR_OPT_ONE_SHOT或OS_TMR_OPT_PERIODIC,如果设置为前者,说明是一个单次定时器;设置为后者则表示是周期定时器。
callback,为软件定时器的回调函数,当软件定时器的定时时间到达时,会调用该函数。
callback_arg,回调函数的参数。
pname,为软件定时器的名字。
perr,为错误信息。
软件定时器的回调函数有固定的格式,我们必须按照这个格式编写,软件定时器的回调函数格式为:void (*OS_TMR_CALLBACK)(void *ptmr, void *parg)。其中,函数名我们可以自己随意设置,而ptmr这个参数,软件定时器用来传递当前定时器的控制块指针,所以我们一般设置其类型为OS_TMR*类型,第二个参数(parg)为回调函数的参数,这个就可以根据自己需要设置了,你也可以不用,但是必须有这个参数。
2) 开启软件定时器函数
任务可以通过调用函数OSTmrStart开启某个软件定时器,该函数的原型为:BOOLEAN OSTmrStart (OS_TMR *ptmr, INT8U *perr)。其中ptmr为要开启的软件定时器指针,perr为错误信息。
3) 停止软件定时器函数
任务可以通过调用函数OSTmrStop停止某个软件定时器,该函数的原型为:BOOLEAN OSTmrStop (OS_TMR *ptmr,INT8U opt,void *callback_arg,INT8U *perr)。
其中ptmr为要停止的软件定时器指针。
opt为停止选项,可以设置的值及其对应的意义为:
OS_TMR_OPT_NONE,直接停止,不做任何其他处理
OS_TMR_OPT_CALLBACK,停止,用初始化的参数执行一次回调函数
OS_TMR_OPT_CALLBACK_ARG,停止,用新的参数执行一次回调函数
callback_arg,新的回调函数参数。
perr,错误信息。