在使用UCOS时,其中心跳时钟是由Systick来提供的,因此要实现任务调度,那么首先得配置systick时钟:主要是根据时钟节拍数(配置文件中)和systick时钟源来配置systick的LOAD值(重装载值);同时开启systick中断;使能时钟开始工作。
由于上面开启了systick中断,那么就要编写中断服务函数(异常服务函数)SysTick_Handler()在该函数中,主要是做了一件事:如果当前OS在正常运行,那么进入中断,调用UCOS的时钟服务程序,最后退出中断,触发任务切换软中断。
这里UCOS的时钟服务函数里面做的事情:
a、遍历TCB任务链表,维护其中的延时时间参数和任务状态标志。
b、首先指向第一个TCB,遍历TCB,更改当前TCB中的延时参数,如果当前TCB中的延时时间已经到了,那么就将其TCB中的状态更改为就绪态,然后指向下一个TCB,直到指向空闲任务。
这里退出中断函数中做的事情:
a、首先中断嵌套层数变量递减
b、然后从就绪表中找出优先级最高的任务
c、最后触发PENDSV实现任务切换。
该代码中主要是定义了一些数据类型,这和编译器、处理器相关;
定义进入临界区的方法(开中断/关中断):和处理器有关。取决于处理器类型以及编译器的特性,
更改栈的增长方向;(大多数都是向下生长的)
定义任务切换宏(汇编实现):和处理器有关
这里主要涉及到的是任务堆栈初始化函数,其主要是在任务创建时被调用,初始化任务的堆栈结构。因此,堆栈看起来就像是刚刚发生过中断一样,所有寄存器都保存在堆栈中。
a、模拟带参数(pdata)的函数调用;
b、模拟ISR向量;
c、按照预先设计的寄存器值初始化堆栈结构;
d、返回栈顶指针给调用该函数的函数;
也就是模拟寄存器入栈过程。这里需要注意的是入栈顺序,硬件会自动把xPSR、PC、LR、R12、R0-R3寄存器入栈、剩下的R4-R11就需要我们手动入栈。
只有在特权级下才能访问这三个寄存器,同时CM3专门设置了一条CPS指令用于开关中断
开中断:OS_CPU_SR_Restore
将之前保存的状态写入到中断屏蔽寄存器(写0),返回
调度器运行第一个任务(就绪态最高优先级任务):OSStartHighRdy,由OSStart()调用,用来开启多任务
设置PendSV的优先级为NVIC_PENDSV_PRI(0xFFFF);
设置线程堆栈指向0,获取将要运行的任务的堆栈指针;
将OSRuning标志量置1;
触发PendSV中断(直接给中断控制寄存器写0x10000000);
开中断(给中断屏蔽寄存器写1);
任务级上下文切换: OSCtxSw
将R4、R5寄存器压栈;
触发PendSV中断(异常);
然后出栈R4、R5寄存器;
返回;
中断级上下文切换:OSIntCtxSw
将R4、R5寄存器压栈;
触发PendSV中断(异常);
然后出栈R4、R5寄存器;
返回;
任务级切换和中断级切换代码一样,都是触发一个pendsv异常,然后具体的切换过程是在PendSV中断服务函数中进行的,与任务级切换不同的是,中断级切换任务时,CPU的所有寄存器已经入栈,不需要手动对其进行入栈。
PendSV上下文切换:
a. 按照任务堆栈初始化函数一样的顺序将处理器中的寄存器内容保存到堆栈中(部分寄存器已经自动保存);
b. 将堆栈指针(栈顶指针)保存到TCB中;
c. 将指向当前任务的指针,指向即将恢复运行的任务,即新任务取代当前任务;
d. 将新任务的优先级复制给当前任务;
e. 从新任务的TCB中得到新任务的堆栈指针;
f. 从新任务的堆栈中按既定的顺序恢复相应的寄存器到CPU;
g. 中断返回,使得PC和PSW从堆栈中弹出,从而使得用户任务恢复运行。
关中断;
判断PSP是否为0,若是0,就转移到PendSV_Handler_Nosave();
如果PSP为0,说明是第一次做任务切换,任务创建时会调用堆栈初始化函数,初始化时已经做了入栈处理,因此这里不需要再做入栈处理,而直接跳转到后面的函数PendSV_Handler_Nosave做任务切换;
做入栈处理:
R0-=0x20:SP指针先减小,再入栈;
保存剩余的R4-R11寄存器;
调整SP指向OSTCBcur;
PendSV_Handler_Nosave()函数详解:
将R14(LR)入栈,调用OSTaskSWHook(),出栈R14;
更改当前任务优先级为最高优先级任务的优先级:OSPrioCur= OSPrioHighRdy;
更改当前任务指针指向为最高优先级的就绪任务:OSTCBCur= OSTCBHighRdy;
R0 = OSTCBHighRdy;
从堆栈中恢复R4-R11;
更改SP指针为R0+=0x20:出栈以后,SP指针递增;0x20=32,8个寄存器,每一个都是4字节(32位)的,因此这里刚好;
用新的任务的R0(SP)加载到PSP;
确保LR的位2为1,返回后使用进程堆栈:因为进入中断使用的是MSP,退出中断时需要更改为PSP;
开中断,中断返回(返回的应该是LR寄存器);(这里在进入异常服务程序以后,LR的值被自动更新为特殊的EXC_RETURN,在M3中[3:0]有意义,其意义如下);
这里之所要将官方文件里面的PendSV名字进行更改,是因为ST的官方文件和启动文件里面的名字都是这个,因此要将stm32f10x_it.c中的函数屏蔽,同时更改汇编文件里面的中断名字。以保持和启动文件中的中断向量表中名字一样。