03-GPIO输入.md 6.8 KB

03 GPIO 输入(HAL)

目标:读取按键和传感器模块电平,实现按键控制 LED、光敏传感器控制蜂鸣器

原理简述

详细工作原理

GPIO 输入路径

外部信号进入 MCU 须经过以下各级:

外部引脚 → 保护二极管 → 上拉/下拉电阻 → 施密特触发器 → 输入数据寄存器 (IDR)
  1. 保护二极管:与输出级共用,将输入电压钳位在 VDD ~ GND 之间,防止 ESD 和过压损坏。
  2. 上拉/下拉电阻:可控的弱上拉(约 40kΩ)或弱下拉(约 40kΩ),由 GPIO 控制寄存器独立配置。
  3. 施密特触发器:整形的关键,为输入信号提供滞回特性,抑制噪声和缓变边沿引起的多次触发。
  4. 输入数据寄存器(IDR):32 位只读寄存器,每位对应一个引脚,CPU 通过读 IDR 获取引脚当前电平。

输入模式对比

施密特触发器

模式 上拉/下拉 施密特触发器 典型用途
浮空输入 关闭 使能 引脚外部已接上拉/下拉电阻(如按键接外部上拉)
上拉输入 上拉 ~40kΩ 至 VDD 使能 按键接 GND(松开默认高电平)
下拉输入 下拉 ~40kΩ 至 GND 使能 按键接 VDD(松开默认低电平)

浮空输入在引脚悬空时电平不确定(由环境耦合决定),实际使用中务必确保引脚有确定的电平来源。

施密特触发器滞回特性

施密特触发器通过正反馈实现两个不同的阈值电压:

  • 正向阈值 V_T+:输入电压从低到高穿越此值时,输出翻转为高。典型值约 1.5V(VDD = 3.3V 时)。
  • 负向阈值 V_T-:输入电压从高到低穿越此值时,输出翻转为低。典型值约 0.9V(VDD = 3.3V 时)。
  • 滞回宽度 ΔV = V_T+ - V_T- ≈ 0.6V

滞回特性确保输入信号在阈值附近噪声不会导致输出在高低之间频繁震荡。没有施密特触发的普通 CMOS 输入在缓变信号(如 RC 充放电波形)上会产生多次触发脉冲。

按键消抖原理

机械按键依靠金属弹片接触导通,按下和松手瞬间弹片会发生多次弹跳,持续时间 5~20ms:

开关接触时弹跳曲线:‾‾‾\_‾\_‾\_‾\_‾\_\_\_\_\_\_‾\_‾\_‾‾‾‾‾

弹跳源自机械阻尼振荡:弹片接触时的动能转换为弹性势能,反复弹跳直到能量被摩擦消耗殆尽。消抖的实质是低通滤波

  • 硬件消抖:在按键两端并联电容(通常 0.1μF),与上拉电阻形成 RC 低通滤波,时间常数 τ = R × C。以 10kΩ 上拉 + 0.1μF 为例,τ = 10k × 0.1μ = 1ms,约 5τ = 5ms 后电平稳定,可滤除大多数弹跳。
  • 软件消抖:检测电平变化后等待 10~20ms,再次读取确认。延时时间应略大于实测弹跳周期(常规按键取 10ms 即可)。

按键

按键是最简单的输入设备,按下导通,松手断开。由于内部采用机械弹簧片,按下和松手瞬间会产生抖动(5-10ms 的不稳定电平):

理想波形: 高‾‾‾‾‾‾‾‾‾‾‾\_\_\_\_\_‾‾‾‾‾‾‾‾‾‾‾
实际波形: 高‾‾‾‾\_‾\_‾\_‾\_\_\_\_\_\_‾\_‾\_‾‾‾‾‾
          ↑按下       ↑稳定      ↑松手
          抖动区      低电平     抖动区

软件消抖:检测到电平变化后,延时 10ms 再次读取确认。

传感器模块

传感器模块内部结构:

传感器(光敏/热敏/红外) ──┬── 定值电阻 ─── VCC
                        │
                        └── 分压输出(模拟) ── 电压比较器(LM393) ── DO(数字输出)
                                                    ↑
                                              参考电压(电位器分压)
  • 敏感电阻随外界物理量变化 → 分压变化 → 比较器二值化 → DO 输出数字信号(0/1)
  • AO 输出原始模拟电压(需 ADC 读取)

电路连接

  • KEY1(轻触按键):一端接 GND,一端接 PB0,内部上拉
    • 按下:PB0 = 低电平
    • 松开:PB0 = 高电平(上拉)
  • 光敏传感器模块
    • VCC → 3.3V, GND → GND
    • DO → PA0(数字输出)
    • AO → PA1(模拟输出,暂不接)

CubeMX 配置

  • PB0 → GPIO_Input,Pull-up
  • PA0 → GPIO_Input,Pull-up(传感器 DO)
  • 保持其他默认,不需要任何中断

关键 HAL API

函数 说明
HAL_GPIO_ReadPin(GPIOx, GPIO_PIN_x) 读取引脚电平,返回 GPIO_PIN_SET(1) 或 GPIO_PIN_RESET(0)

完整代码

按键控制 LED

/* main.c */
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();     // PB0_Input + PC13_Output

    uint8_t key_flag = 0;   // 防止一次按下触发多次

    while (1)
    {
        /* 检测按键按下(低电平有效) */
        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
        {
            HAL_Delay(10);                      // 消抖
            if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
            {
                if (key_flag == 0)
                {
                    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
                    key_flag = 1;               // 防止重复触发
                }
            }
        }
        else
        {
            key_flag = 0;                       // 松手后清零
        }
    }
}

松手沿检测(另一种更可靠的写法):

uint8_t pre_key = 1, cur_key = 1;

while (1)
{
    pre_key = cur_key;
    cur_key = (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) ? 0 : 1;

    if (pre_key != cur_key)         // 电平变化
    {
        HAL_Delay(10);              // 消抖
        if (cur_key == 1)           // 松手沿:上一次按下,这一次松开
        {
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        }
    }
}

光敏传感器控制蜂鸣器

/* PA0(DO) 读取传感器输出 */
while (1)
{
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
    {
        // 光线暗 → DO 输出低电平 → 蜂鸣器响
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_RESET);  // 低电平驱动
    }
    else
    {
        // 光线亮 → DO 输出高电平 → 蜂鸣器关
        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_SET);
    }
}

常见坑点

  • ⚠️ 机械按键必须消抖,否则一次按下可能触发多次
  • ⚠️ GPIO 浮空输入时引脚悬空会导致电平不确定,务必使用上拉/下拉
  • ⚠️ 传感器模块的 DO/AO 区别:DO 是数字开关量,AO 是模拟电压(需要 ADC)
  • ⚠️ 按键按下为低电平是因为 GND 导通,设计电路时注意按键接法