CM3 异常处理
简述 #
异常包括:
- 系统异常:由内核、内核外设(如 SysTick 、MPU 等)触发。
- 外部中断:由设备外设(如 GPIO 、USART 等)触发。
异常编号 #
- 异常编号:硬件层面的编号体系。
- CMSIS-Core 枚举值:软件层面的编号体系,使用负数区分系统异常与外部中断,能提高部分 API 函数的效率(设置优先级)。
| 异常编号 | 异常类型 | CMSIS-Core 枚举 | CMSIS-Core 枚举值 | 异常处理名 | 优先级 |
|---|---|---|---|---|---|
| 1 | 复位 | – | – | Reset_Handler | -3(最高) |
| 2 | 不可屏蔽中断 | NonMaskableInt_IRQn | -14 | NMI_Handler | -2 |
| 3 | 硬件故障 | MemoryManagement_IRQn | -13 | HardFault_Handler | -1 |
| 4 | 内存管理故障 | BusFault_IRQn | -12 | MemManage_Handler | 可编程 |
| 5 | 总线故障 | BusFault_IRQn | -11 | BusFault_Handler | 可编程 |
| 6 | 使用故障 | UsageFault_IRQn | -10 | UsageFault_Handler | 可编程 |
| 7 ~ 10 | – | – | – | – | – |
| 11 | 系统服务调用 | SVCall_IRQn | -5 | SVC_Handler | 可编程 |
| 12 | 调试监控 | DebugMonitor_IRQn | -4 | DebugMon_Handler | 可编程 |
| 13 | – | – | – | – | – |
| 14 | 可挂起的服务调用 | PendSV_IRQn | -2 | PendSV_Handler | 可编程 |
| 15 | 系统节拍定时器 | SysTick_IRQn | -1 | SysTick_Handler | 可编程 |
| 16 ~ 255 | 外部中断 #0 ~ #239 | 设备定义 | 0 ~ 239 | 设备定义 | 可编程 |
向量表偏移寄存器 #
- 复位值为 0 。
- TBLOFF:向量表基地址偏移。
- TBLBASE:
0表示向量表基于 CODE 区,1表示向量表基于 SRAM 区。
向量表 #
- 向量地址 = 向量表偏移地址 + 异常编号 × 4 。
- 向量的最低位必须置 1 以表示 Thumb 状态。
CONTROL 寄存器 #
- 复位值为 0 。
- nPRIV:
0表示特权模式,1表示非特权模式。- 特权模式:可访问 NVIC 、SCB 等寄存器,主要用于 RTOS 的安全隔离。
- SPSEL:
0表示线程模式下使用主栈指针 MSP ,1表示线程模式下使用进程栈指针 PSP 。- 线程模式:上电复位后默认进入。
- 处理模式:发生异常或中断时硬件自动切换,处理模式下 SPSEL 始终为 0 。
- FPCA:
1表示在异常产生时浮点单元在使用中,浮点寄存器需保存(具有 FPU 的 CM4 处理器)。
AAPCS #
ARM 架构过程调用标准(ARM Architecture Procedure Call Standard)。
| 寄存器 | 别名 | 作用 |
|---|---|---|
| R0 | a1 | 参数1 / 返回值 |
| R1 | a2 | 参数2 / 返回值(64 位结果时与 R0 组合) |
| R2 | a3 | 参数3 |
| R3 | a4 | 参数4 |
| R4 ~ R11 | v1 ~ v8 | 变量寄存器 |
| R12 | IP | 内部调用临时寄存器 |
| R13 | SP | 堆栈指针 |
| R14 | LR | 链接寄存器 |
| R15 | PC | 程序计数器 |
此表仅截取了部分内容。
- 调用者保存寄存器:R0 ~ R3 、 R12 、LR 、xPSR 。
- 被调用者保存寄存器:R4 ~ R11 ,被调用的函数需要确保这些寄存器在进入与退出函数时保持一致。
通常 C 函数调用由编译器自动生成汇编代码保存 “调用者保存寄存器” 。但异常处理函数不由用户程序调用,所以异常发生时由硬件自动将 xPSR 、返回地址(PC)、LR 、R12 、R3 、R2 、R1 、R0 共 8 个寄存器压栈。
这 8 个寄存器并非依次压栈,处理器首先将 PC 和 xPSR 压栈,这样在取向量时会尽快更新 PC 。压栈操作中用的栈可以是 MSP 或 PSP ,取决于 CONTROL[1] 。
双字栈对齐 #
双字栈对齐特性由 SCB 中的 CCR 寄存器的第 9 位 STKALIGN 控制。
- Cortex-M3 r0p0 中不可用。
- Cortex-M3 r1p0 和 r1p1 中默认禁止。
- Cortex-M3 r2p0 及之后的版本默认使能。
- Cortex-M4 中默认使能。
AAPCS 中要求函数栈帧的起始地址应是双字(8 字节)对齐的。若异常发生时栈帧未对齐到双字地址上,CM3/4 处理器会自动插入一个字即 SP = SP - 4,然后将压栈的 xPSR 值的第 9 位设置为 1 (表示 SP 调整过)。异常返回时,若栈帧中的 xPSR(bit9=1), 则出栈后 SP = SP + 4 。
xPSR 寄存器的中的第 9 位不会被修改,修改的是栈帧中的 xPSR 值的第 9 位。
普通函数调用不会触发硬件自动的双字栈对齐,应该是编译器通过汇编指令调整的 SP 对齐(存疑)。
异常是突然发生的,CPU 不知道中断何时来,也不知道来的时候 SP 是几字节对齐的,所以需要硬件自动的双字栈对齐。
EXC_RETURN #
EXC_RETURN 是一个特殊值,处理器在进入异常处理函数后,会设置 LR = EXC_RETURN 。当异常函数返回时会将 LR 加载到 PC ,硬件检测到 PC = EXC_RETURN 后会触发异常返回机制。
| 栈帧类型 | 返回模式 | 返回栈 | |
|---|---|---|---|
0 | 26 字 | 处理模式 | MSP |
1 | 8 字 | 线程模式 | PSP |
C 函数调用流程 #
其汇编的流程大致如下:
.global _start
_start:
MOV R0, #3 /* 参数 1 */
MOV R1, #5 /* 参数 2 */
LDR R4, =0x1234 /* 测试 R4-R6 是否会被破坏 */
LDR R5, =0x5678 /* 测试 R4-R6 是否会被破坏 */
LDR R6, =0x9ABC /* 测试 R4-R6 是否会被破坏 */
BL add /* 设置 LR = 返回地址并跳转到 add */
LDR R3, =0x8888 /* LR = 此处地址:返回后从这里继续执行 */
add:
PUSH {R4-R6,LR} /* 保存 R4-R6 和返回地址 LR */
MOV R4, R0
MOV R5, R1
ADD R6, R4, R5
MOV R0, R6 /* R0 保存返回值 */
POP {R4-R6,PC} /* 恢复 R4-R6,将 LR 的值弹入 PC 寄存器 */
对于 PUSH 指令,编号高的寄存器总是先存储;对于 POP 指令,编号低的寄存器总是先加载。
- 寄存器保存使用 PUSH 或 STM 等指令,取决于编译器。
- 要保存哪些寄存器?编译器会按 AAPCS 保存必要的寄存器并生成相应的汇编指令(并不是所有的函数都需保存)。
- 寄存器恢复使用 POP 或 LDM 等指令,取决于编译器。
- 函数如何返回?只要将返回地址(LR)加载到 PC 寄存器中即可,使用 POP 或 BX 等指令,取决于编译器。
异常处理函数调用流程 #
总结 #
异常处理相当于在标准 C 函数套了一层 “硬件封装” ,异常处理函数的进入与返回都由硬件自动完成。 标准 C 函数的进入与返回则是由编译器按照 AAPCS 约定生成的汇编指令完成。