UCOSII从V2.83版本以后,加入了软件定时器,这使得UCOSII的功能更加完善,在其上的应用程序开发与移植也更加方便。在实时操作系统中一个好的软件定时器实现要求有较高的精度、较小的处理器开销,且占用较少的存储器资源。
ucosII关于定时器的内容在os_tmr.c文件内,需使能os_cfg.h中的OS_CFG_TMR_EN来启动定时器服务。
定时器服务为在协议栈处理,IO定时轮询的设备中提供了很大的方便。
软件定时器同样由OSTimTick提供时钟,但是软件定时器的时钟还OS_TMR_CFG_TICKS_PER_SEC设置的控制,也就是在UCOSII的时钟节拍上面再做了一次“分频”,软件定时器的最快时钟节拍就等于UCOSII的系统时钟节拍。这也决定了软件定时器的精度。
定时时间一到,则系统会调用用户函数实现特定功能。
软件定时器也需要一个时钟节拍驱动,这个驱动也是由硬件实现的,一般使用uCOS中的任务延时节拍驱动来驱动软件定时器,每个时钟节拍OSTmrCtr(全局变量,初始值为0)加1,当OSTmrCtr的值等于OS_TICKS_PER_SEC /OS_TMR_CFG_TICKS_PER_SEC(前者是ucos节拍频率,后者是软件定时器的节拍频率,相当于分频)时,调用函数OSTmrSignl()函数,发送信号量OSTmrSemSignal(初始值0,决定软件定时器扫描任务OSTmr_Task的运行),也就是说,对软件定制器的处理不在时钟节拍中断函数中执行,而是以发生信号量的方式激活任务OSTmr_Task,任务OSTmr_Task对定时器进行检测,包括定时器定时完成的判断,回调函数的执行。
举例来讲,ucos中的OS_TICKS_PER_SEC设定值为1000(最大),OS_TMR_CFG_TICKS_PER_SEC为100,则可知uCOS的节拍时间为1ms,软件定时器的节拍时间为1000/100*1=10ms
1. 创建定时器
OS_TMR *OSTmrCreate
(
INT32U dly,
INT32U period,
INT8U opt,
OS_TMR_CALLBACK callback,
void *callback_arg,
INT8U *pname,
INT8U *perr
)
入口参数解释:
(1)dly:延时时间(单位:(1/OS_TMR_CFG_TICKS_PER_SEC)秒)
如果参数opt为:OS_TMR_OPT_ONE_SHOT,则dly表示定时器超时时间
如果参数opt为:OS_TMR_OPT_PERIODIC,则dly表示定时器在开始循环模式前等待第一次启动的超时时间
(2)period:定时器重复运行的时间周期(单位:(1/OS_TMR_CFG_TICKS_PER_SEC)秒)
如果参数opt为:OS_TMR_OPT_PERIODIC,则period表示定时器自动周期重启的时间
如果参数opt为:OS_TMR_OPT_ONE_SHOT,则period无效
(3)opt:选择定时器运行模式
如果参数opt为:OS_TMR_OPT_PERIODIC,则表示定时器会自动重载超时时间,周期运行
如果参数opt为:OS_TMR_OPT_ONE_SHOT,则表示定时器只运行一次
(4)callback:超时处理回调函数(定时器中断服务程序)
必须定义成类似这种函数接口的形式:void MyCallback (OS_TMR *ptmr, void *p_arg);
当定时器时间到了,就执行这个函数
(5)callback_arg:当callback被调用时传递给callback的入口参数
(6)pname:定时器的名称(是一个字符串,如"Timer0")
(7)*perr:定时器错误码,是下面之一:
OS_ERR_NONE:没有错误
OS_ERR_TMR_INVALID_DLY:指定了一个无效的延时时间
OS_ERR_TMR_INVALID_PERIOD:指定了一个无效的周期
OS_ERR_TMR_INVALID_OPT:指定了一个无效的选项
OS_ERR_TMR_ISR :在中断服务程序里调用错误
OS_ERR_TMR_NON_AVAIL:定时器池里没有空闲的定时器
2、启动定时器
BOOLEAN OSTmrStart (OS_TMR *ptmr, INT8U *perr)
这个函数是用来被应用程序调用,来启动定时器的。
入口参数解释:
(1)ptmr:指向待启动的定时器
(2)*perr:错误码
μC/OS-II定时器的创建、启动等函数不能在中断服务程序里调用
UCOSII中软件定时器的实现方法是,将定时器按定时时间分组,使得每次时钟节拍到来时只对部分定时器进行比较操作,缩短了每次处理的时间。但这就需要动态地维护一个定时器组。定时器组的维护只是在每次定时器到时时才发生,而且定时器从组中移除和再插入操作不需要排序。这是一种比较高效的算法,减少了维护所需的操作时间。
UCOSII软件定时器实现了3类链表的维护:
其中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两个指针同样也组织了所在分组中定时器控制块的双向链表。软件定时器管理所需的数据结构示意图如图所示
如果使能了定时器服务,在ucos初始化的时候,系统便自动创建了一个任务OSTmr_Task(),每次节拍到达便判断相应定时轮中定时器的定时是否到达,如果到达则调用相应的回调函数。
软件定时器由硬件定时器提供基准Tr,可以通过宏定义OS_TMR_CFG_TICKS_PER_SECOND对硬件定时器节拍进行分频即软件定时器时钟节拍为 Tr*OS_TMR_CFG_TICKS_PER_SECOND。
硬件定时器节拍时刻到后触发中断。如果使能了定时器服务,在SysTic_Handler()中断函数中通过一系列调用,最后到执行到OSTimeTickhook()函数,在该函数中会判断该时刻是否是软件定时器的节拍时刻,如果是,则通过释放一个信号量OSTmrSemSignal来激活正在等待改信号量的OSTmrTask()任务。
在ucos初始化的时候,系统便自动创建了一个任务OSTmr_Task(),其优先级,堆栈大小等于任务相关的参数在os_cfg.h中定义。在OSTmr_Task()任务中等待OSTmrSemSignal信号量。并定义了一个变量OSTimTick来计时软件定时器的节拍。OSTmrSemSignal信号量一旦有效OSTimTick便加1并判断相应定时轮中定时器的定时是否到达,如果到达则调用相应的回调函数完成定时器操作。
在ucos_ii.h中,声明了下面8个函数供用户使用。
1、定时器任务参数配置
定时任务有OSInit()在初始化时创建,其优先级,堆栈大小等于任务相关的参数在os_cfg.h中定义。用户可通过项目实际需求修改相应参数。
2、创建定时器
3、编写回调函数
4、启动定时器
5、关于回调函数
回调函数执行时所用到的堆栈是是定时器任务堆栈,所以要确保分配的定时器任务堆栈大小能够满足回调函数的堆栈要求回调函数的执行是根据它们在定时器链表中的位置先后执行(一个定时器只能执行一个回调函数)。
定时器任务的执行时间极大程度是有溢出的定时器个数和回调函数执行时间决定。回调函数执行期间,调度是处于被锁状态,所以回调函数越快执行越好,更不要去在回调函数中去等待事件。