正点原子 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 的“动力系统”——时钟系统。

继续阅读

嵌入式开发面试知识手册

嵌入式开发工程师面试知识手册 适用对象:零基础起步、目标是通过嵌入式软件/驱动/BSP 方向面试的读者 编写依据:赵哲简历全部知识点 + 智能车 AGV 控制平台完整项目代码(i.MX6ULL Linux 端 + STM32F407 固件)+ Linux 驱动练习代码包 + embedded-agent 嵌入式开发智能体仓库 + 真实面试手撕代码与追问记录 使用方法:每篇按"通俗解释 → 原理细节 → 命令/API → 代码实例 → 项目结合 → 记忆口诀 → 面试问答"组织;口诀用于快速记忆,问答用于自测。第9篇是讲项目的演练场,第10篇是手撕代码实战题库。 总目录 * 第1篇 C语言与数据结构 * 1.1 编译链接全过程 * 1.2 数据类型与内存

作者:azhezhezhe

正点原子 I.MX6ULL MINI 开发板学习笔记:Linux 驱动架构详解——编译、树内树外与运行时效果

这是 Linux 驱动小系列的总结篇。前三篇把 LED 驱动从内核模块写到 misc 设备,属于“会用”;这篇把背后的架构讲透:驱动到底怎么编译、内核树内和树外两种方式的区别、以及驱动里每个操作在系统跑起来之后到底产生了什么效果。内容整理自我的学习笔记(note.pdf)和实际工程 code/2026/20260803。 1. 驱动代码到底怎么编译的? 不管哪种方式,最终都靠内核的 kbuild 系统。核心就三个文件的关系: * Kconfig:定义 menuconfig 里的选项(如 CONFIG_MYMODULE),三态 y/m/n——y 编进内核,m 编成模块,n 不编; * .config:menuconfig 的选择结果,保存在源码顶层; * Makefile:

作者:azhezhezhe

正点原子 I.MX6ULL MINI 开发板学习笔记:misc 设备——字符设备驱动的快捷方式

上两篇分别用手动 mknod 和 class_create+device_create 注册字符设备,调用链一长串。这篇用 misc(混杂)设备 一步搞定。代码来自实际工程 code/2026/20260803/05_misc_led,已验证可用。 1. 完成什么实验? 用 misc_register 重写 LED 驱动:一步完成设备号分配、字符设备注册、/dev/myled0 节点创建,同时保留上一篇的 sysfs 属性 led_status。 2. 什么是 misc 设备? misc(混杂)设备是 Linux 给“不好归类的小字符设备”

作者:azhezhezhe