正点原子 I.MX6ULL MINI 开发板学习笔记:GPIO 输入——按键与蜂鸣器

这篇笔记继续正点原子 I.MX6ULL MINI 开发板的 GPIO 学习,主题是GPIO 输入——按键检测和蜂鸣器控制。代码来自实际工程 /home/zhao/code/2026/touch_screen。
1. 完成什么实验?
把 GPIO1_IO18 配置为输入模式,检测按键是否被按下;按下时通过中断翻转 LED 状态。同时把 GPIO5_IO01 配置为输出,控制蜂鸣器响/停。
2. GPIO 输入和输出有什么区别?
- 输出模式:CPU 通过
DR寄存器控制引脚电平,如点灯、驱动蜂鸣器。 - 输入模式:CPU 通过
PSR寄存器读取引脚真实电平(注意:I.MX6ULL 输入模式下读DR拿不到真实电平,是常见错误),如检测按键。 - 方向寄存器 GDIR:对应位为 1 表示输出,为 0 表示输入。
- 输入通常需要上下拉:按键一端接地时,另一端要用上拉电阻,保证未按下时电平稳定。
3. 什么是按键消抖?
机械按键在按下和释放时,触点会反复抖动几毫秒到十几毫秒,导致 GPIO 电平出现多次跳变。如果每次跳变都响应,就会出现“按一次触发多次”的现象。消抖方法有两种:硬件上并联电容滤波,软件上检测到边沿后延时 10~20ms 再确认电平。

4. 什么是 GPIO 中断?
除了轮询读取按键状态,更高效的方式是中断:当 GPIO 电平发生指定边沿变化时,CPU 暂停当前任务,执行中断服务函数。I.MX6ULL 的 GPIO 中断需要配置 ICR 寄存器选择触发边沿,并通过 GIC(Generic Interrupt Controller)使能中断。
5. 蜂鸣器是有源还是无源?
开发板上一般是有源蜂鸣器:内部已经集成了振荡电路,只要给电平就能发声。本例中蜂鸣器低电平触发,往 GPIO5_IO01 写 0 时蜂鸣器响,写 1 时停止。

6. 真实可用的按键/蜂鸣器代码
下面是工程中验证过的核心代码。
#ifndef _BEEP_H
#define _BEEP_H
extern void init_beep(void);
extern void beep_on(void);
extern void beep_off(void);
extern void beep_nor(void);
#endif
#include "beep.h"
#include "fsl_iomuxc.h"
#include "MCIMX6Y2.h"
void init_beep(void)
{
/* GPIO5_IO01 复用为 GPIO */
IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0);
IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0x10B0);
GPIO5->GDIR |= (1 << 1); /* 输出 */
beep_off();
}
void beep_on(void) { GPIO5->DR &= ~(1 << 1); } /* 低电平响 */
void beep_off(void) { GPIO5->DR |= (1 << 1); } /* 高电平停 */
void beep_nor(void) { GPIO5->DR ^= (1 << 1); } /* 翻转 */
#include "key.h"
#include "fsl_iomuxc.h"
#include "core_ca7.h"
#include "led.h"
#include "interrupt.h"
void key_handler(void)
{
/* 检查是否是 GPIO1_IO18 触发 */
if ((GPIO1->ISR & (1 << 18)) != 0)
{
led_nor(); /* 翻转 LED */
GPIO1->ISR |= (1 << 18); /* 写 1 清除中断标志 */
}
}
void init_key(void)
{
/* 1. 引脚复用为 GPIO1_IO18 */
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
/* 2. 配置上拉等电气属性 */
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);
GPIO1->GDIR &= ~(1 << 18); /* 输入 */
GPIO1->ICR2 |= (3 << 4); /* 下降沿触发 */
GPIO1->IMR |= (1 << 18); /* 使能中断 */
/* 注册中断服务函数并使能 GIC */
system_interrupt_register(key_handler, GPIO1_Combined_16_31_IRQn);
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
}
int key_pressed(void)
{
return (GPIO1->PSR & (1 << 18)) == 0; /* 输入读 PSR:低电平表示按下 */
}
#include "led.h"
#include "beep.h"
#include "key.h"
int main(void)
{
init_clock();
system_interrupt_init();
init_led();
init_beep();
init_key();
while (1)
{
/* 中断方式:按键按下自动翻转 LED */
/* 轮询方式示例:
if (key_pressed())
{
beep_on();
delay_ms(100);
}
else
{
beep_off();
}
*/
}
return 0;
}
7. 关键点说明
- 按键输入要上拉:本例配置字
0xF080使能了上拉,未按键时 GPIO1_IO18 为高。 - 下降沿触发:按键按下时电平由高变低,ICR2 配置为下降沿触发最自然。
- 中断标志要清:ISR 寄存器写 1 清除对应位,否则中断会反复进入。
- 蜂鸣器低电平有效:和 LED 一样,写 0 导通、写 1 截止。
- 输入读 PSR 不读 DR:I.MX6ULL 输入模式下引脚电平要从
PSR读取;DR是输出数据寄存器,复位值为 0,输入模式下读它恒得到 0(工程源码里 key_pressed() 最初误用了 DR,因为主实验走中断路径没踩到;上文代码已修正为 PSR)。 - 消抖:实际工程中可在
key_handler开头加 10~20ms 延时再判断电平,避免抖动误触发。
8. 实验现象
按下 KEY0 时,LED0 状态翻转一次。main 里蜂鸣器控制代码处于注释状态,本实验只演示按键中断翻转 LED;把注释打开后可以实现按住蜂鸣器响、松开停。注意本代码没有消抖:触点抖动可能让一次按下触发多次中断、LED 翻转多次,这是正常现象,工程上需要加 10~20ms 消抖。
总结
| 问题 | 答案 |
|---|---|
| GPIO 输入和输出的区别? | GDIR 为 0/1;输入读电平,输出写电平 |
| 按键为什么要上拉? | 保证未按下时引脚电平稳定为高 |
| 按键消抖是什么? | 机械触点抖动导致多次跳变,软硬件延时过滤 |
| GPIO 中断需要配置什么? | ICR 边沿、IMR 使能、GIC 使能、ISR 清标志 |
| 蜂鸣器怎么触发? | 有源蜂鸣器低电平导通,写 0 响 |
学会了 GPIO 输入输出和中断,就可以做很多人机交互实验了。下一篇将进入 SoC 的“动力系统”——时钟系统。