【ARM嵌入式学习笔记Day5】一文打通GPIO中断全链路:从硬件引脚→GIC→汇编入口→C语言处理

文章目录


前言

今天是ARM嵌入式学习的第四天,核心攻克了Cortex-A7的中断系统。从最底层的GPIO硬件引脚、IOMUX引脚复用,到GIC通用中断控制器的工作原理,再到CPU的异常向量表、汇编中断入口,最后落地到C语言的二级中断向量表和外设驱动代码,完整走通了「按键按下→LED翻转」的整条中断链路。

本文把今天所有知识点按架构梳理,结合寄存器、汇编、C代码三层拆解,彻底搞懂中断到底是怎么从硬件跑到软件里的。


一、中断系统整体三层架构

整个ARM中断系统从上到下可以分为三层,对应硬件信号的流向:

  1. 外设层:以GPIO、UART、定时器等为代表的片内外设,负责检测硬件事件,产生中断标志信号。
  2. 中断控制器层(GIC):通用中断控制器,是外设和CPU之间的"总调度室",负责所有中断的使能、屏蔽、优先级仲裁、CPU路由。
  3. CPU内核层:Cortex-A7内核,接收GIC的中断请求,自动保存程序状态,跳转到异常向量表对应的中断入口。

中断信号的完整流向: GPIO引脚电平变化 → GPIO外设置中断标志 → GIC仲裁路由 → CPU触发IRQ异常 → 执行中断处理代码


二、GIC中断控制器:三类中断源

GICv2(Cortex-A7配套的中断控制器版本)将所有中断按来源和使用场景分为三大类,这是理解中断号的基础:

中断类型 全称 中断号范围 中断来源 核心特性 典型用途
SGI Software Generated Interrupt(软件生成中断) 0~15 软件写寄存器触发 可定向发送给任意CPU核 多核核间通信(IPC)
PPI Private Peripheral Interrupt(私有外设中断) 16~31 CPU核内置私有外设 每个核独立一套,不可路由 内核通用定时器、PMU性能监控
SPI Shared Peripheral Interrupt(共享外设中断) 32~1019 片上公共外设 全局唯一,可路由到任意CPU核 UART、GPIO、SPI、以太网等外设中断

我们日常裸机开发接触最多的就是SPI,比如GPIO、串口的中断都属于这一类。


三、GICC核心寄存器:中断的"入场"与"退场"握手

每个CPU核都对应一个独立的GICC(CPU接口),它是GIC和CPU直接交互的窗口,有两个最核心的寄存器,控制着中断的完整生命周期。

1. GICC_IAR:中断应答寄存器

  • 全称:Interrupt Acknowledge Register
  • 偏移地址 :GICC基地址 + 0x0C
  • 操作方式:只读
  • 核心作用 :软件读取该寄存器,硬件自动完成两件事:
    1. 返回当前触发的中断号(Interrupt ID),后续作为C函数的入参
    2. GIC内部将该中断状态从Pending(挂起)修改为Active(正在处理)

2. GICC_EOIR:中断结束寄存器

  • 全称:End Of Interrupt Register
  • 偏移地址 :GICC基地址 + 0x10
  • 操作方式:只写
  • 核心作用:软件将中断号写入该寄存器,通知GIC中断处理完毕,GIC清除该中断的Active状态。

❗ 重要提醒:如果不写EOIR,GIC会认为中断一直在处理中,同优先级及更低优先级的中断将永远无法触发,表现为"进一次中断后再也不进了"。


四、中断全链路执行流程详解

这是今天最核心的内容,我们按时间顺序,把从复位启动到中断触发再到返回的完整流程拆成5个阶段,结合汇编和C代码彻底讲透。

阶段1:系统启动与中断初始化

芯片上电后,先执行汇编复位入口,完成最底层硬件初始化,再进入C语言的外设初始化。

1.1 汇编复位流程(_reset_handler)
  1. 关闭总中断,配置系统控制寄存器,打开指令Cache,选择VBAR作为向量表基地址
  2. 分别设置IRQ模式和系统模式的栈指针(SP),两种模式使用独立的栈空间
  3. 初始化BSS段(全局变量清零),最终跳转到C语言的main函数
1.2 C语言中断初始化

在main中调用初始化函数,完成三层配置:

  1. GPIO层:配置引脚复用为GPIO、设置输入方向、配置中断触发方式、打开引脚中断屏蔽
  2. GIC层:设置中断优先级、使能对应中断号
  3. 软件层:向二级中断向量表注册外设的中断处理函数

阶段2:硬件中断触发

  1. 按键按下,GPIO引脚电平变化,符合配置的触发条件(上升沿/下降沿)
  2. GPIO外设置位ISR(中断状态寄存器)对应位,向GIC发出中断请求
  3. GIC分发器(GICD)仲裁,选出当前最高优先级中断,路由到目标CPU的GICC
  4. GICC判断中断优先级高于阈值,向CPU的IRQ引脚输出有效信号

阶段3:汇编层:IRQ中断入口处理

CPU检测到IRQ信号后,硬件自动保存CPSR和返回地址,切换到IRQ模式,跳转到_irq_handler,随后执行汇编代码:

  1. 修正返回地址sub lr, lr, #4,适配ARM三级流水线特性,保证返回地址正确
  2. 保存现场:将r0~r12、lr压入IRQ栈,防止破坏主程序的寄存器环境
  3. 读取中断号:读GICC_IAR寄存器,拿到中断号存在r0中
  4. 切换系统模式:切换到System模式,使用系统大栈,支持C函数调用和中断嵌套
  5. 调用C语言处理函数bl system_interrupt_handler,通过r0寄存器传递中断号参数

