下面的描述参考于这里的文档。
我们首先插入介绍一下 C o r t e x M 3 Cortex\quad M3 CortexM3和 C o r t e x M 4 Cortex\quad M4 CortexM4的内核寄存器。和其它几乎所有处理器一样 C o r t e x M 3 Cortex\quad M3 CortexM3和 C o r t e x M 4 Cortex\quad M4 CortexM4的内核里面有一些寄存器用来进行数据处理和控制,这些寄存器里面的大部分寄存器被编成一组( R e g i s t e r B a n k Register\quad Bank RegisterBank), C o r t e x M 3 Cortex\quad M3 CortexM3和 C o r t e x M 4 Cortex\quad M4 CortexM4的寄存器组里面一共有16个寄存器,其中的13个是通用的32位寄存器,另外三个有特殊的用途。这16个寄存器分别是:
- R 0 R0 R0:通用寄存器
- R 1 R1 R1:通用寄存器
- R 2 R2 R2:通用寄存器
- R 3 R3 R3:通用寄存器
- R 4 R4 R4:通用寄存器
- R 5 R5 R5:通用寄存器
- R 6 R6 R6:通用寄存器
- R 7 R7 R7:通用寄存器
- R 8 R8 R8:通用寄存器
- R 9 R9 R9:通用寄存器
- R 10 R10 R10:通用寄存器
- R 11 R11 R11:通用寄存器
- R 12 R12 R12:通用寄存器
- R 13 R13 R13:它是一个特殊的寄存器,它是栈指针 S t a c k P o i n t e r , S P Stack\quad Pointer,SP StackPointer,SP,它用来访问栈存储空间。通常有两个不同的栈指针, M a i n S t a c k P o i n t e r , M S P Main\quad Stack\quad Pointer,MSP MainStackPointer,MSP和 P r o c e s s S t a c k P o i n t e r , P S P Process\quad Stack\quad Pointer,PSP ProcessStackPointer,PSP, M a i n S t a c k P o i n t e r , M S P Main\quad Stack\quad Pointer,MSP MainStackPointer,MSP是默认的栈指针,复位后或处理器处于 H a n d l e r Handler Handler模式的时候都会选择这个栈指针, P r o c e s s S t a c k P o i n t e r , P S P Process\quad Stack\quad Pointer,PSP ProcessStackPointer,PSP仅仅用在 T h r e a d Thread Thread模式,它的选择与否由特殊寄存器 C O N T R O L CONTROL CONTROL所决定。在正常的程序中,这两个栈指针中仅仅有一个被使用。 M a i n S t a c k P o i n t e r , M S P Main\quad Stack\quad Pointer,MSP MainStackPointer,MSP和 P r o c e s s S t a c k P o i n t e r , P S P Process\quad Stack\quad Pointer,PSP ProcessStackPointer,PSP都是32位的,它们的最低两位一直都是0,这是因为在 C O R T E X M CORTEX\quad M CORTEXM处理器中,栈操作都是32位的,因为要求是32位对齐的。在大部分情况下,如果应用上不需要嵌入式操作系统,那就没必要使用 P r o c e s s S t a c k P o i n t e r , P S P Process\quad Stack\quad Pointer,PSP ProcessStackPointer,PSP。许多简单的应用完全可以依赖于 M a i n S t a c k P o i n t e r , M S P Main\quad Stack\quad Pointer,MSP MainStackPointer,MSP,一般 P r o c e s s S t a c k P o i n t e r , P S P Process\quad Stack\quad Pointer,PSP ProcessStackPointer,PSP只在有嵌入式操作系统需求的时候才使用,这种情况下操作系统核的栈和应用程序的栈是分开的。
- R 14 R14 R14:它是一个特殊的寄存器,它也被称做 L i n k R e g i s t e r , L R Link\quad Register,LR LinkRegister,LR,在一个函数中调用另一个函数的时候它用来保存返回地址,当被调用函数执行完之后,可以通过将 L i n k R e g i s t e r , L R Link\quad Register,LR LinkRegister,LR里面的值赋值给 P r o g r a m C o u n t e r , P C Program\quad Counter,PC ProgramCounter,PC来使得程序逻辑重新回到调用函数里面。如果这个被调用函数还需要调用另外一个函数,则这时 L i n k R e g i s t e r , L R Link\quad Register,LR LinkRegister,LR就需要被如栈保存,否则当回到调用函数里面的时候 L i n k R e g i s t e r , L R Link\quad Register,LR LinkRegister,LR里面的值就丢失了,从而失去了调用函数里面的逻辑。但是在有触发中断函数的情景里面, L i n k R e g i s t e r , L R Link\quad Register,LR LinkRegister,LR就不是实际的返回地址了,(根据这篇文章的描述,对于一般的函数调用的现场保存和恢复,编译器在编译代码的时候自动的会插入一些入栈和出栈的指令,但是对于中断函数的情景,编译器很难去自动的插入现场保存和恢复的指令,这是因为不知道中断函数什么时候会被调用),而是一个特殊的返回地址 E X C _ R E T U R N EXC\_RETURN EXC_RETURN,用这个特殊的返回地址来恢复中断函数调用之后的场景恢复,后面会讲到这个特殊的返回地址。
- R 15 R15 R15:它是一个特殊的寄存器,它也被称做 P r o g r a m C o u n t e r , P C Program\quad Counter,PC ProgramCounter,PC,它可读可写,读操作返回当前的指令地址加4,写的话就跳转到对应的地址去执行了。
除了上面讲到的 R e g i s t e r B a n k Register\quad Bank RegisterBank里面的寄存器, C o r t e x M 3 Cortex\quad M3 CortexM3和 C o r t e x M 4 Cortex\quad M4 CortexM4的内核里面还有一些特殊的寄存器, x P S R , P R I M A S K , F A U L T M A S K , B A S E P R I , C O N T R O L xPSR,PRIMASK,FAULTMASK,BASEPRI,CONTROL xPSR,PRIMASK,FAULTMASK,BASEPRI,CONTROL,我这里现在只讲一个特殊的寄存器 x P S R xPSR xPSR,这个寄存器是三个寄存器, A P S R , E P S R , I P S R APSR,EPSR,IPSR APSR,EPSR,IPSR,组合起来的,但是可以通过一个结合的32位寄存器来访问。如图1所示。 E x c e p t i o n N u m b e r Exception\quad Number ExceptionNumber是当前正在处理的中断的中断号,比如如果当前在 H a r d F a u l t h a n d l e r Hard\quad Fault\quad handler HardFaulthandler里面的话,这个域的值就是3,如果当前没有再处理中断函数的话,这个域的值就是0。其它的位的描述直接看图2了,文档上的描述我暂时也不明白。
R
0
R0
R0,
R
1
R1
R1,
R
2
R2
R2,
R
3
R3
R3,
R
12
R12
R12,
R
14
(
L
R
)
R14(LR)
R14(LR)和
x
P
S
R
xPSR
xPSR叫做调用者保存的寄存器,程序在调用一个子程序之前需要保存这些寄存器的值(在中断函数的场景下是必须的,但是在一般的函数调用中,会根据子程序调用结束之后是否需要再次用到这些寄存器的情况来选择是否将它们全部入栈保存)。
R
0
−
>
R
11
R0->R11
R0−>R11寄存器叫做被调用者保存的寄存器,当在包调用的函数中运行的时候,需要保证进入这个被调用函数的时候和退出这个被调用函数的时候
R
0
−
>
R
11
R0->R11
R0−>R11寄存器的值是一致的,但是在这个被调用函数的中间
R
0
−
>
R
11
R0->R11
R0−>R11寄存器的值是可以被改变的,比如用来存储这个函数中的临时变量的值。
这里有一点需要注意的是,如果处理器支持硬件浮点单元的话,还会有
S
0
−
>
S
31
S0->S31
S0−>S31这些额外的寄存器,其中
S
0
−
>
S
15
S0->S15
S0−>S15是调用者保存的寄存器,
S
16
−
>
S
31
S16->S31
S16−>S31被调用者保存的寄存器。一般情况下,函数调用使用
R
0
R0
R0,
R
1
R1
R1,
R
2
R2
R2,
R
3
R3
R3作为输入参数,
R
0
R0
R0用来存储函数返回结果,如果返回值是64比特的话,那么
R
1
R1
R1也会额外被用来存储这个64位的返回值。
为了能够正确的响应中断函数并退出,内核在进入中断函数的时候会自动的保存寄存器
R
0
R0
R0,
R
1
R1
R1,
R
2
R2
R2,
R
3
R3
R3,
R
12
R12
R12,
R
14
(
L
R
)
R14(LR)
R14(LR)和
x
P
S
R
xPSR
xPSR并在退出中断函数的时候自动去恢复它们,这样在调用完中断函数之后就可以恢复到以前的环境。因为在进入中断函数的那一刻,
R
14
(
L
R
)
R14(LR)
R14(LR)寄存器存储的不是实际的返回地址,而是一个特殊的返回地址,因此和普通的函数调用不同的是,在进入中断函数的的时候也会将实际的返回地址压栈保存。因此对于不支持硬件浮点单元的
C
o
r
t
e
x
M
3
Cortex\quad M3
CortexM3和
C
o
r
t
e
x
M
4
Cortex\quad M4
CortexM4处理器,在进入中断函数的时候会有8个值被自动入栈。对于
C
o
r
t
e
x
M
4
Cortex\quad M4
CortexM4处理器如果支持硬件浮点单元的话还会有
S
0
−
>
S
15
S0->S15
S0−>S15和
F
P
S
C
R
FPSCR
FPSCR寄存器会被自动入栈。图4和图5都是不支持硬件浮点单元的情况下的栈帧结构,其中图4是不需要双字对齐的情况,图5是需要双字对齐的情况。图6是
C
o
r
t
e
x
M
4
Cortex\quad M4
CortexM4处理器支持硬件浮点单元的情况。
在进入中断函数的时候,被压入栈里面的 数据称为栈帧,对于不支持硬件浮点单元的 C o r t e x M 3 Cortex\quad M3 CortexM3和 C o r t e x M 4 Cortex\quad M4 CortexM4处理器,栈帧的大小为8个字,对于 C o r t e x M 4 Cortex\quad M4 CortexM4处理器如果支持硬件浮点单元的话,栈帧的大小为8个字或26个字。在有的情况下还会要求堆栈指针在进入中断函数的时候是双字对齐的,如果在进入中断函数的时候堆栈指针不是双字对齐的,则 C o r t e x M 3 Cortex\quad M3 CortexM3和 C o r t e x M 4 Cortex\quad M4 CortexM4处理器会自动的额外的添加一个填充字,也就是相当于在保存当前环境变量之前会自动入栈一个无效数据来使得满足双子对齐的要求。 x P S R xPSR xPSR寄存器的比特9可以用来表明是否堆栈指针需要满足双字对齐,0是不需要满足,1是需要满足。堆栈指针的双字节对齐的功能可以通过系统控制模块( S y s t e m C o n t r o l B l o c k , S C B System\quad Control\quad Block,SCB SystemControlBlock,SCB)的配置寄存器( C o n f i g u r a t i o n C o n t r o l R e g i s t e r , C C R , a d d r e s s 0 x E 000 E D 14 ) Configuration\quad Control\quad Register,CCR, address\quad 0xE000ED14) ConfigurationControlRegister,CCR,address0xE000ED14))的一个控制位来使能。
前面我们说到,当进入中断函数之后, R 14 ( L R ) R14(LR) R14(LR)寄存器里面的值不是实际的返回地址,而是一个特殊的值 E X C _ R E T U R N EXC\_RETURN EXC_RETURN,当这个值被再次加载到 R 15 , P r o g r a m C o u n t e r , P C R15,Program\quad Counter,PC R15,ProgramCounter,PC寄存器里面的时候,它就会触发从中断函数返回, E X C _ R E T U R N EXC\_RETURN EXC_RETURN的有效值以及各个比特位的含义分别如图7和图8所示。
正在使用的栈,要么是 M a i n S t a c k Main\quad Stack MainStack(使用 M S P MSP MSP堆栈指针)或 P r o c e s s S t a c k Process\quad Stack ProcessStack(使用 P S P PSP PSP堆栈指针)。如果处理器处于 T h r e a d Thread Thread模式并且使用 M S P MSP MSP堆栈指针( C O N T R O L CONTROL CONTROL寄存器的比特位1为0),则栈操作用 M S P MSP MSP堆栈指针在 M a i n S t a c k Main\quad Stack MainStack中进行。如图9所示。如果处理器处于 T h r e a d Thread Thread模式并且使用 P S P PSP PSP堆栈指针( C O N T R O L CONTROL CONTROL寄存器的比特位1为1),则栈操作用 P S P PSP PSP堆栈指针在 P r o c e s s S t a c k Process\quad Stack ProcessStack中进行。进入 H a n d l e r Handler Handler模式之后,必须使用 M a i n S t a c k Main\quad Stack MainStack,所有嵌套的中断调用的栈操作必须在 M a i n S t a c k Main\quad Stack MainStack中进行。如图10所示。
当从中断函数中返回的时候, E X C _ R E T U R N EXC\_RETURN EXC_RETURN的值得第2个比特位的值决定应该从哪一个栈中恢复环境,如果这个比特位为0,则应该从 M a i n S t a c k Main\quad Stack MainStack中出栈帧来恢复环境,否则应该选择 P r o c e s s S t a c k Process\quad Stack ProcessStack。在出栈恢复环境的末尾,还需要根据 x P S R xPSR xPSR寄存器的第9个比特位来决定是否对堆栈指针进行修正,因为前面提到了 x P S R xPSR xPSR寄存器的比特9可以用来表明是否堆栈指针需要满足双字对齐,如果使能了满足双字对齐的要求且入栈之前有入栈一个无效数据来满足双字对齐的要求的话,那最后就需要将堆栈指针的值再加4。看看图11,图12和图13就清晰了。
当一个中断
A
A
A发生的时候,如果此时处理器正在处理另外一个中断
B
B
B且中断
B
B
B的优先级和中断
A
A
A的优先级一样或者比中断
A
A
A的高,那么中断
A
A
A将处于挂起状态,等到处理器处理完中断
B
B
B之后,这时处理器不会进行出栈恢复进入中断
B
B
B前的环境以及入栈保存进入中断
A
A
A前的环境的操作,而是直接去处理中断
A
A
A,这样就减少了两个中断处理之间的延迟,这个处理器的特性叫做
T
a
i
l
C
h
a
i
n
i
n
g
Tail\quad Chaining
TailChaining,如图14所示。
当一个中断
A
A
A发生的时候,处理器响应它并开始做入栈保存进入中断
A
A
A前的环境的操作,如果在这个入栈操作
A
A
A的时候,另一个比中断
A
A
A优先级高的中断
B
B
B来了,那此时在前面的入栈操作
A
A
A完成后会马上响应中断
B
B
B,如图15所示。
(
L
a
t
e
A
r
r
i
v
a
l
)
(Late\quad Arrival)
(LateArrival)。这里文档没有讲清楚的是,入栈操作
A
A
A完成之后响应中断
B
B
B,那么此时是否针对中断
B
B
B又有一次入栈的操作,入栈保存进入中断
B
B
B前的环境的操作,然后中断
B
B
B执行完之后出栈,然后中断
A
A
A执行完之后再出栈。
当一个中断 A A A执行完成之后正在进行恢复进入中断 A A A之前的环境的出栈操作 A A A的时候,如果这时来了一个中断 B B B的请求,那么此时处理器会停止出栈操作 A A A,而是马上去响应中断 B B B,这个处理器属性叫做 P o p P r e e m p t i o n Pop\quad Preemption PopPreemption,如图16所示。这里文档没有讲清楚的是,在响应中断 B B B的时候,那么此时是否针对中断 B B B又有一次入栈的操作,入栈保存进入中断 B B B前的环境的操作,然后中断 B B B执行完之后出栈。中断 B B B执行完之后出栈恢复了进入中断 B B B之前的环境,前面也提到了,中断 A A A的出栈操作 A A A是被中断 B B B的请求给打断了的,也就是中断 A A A的出栈操作只恢复了进入中断 A A A前的环境的部分,剩下的应该还在栈中,那么此时是否会继续恢复被中断 B B B打断的恢复进入中断 A A A前的环境的出栈操作 A A A的剩余的操作。
在
C
o
r
t
e
x
−
M
4
Cortex-M4
Cortex−M4处理器中如果使能了浮点运算单元,那么在中断处理中需要保存环境而入栈的寄存器的数量会增加许多,这是因为和浮点运算有关的寄存器也需要入栈,这样的话会导致中断延迟的增加。为了减少由于浮点寄存器的入栈而带来的中断延迟,在
C
o
r
t
e
x
−
M
4
Cortex-M4
Cortex−M4处理器中采用了一种叫做
L
a
z
y
S
t
a
c
k
i
n
g
Lazy\quad Stacking
LazyStacking的优化技术。在这种优化技术下,在进入中断之前只会对
R
0
R0
R0,
R
1
R1
R1,
R
2
R2
R2,
R
3
R3
R3,
R
12
R12
R12,
R
14
(
L
R
)
R14(LR)
R14(LR),
x
P
S
R
xPSR
xPSR以及返回地址做入栈操作,但是这里要注意的是,虽然这里和没有浮点运算单元的处理器或者没有开启浮点运算单元的处理器一样只对
R
0
R0
R0,
R
1
R1
R1,
R
2
R2
R2,
R
3
R3
R3,
R
12
R12
R12,
R
14
(
L
R
)
R14(LR)
R14(LR),
x
P
S
R
xPSR
xPSR以及返回地址做入栈操作,但是这时这里的栈帧的大小不是8个字而是相当于
8
+
17
8+17
8+17个字,也就是说这里虽然没有实际的对和浮点运算单元有关的寄存器做入栈操作,但是还是为这些寄存器预留了栈空间。此时寄存器LSPACT
(
L
a
z
y
S
t
a
c
k
i
n
g
P
r
e
s
e
r
v
a
t
i
o
n
A
c
t
i
v
e
)
(Lazy\quad Stacking\quad Preservation\quad Active)
(LazyStackingPreservationActive)被置位且寄存器FPCAR
(
F
l
o
a
t
i
n
g
P
o
i
n
t
C
o
n
t
e
x
t
A
d
d
r
e
s
s
R
e
g
i
s
t
e
r
)
(Floating\quad Point\quad Context\quad Address\quad Register)
(FloatingPointContextAddressRegister)存储着栈帧中为浮点运算单元的寄存器预留的那部分空间的地址。如果中断函数中不需要进行浮点运算操作,则从进入中断函数到退出就不会有浮点运算单元相关寄存器的入栈和出栈操作,但是如果中断函数中是有浮点运算操作则在执行浮点运算指令之前,处理器会停下来将浮点运算相关的寄存器入栈到寄存器FPCAR指定的位置且寄存器LSPACT被清0,然后寄存器执行中断函数,当中断函数执行完之后,这些和浮点运算有关的寄存器也会被出栈来恢复环境。
在上面我们提到,如果中断函数
A
A
A中是有浮点运算操作则在执行浮点运算指令之前,处理器会停下来将浮点运算相关的寄存器入栈到寄存器FPCAR指定的位置。如果在入栈浮点运算相关的寄存器的时候此时出现了更高优先级的中断请求
B
B
B,则对于中断函数
A
A
A的入栈浮点运算相关的寄存器的操作将停止,此时对于这个更高优先级的中断请求
B
B
B会进行相关保存环境的入栈操作,对于断请求
B
B
B的入栈操作保存的返回地址是中断函数
A
A
A中的第一条即将执行的浮点单元运算指令的地址。当中断函数
B
B
B执行完,出栈恢复环境之后去执行中断函数
A
A
A中的第一条即将执行的浮点单元运算指令的时候又将继续触发入栈浮点运算相关的寄存器的操作。
下面我们以两个实际的例子来看一下实际的中断函数调用的时候的相关寄存器的入栈过程,这里我测试的时候用的是正点原子的
S
T
M
32
F
103
STM32\quad F103
STM32F103的开发板,
S
T
M
32
F
103
STM32\quad F103
STM32F103是
M
3
M3
M3的内核。首先我们看一个正常的
U
S
A
R
T
USART
USART模块的
R
X
N
E
RXNE
RXNE中断,测试代码如下所示。
void USRAT_NVIC_Configuration()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void USART1_IRQHandler(void)
{
u8 index1=2;
index1++;
}
int main(void)
{
u8 index=1;
index++;
uart_init(115200);
USRAT_NVIC_Configuration();
USART_ITConfig(USART1,USART_IT_RXNE , ENABLE);
printf("Normal interrupt test start.\r\n");
while(1);
}
首先我们以单步模式运行到 m a i n main main函数中的 w h i l e ( 1 ) while(1) while(1)死循环处,这时看一下各个寄存器的值,如图17所示,然后我们在 U S A R T USART USART模块的中断函数里面打一个断点,并开启连续运行模式,最后我们通过串口上位机向 S T M 32 F 103 STM32\quad F103 STM32F103发送一个字符让程序进入中断函数并停留在断点的地方,这时我们再看一下各个寄存器的值,如图18所示。从图17中可以看到此时的堆栈指针的值为 0 x 20000678 0x20000678 0x20000678,从图18中可以看到此时的堆栈指针的值为 0 x 20000658 0x20000658 0x20000658,因此可以知道进入中断函数时候有 0 x 20 = 34 0x20=34 0x20=34个字节,即8个字,的数据进栈了,刚好和我们前面讲到的一致( C o r t e x − M 3 Cortex-M3 Cortex−M3不支持浮点单元)。从图18中可以看到此时的堆栈指针的值为 0 x 20000658 0x20000658 0x20000658,我们根据这个堆栈指针值,可以去 M e m o r y Memory Memory窗口看一下此时堆栈中的内容,即确认一下刚刚入栈的那8个字的内容,如图19所示。从地地址往高地址看,这里连续的8个字按照 R 0 R0 R0, R 1 R1 R1, R 2 R2 R2, R 3 R3 R3, R 12 R12 R12, R 14 ( L R ) R14(LR) R14(LR),返回地址以及 x P S R xPSR xPSR寄存器的顺序刚好和图17中 R 0 R0 R0, R 1 R1 R1, R 2 R2 R2, R 3 R3 R3, R 12 R12 R12, R 14 ( L R ) R14(LR) R14(LR)以及 x P S R xPSR xPSR寄存器的一致,至于第七个字的内容,返回地址 0 x 080008 C C 0x080008CC 0x080008CC,我们可以通过 D i s a s s e m b l y Disassembly Disassembly窗口定位到这个地址就是 m a i n main main函数中的 w h i l e ( 1 ) while(1) while(1)死循环处,这也是符合预期的,因为我们的程序处于死循环的时候才出发 U S A R T USART USART模块中断的,如图20所示。从图17和图18中可以看出,在没有进入中断函数之前是 T h r e a d Thread Thread模式,进入之后是 H a n d l e r Handler Handler模式。从图18中我们可以看到此时 L R LR LR寄存器的值为 0 x F F F F F F F 9 0xFFFFFFF9 0xFFFFFFF9,这就是我们前面提到的 E X C _ R E T U R N EXC\_RETURN EXC_RETURN,这个值和图7中的描述也是一致的。
前面我们看的是正常的中断,下面我们来看一下异常的中断在进入的时候的入栈状况,这里我们以 H a r d F a u l t HardFault HardFault中断为例子,具体构造 H a r d F a u l t HardFault HardFault中断手段就是去向地址为 0 x 1 F F F F 007 0x1FFFF007 0x1FFFF007内存去写数据,因为这个地址的地方在一般模式下,是不允许去写的,因此这里可以构造出 H a r d F a u l t HardFault HardFault中断,至于具体的原因可以去查找以下相关的资料,测试代码如下:
int main(void)
{
u32 * hard_fault_handler_pointer=(u32 *)0x1FFFF007;
u8 index=1;
index++;
uart_init(115200);
printf("Abnormal interrupt test start.\r\n");
(* hard_fault_handler_pointer)=3;
while(1);
}
这里我在测试的时候发现了一点,单步执行完语句 ( ∗ h a r d _ f a u l t _ h a n d l e r _ p o i n t e r ) = 3 ; (* hard\_fault\_handler\_pointer)=3; (∗hard_fault_handler_pointer)=3;是不会马上进入 H a r d F a u l t HardFault HardFault中断的,但是执行接下来的语句马上就会进入 H a r d F a u l t HardFault HardFault中断,至于其中的原因我展示还不清楚。和正常中断的情况下一样,我们这里直接上图吧。单步执行完语句 ( ∗ h a r d _ f a u l t _ h a n d l e r _ p o i n t e r ) = 3 ; (* hard\_fault\_handler\_pointer)=3; (∗hard_fault_handler_pointer)=3;各个寄存器的值如图21所示,进一步执行 w h i l e ( 1 ) while(1) while(1)死循环之后进入 H a r d F a u l t HardFault HardFault中断各个寄存器的值如图22所示,这里和正常的中断一样,这里我就不进一步的去详细的分析了,直接参考我上面的对正常中断的分析就好了。这样当我们碰见像 H a r d F a u l t HardFault HardFault中断这种异常中断的时候,我们就可以简单的分析一下造成 H a r d F a u l t HardFault HardFault中断的代码的位置,从图22中,从地址 0 x 20000658 0x20000658 0x20000658开始向高地址的第7个字就应该是存储的造成 H a r d F a u l t HardFault HardFault中断的代码的大概位置 0 x 080007 D C 0x080007DC 0x080007DC,我们可以通过 D i s a s s e m b l y Disassembly Disassembly窗口定位到这个地址就是 m a i n main main函数中的 w h i l e ( 1 ) while(1) while(1)死循环处,虽然这个位置不是造成进入 H a r d F a u l t HardFault HardFault中断的直接原因,但是我们知道造成 H a r d F a u l t HardFault HardFault中断的直接原因就是紧挨着 w h i l e ( 1 ) while(1) while(1)死循环处的前一个语句。
下面我们来看一下普通函数的调用过程,测试代码如下所示:
void function_call(u8 p1,u8 p2)
{
u8 index1=p1+p2;
index1++;
return;
}
int main(void)
{
u8 index=1;
index++;
uart_init(115200);
printf("Normal function call test start.\r\n");
function_call(5,index);
index++;
while(1);
}
我们首先单步运行到语句 f u n c t i o n _ c a l l ( 5 , i n d e x ) ; function\_call(5,index); function_call(5,index);,注意我们此时还没有执行该语句,也就是还没有进入函数,只是刚刚执行完语句 “printf(“Normal function call test start.\r\n”);”,此时各个寄存器的值如图23所示。进入函数 f u n c t i o n _ c a l l function\_call function_call之后各个寄存器的值如图24所示。我们可以看到就算此时已经进入被调用的函数,但是调用前后堆栈指针的值并没有变化,说明此时没有响应的值入栈,这时因为编译器优化觉得此时没有什么需要入栈保存的,我们前面也提到过了一般的函数调用与中断函数调用不同,中断函数调用无论什么情况都必须压栈保存至少8个字的值,因为编译器不知道中断什么时候发生,所以每次中断调用都必须压栈保存至少8个字的值。但是对于一般的函数调用,编译器已经知道函数调用的明确位置,因此可以根据实际情况选择压栈保存相关参数或者不压栈。我们这里的测试因为没啥好保存的因此就没有入栈。我们可以看到此时寄存器 R 0 R0 R0和 R 1 R1 R1,存储的是函数 f u n c t i o n _ c a l l function\_call function_call的两个参数的值,前面也提到过了函数调用使用 R 0 R0 R0, R 1 R1 R1, R 2 R2 R2, R 3 R3 R3保存输入参数。此时 L R LR LR寄存器的值应该就是函数 f u n c t i o n _ c a l l function\_call function_call执行完之后的返回地址,我们用 D i s a s s e m b l y Disassembly Disassembly窗口定位到这个地址之后就可以看到大概就是函数 f u n c t i o n _ c a l l function\_call function_call调用语句之后的那个 i n d e x + + index++ index++语句。