目录
[1.1 轮询方式的局限性](#1.1 轮询方式的局限性)
[1.2 中断系统简介](#1.2 中断系统简介)
[二、ARM 中断系统硬件架构](#二、ARM 中断系统硬件架构)
[2.1 通用中断控制器 GIC](#2.1 通用中断控制器 GIC)
[2.1.1 GIC 中断分类](#2.1.1 GIC 中断分类)
[2.1.2 架构组成](#2.1.2 架构组成)
[2.2 协处理器 CP15](#2.2 协处理器 CP15)
[2.2.1 访问指令](#2.2.1 访问指令)
[2.2.2 关键寄存器](#2.2.2 关键寄存器)
[3.1 向量表管理与初始化](#3.1 向量表管理与初始化)
[3.2 GPIO 与中断配置](#3.2 GPIO 与中断配置)
[3.3 中断服务函数 (ISR)](#3.3 中断服务函数 (ISR))
[3.4 主程序流程](#3.4 主程序流程)
[四、软件设计原则:低耦合与 OCP](#四、软件设计原则:低耦合与 OCP)
一、中断的引用
1.1 轮询方式的局限性
在学习中断之前,我们通常使用轮询(Polling) 的方式来处理按键等外设输入:
cpp
while (1)
{
if ((GPIO1->DR & (1 << 18)) == 0) // 检测到低电平
{
led_nor();
beep_nor();
}
delay(0x7FFFFF); // 模拟大量复杂业务
}
**轮询方式的原理:**CPU 周期性地读取 GPIO 引脚状态,判断是否有按键按下。
轮询的致命缺陷:
- 漏查风险:当主程序需要处理大量、复杂且耗时的业务时(例如模拟一个 delay(0x7FFFFF) 的长延时),CPU 无法及时检查按键状态。
- 实时性差:就像"汽车刹车"这种高实时性场景,如果 CPU 正在处理其他任务而无法立即响应刹车信号,后果不堪设想。
因此,我们需要引入中断(Interrupt)。
1.2 中断系统简介
定义:中断是指 CPU 能打断当前正在进行的工作,去处理更为紧急的任务(中断服务函数),并且在处理完后,能自动回到原先的地方继续工作。
中断处理的标准流程:
- 中断请求:中断源(外设)发出信号。
- 中断响应检查:CPU 检查是否响应该中断,以及该中断是否被屏蔽。
- 优先级检查:GIC 判定当前中断的优先级。
- 保护现场:保存被暂停程序的上下文。
- 执行中断服务函数(ISR) :处理紧急任务。
- 恢复现场:还原上下文,继续执行原程序。
二、ARM 中断系统硬件架构
2.1 通用中断控制器 GIC
IMX6ULL 使用的是单核 Cortex-A7,其内部集成了 GIC v2.0 控制器。GIC 负责管理所有的中断源,并决定分发给哪个核心处理。GIC逻辑分区如下:
GIC逻辑分区
2.1.1 GIC 中断分类
根据 ARM GIC v2.0 规范,中断源被分为三类(共 1020 个 ID):
| 类型 | 全称 | ID 范围 | 描述 |
|---|---|---|---|
| SGI | Software-generated Interrupt (软件中断) | 0 - 15 | 由软件向寄存器 GICD_SGIR 写入数据触发,常用于多核间通信。 |
| PPI | Private Peripheral Interrupt (私有中断) | 16 - 31 | 每个核心独有的中断(如 Generic Timer),必须由指定核心处理。 |
| SPI | Shared Peripheral Interrupt (共享中断) | 32 - 1019 | 外部外设产生的中断(如 GPIO、I2C 等),所有核心共享。 |
注意:这里的 SPI 指的是共享中断,不要和通信协议 SPI 总线混淆。
2.1.2 架构组成
GIC 主要由两部分组成:
- Distributor (分发器) :负责检测、排序和分发中断。
- CPU Interface (CPU 接口) :负责将分发器发送的中断信号传输给处理器核心。
2.2 协处理器 CP15
在配置中断和系统底层时,离不开协处理器 CP15。它负责系统控制、MMU 配置、Cache 管理以及虚拟化等任务。
2.2.1 访问指令
CP15 包含 c0 到 c15 组寄存器,通过专用指令访问:
- MRC:读 CP15 寄存器。
- MCR:写 CP15 寄存器。
2.2.2 关键寄存器
- MIDR (Main ID Register, c0) :存储内核的基本信息。
- SCTLR (System Control Register,c1) :系统控制寄存器,其中两个位至关重要:
- Bit 13 (V 位) :控制异常向量表的基地址。
- 0:基地址为 0x00000000(软件可通过 VBAR 重映射)。
- 1:基地址为 0xFFFF0000(高地址向量,不可重映射)。
- Bit 12 (I 位) :指令 Cache 开关。
- Bit 13 (V 位) :控制异常向量表的基地址。
- **VBAR (Vector Base Address Register, c12):**向量基地址寄存器,用于重新映射异常向量表的基地址。在IMX6ULL中,我们将向量表重映射到 0x87800000。
- **CBAR (Configuration Base Address Register, c15):**配置基地址寄存器,指向GIC控制器的物理基地址。
三、代码实现:中断驱动的按键控制
本文的核心目标是实现 "按键中断触发 LED 翻转 + 蜂鸣器翻转",遵循 "低耦合、高可扩展" 的设计原则。
3.1 向量表管理与初始化
首先,我们需要定义一个中断服务函数指针数组,用于注册和管理中断。
cpp
// 定义中断向量表
typedef void (*irq_handler_t)(void);
irq_handler_t Vector_table[160];
// 系统中断初始化
void system_interrupt_init(void)
{
// 1. 重映射异常向量表基地址
__set_VBAR(0x87800000);
// 2. 初始化GIC控制器
GIC_Init();
}
// 注册中断处理函数
int system_interrupt_register(IRQn_Type irq, irq_handler_t handler)
{
if (irq > PMU_IRQ2_IRQn || irq < IOMUXC_IRQn) return -1;
if (handler == NULL) return -2;
Vector_table[irq] = handler; // 将中断号与处理函数关联
return 0;
}
3.2 GPIO 与中断配置
在 key_init 中,我们不仅配置 GPIO 为输入,还需要配置 GPIO 的中断触发方式和使能。
- **复用配置:**将 UART1_CTS_B 复用为 GPIO1_IO18。
- **电气特性:**配置上拉电阻和输入迟滞。
- **中断触发:**配置 GPIO1->ICR2 寄存器,选择下降沿触发(按下时电平由高变低)。
- **中断使能:**配置 GPIO1->IMR 寄存器,置位对应位以允许中断。
cpp
void key_init(void)
{
// 1. 复用功能配置
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
// 2. 电气特性配置
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF0B0);
// 3. 引脚方向设置为输入模式
GPIO1->GDIR &= ~(1 << 18);
// 4. 配置中断触发方式 (ICR2寄存器,bit4-5对应GPIO1_18)
GPIO1->ICR2 |= (3 << 4); // 下降沿触发
// 5. 中断屏蔽寄存器 (IMR) - 解除屏蔽
GPIO1->IMR |= (1 << 18);
// 6. GIC配置
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); // 使能该中断
GIC_SetPriority(GPIO1_Combined_16_31_IRQn, 0); // 设置优先级
// 7. 注册回调函数
system_interrupt_register(GPIO1_Combined_16_31_IRQn, key_irq_handler);
}
3.3 中断服务函数 (ISR)
当中断发生时,CPU会跳转到对应的处理函数。
cpp
void key_irq_handler(void)
{
// 检查GPIO中断状态寄存器 (ISR)
// 注意:ISR用于指示中断是否发生,IMR用于使能/屏蔽。
if ((GPIO1->ISR & (1 << 18)) != 0)
{
// 执行业务逻辑:翻转LED和蜂鸣器
led_nor();
beep_nor();
// 必须手动清除中断标志位
GPIO1->ISR |= (1 << 18);
}
}
3.4 主程序流程
主程序在初始化完成后,进入无限循环,此时CPU可以自由执行其他任务(如 g_delay),而按键的中断请求会被GIC捕获并打断主循环。
cpp
int main(void)
{
system_interrupt_init(); // 初始化中断系统
clock_cg_init(); // 开启时钟
beep_init();
led_init();
key_init(); // 配置按键中断
while (1)
{
g_delay(0x7FFFF); // 主循环执行耗时任务,但不会阻塞中断响应
}
return 0;
}
四、软件设计原则:低耦合与 OCP
在编写中断驱动代码时,我们遵循了良好的软件工程原则:
- 低耦合:
- 中断模块只负责中断的底层处理(如向量表管理)。
- GPIO模块只负责引脚的输入输出。
- 业务逻辑(按键处理)独立封装。
- 开闭原则 (OCP):
- 对修改关闭:核心的中断分发逻辑(system_interrupt_handler)不需要因为新增设备而修改。
- 对扩展开放:当需要增加一个新的按键或设备时,只需调用 system_interrupt_handler 注册一个新的函数指针,无需改动核心代码。