I.MX6ULL按键实现(轮询及中断)及工程优化
一、硬件介绍
1.1 按键硬件结构
- 按键数量与功能:共 3 个按键(两红一黄),功能分工明确
- 左侧(on/off):低功耗控制按钮
- 中间(reset):系统复位按钮
- 右侧(KEY0):用户独立控制的试验按键<本次实验主要使用KEY0>
- 电平逻辑:开关断开时,引脚为高电平;开关按下时,引脚为低电平(关键逻辑,代码设计核心依据)
1.2 核心引脚与手册参考
硬件模块 | 参考文档 | 核心章节 | 说明 |
---|---|---|---|
引脚复用 | 《IMX6ULL 参考手册.pdf》 | Chapter 32(IOMUX Controller) | 配置 GPIO 功能复用 |
GPIO 控制 | 《IMX6ULL 参考手册.pdf》 | Chapter 28(General Purpose Input/Output) | GPIO 方向、数据读取 |
时钟控制 | 《IMX6ULL 参考手册.pdf》 | Chapter 18(Clock Controller Module) | 使能 GPIO 时钟 |
中断控制器 | 《ARM Generic Interrupt Controller V2.0.pdf》 | 第 23 页 | GIC 中断分发与 CPU 接口 |
内核控制 | 《Cortex-A7 Technical Reference Manual.pdf》 | Chapter 4(System Control) | 协处理器 CP15 配置 |
二、轮询方式按键实现
2.1 轮询方式原理
通过循环读取 GPIO 引脚电平判断按键状态,适用于业务逻辑简单、无实时性要求的场景。但当主程序存在大量耗时业务时,会出现按键漏检问题(如汽车刹车、工业急停等实时场景禁用)。
2.2 代码实现
步骤 1:初始化配置(4 大核心模块)
(1)引脚复用配置(IOMUXC)
- 功能:将
UART1_CTS_B
引脚复用为GPIO1_IO18
- 关键寄存器:
IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B
(低 4 位控制复用模式)
(2)电气特性配置(PAD_CTL)
- 功能:配置引脚的上拉 / 下拉、速度、驱动能力等,确保电平稳定
- 关键寄存器:
IOMUXC_SW_PAD_CTL_PAD_UART1_CTS_B
参数拆解(二进制对应位):
HYS=0
:禁用输入滞回(无需防抖时关闭)PUS=11
:22KΩ 上拉(匹配 “断开高电平” 硬件逻辑)<与原理图中KEY0的上拉电阻并联后阻止小于10Ω>PUE=1
:选择上拉模式(而非保持模式)PKE=1
:使能上拉 / 保持功能ODE=0
:禁用漏极开漏(GPIO 输入模式无需开漏)SPEED=10
:中等速度(100MHz)DSE=000
:禁用输出驱动(输入模式无需驱动)SRE=0
:慢压摆率(减少信号干扰)
(3)GPIO 方向配置(GDIR)
- 功能:设置 GPIO 为输入模式(读取按键电平)
- 关键寄存器:
GPIO1->GDIR
(bit18 对应 GPIO1_IO18,0 = 输入,1 = 输出)
(4)GPIO 时钟使能(CCM)
- 功能:使能 GPIO1 组时钟(默认时钟关闭,不使能则 GPIO 无响应)
- 关键寄存器:
CCM_CCGR1
(GPIO1 组共用该时钟门控)
mrc与mcr命令
对比 | MRC(Move to ARM Register from Coprocessor)<“读”> | MCR(Move from ARM Register to Coprocessor)<“写”> |
---|---|---|
核心功能 | 将协处理器寄存器的数据读取到ARM 核心通用寄存器 | 将ARM 核心通用寄存器的数据写入到协处理器寄存器 |
数据流向 | 协处理器寄存器 → ARM 核心寄存器(读操作) | ARM 核心寄存器 → 协处理器寄存器(写操作) |
指令格式(ARM 汇编) | MRC{<cond>} <coproc>, <opcode1>, <Rd>, <Crn>, <Crm>{, <opcode2>} | MCR{<cond>} <coproc>, <opcode1>, <Rd>, <Crn>, <Crm>{, <opcode2>} |
关键字段含义 | 各字段功能与 MCR 完全一致,仅数据流向相反: - <coproc> :协处理器编号(如 CP15 对应 15)- <Rd> :目标 ARM 寄存器(数据读入此寄存器)- <Crn>/<Crm> :协处理器主 / 次寄存器- <opcode1>/<opcode2> :协处理器操作码 | 各字段功能与 MRC 完全一致,仅数据流向相反: - <coproc> :协处理器编号(如 CP15 对应 15)- <Rd> :源 ARM 寄存器(数据从此寄存器写出)- <Crn>/<Crm> :协处理器主 / 次寄存器- <opcode1>/<opcode2> :协处理器操作码 |
应用场景(CP15:协处理器) | 1. 读取处理器 ID(c0/c0 寄存器)2. 读取系统控制状态(如 MMU 使能状态, c1/c0 寄存器)3. 读取中断屏蔽状态( c12/c0 寄存器)4. 读取 Cache 配置信息( c9/c0 寄存器) | 1. 使能 MMU(修改 c1/c0 寄存器的 M 位)2. 设置中断向量表基址(修改 c12/c0 寄存器)3. 刷新 Cache(操作 c7/c6 寄存器)4. 配置内存权限(修改 c2/c0 寄存器) |
步骤 2:按键检测(轮询核心)
- 功能:循环读取 GPIO 数据寄存器,判断按键按下 / 松开状态
- 关键寄存器:
GPIO1->DR
(bit18 对应引脚电平,1 = 高电平(断开),0 = 低电平(按下))
#include "key.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"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);
}int key_check(void)
{return (GPIO1->DR & (1 << 18)) == 0;
}
2.3 轮询方式缺点
- 漏检问题:当
delay
耗时操作执行时,主循环暂停,按键按下无法被检测 - 资源占用:循环读取 GPIO 占用 CPU 资源,无法并行处理其他业务
- 实时性差:不适用于汽车电子、工业控制等需毫秒级响应的场景
三、中断方式按键实现
3.1 中断方式原理
当按键按下时,GPIO 触发外部中断(EINT),CPU 暂停当前业务,优先执行中断服务函数(ISR),处理完成后返回原业务,解决轮询的漏检与实时性问题。
3.2 中断处理流程(6 步核心)
- 中断请求:按键按下 --> GPIO 引脚电平变化 --> 触发外部中断请求
- 中断响应检查:CPU 判断中断是否被屏蔽(GIC 中是否允许该中断)
- 优先级判断:GIC 分发器(Distributor)选择最高优先级中断
- 保护现场:保存当前 CPU 寄存器值(如 R0~R15),避免数据丢失
- 执行中断服务函数:处理按键逻辑
- 恢复现场:恢复保存的寄存器值,回到原业务继续执行
kernal:被打断的 外设: 发出中断 GPIO发出的中断:外部中断(EINT)
3.3 GIC 中断控制器
GIC(Generic Interrupt Controller)是 ARM 架构的通用中断控制器,负责中断的分发与管理,IMX6ULL 中 GIC V2.0 支持 1020 个中断源,分类如下:
中断类型 | 范围 | 特点 | 用途 |
---|---|---|---|
SGI(软件中断) | 0~15 | 软件触发,多核间通信 | 多核 CPU 同步(如核 0 通知核 1) |
PPI(私有中断) | 16~31 | 每个 CPU 独有,绑定核心 | 核内定时器、看门狗等 |
SPI(共享中断) | 32~1019 | 多 CPU 共享,外设中断 | GPIO、UART、SPI 等外设中断 |
Distributor(分发器): (1)SGI(Software-generated Interrupt),软件中断:由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。 (2)PPI(Private Peripheral Interrupt),私有中断:我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断; (3)SPI(Shared Peripheral Interrupt),共享中断:(注意!不是 SPI 总线那个中断),这类中断泛指所有的
3.4 协处理器 CP15 配置
CP15 是 Cortex-A7 的系统控制协处理器,负责中断向量表、缓存、MMU 等配置,中断相关核心寄存器如下:
寄存器 | 功能 | 关键配置 | 代码示例 |
---|---|---|---|
SCTLR(c1) | 系统控制寄存器 | 中断向量表位置(V 位)、指令缓存(I 位) | mrc p15,0,r0,c1,c0,0; bic r0,r0,#(1<<13); mcr p15,0,r0,c1,c0,0; (向量表基址 0x00000000) |
VBAR(c12) | 向量基址寄存器 | 中断向量表物理地址 | __set_VBAR(0x87800000); (设置向量表基址为 0x87800000) |
CBAR(c15) | 配置基址寄存器 | GIC 寄存器物理基址 | mrc p15,4,r0,c15,c0,0; (读取 GIC 基址) |
MIDR(c0) | 主 ID 寄存器 | 内核型号、版本信息 | 调试时确认内核型号 |
通过查阅手册,确定寄存器(以下图为例)
3.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:中断初始化(GIC+GPIO + 向量表)
(1)初始化 GIC 中断控制器
(2)GPIO 中断配置(触发方式 + 使能)
(3)中断向量表配置
步骤 2:中断服务函数(ISR)
- 功能:处理按键中断逻辑,需快速执行(避免阻塞其他中断)
步骤 3:主函数(业务逻辑 + 中断使能)
整体函数实现
- key.c
//key.c#include "key.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "core_ca7.h"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);//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;
}
- start.S
.global _start_start:ldr pc, = _start_handerldr pc, = _undef_handerldr pc, = _supervisor_handerldr pc, = _prefetch_handerldr pc, = _data_handerldr pc, = _notuse_handerldr pc, = _irq_handerldr pc, = _fiq_hander_undef_hander:b _undef_hander_supervisor_hander:b _supervisor_hander_prefetch_hander:b _prefetch_hander_data_hander:b _data_hander_notuse_hander:b _notuse_hander_irq_hander:sub lr, #4stmfd sp!, {r0-r12, lr}mrc p15, 4, r1, c15, c0, 0add r1, r1, #0x2000ldr r0, [r1, #0x0C]bl system_interrupt_handlerstr r0, [r1, #0x10]ldmfd sp!, {r0-r12, pc}^_fiq_hander:b _fiq_hander_start_hander:/* mrs r0, cpsrbic r0, r0, #(0x1f << 0)bic r0, r0, #(1 << 7)orr r0, r0, #(0x12 << 0) //irqmrs cpsr, r0*/mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #(1 << 13) //修改异常向量表映射方式orr r0, r0, #(1 << 12) //打开ICachemcr p15, 0, r0, c1, c0, 0 cpsid icps #0x12ldr sp, =0x82000000/* mrs r0, cpsrorr r0, r0, #(0x1f << 0) //sysmrs cpsr, r0*/cps #0x1F ldr sp, =0x84000000cpsie i bl _bss_initb mainb finishfinish:b finish_bss_init:ldr r0, =__bss_startldr r1, =__bss_end mov r2, #0
loop: str r2, [r0]add r0, #4cmp r0, r1blt loop bx lr
特别提醒:
启动代码中添加对IRQ中断的handler函数跳转;
在_start_hander中加入对异常向量表的修改函数
- main.c
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "core_ca7.h"
#include "led.h"
#include "beep.h"
#include "key.h"void clock_init(void)
{CCM->CCGR0 = 0xFFFFFFFF;CCM->CCGR1 = 0xFFFFFFFF;CCM->CCGR2 = 0xFFFFFFFF;CCM->CCGR3 = 0xFFFFFFFF;CCM->CCGR4 = 0xFFFFFFFF;CCM->CCGR5 = 0xFFFFFFFF;CCM->CCGR6 = 0xFFFFFFFF;
}void led_delay(unsigned int t)
{while(t--);
}void system_interrupt_handler(IRQn_Type irq)
{if (irq == GPIO1_Combined_16_31_IRQn){if((GPIO1->ISR & (1 << 18)) !=0){led_nor();//中断处理GPIO1->ISR |= (1 << 18); }}
}void system_interrupt_init(void)
{//基地址映射__set_VBAR(0x87800000);//1.GIC初始化GIC_Init();
}int main(void)
{system_interrupt_init();clock_init();led_init();beep_init();key_init();while (1){led_delay(0x7FFFFF);}
}
四、扩展性优化
4.1 遵循OCP原则进行优化
原始代码中,GPIO 配置、中断逻辑、业务处理高度耦合,修改按键引脚或业务时需大量改动代码,违反开放 - 封闭原则(OCP):对修改封闭,对扩展开放。
降低程序耦合性 (1)满足用户基本需求 (2)程序稳定可靠 (3)满足OCP(open close principle)原则对代码的修改是关闭的,对代码的拓展是开放的
4.2 分层封装
模块分层封装(3 层架构)
层级 | 功能 | 接口设计 | 职责边界 |
---|---|---|---|
硬件抽象层(HAL) | 封装 GPIO、GIC 寄存器操作 | GPIO_SetMux() , GIC_EnableInterrupt() | 与硬件强相关,不涉及业务 |
驱动层(Driver) | 封装按键驱动逻辑 | Key_Init() , Key_RegisterCallback() | 处理中断触发、防抖,提供业务接口 |
应用层(App) | 实现业务逻辑 | Key_Press_Callback() , Key_Release_Callback() | 只调用驱动接口,不关心硬件细节 |
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));
}