一、ucos中任务切换
一个内核要做的事情就是保持和回复寄存器,而ucos中保持与回复寄存器是在PendSV中进行的,也就是任务切换的过程在这里进行。
启用ucos前,首先要初始化相关寄存器:
切换过程总体分为四步:
1.保存当前任务的状态寄存器和从计时器到当前任务堆栈
2.将CPU当前堆栈指针(PSP)的当前值保存到将要停止运行的任务控制块OS_TCB中,即OSTCBCurPtr = SP
3.将新任务的OS_TCB中保存的该任务的堆栈指针载入CPU的PSP中,该指针是上一次进行任务切换保存的,指向的是当时任务堆栈的栈顶,即SP = OSTCBHighRdyPtr->StkPtr
4.从新的堆栈中回复CPU寄存器的值
PendSV_Handler
CPSID I ; 关中断
MRS R0, PSP ; PSP 如果=0,则说明是第一次进行任务切换,因为OSStartHighRdy中已经将PSP初始化为0了,不用在进行入栈操作
;任务刚创建的时候,任务堆栈已经按照ISR入栈的顺序将寄存器推入堆栈了
CBZ R0, PendSVHandler_nosave ;跳转到PendSVHandler_nosave
SUBS R0, R0, #0x20 ;能运行到此处,说明不是第一次进行任务切换(任务切换),因为只有在OS启动的时候PSP才有可能为0,
;如调用了OSStartHighRdy,而任务在初始化的时候已经进行过堆栈的处理了,这里就不用再保存寄存器了。
STM R0, {R4-R11} ; 保存R4-R11
; OSTCBCurPtr->OSTCBStkPtr = SP
LDR R1, =OSTCBCurPtr ;R1 = & OSTCBCurPtr
LDR R1, [R1] ; R1 = *R1,即R1 = OSTCBCurPtr
STR R0, [R1] ; *R1 = R0 其中OSTCBCurPtr首地址为StkPtr,即 OSTCBCurPtr->OSTCBStkPtr = SP (R0即PSP)
PendSVHandler_nosave
PUSH {R14}
LDR R0, =OSTaskSwHook
BLX R0
POP {R14}
; 实现OSPrioCur = OSPrioHighRdy;
LDR R0, =OSPrioCur ;R0 = &OSPrioCur
LDR R1, =OSPrioHighRdy ;R1 = &OSPrioHighRdy
LDRB R2, [R1] ;R2 = *R1 即 R2 = OSPrioHighRdy
STRB R2, [R0] ;*R0 = R2
; 实现OSTCBCurPtr = OSTCBHighRdyPtr
LDR R0, =OSTCBCurPtr ;R0 = &OSTCBCurPtr
LDR R1, =OSTCBHighRdyPtr ;R1 = &OSTCBHighRdyPtr
LDR R2, [R1] ;R2 = *R1 即 R2 = OSTCBHighRdyPtr
STR R2, [R0] ;*R0 = R2 即 OSTCBCurPtr = OSTCBHighRdyPtr
;将高优先级任务控制块及任务赋值给系统任务控制块 OSPrioHighRdy与 OSTCBHighRdyPtr在进行任务切换前已经更行,例如OSIntExit() OSSched()
LDR R0, [R2] ;R0 = *R2 即 R0 = OSTCBHighRdyPtr->OSTCBStkPtr
LDM R0, {R4-R11} ;恢复R4-R11寄存器, 由于OSTCBHighRdyPtr首地址表示StkPtr(指向任务当前的栈顶),即将R4-R11保存到该位置
ADDS R0, R0, #0x20
MSR PSP, R0 ; PSP = R0
ORR LR, LR, #0x04 ; 赋值,确保LR位2是1,返回后使用PSP
CPSIE I ;开中断
BX LR ;中断返回
END
二、简化的任务切换
由之前的介绍可知,ucos要完成4个步骤,而我自己做任务切换也大致可分为4个过程:
1.任务的创建(初始化)
任务的创建(初始化)其实就是模拟一个中断,并按照ISR将寄存器入栈的顺序把寄存器推入堆栈中,
void Task_Create(void)
{
U16 i;
//初始化堆栈
for(i=0; i<TAST_SIZE; i++)
{
Task_Stk_1[i] = 0; // 初始化堆栈
Task_Stk_2[i] = 0;
}
StkPtr_1 = &Task_Stk_1[TAST_SIZE];
*--StkPtr_1 = (U32)0x01000000u; /* xPSR */
*--StkPtr_1 = (U32)*Task1; /* Entry Point */
*--StkPtr_1 = (U32)0x14141414u; /* R14 (LR) */
*--StkPtr_1 = (U32)0x12121212u; /* R12 */
*--StkPtr_1 = (U32)0x03030303u; /* R3 */
*--StkPtr_1 = (U32)0x02020202u; /* R2 */
*--StkPtr_1 = (U32)0x10101010u; /* R1 */
*--StkPtr_1 = (U32)0x00000000u; /* R0 : */
*--StkPtr_1 = (U32)0x11111111u; /* R11 */
*--StkPtr_1 = (U32)0x10101010u; /* R10 */
*--StkPtr_1 = (U32)0x09090909u; /* R9 */
*--StkPtr_1 = (U32)0x08080808u; /* R8 */
*--StkPtr_1 = (U32)0x07070707u; /* R7 */
*--StkPtr_1 = (U32)0x06060606u; /* R6 */
*--StkPtr_1 = (U32)0x05050505u; /* R5 */
*--StkPtr_1 = (U32)0x04040404u; /* R4 */
StkPtr_2 = &Task_Stk_2[TAST_SIZE];
......
}
2.设置PendSV优先级(为最低)
void PendSVPriSet(void)
{
#define NVIT_SYSPRI *((U32 *)0xE000ED22) //优先级寄存器
#define NVIT_PENDSV_PRI 0xff //设置PendSV优先级为0xFF
NVIT_SYSPRI = NVIT_PENDSV_PRI;
}
3.初始化PSP
__ASM void InitPSP(void)
{
MOVS R0,#0
MSR PSP,R0
}
4.编写PendSV程序
PendSV_Handler
CPSID I ; 关中断
MRS R0, PSP ; PSP 如果=0,则说明是第一次进行任务切换,不用在进行入栈操作
CBZ R0, PendSVHandler_nosave ;跳转到PendSVHandler_nosave
SUBS R0, R0, #0x20
STM R0, {R4-R11} ; 保存R4-R11
LDR R1, =StkPtr_Cur ; R1 = & StkPtr_Cur ,
STR R0, [R1] ; *R1 = R0, *StkPtr_Cur = PSP
PendSVHandler_nosave
LDR R0, =StkPtr_Next ;R0 = &StkPtr_Next
LDR R0, [R0] ;R0=*R0
LDM R0, {R4-R11} ;恢复R4-R11寄存器, 由于OSTCBHighRdyPtr首地址表示StkPtr(指向任务当前的栈顶),即将R4-R11保存到该位置
ADDS R0, R0, #0x20
MSR PSP, R0 ; PSP = R0
ORR LR, LR, #0x04 ; 赋值,确保LR位2是1,返回后使用PSP
CPSIE I ;开中断
BX LR ;中断返回
END
5.开始任务切换
void Tast_SW(void)
{
#define NVIT_CTRL *((U32 *)0xE000ED04) //中断控制寄存器
#define NVIC_PENDSVSET 0x10000000 //向中断控制寄存器28位写1,出发PendSV中断
NVIT_CTRL = NVIC_PENDSVSET;
}
至此,任务已经可以正常运行。具体程序在附件中