前言
本文通过一个代码实例深度分析中断发生时,stm32做了哪些操作用于保护现场,中断退出时又做了哪些操作用于恢复现场。
基础知识
Cortex-M3寄存器组如下:

在【Cortex-M3】C语言函数调用过程汇编层面分析 一文中介绍到,寄存器组中:
R0-R3、R12、LR和xPSR被称为“调用者保护寄存器”,
R4-R11为“被调用者保护寄存器”
这一规则来自AAPCS(ARM架构过程调用标准)。
在函数调用过程中,子函数会保证执行前后R4-R11内容不变,而中断服务函数也是一种子函数,所以在中断发生时,处理器只负责保证在进入中断服务函数前和退出中断服务函数后,R0-R3、R12、LR和xPSR的内容不变,另外处理器需要保证中断服务函数执行完可以跳转回中断发生时的代码位置继续执行。
所以,中断发生时,处理器会首先进行压栈操作,对一些寄存器的内容进行保存,压栈内容如下:

压栈顺序为:xPSR、返回地址(中断产生时的PC值)、LR、R12、R3、R2、R1、R0。
退出中断时,将这些内容依次出栈,也就恢复到了中断发生前的状态。
接下来通过实验对这一过程进行详细分析。
实验
本实验在stm32单片机上实现了一个外部中断,正常情况下会在主函数中执行,当开发板上的按键被按下时,单片机会产生一个外部中断,并跳转到中断服务函数执行。
主函数:
void mymain()
{
volatile int a = 1;
volatile int b = 2;
volatile int c = 3;
volatile int d;
while (1)
{
d = a + b + c;
}
}
外部中断服务函数:
void EXTI0_IRQHandler(void)
{
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int e = 5;
int f;
e = a + b + c + d + e;
EXTI_ClearITPendingBit(EXTI_Line0);
}
主函数对应汇编代码如下:

中断服务函数对应的汇编代码如下:

想查看汇编代码可以设置Keil编译后生成.dis文件。
接下来在中断服务函数开始位置打个断点:

按下按键触发中断后,显示当前寄存器的状态如下:

可以看到当前的栈指针SP为0x20000714,接下来用Keil5找到RAM空间中这个位置,内容如下:

在中断服务函数汇编代码开始处有段如下代码:
0x0800067c: e92d41f0 -..A PUSH {r4-r8,lr}
所以栈指针后面的6个32位数据是这5个寄存器,也就是:
| 寄存器 | Value |
|---|---|
| R4 | 0x00000000 |
| R5 | 0x20000100 |
| R6 | 0x00000000 |
| R7 | 0x00000000 |
| R8 | 0x00000000 |
| LR | 0xFFFFFFF9 |
以上这些寄存器是中断服务函数压栈的,接下来就是处理器在执行中断时自动压栈的,也就是
| 寄存器 | Value |
|---|---|
| R0 | 0x00000003 |
| R1 | 0x00000003 |
| R2 | 0x00000001 |
| R3 | 0x28010800 |
| R12 | 0x00000001 |
| LR | 0x08000217 |
| 返回地址 | 0x080001E4 |
| xPSR | 0xFFFFFFF9 |
查看mymain函数对应的汇编代码,0x080001E4处对应为:
0x080001e4: 4408 .D ADD r0,r0,r1
所以中断退出后,首先执行的是mymian函数中的这段代码。
接下来通过单步调试退出中断服务函数,返回到mymain,如下:

这时寄存器组的内容为:

可以看到R1-R8、LR、xPSR与中断发生时处理器自动压栈的、中断服务函数开始执行时压栈的内容一模一样,只有R0和PC值不一样,这是因为Keil5是在C语言层面单步执行的,没有在汇编层面单步执行,mymain函数相关位置处的汇编代码如下:
0x080001dc: e9dd1002 .... LDRD r1,r0,[sp,#8]
0x080001e0: 4408 .D ADD r0,r0,r1
0x080001e2: 9901 .. LDR r1,[sp,#4]
0x080001e4: 4408 .D ADD r0,r0,r1
0x080001e6: 9000 .. STR r0,[sp,#0]
0x080001e8: e7f8 .. B 0x80001dc ; mymain + 16
可以看到中断返回后到断点处,处理器执行了0x080001e4、0x080001e6、0x080001e8三个位置的机器码,在0x080001e4有ADD r0,r0,r1这个操作,正好对应了
R0 = R0 + R1 = 3 + 3 = 6
而 B 0x80001dc 也就对应了将PC赋值为0x80001dc。
总结
中断发生后,压栈行为分两步:
第一步:处理器自动压栈“调用者保护寄存器”,顺序为xPSR、LR、R12、R3、R2、R1、R0。
第二步:中断服务函数首先压栈LR(这时的LR已经和进入中断前的LR不一样了),然后压栈一些用到的“被调用者保护寄存器”R4-R11。
2612





