ARM(7)IMX6ULL 按键控制(轮询 + 中断)优化工程
一、硬件介绍
1. 开关功能定义
- 共 3 个开关(两红一黄),功能分工明确:
- 中间开关:复位按钮
- 左边开关:低功耗按钮
- 右边开关:用户独立控制的试验按键(核心控制对象)
2. 核心电平逻辑(原理图关键信息)
- 开关状态与电平对应关系:
- 开关断开时:引脚输出高电平
- 开关按下时:引脚输出低电平(代码中判断按键状态的核心依据)
二、轮询方式按键控制
轮询方式通过 “反复查询引脚电平” 判断按键状态,适用于简单、无实时性要求的场景。
1. 前期准备:手册查阅
需参考的核心文档:
- 《IMX6ULL_MINI_V2.2 (Mini 底板原理图).pdf》(确定引脚连接)
- 《IMX6ULL 参考手册.pdf》(配置引脚复用、电气特性、GPIO 寄存器)
2. 关键步骤:按键初始化
初始化需完成 “引脚复用→电气特性配置→GPIO 方向→时钟使能” 四步,以GPIO1_IO18(对应 UART1_CTS_B 引脚复用) 为例:
(1)引脚复用功能配置(IOMUXC)
- 参考手册章节:Chapter 32(IOMUX Controller)
- 核心寄存器:
IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B
: 低四位(0101) - 配置函数:
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
- 关键位解析:
- SION(信号监控)1:0(禁用,输入路径由功能决定)
- MUX_MODE(复用模式)4:0101(ALT5,将引脚复用为 GPIO1_IO18)
(2)引脚电气特性配置(PAD_CTL)
- 参考手册章节:Chapter 32:IOMUX Controller (IOMUXC)
- 核心寄存器:
IOMUXC_SW_PAD_CTL_PAD_UART1_CTS_B
- 配置函数:
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);
- 配置位拆解(按手册定义):
配置项 位值 含义说明 HYS(压摆率) 0 禁用滞后功能(输入无需开启) PUS(上下拉) 11 22KΩ 上拉(确保开关断开时为高电平) PUE(拉 / 保持) 1 选择 “拉模式”(而非保持模式) PKE(拉 / 保持使能) 1 使能拉功能 ODE(漏极开漏) 0 禁用漏极开漏(输入场景无需开启) SPEED(速度) 10 中速(100MHz,满足按键检测需求) DSE(驱动能力) 000 禁用输出驱动(引脚为输入,输出驱动无效) SRE( slew rate) 0 慢摆率(输入无影响)
(3)GPIO 方向配置(输入模式)(GDIR)
- 参考手册章节:Chapter 28(General Purpose Input/Output)
- 核心寄存器:
GPIOx_GDIR
(GPIO 方向寄存器,x=1 对应 GPIO1 组) - 配置代码:
GPIO1->GDIR &= ~(1 << 18);
(清第 18 位,0 表示输入模式;1 为输出模式)
(4)GPIO 时钟使能(CCM)
- 参考手册章节:Chapter 18(Clock Controller Module)
- 核心寄存器:
CCM_CCGR1
(GPIO1 组共用时钟门控寄存器) - 功能:开启 GPIO1 组时钟(外设需时钟才能工作,默认可能关闭)
3. 运行时:按键状态检测
通过读取 GPIO 数据寄存器判断按键状态:
- 参考手册章节:Chapter 28:General Purpose Input/Output (GPIO)
- 核心寄存器:
GPIOx_DR
(GPIO 数据寄存器) - 检测逻辑:
- 若
(GPIO1->DR & (1 << 18)) != 0
:开关断开(高电平)→ 按键未按下 - 若
(GPIO1->DR & (1 << 18)) == 0
:开关按下(低电平)→ 按键触发
- 若
4. 轮询方式的致命问题:漏查
(1)问题场景
当主程序承担大量、复杂、耗时的业务(如数据处理、多设备控制)时,轮询周期被拉长,可能错过按键触发的 “短暂低电平”(即漏查)。
(2)模拟验证
用delay(0x7FFFFF);
模拟耗时业务,此时按键按下的低电平可能在 delay 期间结束,轮询无法检测到。
(3)典型风险场景
在实时性要求高的场景(如汽车刹车信号检测、工业急停按钮),漏查会导致严重安全事故。
三、中断方式按键控制
中断可让 CPU“优先处理紧急任务(如按键)”,处理完后返回原业务,解决轮询漏查问题。
1. 中断核心概念
- 定义:CPU 可打断当前执行的任务,转去处理更紧急的中断请求,处理完成后恢复现场并继续原任务。
- 本场景中断源:GPIO 外部中断(EINT,由按键按下触发)
- 角色分工:
- Kernel(内核):被中断的 “原任务执行者”
- 外设(GPIO):发出中断请求的 “触发者”
---中断相关寄存器 I 开头
这些寄存器是 NXP i.MX 系列(如 i.MX6ULL、i.MX RT1062 等)通用 I/O(GPIO)模块的核心寄存器,从上到下依次为:
GPIOx_DR:Data Register(数据寄存器)
- 功能:读写 GPIO 引脚的电平状态(输出模式下写值、输入模式下读值)。
GPIOx_GDIR:Direction Register(方向寄存器)
- 功能:配置引脚为输入(位设为
0
)或输出(位设为1
)。GPIOx_PSR:Pin Status Register(引脚状态寄存器)
- 功能:实时反映物理引脚的当前电平(与
DR
可能存在差异,推荐读取PSR
获取实际状态)。GPIOx_ICR1:Interrupt Configuration Register 1(中断配置寄存器 1)
- 功能:配置低 16 位引脚的中断触发方式(上升沿、下降沿、高电平或低电平)。
GPIOx_ICR2:Interrupt Configuration Register 2(中断配置寄存器 2)
- 功能:配置高 16 位引脚的中断触发方式(与
ICR1
功能一致,仅对应引脚范围不同)。GPIOx_IMR:Interrupt Mask Register(中断屏蔽寄存器)
- 功能:控制是否使能引脚的中断(对应位设为
1
则允许中断,0
则屏蔽)。GPIOx_ISR:Interrupt Status Register(中断状态寄存器)
- 功能:记录哪些引脚发生了中断(写
1
可清除中断标志)。GPIOx_EDGE_SEL:Edge Select Register(边沿选择寄存器)
- 功能:选择引脚是否使用边沿触发(设为
1
时,ICR1/ICR2
配置无效,仅响应边沿;设为0
时,使用ICR1/ICR2
的配置)。这些寄存器共同实现了 GPIO 的输入输出控制、中断配置与状态管理,是嵌入式开发中操作 GPIO 的核心工具。
2. 中断处理流程(6 步)
- 中断请求:外设(GPIO)检测到按键按下,发出中断信号。
- 中断检查:CPU 判断是否允许响应中断(需未被屏蔽)。
- 优先级判断:若存在多个中断,优先处理高优先级请求。
- 保护现场:保存当前 CPU 寄存器状态(确保后续能恢复原任务)。
- 执行中断服务函数(ISR):处理中断逻辑(如按键触发的业务)。
- 恢复现场:恢复保存的寄存器状态,回到原任务继续执行。
3. 关键组件:中断控制器 GIC
GIC(Generic Interrupt Controller,通用中断控制器)是 ARM 架构中管理中断的核心模块,负责中断分发、优先级控制。
(1)参考文档
- 《ARM Generic Interrupt Controller (ARM GIC 控制器) V2.0.pdf》
- 《IMX6ULL 参考手册.pdf》Chapter 3.2(Cortex A7 interrupts)
- 《MCIMX6Y2.h》(定义 IRQn_Type 中断编号)
(2)GIC V2.0 核心特性
- V2.0设计可以为8个内核提供中断控制服务,但我们使用的IMX6ULL只有一个内核,图中所示只有processor 0
- 每个内核能够相应1020个中断源,其中0~15是SGI,16~31是PPI,能够作为外设中断源的是SPI32~1019
- 支持 8 个 CPU 核心(IMX6ULL 仅用 1 个核心,对应 processor 0)。
- 共 1020 个中断源,按类型分为 3 类:
中断类型 范围 特点与用途 SGI 0~15 软件中断(Software-generated),用于多核通信 PPI 16~31 私有中断(Private),每个 CPU 核心独有 SPI 32~1019 共享中断(Shared),外设中断专用(如 GPIO)
Distributor(分发器):
- (1)SGI(Software-generated Interrupt),软件中断:
由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。
- (2)PPI(Private Peripheral Interrupt),私有中断:
我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断;
- (3)SPI(Shared Peripheral Interrupt),共享中断:
(注意!不是 SPI 总线那个中断),这类中断泛指所有
(3)GIC 核心模块
- Distributor(分发器):
- 管理所有中断源(SGI/PPI/SPI)。
- 功能:中断屏蔽、优先级设置、向目标 CPU 分发中断。
- CPU Interface(CPU 接口):
- 连接 GIC 与 CPU 核心。
- 功能:传递中断请求给 CPU、确认中断处理完成。
(4)IMX6ULL 中断编号对应
- 手册定义:Cortex A7 中断编号范围 0~128。
- 头文件定义:
IRQn_Type
(外设中断编号 0~159,需与 GIC 的 SPI 编号对应)。
(5) GIC相关(图)
core_ca7.h
4. 底层支撑:协处理器 CP15--------主要使用cp10.11.15
Cortex-A7 的协处理器 CP15(CP0~CP15)负责系统级控制,中断配置需依赖其关键寄存器。
(1)参考文档
- 架构参考手册《Cortex-A7 Technical Reference Manual.pdf》Chapter 4(System Control)
(2)CP15 核心功能
- 系统控制与配置、MMU(内存管理单元)配置、Cache 配置、虚拟化与安全、性能监控。
(3)CP15 寄存器读写指令 MRC
- 读寄存器:
MRC <coproc>, <opc1>, <Rt>, <CRn>, <CRm>, {, <opc2>}
(如读取 SCTLR 寄存器:mrc p15,0,r0,c1,c0,0
)- 写寄存器:
MCR <coproc>, <opc1>, <Rt>, <CRn>, <CRm>, {, <opc2>}
(如写入 SCTLR 寄存器:mcr p15,0,r0,c1,c0,0
)
(4)中断相关关键寄存器
寄存器 | 功能描述 | 关键位 / 配置示例 |
---|---|---|
c0: MIDR | 存储内核基本信息(如型号、版本) | 调试时确认内核型号 |
c1: SCTLR | 系统控制寄存器(中断向量、Cache 使能) | bit13(V):0 = 向量基址 0x00000000;1=0xFFFF0000 bit12(I):1 = 使能指令 Cache demo: mrc p15,0,r0,c1,c0,0 bic r0,r0,#(1<<13) orr r0,r0,#(1<<12) mcr p15,0,r0,c1,c0,0 |
c12: VBAR | 中断向量基地址寄存器(指定中断服务表地址) | demo:__get_VBAR(0x87800000); (设置基址为 0x87800000) |
c15: CBAR | 存储 GIC 寄存器的物理基地址(定位 GIC) | demo:mrc p15,4,r0,c15,c0,0 (读取基地址到 r0) |
(5)相关图(c0-c15)
c0 - MIDR
c12 - VBAR
c1 - SCTLR4
5. 中断代码设计要点
中断相关寄存器组
c0 registers:
MIDR(Main ID Register):存储内核的一些基本信息
c1 registers:
SCTLR(System Control Register)
bit13:V
bit12:I
c12 registers:
VBAR(Vector Base Address Register)
c15 registers:
CBAR(Configuration Base Address Register)
(1)代码编写逻辑
需严格遵循 “中断流程”,对应代码步骤(结合上面的图):
- 配置 GPIO 为中断触发模式(如下降沿触发:按键按下时电平从高→低)。
- 配置 GIC(使能对应 SPI 中断、设置优先级、绑定 CPU 核心)。
- 配置 CP15(设置中断向量基址 VBAR、使能中断响应)。
- 编写中断服务函数(ISR):处理按键逻辑(如置位标志、调用业务函数)。
- 使能全局中断(CPU 允许响应中断)。
(2)完整代码 (独立interrupt.c/.h) key5
main.c
key.c
interrupt.c
interrupt.h
start.S
**********************************
启动代码中添加对IRQ中断的handler函数跳转;
在_start_hander中加入对异常向量表的修改函数
(3)降低程序耦合性(遵循 OCP 原则)
OCP 原则(开放 - 关闭原则):对修改关闭,对拓展开放(避免修改原有代码即可新增功能)。
降低程序耦合性(1)满足用户基本需求(2)程序稳定可靠(3)满足OCP(open close principle)原则对代码的修改是关闭的,对代码的拓展是开放的
实现措施:
- 模块封装:
- 封装 GPIO 模块:提供
GPIO_Init()
、GPIO_SetInterruptMode()
等接口,屏蔽底层寄存器操作。 - 封装中断模块:提供
GIC_Init()
、Interrupt_Register()
等接口,统一管理中断配置。
- 封装 GPIO 模块:提供
- 回调函数注册:
- 中断服务函数中不直接写业务逻辑,而是调用 “注册的回调函数”。
- 示例:用户新增按键业务时,只需调用
Interrupt_Register(KEY_IRQn, User_KeyCallback)
,无需修改中断模块代码。
- 稳定性保障:
- 中断服务函数需 “快进快出”(避免耗时操作,如 printf、delay)。
- 增加中断标志位(避免中断嵌套导致的逻辑混乱)。
KEY、中断模块优化
//interrupt.c#include "interrupt.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "core_ca7.h"static irq_interrupt_t interrupt_Vector_table[160] = {NULL};void system_interrupt_init(void)
{//基地址映射__set_VBAR(0x87800000);//1.GIC初始化GIC_Init();
}void system_interrupt_register(IRQn_Type irq, irq_interrupt_t handler)
{interrupt_Vector_table[irq] = handler;
}void system_interrupt_handler(IRQn_Type irq)
{if (interrupt_Vector_table[irq] != NULL){interrupt_Vector_table[irq]();}
}//key.c#include "key.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "interrupt.h"
#include "led.h"void key_16_31_handler(void)
{if((GPIO1->ISR & (1 << 18)) !=0){led_nor();//中断处理GPIO1->ISR |= (1 << 18); }
}void key_init(void)
{//复用功能 配置为GPIO1_18IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);//电气特性配置IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF0B0);//引脚方向GPIO1->GDIR &= ~(1 << 18);//中断触发方式配置GPIO1->ICR2 |= (3 << 4); //中断源屏蔽寄存器解除屏蔽GPIO1->IMR |= (1 <<18);//注册中断处理函数system_interrupt_register(GPIO1_Combined_16_31_IRQn, key_16_31_handler);//2.GIC中断使能GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);GIC_SetPriority(GPIO1_Combined_16_31_IRQn, 0);
}int key_check(void)
{return (GPIO1->DR & (1 << 18)) == 0;
}
GPIO 模块优化(支持多引脚扩展)
通过结构体参数传递 GPIO 配置,支持多按键扩展,无需重复编写代码
//gpio.c#include "gpio.h"void gpio_init(GPIO_Type *gpio, int pin, gpio_pin_t *data)
{if (data->dir == gpio_output){gpio->GDIR |= (1 << pin);if(data->def_val == 1){gpio->DR |= (1 << pin);}else{gpio->DR &= ~(1 << pin);}}else{gpio->GDIR &= ~(1 << pin);}
}int gpio_read(GPIO_Type *gpio, int pin)
{return ((gpio->DR & (1 << pin)) != 0);
}void gpio_write(GPIO_Type *gpio, int pin, int data){if (data == 0){gpio->DR &= ~(1 << pin);}else{gpio->DR |= (1 << pin);}
}//interrupt.c#include "interrupt.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "core_ca7.h"static irq_interrupt_t interrupt_Vector_table[160] = {NULL};void system_interrupt_init(void)
{//基地址映射__set_VBAR(0x87800000);//1.GIC初始化GIC_Init();
}void system_interrupt_register(unsigned int irq, irq_interrupt_t handler)
{interrupt_Vector_table[irq] = handler;
}void system_interrupt_handler(IRQn_Type irq)
{if (interrupt_Vector_table[irq] != NULL){interrupt_Vector_table[irq]();}
}//key.c#include "key.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "core_ca7.h"
#include "interrupt.h"
#include "led.h"void key_16_31_handler(void)
{if((GPIO1->ISR & (1 << 18)) !=0){led_nor();//中断处理GPIO1->ISR |= (1 << 18); }
}void key_init(void)
{//复用功能 配置为GPIO1_18IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);//电气特性配置IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF0B0);//引脚方向GPIO1->GDIR &= ~(1 << 18);//中断触发方式配置GPIO1->ICR2 |= (3 << 4); //中断源屏蔽寄存器解除屏蔽GPIO1->IMR |= (1 <<18);//注册中断处理函数system_interrupt_register(GPIO1_Combined_16_31_IRQn, key_16_31_handler);//2.GIC中断使能GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);GIC_SetPriority(GPIO1_Combined_16_31_IRQn, 0);
}int key_check(void)
{return (GPIO1->DR & (1 << 18)) == 0;
}//led.c#include "led.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "gpio.h"void led_init(void)
{IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x10B0);//GPIO1->GDIR |= (1 << 3);//led_off();gpio_pin_t io_3;io_3.dir = gpio_output;io_3.def_val = 1;gpio_init(GPIO1, 3, &io_3);
}void led_on(void)
{gpio_write(GPIO1, 3, 0);
}void led_off(void)
{gpio_write(GPIO1, 3, 1);
}void led_nor(void)
{gpio_write(GPIO1, 3, !gpio_read(GPIO1, 3));
}
(4) 代码编写思路历程(根据框 图)
使能
传递中断号
优先级·0最高---------越小越高
强制内联静态内联函数
地址访问问题·
C_IAR 基地址 + 0x200C (偏移地址)
C_EOIR 基地址 + 0x2010 (偏移地址)
获取GIC基地址 ==== 手册
对照写------ 把GIC基地址读到R0寄存器里面(R0 base 基地址)
、
coproc 协处理器
存到R1里
c15 CBAR->
当前进度
下面是kernal
mrc p15, 0, r0, c1, c0, 0 参数对应cp1内容,修改异常向量表映射方式
bic r0, r0, #(1 << 13) bit13位为V清零,VBAR配置中断向量基地址
orr r0, r0, #(1 << 12) bit12位为I 置一,iche使能位打开
mcr p15, 0, r0, c1, c0, 0对照下图
![]()
跳转
为什么有偏移量?
四、核心对比:轮询 vs 中断
对比维度 | 轮询方式 | 中断方式 |
---|---|---|
实时性 | 差(易漏查) | 好(优先响应紧急任务) |
CPU 资源占用 | 高(持续查询) | 低(仅中断时占用) |
适用场景 | 简单、无实时性需求(如 LED 闪烁) | 实时性需求高(如刹车、急停) |
代码复杂度 | 低(无需配置中断) | 高(需配置 GIC、CP15) |
--------------知识点梳理补充------------------
在 ARM 架构中,GIC(Generic Interrupt Controller,通用中断控制器) 是负责管理系统中断的关键组件,它统一处理来自外设、软件或其他硬件的中断请求,并将其分发到相应的处理器核心(CPU)进行处理。GIC 的存在使得多核心 ARM 系统中的中断管理更加标准化、高效化。
GIC 的核心功能
- 中断源管理:识别和分类系统中的各种中断(如外设中断、软件触发中断、定时器中断等)。
- 优先级控制:为不同中断分配优先级,确保高优先级中断优先被处理。
- 中断路由:将中断分发到指定的 CPU 核心(支持多核系统中中断的灵活分配)。
- 中断状态跟踪:记录中断的触发、处理、完成等状态,协调 CPU 与中断源的交互。
GIC 的主要版本
GIC 随着 ARM 架构的发展迭代了多个版本,每个版本新增了对多核、虚拟化等场景的支持:
- GICv1:早期版本,支持基本的中断分发,主要用于单核心或简单多核系统(如 ARMv7 架构)。
- GICv2:增加了对多核系统的完善支持,引入了虚拟化相关的基础功能(如安全扩展),支持最多 8 个 CPU 核心。
- GICv3/GICv4:针对 ARMv8-A(64 位)架构设计,支持更多核心(最多 255 个),强化了虚拟化能力(如支持虚拟机直接处理中断),并引入了新的中断类型和分发机制(如 LPI,Locality-specific Peripheral Interrupt)。
GIC 的核心组件
GIC 的功能通过两个主要逻辑单元实现:
Distributor(分发器)
- 全局管理所有中断源,维护中断的使能 / 禁用状态。
- 对中断进行优先级排序(优先级由软件配置)。
- 根据配置将中断路由到一个或多个 CPU 核心(支持 “中断亲和性” 设置)。
CPU Interface(CPU 接口)
- 每个 CPU 核心对应一个 CPU 接口,负责与本核心交互。
- 接收来自 Distributor 的中断请求,通知 CPU(如通过 IRQ/FIQ 信号)。
- 处理 CPU 对中断的响应(如中断确认、处理完成通知)。
中断类型
GIC 管理的中断分为三类,以适应不同场景:
- SGI(Software Generated Interrupt,软件生成中断):由软件通过写寄存器触发,用于核心间通信(如多核系统中核心 A 通知核心 B 处理任务)。
- PPI(Private Peripheral Interrupt,私有外设中断):属于特定 CPU 核心的私有中断(如每个核心独立的定时器中断),仅能被对应的核心处理。
- SPI(Shared Peripheral Interrupt,共享外设中断):由系统中的共享外设(如 UART、GPIO)产生,可被路由到任意一个或多个 CPU 核心。
工作流程简述
- 外设或软件产生中断请求,发送到 GIC 的 Distributor。
- Distributor 检查中断是否被使能、优先级是否满足,并根据路由规则选择目标 CPU。
- 目标 CPU 的 CPU Interface 接收中断,通过 IRQ/FIQ 信号通知 CPU。
- CPU 响应中断,通过 CPU Interface 向 GIC 确认中断(标记为 “处理中”)。
- CPU 处理完中断后,通过 CPU Interface 通知 GIC 中断处理完成,GIC 更新中断状态。
总结
GIC 是 ARM 系统中中断管理的 “中枢”,通过标准化的架构支持多核、虚拟化等复杂场景,确保中断高效、有序地被处理,是嵌入式系统、服务器等 ARM 设备正常运行的核心组件之一。