μC/OS-II大部分代码是用移植性很高的ANSI C语言编写的,只包含一小部分汇编语言代码,因此可以很方便地将它移植到各种不同构架的微处理器上。移植μC/OS-II,所用处理器和该处理器所用的C语言编译器必须满足以下条件:
1.处理器的C编译器能产生可重入代码;
2.处理器支持中断,并且能产生定时中断;
3.用C语言就可以开/关中断;
4.处理器能支持一定数量的数据存储硬件堆栈;
5.处理器有将堆栈指针以及其他CPU寄存器的内容读出、并存储到堆栈或内存中去的指令。
首先,由于处理器字长不同为了方便,我们通常要修改下字长定义,s3c2410是32位处理器所以定义如下:
#define U32 unsigned int
#define U16 unsigned short
#define S32 int
#define S16 short int
#define U8 unsigned char
#define S8 signed char
下面我对以上的移植要求一次进行移植分析:
1.修改ucos源代码中OS_CPU.S中的代码如下,主要是用开关中断实现代码的可重入要求:
EXPORT OSCPUSaveSR
OSCPUSaveSR
mrs r0,CPSR
orr r1,r0,#NOINT ;屏蔽irq, fiq
msr CPSR_c,r1
mov pc,lr ;跳回
EXPORT OSCPURestoreSR
OSCPURestoreSR
msr CPSR_c,r0
mov pc,lr
2. s<?XML:NAMESPACE PREFIX = ST1 />2c2410支持中断,选择一个定时器作为时钟滴答,来对任务做时间片的调度,有关s3c2410的中断介绍可以参考我得另一片文章。
产生中断后,代码自动跳转到0x0的中断向量表,然后在从中断向量表中跳到下面的程序,进行中断号的分析,然后利用ucos的中断任务切换到中断服务子程序中。
UCOS_IRQHandler
stmfd sp!,{r0-r3,r12,lr} ;保存现场
bl OSIntEnter ;跳到下面程序,实际上是中断嵌套
bl C_IRQHandler ;计算出中断号
bl OSIntExit ;中断减1 ,切换最高优先级任务
ldr r0,=OSIntCtxSwFlag ; 判断是否需要中断切换
ldr r1,[r0]
cmp r1,#1
beq _IntCtxSw ;调用中断任务切换函数,后面分析
ldmfd sp!,{r0-r3,r12,lr} ;恢复现场
subs pc,lr,#4
void OSIntEnter (void)
{
if (OSRunning == OS_TRUE) {
if (OSIntNesting < 255u) {
OSIntNesting++; /* 中断记数+1 */
}
}
}
void OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr = 0;
#endif
if (OSRunning == OS_TRUE) {
OS_ENTER_CRITICAL();
if (OSIntNesting > 0) { 有中断,中断数-1
OSIntNesting--;
}
if (OSIntNesting == 0) { 如果没有中断的话
if (OSLockNesting == 0) {
OS_SchedNew();
if (OSPrioHighRdy != OSPrioCur) {
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
OSTCBHighRdy->OSTCBCtxSwCtr++;
#endif
OSCtxSwCtr++; 将最高优先级的任务调入
OSIntCtxSw();
}
}
}
OS_EXIT_CRITICAL();
}
}
void C_IRQHandler(void)
{
U32 wTemp;
wTemp = rINTOFFSET<<2; 根据中断偏移量判断是什么中断
((void(*)(void))(*((U32 *)(aISR_EINT0+wTemp))))(); 关键!跳到相应中断服务程序
}
其中的UCOS_IRQHandler就是实现中断跳转的关键代码,C语言中将irq中断函数的入口
都指向它实现中断跳转到C中对应的服务函数,代码如下:
pISR_IRQ = (U32)UCOS_IRQHandler; 中断入口挂接
然后初始化一个定时器作为时钟滴答,一般选择100ms左右,产生一次中断溢出,进行
一次任务调度。
3.有关堆栈的设置。因为ucos进行任务切换时,就是相当于模拟的做中断操作。所以在切换任务之前必须先保存任务的现场:包括R0~R12,sp,lr,pc,cprs,sprs。然后用sp从旧的任务堆栈指向新的任务的堆栈。(这个堆栈在任务创建的时候已经分配好了)。
在初始化任务堆栈之前,首先要设置两个参数,在CONFIG文件中: 大小端编译模式,堆栈得生长方向。代码如下:
#define OS_STK_GROWTH 1 ;堆栈向上生长
然后在任务创建时的堆栈初始化函数,在OS_CPU_C.c文件中:
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
{
OS_STK *stk;
opt = opt; /* 'opt' is not used, prevent warning */
stk = ptos; /* Load stack pointer */
*(stk) = (OS_STK)task; /* Entry Point */
*(--stk) = (INT32U)0; /* lr */
*(--stk) = (INT32U)0; /* r12 */
*(--stk) = (INT32U)0; /* r11 */
*(--stk) = (INT32U)0; /* r10 */
*(--stk) = (INT32U)0; /* r9 */
*(--stk) = (INT32U)0; /* r8 */
*(--stk) = (INT32U)0; /* r7 */
*(--stk) = (INT32U)0; /* r6 */
*(--stk) = (INT32U)0; /* r5 */
*(--stk) = (INT32U)0; /* r4 */
*(--stk) = (INT32U)0; /* r3 */
*(--stk) = (INT32U)0; /* r2 */
*(--stk) = (INT32U)0; /* r1 */
*(--stk) = (INT32U)pdata; /* r0 : argument */
*(--stk) = (INT32U)(SVCMODE|0x0); /* PSR */
*(--stk) = (INT32U)(SVCMODE|0x0); /* SPSR */
return (stk);
}
4.有关任务调度方面的移植:
首先就是当运行OSSTART()后,系统要进入优先级最高的任务中,即第一个任务,这短代码的移植如下:
OSStartHighRdy
bl OSTaskSwHook ;
ldr r4,=OSRunning ; 运行多任务
mov r5,#1
strb r5,[r4]
ldr r4,=OSTCBHighRdy ; 得到最高优先级任务的TCB地址
ldr r4,[r4] ; 得到其堆栈地址
ldr sp,[r4] ; 切换到新任务的堆栈
ldmfd sp!,{r4} ; 把新任务堆栈的内容环境载进当片CPU
msr SPSR_cxsf,r4
ldmfd sp!,{r4} ; pop new task's psr
msr CPSR_cxsf,r4
ldmfd sp!,{r0-r12,lr,pc} ; pop new task's r0-r12,lr & pc
其次时对任务调度函数的移植,主要是在时间片到时,读取就绪表中优先级最高的任务,并切换到该任务的环境中:
OSCtxSw
;保存要被切换出去的任务环境
stmfd sp!,{lr} ; push pc (lr should be pushed in place of PC)
stmfd sp!,{r0-r12,lr} ; push lr & register file
mrs r4,cpsr
stmfd sp!,{r4} ; push current psr
mrs r4,spsr
stmfd sp!,{r4} ; push current spsr
;把就绪表中优先级 最高的任务载入到当前任务指针指向
; OSPrioCur = OSPrioHighRdy
ldr r4,=OSPrioCur
ldr r5,=OSPrioHighRdy
ldrb r6,[r5]
strb r6,[r4]
;得到当前任务的TCB地址
; Get current task TCB address
ldr r4,=OSTCBCur
ldr r5,[r4]
str sp,[r5] ; store sp in preempted tasks's TCB
bl OSTaskSwHook ; call Task Switch Hook
;得到最高优先级任务的地址,并把堆栈指针指向它的堆栈地址
; Get highest priority task TCB address
ldr r6,=OSTCBHighRdy
ldr r6,[r6]
ldr sp,[r6] ; get new task's stack pointer
;调出新任务的环境实现切换
; OSTCBCur = OSTCBHighRdy
str r6,[r4] ; set new current task TCB address
ldmfd sp!,{r4} ; pop new task's spsr
msr SPSR_cxsf,r4
ldmfd sp!,{r4} ; pop new task's psr
msr CPSR_cxsf,r4
ldmfd sp!,{r0-r12,lr,pc} ; pop new task's r0-r12,lr & pc
最后是中断级的任务切换,这种情况发生在,在中断服务程序中报告了一个或多个事件的发生,而这些事件的发生使一些更高优先级的任务进入就绪状态。因此在中断退出时不应该返回到被中断的任务,而应该返回到就绪态中优先级最高的任务。函数OSIntCtxSw()的主要功能是完成中断级的任务切换。与任务级的任务切换不同的时,在进入中断后,所有寄存器的值都被入栈了。因此在进入任务级切换时寄存器的入栈操作应该再有。另外,在中断服务程序中调用了函数OSIntExit()和OSIntCtxSw(),因此应该调整当前任务的堆栈指针,让任务下次再被执行时返回中断服务程序而不是返回函数OSIntExit()和OSIntCtxSw()。剩下的任务就是进行任务切换了,这些代码同函数OSCtxSw()的大部分代码都相同。
_IntCtxSw
;对堆栈指针的调整
mov r1,#0
str r1,[r0]
ldmfd sp!,{r0-r3,r12,lr}
stmfd sp!,{r0-r3}
mov r1,sp
add sp,sp,#16
sub r2,lr,#4
mrs r3,spsr
orr r0,r3,#NOINT
msr spsr_c,r0
ldr r0,=.+8
movs pc,r0
;以下代码和正常的任务切换过程完全一样
stmfd sp!,{r2} ; push old task's pc
stmfd sp!,{r4-r12,lr} ; push old task's lr,r12-r4
mov r4,r1 ; Special optimised code below
mov r5,r3
ldmfd r4!,{r0-r3}
stmfd sp!,{r0-r3} ; push old task's r3-r0
stmfd sp!,{r5} ; push old task's psr
mrs r4,spsr
stmfd sp!,{r4} ; push old task's spsr
; OSPrioCur = OSPrioHighRdy
ldr r4,=OSPrioCur
ldr r5,=OSPrioHighRdy
ldrb r5,[r5]
strb r5,[r4]
; Get current task TCB address
ldr r4,=OSTCBCur
ldr r5,[r4]
str sp,[r5] ; store sp in preempted tasks's TCB
bl OSTaskSwHook ; call Task Switch Hook
; Get highest priority task TCB address
ldr r6,=OSTCBHighRdy
ldr r6,[r6]
ldr sp,[r6] ; get new task's stack pointer
; OSTCBCur = OSTCBHighRdy
str r6,[r4] ; set new current task TCB address
ldmfd sp!,{r4} ; pop new task's spsr
msr SPSR_cxsf,r4
ldmfd sp!,{r4} ; pop new task's psr
msr CPSR_cxsf,r4
ldmfd sp!,{r0-r12,lr,pc} ; pop new task's r0-r12,lr & pc
这样我们就可以实现ucos在s3c2410的移植了。注意如果要是在ram中仿真时,必须把中断向量表预先烧录到0x0处才能正常实现中断的跳转。
文章评论(0条评论)
登录后参与讨论