学会使用PendSV中断进行压栈和出栈操作,是实现任务调度的关键。今天我们就来学习一下如何使用不超过20行的汇编实现压栈和出栈操作。
我们现在来实现这么一个例子:先把R4-R11通用寄存器的值保存到一个缓冲区里面,然后再把缓冲区里面的值恢复到R4-R11。
实现这个功能的意义在于,在切换一下个任务之前,要先把当前任务的状态先保存起来,然后把要下面要执行的任务状态从缓冲区里面恢复出来。
下面我们就要上一次创建的工程上进行本次实例的讲解
首先,我们先定义一个结构体:
typedef uint32_t StackType_t;
typedef struct tskTaskControlBlock
{
StackType_t *pxTopOfStack;
}TCB_t;
然后定义一个结构体变量以及结构体指针:
TCB_t *pxCurrentTCB;
TCB_t CurrentTCB;
接下来,我们要对PendSV相关的寄存器进行定义和定义一个写寄存器操作:
#define NVIC_INT_CTRL 0xE000ED22
#define portNVIC_INT_CTRL_REG 0xE000ED04
#define portNVIC_PENDSVSET_BIT 0x10000000
#define portNVIC_PENDSVSET_PRI 0xFF
#define REG_WRITE_32(ADDRESS) (*(volatile unsigned long *)(ADDRESS))
#define REG_WRITE_8(ADDRESS) (*(volatile unsigned char *)(ADDRESS))
下面我们来实现一下触发PendSV异常的函数:
void TrigetPendSVC(void)
{
REG_WRITE_8(NVIC_INT_CTRL) = portNVIC_PENDSVSET_PRI;
REG_WRITE_32(portNVIC_INT_CTRL_REG) = portNVIC_PENDSVSET_BIT;
}
这个函数很简单,先把PendSV中断设置为最低优先级,然后再触发发PendSV异常。
在main函数中对结构体进行初始化:
CurrentTCB.pxTopOfStack = &puxStackBuffer[1024];
pxCurrentTCB = &CurrentTCB;
最后我们来实现一下PendSV的中断函数:
__asm void PendSV_Handler(void)
{
extern pxCurrentTCB; /*引用外部变量pxCurrentTCB*/
ldr r1, =pxCurrentTCB; /*读取结构体指针pxCurrentTCB地址的值*/
ldr r2, [r1]; /*读取结构体指针pxCurrentTCB指向的值,即CurrentTCB的地址*/
/*也是CurrentTCB.pxTopOfStack的地址*/
ldr r0, [r2]; /*读取CurrentTCB.pxTopOfStack指针指向的地址,并赋给r0*/
stmdb r0!, {r4-r11}; /*依次把r11到r4保存到r0指向的缓冲区中*/
str r0, [r2]; /*再把此时移位后栈顶指针的值赋给CurrentTCB.pxTopOfStack*/
add r4, r4, #1; /*测试代码*/
add r5, r5, #1; /*测试代码*/
ldmia r0!, {r4-r11}; /*把r0指向的缓冲区中的值恢复给r11-r4*/
bx lr; /*返回*/
}
现在我们来测试一下我们写的代码是否有问题,进入仿真模式:
我们停在这个触发PendSV中断的地方:
点击下一步看下能否进行中断,能够正常进入
我们再把相关的变量加入到Watch1中进行观察,看下数据是否正确:
第一步,把pxCurrentTCB指针的地址赋给r0,从下图可以看出,操作正常:
下一步,读出pxCurrentTCB结构体指针所指针向的结构体地址的值,&pxCurrentTCB的值为0x20000004,&CurrentTCB = pxCurrentTCB = 0x20000008,操作正确:
下一步,把CurrentTCB.pxTopOfStack的值赋给r0,即r0=CurrentTCB.pxTopOfStack=&puxStackBuffer[1024];这里要说一下为什么要取puxStackBuffer[1024]的地址,这样不是数据越界了吗?是不是我写错了?其实没有写错,刚开始的时候我也不理解,看到后面指令我就明白了,因为Cotex-M3是满减栈,即操作SP指针的时候,要先把SP-1,然后再对SP指向的地址进行赋值,所以到后面还是会把第一个数写在puxStackBuffer[1023]上,不会越界。
下一步,把r11-r4依次保存在缓冲区puxStackBuffer数组中,数据可以一一对应,见下图:
下一步,把缓冲区压栈完成后指向的地址保存到CurrentTCB.pxTopOfStack中,以便下次恢复的时候用,可以看到CurrentTCB.pxTopOfStack的值已经变成0x20000FF0,与r0的值一致:
下面执行一下测试代码,r4=0,r5=0x20001010;我们执行一下加1运算:
下一步,恢复数据,可以看到r4,r5已经变回原来的数值了:
到这里,使用PendSV保存上下文和恢复的完成了,下一篇说一下任务的切换。