阶段4:C语言层:中断分发与业务处理

进入C语言的中断分发函数后,通过二级向量表找到对应的外设处理函数:

  1. 校验中断号合法性,防止数组越界访问
  2. VectorIrq二级向量表中,按中断号取出对应的处理函数地址
  3. 执行外设中断处理函数:
    • 读GPIO的ISR寄存器,判断具体是哪个引脚触发的中断(因为多个引脚共用一个GIC中断号)
    • 执行业务逻辑:翻转LED、控制蜂鸣器等
    • 写GPIO的ISR寄存器,清除中断标志(W1C机制,写1清0)

阶段5:中断返回与现场恢复

C函数执行完毕,返回汇编入口,完成中断收尾:

  1. 切回IRQ模式,恢复r0、r1寄存器
  2. 写EOIR寄存器:将中断号写入GICC_EOIR,通知GIC中断处理完成
  3. 恢复现场:从栈中弹出r0~r12,将返回地址直接赋值给PC,同时恢复CPSR
  4. 回到主程序被打断的位置,继续执行

五、实战代码解析:GPIO按键中断驱动

下面结合实际代码,看每个配置在驱动里是怎么落地的。

5.1 按键初始化函数 key_init

复制代码
void key_init(void)
{
    //1. 引脚复用:将UART1_CTS_B引脚复用为GPIO1_IO18,关闭强制输入
    IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
    
    //2. 引脚电气特性:配置上拉、施密特触发等参数
    IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF0B0);
    
    //3. GPIO方向:第18位清零,设置为输入模式
    GPIO1->GDIR &= ~(1 << 18);
    
    //4. 中断触发方式:上升沿触发(ICR2管pin16~31,pin18对应bit4~5)
    GPIO1->ICR2 |= (3 << 4);
    
    //5. 引脚中断屏蔽:置1使能该引脚的中断输出
    GPIO1->IMR |= (1 << 18);
    
    //6. GIC设置中断优先级(99为对应中断号,0为最高优先级)
    GIC_SetPriority(99, 0);
    
    //7. 注册中断处理函数到软件二级向量表
    system_interrupt_register(GPIO1_Combined_16_31_IRQn, key_irq_hangdler);
    
    //8. GIC层面使能中断,允许中断转发到CPU
    GIC_EnableIRQ(99);
}

5.2 按键中断处理函数

复制代码
void key_irq_hangdler(void)
{
    // 判断是否是pin18触发的中断(GPIO合并中断必须做引脚判断)
    if ((GPIO1->ISR & (1 << 18))  != 0)
    {
        // 业务逻辑:翻转LED、控制蜂鸣器
        led_nor();
        beep_nor();
        
        // 清除GPIO中断标志(W1C机制,直接写1对应位即可)
        GPIO1->ISR = (1 << 18);
    }
}

六、学习避坑:中断开发常见问题

  1. 忘记清GPIO中断标志:ISR是W1C(写1清0)寄存器,不清除的话GPIO会一直输出中断信号,导致中断反复触发,程序卡死在中断里。
  2. 忘记写GIC的EOIR:GIC不知道中断处理完了,同优先级及以下中断会全部阻塞。
  3. 合并中断不判断引脚:GPIO的16~31引脚共用一个GIC中断号,进入中断必须读ISR区分引脚,否则会误触发。
  4. 触发方式与硬件不匹配:上拉按键(按下接地)应该用下降沿触发,写成上升沿会出现"松开按键才响应"的现象。
  5. 中断里放长延时:中断处理函数要快进快出,长延时、重操作会影响系统实时性,甚至导致低优先级中断丢失。

七、学习总结

今天第五天的学习,彻底把ARM中断的"黑盒"打开了:

  • 理解了GIC的三类中断源和仲裁路由机制
  • 搞懂了IAR和EOIR两个寄存器的握手逻辑
  • 打通了从硬件引脚→GIC→汇编入口→C语言分发→外设处理的完整链路
  • 掌握了GPIO中断驱动的编写方法和常见坑点

中断是嵌入式实时性的核心,理解了全链路之后,后面学习UART、定时器等外设中断就都是同一个套路,只是外设寄存器配置不同而已。

相关推荐
m4Rk_1 小时前
【论文阅读】Agent 记忆机制(76):SEEM——从碎片检索到完整事件重建
论文阅读·人工智能·学习·开源·github
我命由我123451 小时前
Mac 操作系统 - 一些使用记录
运维·windows·学习·系统安全·运维开发·mac·学习方法
千谦阙听1 小时前
【 C++篇】:模板初阶——泛型编程、函数模板与类模板
开发语言·c++·学习·visual studio
loopne1 小时前
AI网文写作全景调研(十):合规篇——检测、平台政策、版权法律
经验分享·笔记·ai写作·智能写作
祖力551 小时前
ARM中断(IMX6ULL中断、GIC通用中断控制器)
嵌入式硬件·arm·imx6ull·中断·gic·通用中断控制器
imDwAaY1 小时前
消息队列四大核心问题:顺序性、幂等性、可靠性与一致性
学习·kafka·rabbitmq
孙严Pay2 小时前
代付 VS 纯代付
笔记·科技·计算机网络·其他·微信
老王爱玩车2 小时前
第6讲:数组和函数实践-----控制台扫雷
c语言·开发语言·学习
是隼人2 小时前
buuctf-pwn [NewStarCTF 2023 公开赛道]orw&rop(shellcode+orw)题解(学习过程持续更新)
c语言·学习·安全·pwn入门·ctf入门