IMX6ULL 裸机开发:按键轮询实验 + 外部中断原理剖析
前言
最近在做 i.MX6ULL 裸机驱动开发,前面已经完成 GPIO 点亮 LED、移植 NXP SDK 驱动蜂鸣器,还重构工程、改写 Makefile、手写链接脚本,把代码段、数据段、BSS 段在 RAM 中的排布全部自己定义。
本次实验先从最简单的按键轮询采集入手,直观发现轮询方式的缺陷;接着引入中断,讲解 ARM Cortex-A7 中断基础、GIC V2 中断控制器、CP15 协处理器相关寄存器,为后面编写外部中断按键驱动打下理论基础。
开发环境:i.MX6ULL-Mini 开发板,裸机无操作系统,C 语言 + ARM 汇编,Makefile 管理工程
一、课前回顾
前面实验要点回顾:
- 使用 C 语言直接操作 GPIO 寄存器,实现 LED 点灯;
- 移植 NXP SDK 包,基于寄存器方式驱动蜂鸣器 Beep;
- 工程重构,手动修改 Makefile,配置编译、链接规则;
- 手写链接脚本,自定义
.text、.data、.bss等段在 RAM 中的地址排布。
课前思考题
volatile关键字的作用?- 链接脚本(
.ld)的作用是什么? - (拓展)裸机程序中,BSS 段为什么需要在代码里手动清零?
二、按键轮询实验
在正式学习中断之前,先实现按键轮询驱动,观察按键读取现象,理解轮询方式的短板。
2.1 硬件介绍
i.MX6ULL-Mini 底板:
- 一共 3 个按键:两个红色、一个黄色;
- 中间按键:硬件复位按键;
- 左侧:低功耗按键;
- 右侧:本次实验使用的用户测试按键;
- 按键电路原理:按键断开时为高电平,按键按下时拉低为低电平。
查阅文档:
- IMX6ULL_MINI_V2.2 (Mini 底板原理图).pdf
- IMX6ULL 参考手册.pdf
2.2 按键驱动代码编写(轮询方式)
实验选用引脚:UART1_CTS_B,复用为 GPIO1_IO18。 按键驱动分为四步:引脚复用配置、PAD 电气属性配置、GPIO 方向配置、开启 GPIO 时钟。
① IOMUX 引脚复用配置
寄存器:IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B 章节:IOMUX Controller (IOMUXC) Chapter32
-
MUX_MODE(低 4 位):
0101,ALT5 模式,映射为 GPIO1_IO18 -
SION:0,关闭输入强制功能,输入路径由硬件功能决定
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
② PAD 电气属性配置
寄存器: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 | 0 | 低速压摆率 |
因为按键是输入引脚,所以 DSE 输出驱动能力关闭,配置上拉。
③ GPIO 方向寄存器 GDIR
GPIO1_IO18 设置为输入:
GPIO1->GDIR &= ~(1 << 18);
- bit = 0:GPIO 输入
- bit = 1:GPIO 输出
④ 开启 GPIO1 时钟
CCM 时钟模块,CCM_CCGR1 寄存器,GPIO1 组时钟门控,打开 GPIO1 外设时钟。
⑤ 运行时读取按键电平
GPIO 数据寄存器 GPIOx_DR
- 按键松开:高电平
- 按键按下:低电平 轮询代码逻辑:循环不断读取 DR 寄存器,判断电平,检测按键状态。
2.3 轮询方式存在的致命问题
核心痛点:轮询方式在主程序有大量耗时业务时,会丢失按键事件,发生漏检!
模拟场景:在主循环中加入长时间延时函数 delay(0x7FFFFF);,模拟 CPU 正在处理复杂业务。 此时如果按下按键,CPU 卡在 delay 里,没有读取 GPIO 电平,按键按下这个事件就丢失了。
举个工业场景例子:汽车刹车信号,如果用轮询检测,CPU 正在处理别的任务时,刹车信号漏检会引发严重事故。 👉 解决方案:中断! 不需要 CPU 一直轮询查询,外设事件主动通知 CPU。
三、中断基础原理
3.1 什么是中断
中断:CPU 暂停当前正在执行的程序,跳转去处理紧急外设事件;中断服务函数处理完成后,返回原来被打断的代码继续执行。
中断参与对象:
- 内核:被打断的主体
- 外设:产生中断请求(GPIO 外部中断)
3.2 中断完整处理流程
- 中断源产生中断请求;
- CPU 判断是否允许响应中断、中断是否被屏蔽;
- 判断中断优先级;
- 保护现场:保存当前寄存器上下文;
- 执行中断服务函数 ISR;
- 恢复现场,回到被打断代码继续运行。
GPIO 产生的中断,属于外部中断 EINT。
3.3 GIC V2 通用中断控制器(重点)
GIC 全称:Generic Interrupt Controller,通用中断控制器。i.MX6ULL 使用 GIC V2 版本。 参考文档:ARM Generic Interrupt Controller(ARM GIC控制器)V2.0.pdf
GIC V2 最多支持 8 个处理器核,i.MX6ULL 只有单核 Cortex-A7,只用 Processor 0。 最多支持 1020 个中断源,中断类型划分:
- SGI(Software-generated Interrupt)软件中断:中断号 0~15 软件写 GICD_SGIR 寄存器触发,多用于多核之间通信。
- PPI(Private Peripheral Interrupt)私有外设中断:中断号 16~31 属于每个 CPU 私有的中断,只能由指定内核处理。
- SPI(Shared Peripheral Interrupt)共享外设中断:中断号 32~1019 所有外设产生的硬件中断都属于 SPI(⚠️注意不是 SPI 总线!),我们的 GPIO 外部中断就是 SPI 中断。
GIC 两大模块:
- Distributor 分发器:管理所有中断源,使能中断、设置中断优先级、中断目标 CPU;
- CPU Interface CPU 接口:CPU 内核和 GIC 分发器之间的接口,CPU 从中读取中断号、确认中断结束。
i.MX6ULL 参考手册中断章节:3.2 Cortex A7 interrupts,中断编号范围 0~128; MCIMX6Y2.h 头文件中 IRQn_Type 定义外设中断 0~159。
3.4 CP15 协处理器(Cortex-A7)
Cortex-A7 有 cp0~cp15 协处理器,CP15 是系统控制协处理器 ,负责系统底层控制。 参考手册:Cortex-A7 Technical ReferenceManua.pdf Chapter4 System Control
CP15 主要功能
- 系统全局配置;
- MMU 内存管理单元配置;
- Cache 配置;
- 虚拟化与安全模式配置;
- 系统性能监控;
CP15 的寄存器编号 c0 ~ c15,不能直接用 C 指针访问,必须使用 ARM 汇编指令:
-
MRC:协处理器寄存器 → 通用寄存器(读) -
MCR:通用寄存器 → 协处理器寄存器(写)MRC{
} , ,
中断相关 CP15 寄存器
-
c0:MIDR 主 ID 寄存器,读取 CPU 内核版本信息;
-
c1:SCTLR 系统控制寄存器
- bit13:V,异常向量表基址选择;0=0x00000000,1=0xFFFF0000;
- bit12:I,指令 Cache 开关;0 关闭,1 开启。
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 向量基址寄存器,设置异常向量表基地址,裸机中断必备;
__get_VBAR(0x87800000);
-
c15:CBAR GIC 基址寄存器,读取 GIC 控制器物理基地址。
mrc p15, 4, r0, c15, c0, 0