μC/OS需要用户提供周期性信号源,用于实现时间延时和确认超时。节拍率应在每秒10次到100次之间,或者说10到100Hz。时钟节拍率越高,系统的额外负荷就越重。时钟节拍的实际频率取决于用户应用程序的精度。时钟节拍源可以是专门的硬件定时器,也可以是来自50/60Hz交流电源的信号。
用户必须在多任务系统启动以后再开启时钟节拍器,也就是在调用OSStart()之后。换句话说,在调用OSStart()之后做的第一件事是初始化定时器中断。通常,容易犯的错误是将允许时钟节拍器中断放在系统初始化函数OSInit()之后,在调启动多任务系统启动函数
OSStart()之前,如程序清单L3.19所示。
程序清单 L3.19 启动时钟就节拍器的不正确做法.
void main(void)
{
.
.
OSInit(); /* 初始化uC/OS-II */
.
.
/* 应用程序初始化代码 ... */
/* ... 通过调用OSTaskCreate()创建至少一个任务 */
.
.
允许时钟节拍(TICKER)中断; /* 千万不要在这里允许时钟节拍中断!!! */
.
.
OSStart(); /* 开始多任务调度 */
}
这里潜在地危险是,时钟节拍中断有可能在μC/OS-Ⅱ启动第一个任务之前发生,此时μC/OS-Ⅱ是处在一种不确定的状态之中,用户应用程序有可能会崩溃。
μC/OS-Ⅱ中的时钟节拍服务是通过在中断服务子程序中调用OSTimeTick()实现的。时钟节拍中断服从所有前面章节中描述的规则。时钟节拍中断服务子程序的示意代码如程序清单L3.20所示。这段代码必须用汇编语言编写,因为在C语言里不能直接处理CPU的寄存器。
程序清单 L3.20 时钟节拍中断服务子程序的示意代码
void OSTickISR(void)
{
保存处理器寄存器的值;
调用OSIntEnter()或是将OSIntNesting加1;
调用OSTimeTick();
调用OSIntExit();
恢复处理器寄存器的值;
执行中断返回指令;
}
时钟节拍函数OSTimeTick()的代码如程序清单3.21所示。OSTimtick()以调用可由用户定义的时钟节拍外连函数OSTimTickHook()开始,这个外连函数可以将时钟节拍函数OSTimtick()予以扩展[L3.2(1)]。笔者决定首先调用OSTimTickHook()是打算在时钟节拍中断服务一开始就给用户一个可以做点儿什么的机会,因为用户可能会有一些时间要求苛刻的工作要做。OSTimtick()中量大的工作是给每个用户任务控制块OS_TCB中的时间延时项
OSTCBDly减1(如果该项不为零的话)。OSTimTick()从OSTCBList开始,沿着OS_TCB链表做,一直做到空闲任务[L3.21(3)]。当某任务的任务控制块中的时间延时项OSTCBDly减到了零,这个任务就进入了就绪态[L3.21(5)]。而确切被任务挂起的函数OSTaskSuspend()挂起的任务则不会进入就绪态[L3.21(4)]。OSTimTick()的执行时间直接与应用程序中建立了多少个任务成正比。
程序清单 L3.21 时钟节拍函数 OSTimtick() 的一个节拍服务
void OSTimeTick (void)
{
OS_TCB *ptcb;
OSTimeTickHook(); (1)
ptcb = OSTCBList; (2)
while (ptcb->OSTCBPrio != OS_IDLE_PRIO) { (3)
OS_ENTER_CRITICAL();
if (ptcb->OSTCBDly != 0) {
if (--ptcb->OSTCBDly == 0) {
if (!(ptcb->OSTCBStat & OS_STAT_SUSPEND)) { (4)
OSRdyGrp |= ptcb->OSTCBBitY; (5)
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
} else {
ptcb->OSTCBDly = 1;
}
}
}
ptcb = ptcb->OSTCBNext;
OS_EXIT_CRITICAL();
}
OS_ENTER_CRITICAL(); (6)
OSTime++; (7)
OS_EXIT_CRITICAL();
}
OSTimeTick()还通过调用OSTime()[L3.21(7)]累加从开机以来的时间,用的是一个无符号32位变量。注意,在给OSTime加1之前使用了关中断,因为多数微处理器给32位数加1的操作都得使用多条指令。
中断服务子程序似乎就得写这么长,如果用户不喜欢将中断服务程序写这么长,可以从任务级调用OSTimeTick(),如程序清单L3.22所示。要想这么做,得建立一个高于应用程序中所有其它任务优先级的任务。时钟节拍中断服务子程序利用信号量或邮箱发信号给这个高优先级的任务。
程序清单 L3.22 时钟节拍任务 TickTask() 作时钟节拍服务.
void TickTask (void *pdata)
{
pdata = pdata;
for (;;) {
OSMboxPend(...); /* 等待从时钟节拍中断服务程序发来的信号 */
OSTimeTick();
}
}
用户当然需要先建立一个邮箱(初始化成NULL)用于发信号给上述任何告知时钟节拍中断已经发生了(程序清单L3.23)。
程序清单L3.23时钟节拍中断服务函数OSTickISR()做节拍服务。
void OSTickISR(void)
{
保存处理器寄存器的值;
调用OSIntEnter()或是将OSIntNesting加1;
发送一个‘空’消息(例如, (void *)1)到时钟节拍的邮箱;
调用OSIntExit();
恢复处理器寄存器的值;
执行中断返回指令;
}