像信号处理一样理解中断:STM32与RK3399中断机制对比及 Linux 驱动开发实战

发布于:2025-09-03 ⋅ 阅读:(19) ⋅ 点赞:(0)

ARM 处理器工作模式、异常向量表与中断机制解析 —— 结合 STM32 与 RK3399 中断对比及按键驱动实例

在嵌入式开发中,ARM 处理器的工作模式、异常向量表、中断向量表是理解中断系统的核心。不同的芯片如 STM32F407(Cortex-M4 内核)和 RK3399(Cortex-A72/A53 内核)在中断处理机制上既有共同点,又有明显区别。本文将系统讲解两者的关系,并通过 RK3399 的按键中断驱动实例来展示代码实现。

一、ARM 处理器的 7 种工作模式

ARM 架构下,CPU 运行时会根据不同的事件切换到不同的模式,共有 7 种:

  1. User 模式(USR):普通用户程序运行模式,无特权。
  2. FIQ 模式(FIQ):快速中断模式,响应高优先级中断。
  3. IRQ 模式(IRQ):普通中断模式,响应一般外设中断。
  4. Supervisor 模式(SVC):系统调用模式,执行 svc 指令时进入。
  5. Abort 模式(ABT):访问非法内存地址时进入。
  6. Undefined 模式(UND):执行未定义指令时进入。
  7. 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. 编写步骤

  1. 确定按键 GPIO 引脚号,并将其配置为外部中断模式。
  2. 将 GPIO 引脚号转换为中断号。
  3. 定义中断处理函数。
  4. 注册中断(申请中断),绑定中断号与 ISR。
  5. 在模块卸载时释放中断。

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. 代码说明

  1. gpio_to_irq(KEY_GPIO):将 GPIO 引脚号转换为内核的中断号。
  2. request_irq():向内核注册一个中断,指定触发方式(上升沿、下降沿等)。
  3. key_irq_handler():中断处理函数,响应按键事件。
  4. free_irq():在驱动卸载时释放中断,避免资源泄漏。

六、总结

  1. ARM 处理器的 7 种模式、异常向量表是架构层面的规定。
  2. 中断向量表由芯片厂商扩展,用于分发多个外设中断。
  3. STM32 的 NVIC 是硬件直接跳转,RK3399 的 GIC 是统一入口再由软件分发。
  4. 在 Linux 下编写中断驱动的步骤包括:引脚到中断号映射、定义 ISR、申请中断、释放中断。
  5. 中断处理与用户态信号处理有异曲同工之妙,都是事件触发 → 注册回调 → 执行回调逻辑。

(完)


网站公告

今日签到

点亮在社区的每一天
去签到