I.MX6U 裸机开发13.中断、中断向量表、中断函数
- 一、STM32 的中断介绍
- 1. 中断概念
- 2. 中断向量表
- 3. 中断向量偏移
- 4. NVIC 嵌套向量中断控制器
- 二、Cortext-A7中断系统
- 1. Cortext-A7的中断向量表
- 2. 中断向量偏移
- 3. GIC (Generic Interrupt Controller) 中断控制器
- (1)GIC V2整体框图
- (2)GIC 组件
- (3)GIC 版本
- (4)GIC 中断源分类
- (5)中断ID
- (6) 中断服务函数
- 三、中断向量表编写
- 1. 原理图
- 2. 创建项目
- 3. 启动文件start.S 添加 中断向量表
- 4. 实现中断服务函数
- 5. 关闭I Cache , D Cache , MMU
- (1)关闭I Cache, D Cache, MMU的作用
- (2)CP15 协处理器
- Control Register(c1)
- Translation Table Register(c2)
- Domain Access Control Register(c3)
- Fault Status Registers(c5)
- Fault Address Register(c6)
- Cache Operation Registers(c7)
- TLB Operations Registers (c8)
- 操作CP15,使用下面指令:
- MCR指令
- MRC
- (3)代码实现
- (4)设置中断向量偏移
- 6. 设置处理器工作模式下的SP指针
- 7. 清除BSS段
- 8. 开关中断
一、STM32 的中断介绍
本文先对比在STM32的中断处理机制。
1. 中断概念
中断是指在程序执行过程中,当某个事件发生时,CPU 暂停当前的程序执行,转而去执行与该事件相关的中断服务程序(ISR)。中断可以分为外部中断和内部中断。外部中断通常由外部设备(如按键、传感器)触发,而内部中断则由内部事件(如定时器溢出、ADC 转换完成)触发。
2. 中断向量表
中断向量表是一个存储在特定内存地址处的表格,其中包含了每个中断源对应的中断服务程序的入口地址。在 STM32 微控制器中,中断向量表通常位于内存的起始地址(0x00000000)。每个中断源在中断向量表中都有一个固定的位置,CPU 根据中断源的编号从中断向量表中读取对应的中断服务程序的地址,并跳转到该地址执行中断服务程序。
3. 中断向量偏移
中断向量偏移是指中断向量表在内存中的起始地址的偏移量。在某些情况下,可能需要将中断向量表移动到内存的其他位置,这时就需要设置中断向量偏移。STM32 微控制器通过 SCB->VTOR
寄存器来设置中断向量表的基地址,从而实现中断向量偏移。
4. NVIC 嵌套向量中断控制器
NVIC 在 ARM Cortex-M 中负责管理中断和异常。它通过提供嵌套中断、优先级级别和向量表重定位等功能,提高了中断处理的效率和灵活性。
NVIC 主要功能如下:
- 嵌套中断:允许高优先级中断抢占低优先级中断,从而实现更快速和高效的中断处理。
- 优先级级别:支持多个中断优先级级别,允许对中断处理进行精细控制。
- 向量表重定位:允许将中断向量表重定位到不同的内存位置,这对于引导加载程序和动态中断管理非常有用。
- 中断屏蔽:提供启用或禁用特定中断的机制,允许选择性地处理中断。
二、Cortext-A7中断系统
1. Cortext-A7的中断向量表
Cortex-A 处理器的中断向量表通常包含以下几种中断和异常向量:
向量地址 | 向量名称 | 描述 |
0x00000000 | Reset Vector | 复位向量 |
0x00000004 | Undefined Instruction Vector | 未定义指令异常向量 |
0x00000008 | Software Interrupt Vector | 软件中断向量 |
0x0000000C | Prefetch Abort Vector | 预取指令中止向量 |
0x00000010 | Data Abort Vector | 数据中止向量 |
0x00000014 | Reserved | 保留 |
0x00000018 | IRQ Vector | 外部中断请求向量 |
0x0000001C | FIQ Vector | 快速中断请求向量 |
其中我们重点关注的是: IRQ。 Cortext-A的中断向量表需要开发者自己定义。
2. 中断向量偏移
对于Cortext-A处理器,中断向量表通常位于内存的起始地址(0x00000000)。
程序的入口地址,通常是复位向量所在的地址,在Cortext-A中,复位向量是中断向量表的第一个条目,即位于 0x00000000,处理器复位时,会从该地址读取程序入口地址。
本系列文章裸机历程,程序入口地址是 0x87800000,不是0x00000000,因此需要设置中断向量偏移。
3. GIC (Generic Interrupt Controller) 中断控制器
GIC 类似于NVIC。
GIC 提供了灵活的中断管理机制,包括中断优先级、中断分发和中断处理等功能。
(1)GIC V2整体框图
(2)GIC 组件
- Distributor:负责中断的分发和管理,包括中断的使能、优先级设置和目标 CPU 核心的选择。
- CPU Interface:每个 CPU 核心都有一个 CPU 接口,用于接收和处理中断请求。
(3)GIC 版本
GIC有多个版本,主要区别如下:
- GICv1:基本中断管理功能。
- GICv2:增强的中断管理功能和多核支持。
- GICv3:扩展的中断管理功能和系统寄存器接口。
- GICv4:增加了对虚拟化环境的支持。
IMX6ULL 使用GICV2,主要特性:
- 增强功能:在 GICv1 的基础上增加了更多的中断管理功能。
- 中断数目:支持最多 1020 个中断。
- 中断优先级:支持 256 级中断优先级。
- 多核支持:支持多核处理器的中断分发和管理。
(4)GIC 中断源分类
GIC 中的中断源可以分为以下几类:
中断类型 | 描述 |
SGI (Software Generated Interrupts) | 软件生成的中断,通常用于进程间通信。 |
PPI (Private Peripheral Interrupts) | 私有外设中断,每个 CPU 核心都有独立的 PPI。 |
SPI (Shared Peripheral Interrupts) | 共享外设中断,所有 CPU 核心共享的中断源。 |
LPI (Locality-specific Peripheral Interrupts) | 特定于 GICv3 和 GICv4,用于支持更大规模的中断,IMX6ULL不支持。 |
(5)中断ID
中断ID(Interrupt ID)是用于标识特定中断源的唯一标识符。每个中断源在中断控制器中都有一个唯一的中断ID,用于区分不同的中断源。
- ID0~ID15:给SGI
- ID16~ID31:给PPI
- ID32~ID1019:给SPI
对于SPI,由芯片厂商定义 。 对于IMX6ULL,在参考手册P183页有详细描述。
根据文档描述, IMX6ULL 支持128个外部中断。前32个给CPU使用。
(6) 中断服务函数
在 i.MX6ULL 处理器中,中断服务函数(ISR, Interrupt Service Routine)用于处理中断事件。以下是如何定义和使用中断服务函数的步骤:
- 定义中断服务函数:编写中断处理代码。
- 注册中断服务函数:将中断服务函数注册到特定的中断源。
- 使能中断:配置中断控制器以使能特定中断。
三、中断向量表编写
本文要实现GPIO捕获按键中断。
1. 原理图
key0 使用 UART1_CTS IO。
2. 创建项目
从上章节复制文件夹。
修改项目名称为 gpio_irq。
3. 启动文件start.S 添加 中断向量表
首先是复位中断,写代码:
_start:
ldr pc, =Reset_Handler
解释 :
- ldr:加载指令,用于将一个值加载到寄存器中。
- pc:程序计数器寄存器,指向当前执行的指令地址。
- =Reset_Handler:表示 Reset_Handler 函数的地址。
这条指令的效果是将 Reset_Handler 函数的地址加载到 pc 寄存器中,从而实现跳转到 Reset_Handler 函数执行。
类似的,添加其它中断向量:
_start:
ldr pc, =Reset_Handler /* 设置复位中断处理函数*/
ldr pc, =Undefine_Handler /* 设置未定义指令中断处理函数*/
ldr pc, =SVC_Handler /* 设置SVC中断处理函数*/
ldr pc, =PreAbort_Handler /* 设置预取中断处理函数*/
ldr pc, =DataAbort_Handler /* 设置数据中断处理函数*/
ldr pc, =IRQ_Handler /* 设置IRQ中断处理函数*/
ldr pc, =FIQ_Handler /* 设置FIQ中断处理函数*/
4. 实现中断服务函数
先写上空函数:
_start:
ldr pc, =Reset_Handler /* 设置复位中断处理函数*/
ldr pc, =Undefine_Handler /* 设置未定义指令中断处理函数*/
ldr pc, =SVC_Handler /* 设置SVC中断处理函数*/
ldr pc, =PreAbort_Handler /* 设置预取中断处理函数*/
ldr pc, =DataAbort_Handler /* 设置数据中断处理函数*/
ldr pc, =IRQ_Handler /* 设置IRQ中断处理函数*/
ldr pc, =FIQ_Handler /* 设置FIQ中断处理函数*/
Reset_Handler:
b Reset_Handler
Undefine_Handler:
b Undefine_Handler
SVC_Handler:
b SVC_Handler
PreAbort_Handler:
b PreAbort_Handler
DataAbort_Handler:
b DataAbort_Handler
IRQ_Handler:
b IRQ_Handler
FIQ_Handler:
b FIQ_Handler
5. 关闭I Cache , D Cache , MMU
(1)关闭I Cache, D Cache, MMU的作用
在启动代码中关闭I Cache(指令缓存)、D Cache(数据缓存)和MMU(内存管理单元),可以确保系统在初始化阶段的稳定性和可预测性。以下是具体原因:
- 确保内存一致性:在系统启动时,内存可能尚未完全初始化。如果缓存和MMU开启,可能会导致内存访问不一致的问题。关闭缓存和MMU可以确保所有内存访问都是直接的、可预测的。
- 简化初始化过程:在初始化阶段,关闭缓存和MMU可以简化内存管理和地址映射的复杂性。这样可以避免在初始化过程中处理缓存一致性和地址转换的问题。
- 避免缓存污染:在系统启动时,缓存中的数据可能是无效的或过时的。关闭缓存可以避免在初始化过程中使用这些无效数据。
- 确保正确的外设初始化:在初始化外设时,通常需要直接访问物理地址。关闭MMU可以确保所有地址访问都是物理地址,避免地址转换带来的问题。
(2)CP15 协处理器
在ARM架构中,CP15(协处理器15)是一个系统控制协处理器,用于管理系统控制寄存器,包括缓存和MMU的控制。通过访问CP15的寄存器,可以启用或禁用I Cache、D Cache和MMU。
CP15主要的寄存器有:
Control Register(c1)
控制MMU、缓存、对齐检查、分支预测等功能,常用位有:
- 0:MMU使能
- 2:D Cache 使能
- 12: I Cache 使能
Translation Table Register(c2)
存储一级页表的基地址,用于地址转换。
Domain Access Control Register(c3)
控制域访问权限 , 用于内存保护。
Fault Status Registers(c5)
存储最近一次发生的故障状态信息。
Fault Address Register(c6)
存储最近一次发生故障的地址。
Cache Operation Registers(c7)
控制缓存操作,如无效化、清除等。
TLB Operations Registers (c8)
控制 TLB(Translation Lookaside Buffer)操作,如无效化等。
操作CP15,使用下面指令:
- MRC:读CP15寄存器
- MCR:写CP15寄存器
MCR指令
语法 : MCR p15, <op1>, <Rt>, <CRn>, <CRm>, <op2>
- p15:协处理器编号,CP15是系统控制协处理器。
- :操作码1。
- :源寄存器,数据将从这个寄存器写入协处理器。
- :主操作寄存器。
- :次操作寄存器。
- :操作码2。
示例:
MCR p15, 0, r0, c1, c0, 0 /* 将r0中的数据写入CP15的c1寄存器 */
MRC
语法 : MRC p15, <op1>, <Rt>, <CRn>, <CRm>, <op2>
- p15:协处理器编号,CP15是系统控制协处理器。
- :操作码1。
- :目标寄存器,数据将被读取到这个寄存器。
- :主操作寄存器。
- :次操作寄存器。
- :操作码2。
示例:
MRC p15, 0, r0, c1, c0, 0 /* 从CP15的c1寄存器读取数据到r0 */
(3)代码实现
MRC P15, 0, R0, C1, C0, 0 /* 从CP15的c1寄存器读取当前值到r0 */
BIC R0, R0, #0x0001 /* 清除r0的第0位,关闭MMU */
BIC R0, R0, #(1<<1) /* 清除r0的第1位,关闭对齐检查 */
BIC R0, R0, #(1<<2) /* 清除r0的第2位,关闭D Cache */
BIC R0, R0, #(1<<12) /* 清除r0的第12位,关闭I Cache */
BIC R0, R0, #(1<<11) /* 清除r0的第11位,关闭高速缓存(分支预测) */
MCR P15, 0, R0, C1, C0, 0 /* 将修改后的值写回CP15的c1寄存器 */
(4)设置中断向量偏移
/** (设置中断向量偏移)跳转到用户程序入口点 */
LDR R0, =0x87800000
DSB
ISB
MCR P15, 0, R0, C12, C0, 0
DSB
ISB
6. 设置处理器工作模式下的SP指针
在使用中断时,需要为IRQ模式设置堆栈指针(SP),以确保在处理中断时有一个有效的堆栈空间。
最好为所有处理器模式(如FIQ、SVC、Abort、Undefined、System、User)设置堆栈指针,以确保在这些模式下执行代码时有足够的堆栈空间。
下面是设置代码:
/** 设置处理器进入IRQ模式 */
MRS R0, CPSR /* 读取 CPSR 到R0*/
BIC R0, R0, #0x1f /* 清除 CPSR 的bit4-0*/
ORR R0, R0, #0x12 /* 使用 IRQ 模式*/
MSR CPSR, R0 /* 将 R0 写入到 CPSR*/
LDR PC, =0x80600000 /* 设置 IRQ模式的PC,即用户程序入口点 */
/* 设置处理器进入SYS模式 */
MRS R0, CPSR /* 读取 CPSR 到R0*/
BIC R0, R0, #0x1f /* 清除 CPSR 的bit4-0*/
ORR R0, R0, #0x1f /* 使用 SYS 模式*/
MSR CPSR, R0 /* 将 R0 写入到 CPSR*/
LDR PC, =0x80400000 /* 设置 SYS模式的PC,即用户程序入口点 */
/* 设置处理器进入SVC模式 */
MRS R0, CPSR /* 读取 CPSR 到R0*/
BIC R0, R0, #0x1f /* 清除 CPSR 的bit4-0*/
ORR R0, R0, #0x13 /* 使用 SVC 模式*/
MSR CPSR, R0 /* 将 R0 写入到 CPSR*/
LDR PC, =0x80200000 /* 设置 SVC模式的PC,即用户程序入口点 */
7. 清除BSS段
/*清除BSS段*/
LDR R0, _bss_start
LDR R1, _bss_end
MOV R2, #0
bss_loop:
STMIA R0!, {R2}
CMP R0, R1 /* 比较R0和R1里面的值 */
BLE bss_loop /*如果R0地址小于等于R1,继续清除bss段*/
8. 开关中断
在操作 i Cache , d Cache,MMU、清BSS段时,先关闭总中断,使用指令:
cpsid i
打开中断,使用:
cpsie i
最后完成的start.S
.global _start
.global _bss_start
_bss_start:
.word __bss_start
.global _bss_end
_bss_end:
.word __bss_end
_start:
LDR PC, =Reset_Handler /* 设置复位中断处理函数*/
LDR PC, =Undefined_Handler /* 设置未定义指令中断处理函数*/
LDR PC, =SVC_Handler /* 设置SVC中断处理函数*/
LDR PC, =PreAbort_Handler /* 设置预取中断处理函数*/
LDR PC, =DataAbort_Handler /* 设置数据中断处理函数*/
LDR PC, =NotUsed_Handler /* 未使用中断 */
LDR PC, =IRQ_Handler /* 设置IRQ中断处理函数*/
LDR PC, =FIQ_Handler /* 设置FIQ中断处理函数*/
Reset_Handler:
cpsid i /* 关闭全局中断 */
MRC P15, 0, R0, C1, C0, 0 /* 从CP15的c1寄存器读取当前值到R0 */
BIC R0, R0, #0x0001 /* 清除R0的第0位,关闭MMU */
BIC R0, R0, #(1<<1) /* 清除R0的第1位,关闭对齐检查 */
BIC R0, R0, #(1<<2) /* 清除R0的第2位,关闭D Cache */
BIC R0, R0, #(1<<12) /* 清除R0的第12位,关闭I Cache */
BIC R0, R0, #(1<<11) /* 清除R0的第11位,关闭高速缓存(分支预测) */
MCR P15, 0, R0, C1, C0, 0 /* 将修改后的值写回CP15的c1寄存器 */
/** (设置中断向量偏移)跳转到用户程序入口点 */
LDR R0, =0x87800000
DSB
ISB
MCR P15, 0, R0, C12, C0, 0
DSB
ISB
/*清除BSS段*/
LDR R0, _bss_start
LDR R1, _bss_end
MOV R2, #0
bss_loop:
STMIA R0!, {R2}
CMP R0, R1 /* 比较R0和R1里面的值 */
BLE bss_loop /*如果R0地址小于等于R1,继续清除bss段*/
/** 设置处理器进入IRQ模式 */
MRS R0, CPSR /* 读取 CPSR 到R0*/
BIC R0, R0, #0x1f /* 清除 CPSR 的bit4-0*/
ORR R0, R0, #0x12 /* 使用 IRQ 模式*/
MSR CPSR, R0 /* 将 R0 写入到 CPSR*/
LDR SP, =0x80600000 /* 设置 IRQ模式的PC,即用户程序入口点 */
/* 设置处理器进入SYS模式 */
MRS R0, CPSR /* 读取 CPSR 到R0*/
BIC R0, R0, #0x1f /* 清除 CPSR 的bit4-0*/
ORR R0, R0, #0x1f /* 使用 SYS 模式*/
MSR CPSR, R0 /* 将 R0 写入到 CPSR*/
LDR SP, =0x80400000 /* 设置 SYS模式的PC,即用户程序入口点 */
/* 设置处理器进入SVC模式 */
MRS R0, CPSR /* 读取 CPSR 到R0*/
BIC R0, R0, #0x1f /* 清除 CPSR 的bit4-0*/
ORR R0, R0, #0x13 /* 使用 SVC 模式*/
MSR CPSR, R0 /* 将 R0 写入到 CPSR*/
LDR SP, =0x80200000 /* 设置 SVC模式的PC,即用户程序入口点 */
cpsie i /* 打开全局中断 */
B main /* 跳转到C语言main函数*/
Undefined_Handler:
LDR R0, =Undefined_Handler
BX R0
SVC_Handler:
LDR R0, =SVC_Handler
BX R0
PreAbort_Handler:
LDR R0, =PreAbort_Handler
BX R0
DataAbort_Handler:
LDR R0, =DataAbort_Handler
BX R0
/* 未使用的中断 */
NotUsed_Handler:
LDR R0, =NotUsed_Handler
BX R0
IRQ_Handler:
LDR R0, =IRQ_Handler
BX R0
FIQ_Handler:
LDR R0, =FIQ_Handler
BX R0