嵌入式开发笔记:GPIO按键与中断系统
一、课前回顾要点
(1) volatile关键字的作用
作用:防止编译器优化,确保每次访问变量时都从内存中读取
-
对编译器:禁止对该变量进行优化
-
对程序:确保变量值的实时性
-
应用场景:
-
多线程共享变量
-
硬件寄存器访问
-
中断服务程序中的变量
-
(2) 链接脚本的作用
作用:控制程序在内存中的布局和组织
-
指定各段(section)的存储位置
-
定义程序的入口点
-
管理内存分配(RAM/ROM)
-
控制符号的地址和顺序
二、按键驱动实验(轮询方式)
1. 硬件介绍
2. 按键代码编写(轮询方式)
(1) 查阅手册
参考文档:
-
IMX6ULL_MINI_V2.2(Mini底板原理图)
-
IMX6ULL参考手册.pdf
(2) 初始化步骤
/* 步骤1:复用功能配置 */
// 引脚:UART1_CTS_B (GPIO1_IO18)
// 寄存器:IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B
// 配置:ALT5模式 → GPIO1_IO18
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
/*
位域说明:
[0] SION: 0 (DISABLED) - 输入路径由功能决定
[3:0] MUX_MODE: 0101 (ALT5) - GPIO1_IO18
*/
/* 步骤2:电气特性配置 */
// 寄存器:IOMUXC_SW_PAD_CTL_PAD_UART1_CTS_B
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);
/*
位域详解(16位):
[16] HYS: 0 - 禁用迟滞
[15:14] PUS: 11 - 22K上拉电阻
[13] PUE: 1 - 选择上拉/下拉
[12] PKE: 1 - 使能上拉/下拉
[11] ODE: 0 - 禁用开漏
[7:6] SPEED: 10 - 中速(100MHz)
[5:3] DSE: 000 - 输出驱动禁用(输入模式)
[0] SRE: 0 - 慢转换速率
*/
/* 步骤3:GPIO方向设置 */
// 寄存器:GPIO1_GDIR
GPIO1->GDIR &= ~(1 << 18); // 设置为输入模式
/*
GDIR位说明:
0 - INPUT (输入模式)
1 - OUTPUT (输出模式)
*/
/* 步骤4:使能GPIO时钟 */
// 寄存器:CCM_CCGR1
// GPIO1组所有引脚共用该时钟门控
CCM->CCGR1 |= (3 << 26); // 使能GPIO1时钟
(3) 运行时开关检测
/* 读取按键状态 */
// 寄存器:GPIO1_DR
int key_value = (GPIO1->DR >> 18) & 0x1;
/*
状态说明:
1 - 开关断开(高电平)
0 - 开关按下(低电平)
*/
3. 轮询方式的问题
void main_task(void) {
while(1) {
// 复杂业务处理(耗时)
process_complex_task(); // 可能执行很长时间
// 按键检测(可能被漏掉)
check_key_status(); // 轮询间隔可能很长
}
}
// 模拟复杂业务
void delay(unsigned int count) {
while(count--); // 简单延时模拟
}
问题分析:
-
响应延迟:主循环中的复杂任务会延迟按键检测
-
漏检风险:快速按键可能被完全错过
-
实时性差:不适用于紧急响应场景(如汽车刹车)
三、中断方式解决方案
1. 中断基本概念
中断:CPU能打断当前工作,处理紧急任务,处理完后返回原处继续工作
2. 中断处理流程
┌─────────────────────────────────┐
│ 中断处理流程 │
├─────────────────────────────────┤
│ 1. 中断源发出中断请求 │
│ 2. CPU检查是否响应中断 │
│ 3. 检查中断优先级 │
│ 4. 保护现场(保存寄存器) │
│ 5. 执行中断服务函数(ISR) │
│ 6. 恢复现场(恢复寄存器) │
│ 7. 返回原程序继续执行 │
└─────────────────────────────────┘
3. 中断控制器GIC详解
(1) GIC架构概览
参考文档 :ARM Generic Interrupt Controller V2.0.pdf
GICv2架构(单核IMX6ULL):
┌─────────────────────────────────┐
│ GIC Distributor │
│ ┌─────────────────────────┐ │
│ │ 中断源管理 (0-1019) │ │
│ │ • SGI (0-15): 软件中断 │ │
│ │ • PPI (16-31): 私有中断 │ │
│ │ • SPI (32-1019):共享中断 │ │
│ └─────────────────────────┘ │
│ │ │
└──────────────┼──────────────────┘
▼
┌─────────────────────────────────┐
│ CPU Interface (Processor0) │
│ ┌─────────────────────────┐ │
│ │ • 中断优先级屏蔽 │ │
│ │ • 中断确认 │ │
│ │ • 中断完成通知 │ │
│ └─────────────────────────┘ │
└─────────────────────────────────┘
(2) 中断类型说明
-
SGI (0-15):软件生成中断,用于核间通信
-
PPI (16-31):私有外设中断,每个核独有
-
SPI (32-1019):共享外设中断,所有核可见
(3) IMX6ULL中断映射
参考 :IMX6ULL参考手册.pdf - Table 3-1
-
总中断号:0-128
-
GPIO中断属于SPI类型
4. 协处理器CP15
(1) 功能概述
位置 :Cortex-A7技术参考手册第4章
作用:系统控制与配置
-
系统控制与配置
-
MMU配置与管理
-
Cache配置与管理
-
虚拟化与安全
-
系统性能监控
(2) 关键寄存器组
/* c1寄存器:SCTLR (System Control Register) */
mrc p15, 0, r0, c1, c0, 0 // 读取SCTLR
bic r0, r0, #(1 << 13) // 清除bit13 (V)
orr r0, r0, #(1 << 12) // 设置bit12 (I Cache使能)
mcr p15, 0, r0, c1, c0, 0 // 写回SCTLR
/*
SCTLR关键位:
bit13 [V]: 异常向量表基地址
0 - 正常异常向量,基地址0x00000000
1 - 高异常向量,基地址0xFFFF0000
bit12 [I]: I Cache使能
0 - 禁用指令Cache
1 - 使能指令Cache
*/
/* c12寄存器:VBAR (Vector Base Address Register) */
__get_VBAR(0x87800000); // 设置异常向量表基地址
/* c15寄存器:CBAR (Configuration Base Address Register) */
mrc p15, 4, r0, c15, c0, 0 // 读取GIC寄存器物理基地址
5. 中断代码实现架构
(1) 模块化设计
/* 层次结构 */
┌─────────────────┐
│ 应用层 │ ← 用户接口
├─────────────────┤
│ 中断管理层 │ ← 中断注册/分发
├─────────────────┤
│ GPIO驱动层 │ ← 硬件操作
├─────────────────┤
│ 硬件寄存器 │ ← 直接硬件访问
└─────────────────┘
(2) GPIO模块封装
/* gpio.h */
typedef struct {
int direction; // 方向:输入/输出
int pull; // 上拉/下拉
int speed; // 速度
int drive_strength; // 驱动能力
} gpio_config_t;
/* 函数声明 */
void gpio_init(GPIO_Type *gpio, int pin, gpio_config_t *config);
int gpio_read(GPIO_Type *gpio, int pin);
void gpio_write(GPIO_Type *gpio, int pin, int value);
void gpio_set_interrupt(GPIO_Type *gpio, int pin, int edge_type, void (*callback)(void));
(3) 中断模块设计(OCP原则)
/* interrupt.h */
typedef void (*irq_handler_t)(int irq, void *data);
/* 中断控制器接口 */
void interrupt_init(void);
int register_irq(int irq_num, irq_handler_t handler, void *data);
void unregister_irq(int irq_num);
void enable_irq(int irq_num);
void disable_irq(int irq_num);
/* 应用示例 */
void key_interrupt_handler(int irq, void *data) {
// 处理按键中断
printf("Key pressed! IRQ: %d\n", irq);
}
int main() {
// 初始化
interrupt_init();
// 注册中断处理函数(开放扩展)
register_irq(GPIO1_IRQn, key_interrupt_handler, NULL);
// 使能中断
enable_irq(GPIO1_IRQn);
while(1) {
// 主任务(不会被中断打断的业务逻辑)
// 按键处理已在中断中完成
}
}
6. 代码优化原则
(1) 满足用户基本需求
-
可靠的按键检测
-
实时响应
-
易于使用
(2) 程序稳定可靠
-
中断嵌套处理
-
临界区保护
-
错误处理机制
(3) OCP原则(开放封闭原则)
-
对修改封闭:核心中断框架稳定,不轻易修改
-
对扩展开放:可以轻松添加新的中断处理函数
四、总结对比
| 特性 | 轮询方式 | 中断方式 |
|---|---|---|
| 响应速度 | 慢(依赖轮询间隔) | 快(立即响应) |
| CPU占用 | 高(持续检查) | 低(事件驱动) |
| 实时性 | 差 | 优秀 |
| 代码复杂度 | 简单 | 较复杂 |
| 适用场景 | 简单应用 | 实时系统 |
五、实践建议
-
调试技巧:
-
使用LED指示灯辅助调试
-
添加串口打印调试信息
-
逐步验证每个配置步骤
-
-
常见问题:
-
中断未触发:检查GIC配置、GPIO中断使能
-
中断频繁触发:检查消抖处理
-
系统卡死:检查中断服务函数执行时间
-
-
性能优化:
-
中断服务函数尽量简短
-
使用中断下半部处理耗时任务
-
合理设置中断优先级
-