本系列文章将以Cortex-M3内核为例,对ARM的异常(exception)进行分析。
文章目录
- 1 异常类型
- 2 优先级
- 3 向量表
1 异常类型
Cortex-M3提供了一个功能丰富的异常体系结构,它支持很多系统异常和外部中断。异常编号1-15表示系统异常,16及以上表示外部中断输入。大多数异常具有可编程优先级,少数具有固定优先级。
Exception Number | Exception Type | Priority | Description |
---|---|---|---|
1 | Reset | -3(Highest) | Reset |
2 | NMI | -2 | Nonmaskable interrupt(external NMI input) |
3 | Hard fault | -1 | 如果其它具体的fault handler没有使能,所有的fault都会归为Hard fault |
4 | MemManage fault | 可编程 | 内存管理错误;MPU(内存保护单元)非法操作或访问了非法地址 |
5 | Bus fault | 可编程 | 总线错误,当AHB接口收到了bus slave的错误响应(指令预取时出错称为prefetch abort,数据访问时出错称为data abort) |
6 | Usage fault | 可编程 | 程序中的错误或访问coprocesser(协处理器) |
7-10 | Reserved | - | - |
11 | SVC | 可编程 | Supervisor Call |
12 | Debug monitor | 可编程 | 代码断点、watch window的变量断点和外部调试请求时触发 |
13 | Reserved | - | - |
14 | PendSV | 可编程 | Pendable Service Call |
15 | SYSTICK | 可编程 | System Tick Timer |
16 | External Interrupt #0 | 可编程 | - |
17 | External Interrupt #1 | 可编程 | - |
… | … | … | - |
255 | External Interrupt #239 | 可编程 | - |
- 前三个异常为负优先级,表示他们的优先级高于其它所有异常
当前正在运行的异常可以通过特殊寄存器IPSR(Interrupt Program Status Register)
或者NVIC中断控制状态寄存器(Nested Vectored Interrupt Controllers Interrupt Control State Register)
中的VECTACTIVE
字段来查看。
- 这里的中断号指的是对Cortex-M3 NVIC的中断输入,在实际的微控制器或SoC中,外部中断输入引脚编号可能与NVIC中的输入编号不同,比如
External Interrupt #0
和External Interrupt #1
可能会分配给片上外设,剩下的再分配给外部,需查询具体芯片的数据手册
当一个使能的异常产生但不能被立即执行的时候(比如有一个更高优先级的中断服务程序正在处理),它会被挂起(有一个例外:当发生这种情况后,可能会触发Hard fault
,具体细节参考 ARM v7-M Architecture Application Level Reference Manual
)。也就是说NVIC的挂起寄存器将保存这个异常请求,直到该异常可以被处理为止。
- 即使请求中断的源撤销了这个请求,产生的异常也还是会被处理。
2 优先级
异常是否可以执行以及何时执行与异常的优先级有关,高优先级异常可以抢占低优先级异常。Cortex-M3支持三种固定的最高优先级(Reset
/NMI
/Hard Fault
)和最大256级的可编程优先级(最多128级抢占,因为还分为抢占优先级和子优先级)。但不同的芯片支持的数量不一样,因为Cortex-M3允许设计师通过删除优先级配置寄存器的LSB来减少支持的优先级层数。
- 例:如果芯片仅允许三位优先级,则读0-4位将返回0,写0-4位的任何数值都将被忽略
芯片支持更多的优先级就意味着芯片内部的门电路就更多,从而增加功耗。对于Cortex-M3来说,允许设置的最低优先级层数为8层(3位)。
问:为什么是删除寄存器的LSB而不是MSB
答:方便移植到其它设备,这样具有4位优先级配置寄存器的设备可以直接移植到具有3位优先级配置寄存器的设备上。如果删除MSB,则移植过后可能产生会优先级反转。
优先级寄存器AIRCR(Application Interrupt and Reset Control Register)
分为两个部分,前半部分是抢占优先级(preempt priority
),后半部分是子优先级(subpriority
)。首先来看看AIRCR
寄存器中的字段:
Bits | Name | Type | Reset Value | Description |
---|---|---|---|---|
31:16 | VECTKEY | R/W | - | 访问密钥,必须写为0x05FA,否则对该寄存器的写操作将被忽略 |
15 | ENDIANNESS | R | - | 1为大端,0为小端,只能在复位后改变 |
10:8 | PRIGROUP | R/W | - | 优先级分组 |
2 | SYSRESETREQ | W | - | 通过访问芯片控制逻辑来产生一个复位 |
1 | VECTCLRACTIVE | W | - | 清除所有异常的激活状态信息,通常用在debug或OS中以允许系统从错误中恢复 |
0 | VECTRESET | W | - | 复位Cortex-M3处理器(除debug部分外),但不会复位处理器外围的电路 |
对于PRIGROUP
来说,有8个分组,如下表所示:
Priority Group | 抢占优先级 | 子优先级 |
---|---|---|
0 | Bit [7:1] | Bit [0] |
1 | Bit [7:2] | Bit [1:0] |
2 | Bit [7:3] | Bit [2:0] |
3 | Bit [7:4] | Bit [3:0] |
4 | Bit [7:5] | Bit [4:0] |
5 | Bit [7:6] | Bit [5:0] |
6 | Bit [7:7] | Bit [6:0] |
7 | None | Bit [7:0] |
抢占优先级决定一个中断可以在另一个中断处理程序执行时产生,子优先级仅在抢占优先级相同时使用。当分组为0时,抢占优先级有128级;当分组为7时,异常之间不会发生抢占,除非发生硬件错误(负优先级的异常)。
例:芯片允许三位优先级,优先级分组为5
3 向量表
当异常发生时,Cortex-M3需要知道异常处理程序的地址并执行,而异常处理函数的地址保存在内存中的向量表中。默认情况下,该向量表保存在0地址处。
Address | Exception Number | Value(Word Size) |
---|---|---|
0x00000000 | - | MSP初始值 |
0x00000004 | 1 | Reset Vector(PC的初始值) |
0x00000008 | 2 | NMI中断处理函数地址 |
0x0000000C | 3 | Hard fault中断处理函数地址 |
… | … | 其它中断处理函数的地址 |
0地址一般为boot程序,它不能在运行时期修改。但是,向量表可以被重定位到其它内存位置或RAM中,这样我们就可以在运行时期修改。这是通过设置NVIC寄存器中的vector table offset regiser
来实现的,如下表所示:
Bits | NAME | Type | Reset Value | Decription |
---|---|---|---|---|
29 | TBLBASE | R/W | 0 | 向量表基地址在code(0)还是RAM(1) |
28:7 | TBLOFF | R/W | 0 | 向量表在code或RAM中的偏移 |
这个offset应该与向量表的大小对齐。
- 由于在boot过程中也可能会发生
NMI
或Hard fault
异常,而其它的异常只要不使能就不会产生,所以这两个向量表也需要写入零地址。当boot结束后,可以将SRAM中的一段内存定义为新的向量表的基地址
例:我们有32个中断向量,加上系统默认的16个异常共48个。首先要2字节对齐到64,接着一个向量表地址占4字节,即最后这个偏移量应该设置为256(0x100)。