现在开始仔细分析,整个点灯大法是如何实现的。
一、硬件初始化。
在我这里是指对GPIO进行初始化
LED_GPIO_Config(); //LED端口初始化
具体的配置就不多说了。
二、操作系统的初始化
OSInit();
接着我们看看这里面具体做了什么:
void OSInit (void)
{
OSInitHookBegin(); /* Call port specific initialization code */
OS_InitMisc(); /* Initialize miscellaneous variables */
OS_InitRdyList(); /* Initialize the Ready List */
OS_InitTCBList(); /* Initialize the free list of OS_TCBs */
OS_InitEventList(); /* Initialize the free list of OS_EVENTs */
OS_InitTaskIdle(); /* Create the Idle Task */
OS_InitTaskStat(); /* Create the Statistic Task */
OSInitHookEnd(); /* Call port specific init. code */
}
上面是去掉一些宏开关的简化代码:
1、首先是hook函数,下面结合官方文档以及我自己的理解,谈谈这个hook函数
本人理解,对于我们初学者来说hook函数是不需要管的,直接把宏开关关掉,因为操作系统的功能扩充
我们暂时还用不到,而这个hook函数就是实现对操作系统功能的扩充,之所以加上了这个hook函数,就是为了
防止我们直接修改源代码。从而对源代码产生破坏!
也就是说我们可以直接把这两个hook函数注释掉,自己动手试试吧,我试过了!
2、接着是这个 OS_InitMisc(); 看注释就是初始化各种各样的变量。我们可以进去看看,代码如下:
static void OS_InitMisc (void)
{
#if OS_TIME_GET_SET_EN > 0
OSTime = 0L; /* Clear the 32-bit system clock */
#endif
OSIntNesting = 0; /* Clear the interrupt nesting counter */
OSLockNesting = 0; /* Clear the scheduling lock counter */
OSTaskCtr = 0; /* Clear the number of tasks */
OSRunning = OS_FALSE; /* Indicate that multitasking not started */
OSCtxSwCtr = 0; /* Clear the context switch counter */
OSIdleCtr = 0L; /* Clear the 32-bit idle counter */
#if OS_TASK_STAT_EN > 0
OSIdleCtrRun = 0L;
OSIdleCtrMax = 0L;
OSStatRdy = OS_FALSE; /* Statistic task is not ready */
#endif
}
从上面的代码中我们可以获得这样的信息:
这几行代码完成的就是对系统定时器初值、中断计数器、当前任务数量等进行清零,没有别的意思了。
3、OS_InitRdyList这个函数看名字意思是初始化就绪态列表
那么就序列表指的就是任务处于三态中的就绪态的任务,关于三态的详细解释可以百度一下!
那么还是一样跟踪进去看看代码里面到底做了些什么、
static void OS_InitRdyList (void)
{
INT8U i;
#if OS_LOWEST_PRIO <= 63
INT8U *prdytbl;
#else
INT16U *prdytbl;
#endif
OSRdyGrp = 0; /* Clear the ready list */
prdytbl = &OSRdyTbl[0];
for (i = 0; i < OS_RDY_TBL_SIZE; i++) {
*prdytbl++ = 0;
}
OSPrioCur = 0;
OSPrioHighRdy = 0;
OSTCBHighRdy = (OS_TCB *)0;
OSTCBCur = (OS_TCB *)0;
}
还是一样从代码来看我们能获取的信息如下
将当前任务的优先级、就绪态任务最高优先级,控制块等都清零。
4、接下来看一个比较重要的函数吧OS_InitTCBList
static void OS_InitTCBList (void)
{
INT8U i;
OS_TCB *ptcb1;
OS_TCB *ptcb2;
OS_MemClr((INT8U *)&OSTCBTbl[0], sizeof(OSTCBTbl)); /* Clear all the TCBs */
OS_MemClr((INT8U *)&OSTCBPrioTbl[0], sizeof(OSTCBPrioTbl)); /* Clear the priority table */
ptcb1 = &OSTCBTbl[0];
ptcb2 = &OSTCBTbl[1];
for (i = 0; i < (OS_MAX_TASKS + OS_N_SYS_TASKS - 1); i++) { /* Init. list of free TCBs */
ptcb1->OSTCBNext = ptcb2;
#if OS_TASK_NAME_SIZE > 1
ptcb1->OSTCBTaskName[0] = '?'; /* Unknown name */
ptcb1->OSTCBTaskName[1] = OS_ASCII_NUL;
#endif
ptcb1++;
ptcb2++;
}
ptcb1->OSTCBNext = (OS_TCB *)0; /* Last OS_TCB */
#if OS_TASK_NAME_SIZE > 1
ptcb1->OSTCBTaskName[0] = '?'; /* Unknown name */
ptcb1->OSTCBTaskName[1] = OS_ASCII_NUL;
#endif
OSTCBList = (OS_TCB *)0; /* TCB lists initializations */
OSTCBFreeList = &OSTCBTbl[0];
}
这里面的代码涉及到一些比较复杂的数据结构,暂时不详述,找个时间专门研究一下,反正也就是完成了一些链表的初始化之类的工作。
5、OS_InitEventList这个函数也同第4步是类似的
到这里,大概的了解了初始化里面都做了些什么,接下来就是这个
三、任务创建代码
OSTaskCreate(Task_LED,(void *)0, &startup_task_stk[STARTUP_TASK_STK_SIZE-1], STARTUP_TASK_PRIO);
好好的来分析下,任务创建是如何实现的
1、函数原型的分析
INT8U OSTaskCreate (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT8U prio)
第一个参数是一个:将要创建的任务名字。形参为指针的函数指针,所以当我们赋值的时候就可以直接给他赋值一个函数名了。
第二个参数就是:任务传递的参数。一个void类型的指针,赋值可以是空
第三个参数:为任务分配的堆栈空间。传递的是一个堆栈的地址,所以我们预先要给这个堆栈分配一个地址,然后将地址传递进去
第四个参数:就是我们创建的任务的优先级了。
OK函数原型说明完毕
2、跟踪这个创建任务函数进去,看看做了些什么呢?
INT8U OSTaskCreate (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT8U prio)
{
OS_STK *psp;
INT8U err;
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0
if (prio > OS_LOWEST_PRIO) { /* Make sure priority is within allowable range */
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting > 0) { /* Make sure we don't create the task from within an ISR */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_CREATE_ISR);
}
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority */
OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ... */
/* ... the same thing until task is created. */
OS_EXIT_CRITICAL();
psp = OSTaskStkInit(task, p_arg, ptos, 0); /* Initialize the task's stack */
err = OS_TCBInit(prio, psp, (OS_STK *)0, 0, 0, (void *)0, 0);
if (err == OS_ERR_NONE) {
if (OSRunning == OS_TRUE) { /* Find highest priority task if multitasking has started */
OS_Sched();
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others */
OS_EXIT_CRITICAL();
}
return (err);
}
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO_EXIST);
}
可以得到做了一下事情
1.将状态寄存器清零,判断优先级是否合法。
2.OS_ENTER_CRITICAL();表示告诉CPU下面这段代码是不能被中断的,也就是说进入临界区后,在这个函数里面是把中断关掉了的。这个在之前的那篇博文有提到的。
3.接下来的代码就是确保我们规定的优先级上面是没有任务建立的,如果没有建立这个任务的话,那么我们给他一个建立任务的标记
4.
psp = OSTaskStkInit(task, p_arg, ptos, 0); /* Initialize the task's stack */
err = OS_TCBInit(prio, psp, (OS_STK *)0, 0, 0, (void *)0, 0);
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
OS_STK *stk;
(void)opt; /* 'opt' is not used, prevent warning */
stk = ptos; /* Load stack pointer */
/* Registers stacked as if auto-saved on exception */
*(stk) = (INT32U)0x01000000L; /* xPSR */
*(--stk) = (INT32U)task; /* Entry Point */
*(--stk) = (INT32U)0xFFFFFFFEL; /* R14 (LR) (init value will cause fault if ever used)*/
*(--stk) = (INT32U)0x12121212L; /* R12 */
*(--stk) = (INT32U)0x03030303L; /* R3 */
*(--stk) = (INT32U)0x02020202L; /* R2 */
*(--stk) = (INT32U)0x01010101L; /* R1 */
*(--stk) = (INT32U)p_arg; /* R0 : argument */
/* Remaining registers saved on process stack */
*(--stk) = (INT32U)0x11111111L; /* R11 */
*(--stk) = (INT32U)0x10101010L; /* R10 */
*(--stk) = (INT32U)0x09090909L; /* R9 */
*(--stk) = (INT32U)0x08080808L; /* R8 */
*(--stk) = (INT32U)0x07070707L; /* R7 */
*(--stk) = (INT32U)0x06060606L; /* R6 */
*(--stk) = (INT32U)0x05050505L; /* R5 */
*(--stk) = (INT32U)0x04040404L; /* R4 */
return (stk);
}
a 这是一个堆栈初始化的函数,模拟的就是入栈操作。
b 栈的增长方向是向下的,返回的就是栈顶的地址
c 各个寄存器入栈的顺序是确定了的,因为我们上面的代码就是模拟R0-R12 +spSR 这些寄存器入栈,这是根据权威手册上面来模拟的
具体的参见权威手册
d 这个函数是不需要返回值的,所以LR(R14)是一个非法值
e xPSR状态寄存器应该设为线程模式,T位置1
接着还有一个重要的函数是初始化任务控制块的函数,它把之前获得的栈顶指针等参数传进去了,不再详述。
一旦OSTaskStkInit()函数完成了建立堆栈的任务,OSTaskCreate()就调用OSTCBInit(),从空闲的OS_TCB池中获得并初始化一个OS_TCB
如果OS_TCB池中有空闲的OS_TCB,它就被初始化。注意一旦OS_TCB被分配,该任务的创建者就已经完全拥有它了,即使这时内核又创建
了其它的任务,这些新任务也不可能对已分配的OS_TCB作任何操作,所以OSTCBInit()在这时就可以允许中断,并继续初始化OS_TCB的数据单元。
到这里任务就创建完成了,那么接下来就是让我们的任务开始运行了。
四、OSStart代码分析
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 */
}
}
注释说的很清楚,当我们没有任务在运行的时候就会选择一个高优先级的任务加以执行,就实现了单任务的系统,接下来就是看如何实现第一任务系统了。