参考代码Embedded/RTOS/taskSwitch · guorong/study - 码云 - 开源中国 (gitee.com)
上一节里讲过使用PSP和进入中断的时候的堆栈操作了,接下来就可以进行RTOS的最核心的功能,切换任务了
切换任务的步骤为:
- 初始化任务栈,原因是在切换任务的时候,在中断中退出跳入任务中的时候,会自动在指定的栈中弹出8个寄存器,具体上一节中讲过
- 初始化任务切换指针,这里的任务指针指向了栈顶的地址,方便从中断中进入任务的时候,从这里弹出需要的寄存器
- 触发进入pendsv中断,并判断PSP是否为空,因为第一次进入中断的时候,并内有使用PSP指针,而是使用了MSP指针,如果PSP为空,则直接进行任务切换,否则进行栈顶保存
- 栈顶地址保存,中断中保存本次任务进来的时候的栈地址,方便下次切换到此任务的时候,再弹出,恢复进入中断的任务
- 取出要跳入的任务指针,即PSP要弹出的栈顶,将值赋给PSP
- 最后退出中断,退出前指定使用PSP指针
以上是最简单的任务切换流程,后续还需要增加除了自动保存的8个寄存器外的R4~R11个寄存器等。
具体的代码分析如下:
任务栈初始化如下,主要就是因为在任务跳转的时候,是从中断中跳转的,退出中断的时候,需要从指定的栈地址中弹出8个寄存器,正规做法还需要加上后续手动压入的R4~R11,此处只做了最简单的处理,没有初始化手动操作的寄存器。 上一节中讲过自动压入的8个寄存器,其中比较重要的就是前两个寄存器,第一个为状态寄存器xPSR,这个寄存器的第24位为运行arm状态还是thumb状态,由于Cortex-M3内核只能运行在thumb状态,所以需要在第一个字节中,将第24位初始化为1。还有第2和寄存器为PC指针,这里是会恢复给PC寄存器,是代码要跳转到的位置,所以需要将任务函数的首地址传入
void taskStackInit(uint32_t *stack,void (*taskFun)(void),uint32_t** taskStackPtr)
{
*(--stack)=(1<<24);
*(--stack)=(uint32_t)taskFun;
*(--stack)=0x14;
*(--stack)=0x12;
*(--stack)=0x3;
*(--stack)=0x2;
*(--stack)=0x1;
*(--stack)=0x0;
*taskStackPtr = stack;
}
任务函数如下,即定义两个函数,不过需要在函数中都加上while(1)
void task1()
{
while(1)
{
varTask1=0;
taskSwitch();
varTask1=1;
}
}
void task2()
{
while(1)
{
varTask2=0;
taskSwitch();
varTask2=1;
}
}
在main函数中,初始化任务,在此处,手动定义了两个数组stack1和stack2,用于作为两个任务的PSP栈
int main()
{
taskStackInit((stack1+200),task1,&pspPtr[0]);
taskStackInit((stack2+200),task2,&pspPtr[1]);
SET_PENDSV_PRI();
taskSwitch();
while(1)
{
}
}
初始化之后,即将pspPtr数组的两个指针都初始化为了两个任务的栈顶地址
后续即将开始任务切换
void taskSwitch()
{
static int task_change=0;
if(0==task_change)
{
currTask = (uint32_t *)&pspPtr[0];
nextTask = (uint32_t *)&pspPtr[1];
task_change=1;
}
else
{
currTask = (uint32_t *)&pspPtr[1];
nextTask = (uint32_t *)&pspPtr[0];
task_change=0;
}
TRIGGER_PENDSV();
}
定义了两个指针变量,currTask和nextTask,在这里按照跳转进来的次数为偶数还是奇数,将之前初始化好的pspPtr的栈顶指针交替赋值给currTask和nextTask,需要在进入中断前运行一次此函数,将currTask和nextTask初始化完成,初始化完之后,就触发一次pendsv中断,跳入中断开始任务切换。
中断中处理如下
PendSV_Handler
IMPORT currTask
IMPORT nextTask
MRS R1, PSP ;保存现在的PSP堆栈指针到R1
CBZ R1, FirstTime ;判断是否为空,为空则不保存到堆栈指针数组中
LDR R2, =currTask ;获取当前指针的数值
LDR R2, [R2] ;获取当前指针的地址
STR R1, [R2] ;将R1的值保存到地址中,即更新了栈指针的值
FirstTime
LDR R0, =nextTask
LDR R0, [R0]
LDR R0, [R0]
MSR PSP, R0 ;
ORR LR, LR, #0X04
BX LR
需要将两个变量导入到中断函数中,首先取出PSP的值,然后判断PSP是否为0,由于第一次进入中断的时候,使用的是MSP,所以PSP为0,然后直接跳转到FirstTime的标志处,如果是后续从任务中跳转进来,则PSP不为0,会取出当前任务的存放地址pspPtr的存放位置,然后将PSP的值写入。
然后取出下一个任务的地址里存放的初始化好的或者后续保存好的栈顶的地址,将值赋值给PSP,然后将LR的第4位置为1,这个也在上一节中说过,是在退出中断后,使用PSP栈的意思
然后退出中断,即可从指向的PSP中弹出8个寄存器,然后跳转到PC指向的位置
第一次的时候,会跳转到两个任务的入口处,因为传入的为函数地址,后续会在调用了触发pendsv的地方,因为进入中断的时候,会将此处压入PSP,然后在中断中手动保存到了pspPtr中
运行起来之后,打开仿真器提供的示波器,查看任务中的两个变量的值,如下,即可说明在不断进行任务切换,即实现了任务的切换
RTOS的任务切换的最小实现就完成了,后续可以在此基础上再增加其他的功能,但只要理解了这里,其余的后续都会比较简单,帮助理解或者检验自己是否真正理解了这个过程,还是需要自己能够将代码重头写一遍,然后调试跑通。