PendSV典型使用场合是在上下文切换时(在不同任务之间切换)。 我们先简单的写几段代码实现PendSV的中断触发,当然也会涉及到CM3内核汇编指令,自从开始挑战的那天起,你不如地狱谁入地狱!
如何触发PendSV中断呢?从Cortex-M3权威指南手册上可以看到如下图所示,控制ICSR的28位置1便可以悬起PendSV 触发PendSV中断。
如何设置PendSV优先级?同样下表也来自Cortex-M3权威指南,设置地址0xE000ED22中的值既可以设置PendSV的优先级。暂且将PendSV的优先级设置为最低0xff;
main.c加入如下代码用于触发PendSV中断异常,主函数中调用trigger_PendSV() 便可以触发。
#include "delay.h"
#include "sys.h"
#define NVIC_INT_CTRL 0xE000Ed04 //PendSV中断控制器地址
#define NVIC_PENDSV_SET 0x10000000 //PendSV触发的值
#define NVIC_SYSPRI2 0xE000Ed22 //PendSV优先级控制地址
#define NVIC_PENDSV_PRI 0x000000ff //PendSV设置为最低优先值
#define MEM32(addr) *(volatile unsigned long *)(addr)
#define MEM8(addr) *(volatile unsigned char *)(addr)
typedef struct _BlockType_t
{
unsigned long *stackPtr;
}BlockType_t;
BlockType_t *blockPtr;
// 触发PendSV中断异常
void trigger_PendSV(void)
{
MEM32(NVIC_INT_CTRL) = NVIC_PENDSV_SET; //触发PendSV
MEM8(NVIC_SYSPRI2) = NVIC_PENDSV_PRI; //设置PendSV优先级
}
u8 flag;
unsigned long stackBuffer[1024];
BlockType_t block;
int main(void)
{
delay_init(); //延时函数初始化
block.stackPtr=&stackBuffer[1024];
blockPtr = █
while(1)
{
flag = 1;
delay_ms(50);
flag = 0;
delay_ms(50);
trigger_PendSV();
}
}
真实的情况是,我们看不到任何现象,我们没有在 PendSV_Hander()做任何处理,一般的PendSV异常处理都是使用汇编代码,我们先尝试使用C文件采用文件内嵌套汇编的形式来在C文件中加入汇编,添加switch.c文件,将其添加到工程中,用来存放PendSV触发后中断处理的函数。__asm__ 表明要在C文件中嵌套汇编,该函数主要是将R4~R11寄存器的值保存到block结构体内定义的指针所指向的数据缓冲区buffer中。人为改变R4 R5寄存器的值,最后再从buffer中恢复原来R4~R11的值,看下是否发生改变。
__asm void PendSV_Handler(void)
{
IMPORT blockPtr //IMPORT 相当于c 语言中的 extern
LDR R0, =blockPtr //加载blockPtr变量的地址
LDR R0,[R0] //取出blockPtr变量的地址的值,也就是unsigned long *stackPtr;
LDR R0,[R0] //取出stackPtr地址对应的值
STMDB R0!,{R4-R11} //批量把寄存器R4~R11的值写入内存中,最后一个寄存器的值写入R0
//后一个任务开始
LDR R1,=blockPtr //加载blockPtr变量的地址
LDR R1,[R1]
STR R0,[R1] //将R0的值写入到R1的地址中
ADD R4,R4,#1 //改变R4的值
ADD R5,R5,#1
LDMIA R0!,{R4-R11} //从内存中恢复R4-R11的值
BX LR //退出异常
}
这里用到了STMDB和LDMIA指令,这里先简单介绍一下。
stmdb:db(decrease before)表示先减后存。
指令 stmdb sp!, { fp, ip, lr, pc} %% "!”表示sp等于最终被修改的sp的值。
假设 sp=4096,此条指令的执行过程如下:
1.先减:sp=sp-4=4092;
2.后存:4092-4095处存放pc的值;
3.先减:sp=sp-4=4088;
4.后存:4088-4091处存放lr寄存器的值;
以此类推,..........。
ldmia:ia(increase after)表示先读后增。
指令ldmia sp, {fp,sp, pc}
假设 sp=4080,此条指令的执行过程如下:
1.先读:fp位于4080-4083处存放原来保存的fp;
2.后增:sp=sp+4=4084;
3.先读:sp位于4084-4087处存放原来保存的ip;
4.后增:sp=sp+4;
以此类推,..........。
进行调试,单步执行到STMDB R0!,{R4-R11} 将R4~R11寄存器的值写入到Stack_buffer缓冲区(内存)。
通过LDMIA R0!,{R4-R11} 来从Stack_buffer缓冲区恢复R4~R11的值。
注意:R0-R3的值由内核自动恢复。