uCOS-II学习-PART 1
uC/OS-II的启动
/**
* 多任务的启动时用户通过调用OSStart()实现的
* 在启动之前,用户至少要建立一个应用任务
*/
int main(void)
{
OSInit(); /* 初始化uC/OS-II */
OSTaskCreate(task,(void *)0,(OS_STK *)&TASK_STK[STK_SIZE-1],TASK_PRIO );
OSStart(); /* 开始任务调度 */
}
1、uC/OS初始化 OSInit
void OSInit(void)
{
OSInitHookBegin(); /* Call port specific initialization code */
OS_InitMisc( ); //完成一般变量初始化
OS_InitRdyList( );//就绪列表初始化
OS_InitTCBList( );//空闲任务链表OSTCBFreeList建立
OS_InitEventList( );//事件空闲链表OSEventFreeList建立。
OS_FlagInit( ); //其它相关功能参数初始化。
OS_MemInit( );
OS_QInit( );
OS_InitTaskIdle( );//创建空闲任务OS_TaskIdle.
OS_InitTaskStat( );
OSInitHookEnd( ); /* Call port specific init. code */
}
①相关变量初始化OS_InitMisc
- OSIntNesting、OSLockNesting、OSTaskCtr分别是中断嵌套的层数、调取器上锁、任务数,均清零
- OSRunning,系统任务的运行状态为 OS_FALSE
- OSCtxSwCtr、OSIdleCtr分别是上下文切换计数和空闲任务计数,均清零
②就绪列表初始化OS_InitRdyList
- OSRdyGrp、OSRdyTbl[]分别是就绪任务组,就绪任务表清零
- OSPrioCur、OSPrioHighRdy当前运行任务优先级,最高就绪任务优先级清零
- OSTCBCur、OSTCBHighRdy当前运行任务控制块,最高就绪任务控制块清零
③任务链表初始化OS_InitTCBList
OSTCBTbl[]任务控制块数组清零,全部变成空闲任务控制块。
- 然后
&OSTCBTbl[i]->OSTCBNext = &OSTCBTbl[i+1]
组成单向的空闲任务控制块链表, OSTCBFreeList = &OSTCBTbl[0];
OSTCBFreeList 为空闲任务控制块链表表头。OSTCBList = (OS_TCB *)0;
OSTCBList 为双向任务控制块链表,创建任务的时候会将这个OS_TCB类型的指针,指向新创建任务的任务控制块,
OSTCBPrioTbl[]对应优先级的任务控制块数组清零。
- 创建任务时,若该任务优先级为prio,创建任务的任务控制块指针为ptcb,则OSTCBPrioTbl[prio] = ptcb。
2、任务创建 OSTaskCreate
可用过一下两个函数之一建立任务
- OSTaskCreate();
- OSTaskCreateExt();
OSTaskCreate的流程为,①初始化任务堆栈、②初始化任务控制块、③如果系统已启动,进行任务调度
/**
* 代码有删减,去除宏定义等,便于梳理工作流程
* OSTaskCreate中,①初始化任务堆栈、②初始化任务控制块、③如果系统已启动,进行任务调度
*/
INT8U OSTaskCreate (void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT8U prio)
{
OS_ENTER_CRITICAL();//系统进入“临界区”,即关中断
psp = OSTaskStkInit(task, p_arg, ptos, 0u); /* Initialize the task's stack */
err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);
if (OSRunning == OS_TRUE) { /* Find highest priority task if multitasking has started */
OS_Sched();
}
OS_EXIT_CRITICAL();//系统退出“临界区”,即开中断
}
①初始化任务堆栈 OSTaskStkInit
1. 入栈顺序
根据Cortex-M3 权威指南
中断/异常触发时,硬件自动依次把xPSR, PC, LR, R12以及R3‐R0压入适当的堆栈中
R11-R4则需要手动压入堆栈中
Cortex-M3先把PC与xPSR的值保存,就可以更早地启动服务例程指令的预取——因为这需要修改PC;同时,也做到了在早期就可以更新xPSR中IPSR位段的值。(时间线不用考虑,只需要了解CM3寄存器组入栈的顺序即可,xPSR, PC, LR, R12以及R3‐R0)
2. M3寄存器组
- R0-R12:通用寄存器
R0‐R12 都是 32 位通用寄存器,用于数据操作。 - Banked R13: 两个堆栈指针
Cortex‐M3 拥有两个堆栈指针,然而它们是 banked,因此任一时刻只能使用其中的一个。- 主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包
括中断服务例程) - 进程堆栈指针(PSP):由用户的应用程序代码使用。
- 主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包
- R14:连接寄存器
当呼叫一个子程序时,由 R14 存储返回地址 - R15:程序计数寄存器
指向当前的程序地址。如果修改它的值,就能改变程序的执行流
3. OSTaskStkInit()
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
OS_STK *p_stk;
p_stk = ptos + 1u; /* Load stack pointer */
/* Align the stack to 8-bytes. */
p_stk = (OS_STK *)((OS_STK)(p_stk) & 0xFFFFFFF8u);
/* Registers stacked as if auto-saved on exception */
*(--p_stk) = (OS_STK)0x01000000uL; /* xPSR */
*(--p_stk) = (OS_STK)task; /* Entry Point */
*(--p_stk) = (OS_STK)OS_TaskReturn; /* R14 (LR) */
*(--p_stk) = (OS_STK)0x12121212uL; /* R12 */
*(--p_stk) = (OS_STK)0x03030303uL; /* R3 */
*(--p_stk) = (OS_STK)0x02020202uL; /* R2 */
*(--p_stk) = (OS_STK)0x01010101uL; /* R1 */
*(--p_stk) = (OS_STK)p_arg; /* R0 : argument */
/* Remaining registers saved on process stack */
*(--p_stk) = (OS_STK)0x11111111uL; /* R11 */
*(--p_stk) = (OS_STK)0x10101010uL; /* R10 */
*(--p_stk) = (OS_STK)0x09090909uL; /* R9 */
*(--p_stk) = (OS_STK)0x08080808uL; /* R8 */
*(--p_stk) = (OS_STK)0x07070707uL; /* R7 */
*(--p_stk) = (OS_STK)0x06060606uL; /* R6 */
*(--p_stk) = (OS_STK)0x05050505uL; /* R5 */
*(--p_stk) = (OS_STK)0x04040404uL; /* R4 */
return (p_stk);
}
②初始化任务控制块TCBInit
找一个空闲任务控制块,写入传入的参数,然后将任务块加入就绪任务控制块链表
INT8U OS_TCBInit (INT8U prio,
OS_STK *ptos,
OS_STK *pbos,
INT16U id,
INT32U stk_size,
void *pext,
INT16U opt)
{
OS_TCB *ptcb; //创建任务控制块指针
OS_ENTER_CRITICAL();
/**
* 关于OSTCBFreeList,可以看一下OS_InitTCBList()函数,该函数在OSInit()中被调用。
* 在OS_InitTCBList()函数中,将空闲任务控制块数组OSTCBTbl[]先清0,
* 然后将OSTCBTbl[]中的前一个空闲任务块元素的OSTCBNext指向下一个空闲任务块元素
* OSTCBFreeList = &OSTCBTbl[0];
*/
/* 这里就是将找一个空闲任务控制块 */
ptcb = OSTCBFreeList; /* Get a free TCB from the free TCB list */
if (ptcb != (OS_TCB *)0) {
/* OSTCBFreeList重新指向下一个空闲任务块 */
OSTCBFreeList = ptcb->OSTCBNext; /* Update pointer to free TCB list */
OS_EXIT_CRITICAL();
/* 将当前任务堆栈的栈顶指针赋值给任务控制块 */
ptcb->OSTCBStkPtr = ptos; /* Load Stack pointer in TCB */
/* 优先级赋值给任务控制块 */
ptcb->OSTCBPrio = prio; /* Load task priority into TCB */
/* 状态先不看 */
ptcb->OSTCBStat = OS_STAT_RDY; /* Task is ready to run */
ptcb->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
ptcb->OSTCBDly = 0u; /* Task is not delayed */
/* 这里都是为了保存优先级,为了能更快的找到任务优先级最高的已就绪任务 */
/* 放在“③任务调度”总结优先级问题 */
/* A- 这里任务优先级小于64 */
ptcb->OSTCBY = (INT8U)(prio >> 3u);
ptcb->OSTCBX = (INT8U)(prio & 0x07u);
/* B- Pre-compute BitX and BitY */
ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY);
ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);
/* OSTCBInitHook\OSTaskCreateHook,这俩不知道干嘛,好像用户自定义的东西,后面再看 */
OSTCBInitHook(ptcb); /* ????????? */
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = ptcb; /* 将赋值好的任务控制块放入对应优先级的任务控制块数组中 */
OS_EXIT_CRITICAL();
OSTaskCreateHook(ptcb); /* Call user defined hook ????????? */
OS_ENTER_CRITICAL();
/* OSTCBList指向的是上一个创建的任务的任务控制块 */
/* 将ptcb放入就绪任务控制块链表的头部 */
ptcb->OSTCBNext = OSTCBList; /* Link into TCB chain */
ptcb->OSTCBPrev = (OS_TCB *)0;
if (OSTCBList != (OS_TCB *)0) {
OSTCBList->OSTCBPrev = ptcb;
}
/* OSTCBList指向最新创建的任务控制块 */
OSTCBList = ptcb;
/* 这里为了能更快的找到任务优先级最高的已就绪任务 */
/* 将优先级分8组,每组八个优先级 */
/* 放在“③任务调度”总结优先级问题 */
OSRdyGrp |= ptcb->OSTCBBitY; /* Make task ready to run */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OSTaskCtr++; /* Increment the #tasks counter */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NO_MORE_TCB);
}
③任务调度OS_Sched/任务优先级
创建任务的时候,如果多任务已开始,则查找优先级最高的任务。
判断当前‘正在进行的任务’是不是比‘所有的已就绪任务’的优先级更高,
不是最高,则进行一次任务调度;
是最高,则跳过。
void OS_Sched (void)
{
OS_ENTER_CRITICAL();
/* 在已就绪的任务中,找到最高优先级OSPrioHighRdy */
OS_SchedNew();
/* 根据优先级OSPrioHighRdy,将OSTCBHighRdy指向该任务的控制块 */
/* OSTCBPrioTbl[prio] = ptcb;在任务创建的时候赋值的 */
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
/* 任务切换 */
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}
OS_EXIT_CRITICAL();
}
//============================================================================================
static void OS_SchedNew (void)
{
INT8U y;
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
}
//============================================================================================
#define OS_TASK_SW() OSCtxSw()
/**
* OSCtxSw 在 os_cpu_a.asm 中
* 啃一下Cortex-M3 权威指南,再来总结
*/
PUBLIC OSCtxSw
OSCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
保存就绪就绪优先级
OSTCBY= prio >> 3; // OSTCBY= 0x00
OSTCBX= prio & 0x07; // OSTCBX= 0x05
OSTCBBitY = 1uL << OSTCBY; // OSTCBBitY = 0x01
OSTCBBitX = 1uL << OSTCBX; // OSTCBBitX = 0x05
OSRdyGrp |= OSTCBBitY ; // OSRdyGrp = 0x01 (00000001)
OSRdyTbl[OSTCBY] |= OSTCBBitX; // OSRdyTbl[0] = 00100000
- 优先级小于64,prio bit5~bit0为有效位。
- 将高三位bit5 ~ bit3作为优先级的组别放入OSRdyGrp ,OSRdyGrp 是一个8位的无符号数,每一位bit代表一个组。
- 将低三位bit2 ~ bit0放入OSRdyTbl[OSTCBY] 。
- 优先级为5时,第一组有任务就绪,OSRdyGrp |= 0x01 ,OSRdyTbl[0]的bit5置1,OSRdyTbl[0] |= 00100000;
- 优先级为20时,第三组有任务就绪,OSRdyGrp |= 0x04 ,OSRdyTbl[2]的bit4置1,OSRdyTbl[0] |= 00010000;
查找优先级最高的就绪任务时
- OSUnMapTbl就是将0-255每个数据中最低位为1的位数一一列举出来
- 优先级不能为0,0x00中没有任何一位为1 OSUnMapTbl[0]无效
y = OSUnMapTbl[OSRdyGrp]; y 就是最小的就绪优先级的组数
x = OSUnMapTbl[OSRdyTbl[y]], x 是确认y组中最小的优先级。
OSPrioHighRdy = (y<<3) + x;
无需遍历,查表即可获得最高就绪优先级,速度MAX。
/*
*********************************************************************************************************
* PRIORITY RESOLUTION TABLE
*
* Note: Index into table is bit pattern to resolve highest priority
* Indexed value corresponds to highest priority bit position (i.e. 0..7)
*********************************************************************************************************
*/
INT8U const OSUnMapTbl[256] = {
0u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x00 to 0x0F */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x10 to 0x1F */
5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x20 to 0x2F */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x30 to 0x3F */
6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x40 to 0x4F */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x50 to 0x5F */
5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x60 to 0x6F */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x70 to 0x7F */
7u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x80 to 0x8F */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x90 to 0x9F */
5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xA0 to 0xAF */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xB0 to 0xBF */
6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xC0 to 0xCF */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xD0 to 0xDF */
5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xE0 to 0xEF */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u /* 0xF0 to 0xFF */
};
3、开始任务调度 OSStart
代码流程
- 找到优先级最高的任务OS_SchedNew
- 将当前正在运行的优先级和任务控制块,赋值为上一步找到的任务。
- 任务切换OSStartHighRdy
void OSStart (void)
{
if (OSRunning == OS_FALSE) {
OS_SchedNew(); /* Find highest priority's task priority number */
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run */
OSTCBCur = OSTCBHighRdy;
OSStartHighRdy(); /* Execute target specific code to start task */
}
}
OSStartHighRdy
;********************************************************************************************************
; START MULTITASKING
; void OSStartHighRdy(void)
;
; Note(s) : 1) This function triggers a PendSV exception (essentially, causes a context switch) to cause
; the first task to start.
;
; 2) OSStartHighRdy() MUST:
; a) Setup PendSV exception priority to lowest;
; b) Set initial PSP to 0, to tell context switcher this is first run;
; c) Set the main stack to OS_CPU_ExceptStkBase
; d) Set OSRunning to TRUE;
; e) Trigger PendSV exception;
; f) Enable interrupts (tasks will run with interrupts enabled).
;********************************************************************************************************
OSStartHighRdy
; 设置PendSV中断优先级
LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority
LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0]
; 初始化PSP=0
MOVS R0, #0 ; Set the PSP to 0 for initial context switch call
MSR PSP, R0
; 初始化MSP地址,MSP=OS_CPU_ExceptStkBase
LDR R0, =OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBase
LDR R1, [R0]
MSR MSP, R1
;OSRunning = 1 表示系统开始运行
LDR R0, =OSRunning ; OSRunning = TRUE
MOVS R1, #1
STRB R1, [R0]
;触发PendSV异常 (进入上下文切换)
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
; 打开中断
CPSIE I ; Enable interrupts at processor level
OSStartHang
B OSStartHang ; Should never get here