1.PendSV异常
PendSV是系统异常,具有可编程的优先级,主要用在OS的任务切换中。可以将它配置为最低优先级(比普通任务的优先级要高,在运行任务期间发生了PendSV异常,CPU可以正常响应该异常)。
2.实现案例
系统启动之后手动触发一个PendSV异常,然后在PendSV异常处理函数中保存R4 - R11的值到指定的栈中。
main.c
#define NVIC_INT_CTRL 0xE000ED04 // 中断控制及状态寄存器
#define NVIC_PENDSVSET 0x10000000 // 触发软件中断的值
#define NVIC_SYSPRI2 0xE000ED22 // 系统优先级寄存器
#define NVIC_PENDSV_PRI 0x000000FF // 配置优先级
#define MEM32(addr) *(volatile unsigned long *)(addr)
#define MEM8(addr) *(volatile unsigned char *)(addr)
void triggerPendSVC (void)
{
MEM8(NVIC_SYSPRI2) = NVIC_PENDSV_PRI; // 向NVIC_SYSPRI2写NVIC_PENDSV_PRI,设置其为最低优先级
MEM32(NVIC_INT_CTRL) = NVIC_PENDSVSET; // 向NVIC_INT_CTRL写NVIC_PENDSVSET,用于PendSV
}
typedef struct _BlockType_t
{
unsigned long * stackPtr;
}BlockType_t;
BlockType_t * blockPtr;
void delay (int count)
{
while (--count > 0);
}
int flag;
unsigned long stackBuffer[1024];
BlockType_t block;
int main ()
{
block.stackPtr = &stackBuffer[1024];//从这里可以体会到ARM使用的是满减栈。
blockPtr = █
for (;;) {
flag = 0;
delay(100);
flag = 1;
delay(100);
triggerPendSVC();
}
}
switch.c
__asm void PendSV_Handler ()
{
//导入变量
IMPORT blockPtr
// 加载寄存器存储地址
LDR R0, =blockPtr
LDR R0, [R0]
LDR R0, [R0]
// 保存寄存器
//为什么只保存R4-R11,因为其他的寄存器的值硬件会自动保存到栈中(这里的栈指MSP)
//STM的作用:批量将寄存器的数据写到R0地址处的空间中,
//D表示地址递减,也就是一开始R0的值是栈顶,栈顶是高地址
//B表示在加载数据之前,先将R0减4
//RO!中的!表示当所有寄存器都加载完毕之后,将此时R0的值/0x20000FF0(此时栈的地址)写回R0
STMDB R0!, {R4-R11}
// 将最后的地址写入到blockPtr中,一开始栈指针变量的值是数组最后一个元素的地址,
//将寄存器中的数据写到了栈里面之后,此时栈肯定是在向下移动的,STMDB能自动移动栈指针(R0/block.stackPtr),
//当所有寄存器的数据都写到了栈中,此时的栈地址必须更新到栈指针变量中。
// 即,将此时的栈地址写到block.stackPtr中,该变量的值为0x20000FF0
LDR R1, =blockPtr
LDR R1, [R1]
STR R0, [R1]
// 修改部分寄存器,用于测试
ADD R4, R4, #1
ADD R5, R5, #1
// 恢复寄存器,这里的R0的值等于0x20000FF0,压栈和弹栈的顺序相反
LDMIA R0!, {R4-R11}
// 异常返回
BX LR
NOP
}
总结:
当发生任务切换/PendSV异常时,如果把保存R4-R11寄存器保存到上一个任务的栈中,然后恢复的是下一个任务的栈,此时就实现了任务切换。