一、知识点
1. Cortex-M3/4在复位后CONTROL寄存器初始值为0,也就是说MCU会处于线程模式、具有特权访问权限且使用主栈指针(MSP)。
2. 当进入异常时CM3会自动入栈,如下图所示:
3. 当异常返回时CM3会自动出栈,如下图所示:
4. CM3进入异常或者中断服务函数(ISR)时,R14(LR)会被自动更新为EXC_RETURN,且EXC_RETURN具有以下位域。
当发生程序调用后,LR为子程序的返回地址。(程序调用的实质是将PC赋值给LR,执行完子程序后,又将LR赋值给PC。这些由硬件自动完成)
二、教程分析
1. 文中指出任务栈在初始化完成后,如下状态,可以看到此时栈顶指针指在最低地址处,当然这时PSP还没有被赋值为栈顶指针。
2. 接下来调用"SVC 0",产生SVC异常服务,教程中SVC异常服务函数如下。
按照出现顺序,这里用到知识点2图8.7、知识点4、知识点3图8.10(#1出栈时)。进入SVC异常服务函数前,CPU会自动将R0,…,xPSR(8个32位寄存器)存储到MSP指向空间,R4-R11不会自动入栈。当然在SVC异常服务函数中,我们是去启动第一个任务,因此主要考虑出栈操作,如下。
- 上图(2)-(4)是获取任务栈顶指针,(5)LDMIA是指先加载指针指向内容至CPU寄存器中,然后增加指针(加载一个内容增加一个指针,!表示指针变量会更新),(6)把当前的栈指针赋值给PSP,(9)给EXC_RETURN或上0xd,表示“返回线程模式且返回后使用进程栈(PSP)”。
- 当使用"bx r14"后会触发异常返回机制,CPU会使用PSP来完成自动出栈,即将R0,…,xPSR存储到PSP指向空间,CPU也是使用LDMIA完成出栈操作的,因而此时PSP指向任务栈的位置如下。
- 此后CPU会取走R15(PC),程序自然跳转到任务1循环体中执行。
3. 任务1入栈
在任务1循环体的最后触发了PendSVC异常服务,这里用到知识点2图8.8,知识点3图8.10(#1压栈时和出栈时)。教程中PendSVC异常服务函数如下。
- 进入PendSVC异常服务函数前,CPU会自动将R0,…,xPSR存储到PSP指向空间,CPU是使用STMDB完成入栈操作的,R4-R11不会自动入栈,此时PSP还是指向第一个任务的栈空间,如下图。STMDB是指先减小指针,然后存储CPU寄存器值至指针指向空间中(减小一个指针存储一个内容,!表示指针变量会更新)。
- 剩下的R4-R11也需要使用STMDB手动入栈,见图二-3中的(4)、(7)。
- 然后将R0的值赋值给第一个任务的栈顶指针,见图二-3中的(5)、(6)、(8),以恢复任务栈顶指针为初始状态,如图二-1。(其实可以不用重新赋值任务的栈顶指针,因为没有变过,但考虑到要逻辑严谨,还是重新赋值比较好)。此时的PSP、R0和第一个任务的栈顶指针状态如下。
- 到这里任务1的入栈已经完成。
4. 任务2出栈
- 接着看图二-3,(9)其实就是保护EXC_RETURN的bit2不会变成0,因为一旦发生程序调用,R14即为子程序的返回地址,原本的EXC_RETURN就会被打乱,导致异常返回时,CPU的SP不一定就是PSP,所以必须加以保护。
- R3也要加以保护,因为它保存了一个指针变量(pxCurrentTCB)的地址,指向要(或正在)执行的任务控制块。
- (12)将pxCurrentTCB指向第二个任务控制块。
- 进入异常或中断后,CPU自动使用主栈,因此(9)和(14)使用的SP就是MSP。
- (15)-(19)与二-2(SVC异常服务)是一样的。
- 至于为什么最后没有给EXC_RETURN或上0xd,其实EXC_RETURN_bit2没改变过,当然你要或上0xd也可以。
5. 总结