时钟节拍中断发生时,CPU12 会自动CPU 把CPU 寄存器推入堆栈,然后是清中断标志。但是页面寄存器PPAGE 并没有被推入堆栈,如果CPU12 的寻址范围超过了64KB,则要把PPAGE 也推入堆栈,本文中没有用到PPAGE 寄存器。
时钟节拍中断服务子程序可能激活一个优先级高于当前被中断任务的优先级的任务。时钟节拍中断服务子程序要连续调用:OSIntEnter()、OSTimerTick()和OSIntExit()
这三个函数。OSIntEnter()通知μC/OS-II 进入中断服务子程序了。OSTimerTick()给要求延迟若干时钟节拍的任务延迟计数器减1,减1 后为0 则该任务进入就绪态。
OSIntExit()函数告诉μC/OS-II 时钟节拍中断服务子程序结束了,如果这时有更高优先级的任务进入了就绪态,OSIntExit()就会调用中断级的任务切换函数OSINTCtxSw()
做任务切换,以便让更高的优先级的任务运行。以下是函数代码:
void OSTickISR(void)
{
/*根据需要决定是否保存PPAGE 寄存器,此处没有保存*/
OSIntEnter();
MCFLG_MCZF=1; //清除模计数器中断标志位
OSTimeTick();
OSIntExit(); //退出中断并进行任务切换
}
3.2.2 任务堆栈初始化函数OSTaskStkInit()
这个C语言写的函数是与CPU硬件相关的。这个函数初始化任务的堆栈,由建立任务的函数OSTaskCreate()或扩展的建立任务函数OSTaskCreatExit()调用。建立任务的函数带有4个形式参数,扩展的建立任务的函数有8个参数。其中pdata用于向任务传递参数。利用了这个参数将页面寄存器PPAGE 参数传给建立的任务。在改写该函数的时候一定要深刻了解S12CPU在中断发生时各个CPU寄存器的入栈的顺序,否则,μC/OS-II是运行不起来的。中断发生时S12CPU各个寄存器入栈的顺序如图3所示。由于该函数是被建立任务的函数所调用的,所以各个CPU寄存器的初始值并不重要。但要CCR寄存器的内容需要注意:如果选择任务启动后允许中断发生,则所有的任务运行期间中断都允许;同样,如果选择任务启动后禁止中断,则所有的任务都禁止中断发生,而不能有所选择。本文选择在任务启动时开启中断。以下是函数代码:
void *OSTaskStkInit (void (*task)(void *pd), void *pdata, void *ptos, INT16U opt)
{
INT16U *stk;
pt = opt; // 'opt'未使用,此处可防止编译器的警告
stk = (INT16U *)ptos; //载入堆栈指针
*--stk = (INT16U)(pdata); //放置向函数传递的参数pdata
*--stk = (INT16U)(task); //函数返回地址PC
*--stk = (INT16U)(0x1122); //寄存器 Y
*--stk = (INT16U)(0x3344); //寄存器 X
((INT8U *)stk)--; // 寄存器A 仅需要1 个字节
*(INT8U *)stk = (INT8U)(0x55); //寄存器 A
((INT8U *)stk)--; // 寄存器B 仅需要1 个字节
*(INT8U *)stk = (INT8U)(0x66); //寄存器 B
((INT8U *)stk)--; // 寄存器CCR 仅需要1 个字节
*(INT8U *)stk = (INT8U)(0x00); //寄存器 CCR,开中断
return ((void *)stk);
}
3.2.3 让优先级最高的就绪态任务开始运行OSStartHightRdy()
OSStartHighRdy()是在多任务启动时被OSStart()调用的,μC/OS-II 做完所有的初始化工作之后,OSStart()就启动运行多任务,而OSStart()调用OSStartHighRdy()
函数运行多个就绪任务中优先级最高的任务。注意,堆栈指针总是储存在任务控制块的开头。
图3 中断发生时S12CPU寄存器入栈的顺序
OSStartHighRdy()将CPU 的堆栈指针SP 的值,改成优先级最高的就绪态任务的堆栈指针的值,然后将该任务的状态字由非运行态“FALSE”,改为运行态“TRUE”,然后
执行中断返回指令RTI 以开始运行这个任务。以下是详细代码:
void OSStartHighRdy(void)
{
OSTaskSwHook(); //调用钩子函数
asm{
ldx OSTCBCur // 加载OSTCBCur 的地址到 x
lds 0,x //把OSTCBStrPtr 载入堆栈指针 sp
ldaa OSRunning
inca // SRunning = TRUE
staa OSRunning
rti
}
}
3.2.4 任务级任务切换函数OSCtxSw()和中断级任务切换函数OSINTCtxSw()
任务级的切换是通过执行软中断指令来实现的。OSCtxSw()实际上就是软中断服务子程序,软中断服务子程序的向量地址指向OSCtxSw()。如果当前任务调用μC/OS-II
提供的功能函数,并使更高优先级任务进入了就绪状态,则μC/OS-II 就会借助上面提到的向量地址找到OSCtxSw()。在系统服务调用的最后,μC/OS-II 会调用任务调度函
数OSSched(),并由此推断出当前任务不再是需要运行的最重要的任务。
OSIntCtxSw()函数中的绝大多数代码同OS_TASK_SW()函数是一样的。而中断退出函数则是通过函数OSIntCtxSw()来从ISR 中执行切换功能,区别只是因为ISR 已经保存了
CPU 的寄存器,而不再需要在OSIntCtxSw()函数中保存CPU 的寄存器。以下只给出任务级任务切换函数OSCtxSw()的代码:
void OSCtxSw(void)
{
asm{
ldx OSTCBCur // 加载当前任务的堆栈指针
sts 0,x // 保存到当前任务的TCB 中
}
OSTaskSwHook(); //调用钩子函数
STCBCur = OSTCBHighRdy; // 改变任务的 OSTCBCur 和OSPrioCur
SPrioCur = OSPrioHighRdy;
asm{
ldx OSTCBCur // 得到新任务的堆栈指针
lds 0,x // 加载新任务的堆栈指针到 sp
rti
}
}
4 移植代码的测试
为了验证移植结果是否正确,对移植后μC/OS-II 代码进行了测试,这是移植中很重要的一个环节。首先对内核自身的运行情况进行了测试,待内核自身的运行正常工作
后,又创建三个任务:任务1 通过PORTA 口点亮LED 灯,该任务每秒运行一次;任务2和任务3 都通过串输出字符串,这两个任务都是每2 秒运行一次,并通过信号量来实现
互斥,以使得每个任务每次运行时均可完成所有字符的输出。实验测试证明在μC/OS-II管理与调度下,使得这三个任务都能正确、可靠地相继运行。
5 小结
通过μC/OS-II在MC9S12DG128上的移植,加深了对μC/OS-II内核工作原理和任务调度实现方法的理解,掌握了μC/OS-II移植的一般方法,测试结果表明移植代码可以稳定可靠的运行,实现了多任务的管理和调度。μC/OS-II实时操作系统的引入,不但可以提高系统的实时性、可靠性和稳定性,还提高了应用软件的可移植性,降低了开发人员的工作量。