ARM 处理器工作模式、异常向量表与中断机制解析 —— 结合 STM32 与 RK3399 中断对比及按键驱动实例
在嵌入式开发中,ARM 处理器的工作模式、异常向量表、中断向量表是理解中断系统的核心。不同的芯片如 STM32F407(Cortex-M4 内核)和 RK3399(Cortex-A72/A53 内核)在中断处理机制上既有共同点,又有明显区别。本文将系统讲解两者的关系,并通过 RK3399 的按键中断驱动实例来展示代码实现。
一、ARM 处理器的 7 种工作模式
ARM 架构下,CPU 运行时会根据不同的事件切换到不同的模式,共有 7 种:
- User 模式(USR):普通用户程序运行模式,无特权。
- FIQ 模式(FIQ):快速中断模式,响应高优先级中断。
- IRQ 模式(IRQ):普通中断模式,响应一般外设中断。
- Supervisor 模式(SVC):系统调用模式,执行
svc
指令时进入。 - Abort 模式(ABT):访问非法内存地址时进入。
- Undefined 模式(UND):执行未定义指令时进入。
- System 模式(SYS):与 User 模式类似,但具有特权,通常内核运行在此模式。
CPU 在不同模式下有独立的寄存器组和堆栈,从而实现快速上下文切换。
二、异常向量表
当 CPU 遇到异常或中断时,需要跳转到相应的入口函数。ARM 架构规定了异常向量表,通常放在固定地址(0x00000000 或 0xFFFF0000)。
ARMv7 的异常向量表示例:
地址偏移 | 异常类型 | 说明 |
---|---|---|
0x00 | Reset | 上电复位 |
0x04 | Undefined Instruction | 未定义指令 |
0x08 | SWI (SVC) | 软件中断 |
0x0C | Prefetch Abort | 预取异常 |
0x10 | Data Abort | 数据访问异常 |
0x14 | 保留 | - |
0x18 | IRQ | 普通中断 |
0x1C | FIQ | 快速中断 |
三、中断向量表
ARM 架构的异常向量表里只有 IRQ/FIQ 两个入口,但一个芯片上可能有数百个外设中断源,因此需要中断控制器来管理。
STM32(Cortex-M 内核)
使用 NVIC(Nested Vectored Interrupt Controller),中断向量表直接存放在 Flash 中,每个中断源对应一个函数入口地址,CPU 进入中断时会直接跳转。RK3399(Cortex-A 内核)
使用 GIC(Generic Interrupt Controller)。所有外设中断先统一进入 IRQ 入口,由 GIC 确定中断号,再由内核软件框架分发给具体的驱动 ISR。
四、STM32 与 RK3399 中断机制对比
特性 | STM32F407 (Cortex-M4) | RK3399 (Cortex-A72/A53) |
---|---|---|
向量表 | 每个外设独立入口,硬件自动分发 | 统一入口,软件分发 |
中断控制器 | NVIC | GIC |
压栈机制 | 硬件自动保存寄存器 | 内核汇编保存现场 |
使用场景 | MCU 裸机/RTOS | SoC,运行 Linux/Android |
五、RK3399 按键中断驱动实例
在 Linux 内核中,中断的处理方式非常类似信号处理:
- 信号:由内核传递到用户态,由用户定义的 handler 来响应。
- 中断:由硬件触发,进入内核,通过中断处理函数来完成响应。
下面通过一个按键中断驱动示例来讲解:
1. 编写步骤
- 确定按键 GPIO 引脚号,并将其配置为外部中断模式。
- 将 GPIO 引脚号转换为中断号。
- 定义中断处理函数。
- 注册中断(申请中断),绑定中断号与 ISR。
- 在模块卸载时释放中断。
2. 关键 API 函数
- 申请中断:
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
- 释放中断:
free_irq(unsigned int irq, void *dev)
- 定义中断处理函数:
irqreturn_t handler(int irq, void *dev_id)
- GPIO 转中断号:
gpio_to_irq(unsigned int gpio)
3. 示例代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/irqreturn.h>
#define KEY_GPIO 23 // 假设按键接在 GPIO23
static int key_irq; // 保存对应的中断号
// 中断处理函数
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
printk(KERN_INFO "Key interrupt triggered! irq=%d\n", irq);
// 这里可以添加按键消抖、状态处理等逻辑
return IRQ_HANDLED;
}
// 模块加载函数
static int __init key_irq_init(void)
{
int ret;
// 将 GPIO 引脚转换为中断号
key_irq = gpio_to_irq(KEY_GPIO);
if (key_irq < 0) {
printk(KERN_ERR "Failed to map GPIO to IRQ\n");
return key_irq;
}
// 申请中断
// IRQF_TRIGGER_FALLING 表示下降沿触发
ret = request_irq(key_irq, key_irq_handler, IRQF_TRIGGER_FALLING,
"key_irq_demo", NULL);
if (ret) {
printk(KERN_ERR "Failed to request IRQ\n");
return ret;
}
printk(KERN_INFO "Key IRQ module loaded, GPIO=%d, IRQ=%d\n", KEY_GPIO, key_irq);
return 0;
}
// 模块卸载函数
static void __exit key_irq_exit(void)
{
free_irq(key_irq, NULL); // 释放中断
printk(KERN_INFO "Key IRQ module unloaded\n");
}
module_init(key_irq_init);
module_exit(key_irq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("CSDN Example");
MODULE_DESCRIPTION("RK3399 Key Interrupt Demo");
4. 代码说明
- gpio_to_irq(KEY_GPIO):将 GPIO 引脚号转换为内核的中断号。
- request_irq():向内核注册一个中断,指定触发方式(上升沿、下降沿等)。
- key_irq_handler():中断处理函数,响应按键事件。
- free_irq():在驱动卸载时释放中断,避免资源泄漏。
六、总结
- ARM 处理器的 7 种模式、异常向量表是架构层面的规定。
- 中断向量表由芯片厂商扩展,用于分发多个外设中断。
- STM32 的 NVIC 是硬件直接跳转,RK3399 的 GIC 是统一入口再由软件分发。
- 在 Linux 下编写中断驱动的步骤包括:引脚到中断号映射、定义 ISR、申请中断、释放中断。
- 中断处理与用户态信号处理有异曲同工之妙,都是事件触发 → 注册回调 → 执行回调逻辑。
(完)