第四章:GPIO与按键诊断

按键,是理疗仪和用户最直接的交互方式。你想想看,用户按一下“开始治疗”,如果没反应,那体验得多糟糕。这一章,我们就来聊聊GPIO和按键的那些事儿。我做了这么多年嵌入式,发现按键问题其实占了故障排查的很大一部分。说白了,很多问题不是芯片坏了,而是配置或者电路上的小细节没处理好。

4.1 GPIO模式配置:推挽与开漏

GPIO的模式配置,是基础中的基础。但恰恰是基础,最容易出问题。我个人习惯,在写代码前,一定会先确认外设的电气特性。

4.1.1 推挽输出

推挽输出,就像两个开关,一个推上去接VCC,一个拉下来接GND。输出高电平时,上管导通;输出低电平时,下管导通。这种模式驱动能力强,速度也快。

适用场景:驱动LED、蜂鸣器、继电器等需要一定电流的负载。

我在项目中遇到过,有人用推挽输出直接驱动一个5V的继电器,结果芯片IO口烧了。为什么?因为推挽输出高电平是3.3V,继电器线圈需要5V,电流倒灌进IO口了。嗯,这里要注意,推挽输出不能直接驱动不同电压域的负载。

4.1.2 开漏输出

开漏输出,只有下拉管,没有上拉管。输出低电平时,管子导通;输出高电平时,管子断开,引脚处于高阻态。所以,开漏输出必须外接上拉电阻才能输出高电平。

我的经验:开漏输出最大的好处,是可以实现“线与”功能。多个开漏输出可以共用一个上拉电阻,任何一个输出低电平,总线就是低电平。这在I2C总线中非常常见。

开漏输出的另一个好处,是电平转换。比如,你的MCU是3.3V,但外设是5V。用开漏输出,外接一个5V的上拉电阻,就能输出5V的高电平。我曾经用这个技巧,让一个3.3V的STM32驱动了一个5V的LCD1602,省掉了一个电平转换芯片。

特性 推挽输出 开漏输出
输出高电平 VCC 需外接上拉电阻
输出低电平 GND GND
驱动能力 弱(取决于上拉电阻)
线与功能 不支持 支持
电平转换 不支持 支持

4.2 按键消抖原理

按键消抖,是每个嵌入式工程师都会遇到的问题。机械按键在按下和释放的瞬间,会产生机械振动,导致电平在短时间内多次跳变。如果不处理,一次按键可能会被误判成多次。

为什么会这样?因为按键内部的金属弹片在接触时,会像弹簧一样来回弹几下。这个时间通常持续5-20ms。所以,消抖的本质就是“等一等,确认稳定了再读取”。

4.2.1 硬件消抖

硬件消抖,就是在按键电路上加一个RC低通滤波器。电容可以吸收电压的跳变,电阻限制充电电流。时间常数τ = R * C,一般取10ms左右。

典型电路:按键一端接GND,另一端接IO口,同时通过10kΩ电阻上拉到VCC,再并联一个0.1μF电容到GND。

我建议,如果PCB空间允许,尽量用硬件消抖。它不占用CPU时间,而且效果稳定。但要注意,电容不能太大,否则按键响应会变慢。

4.2.2 软件消抖

软件消抖更灵活,成本也更低。最常用的方法是:检测到电平变化后,延时10-20ms,再读取一次。如果两次读取一致,就确认按键状态。

// 按键消抖示例代码
uint8_t Key_Scan(void)
{
    static uint8_t last_state = 1;  // 上次按键状态,默认高电平
    uint8_t current_state;
    uint8_t key_value = 0;
    
    current_state = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);
    
    // 检测到电平变化
    if (current_state != last_state)
    {
        // 延时消抖
        HAL_Delay(10);  // 延时10ms
        current_state = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);
        
        // 再次确认
        if (current_state != last_state)
        {
            last_state = current_state;
            if (current_state == 0)  // 按键按下(低电平有效)
            {
                key_value = 1;
            }
        }
    }
    
    return key_value;
}

注意:在中断服务函数中不要使用HAL_Delay(),因为它依赖SysTick中断。如果中断优先级比SysTick高,会导致死锁。我踩过这个坑,后来改用定时器或状态机来处理。

4.3 长按与短按检测

理疗仪上,长按和短按往往对应不同的功能。比如,短按切换模式,长按开关机。实现长按检测,核心是计时。

4.3.1 状态机实现

用状态机来实现按键检测,代码清晰,扩展性好。我一般会定义四个状态:空闲、消抖、按下、释放。

typedef enum {
    KEY_IDLE,      // 空闲状态
    KEY_DEBOUNCE,  // 消抖状态
    KEY_PRESSED,   // 按下状态
    KEY_RELEASE    // 释放状态
} KeyState;

void Key_Task(void)
{
    static KeyState state = KEY_IDLE;
    static uint32_t press_time = 0;
    uint8_t key_level = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);
    
    switch (state)
    {
        case KEY_IDLE:
            if (key_level == 0)  // 检测到按下
            {
                state = KEY_DEBOUNCE;
                press_time = HAL_GetTick();
            }
            break;
            
        case KEY_DEBOUNCE:
            if (HAL_GetTick() - press_time > 10)  // 消抖延时
            {
                if (key_level == 0)  // 确认按下
                {
                    state = KEY_PRESSED;
                    press_time = HAL_GetTick();
                }
                else
                {
                    state = KEY_IDLE;  // 抖动,回到空闲
                }
            }
            break;
            
        case KEY_PRESSED:
            if (key_level == 1)  // 检测到释放
            {
                if (HAL_GetTick() - press_time > 1000)  // 长按超过1秒
                {
                    // 长按事件
                    LongPress_Handler();
                }
                else
                {
                    // 短按事件
                    ShortPress_Handler();
                }
                state = KEY_RELEASE;
            }
            break;
            
        case KEY_RELEASE:
            if (key_level == 1)  // 确认释放
            {
                state = KEY_IDLE;
            }
            break;
    }
}

我的习惯:长按和短按的时间阈值,我一般设为1秒。但具体要看产品需求。理疗仪上,开关机长按我设过2秒,防止误触。这个值最好做成可配置的,方便后期调整。

4.4 常见故障诊断

按键故障,说难不难,说简单也不简单。我总结了几种常见情况,你排查时可以按这个顺序来。

4.4.1 虚焊

虚焊是最常见的硬件故障。按键引脚和焊盘之间没有形成良好的电气连接,导致接触电阻变大,或者时通时断。

诊断方法:

  • 目测:用放大镜观察焊点,看有没有裂纹、锡珠、焊锡不足。
  • 按压测试:用手指轻轻按压按键,看功能是否恢复正常。如果按压后正常,松开后异常,基本就是虚焊。
  • 万用表测量:测量按键两端的电阻。按下时应该接近0Ω,释放时应该无穷大。如果按下时电阻不稳定,就是虚焊。

我曾经:遇到过一个案例,理疗仪按键偶尔失灵。查了半天,发现是按键的一个引脚和焊盘之间有一圈细微的裂纹。用烙铁补焊后,问题解决。所以,别小看虚焊,它是最隐蔽的故障之一。

4.4.2 电平异常

电平异常,就是IO口读到的电平不对。比如,按键没按下时读到低电平,或者按下时读到高电平。

可能的原因:

  • 上拉/下拉电阻失效:电阻虚焊、断裂,或者阻值不对。用万用表测量电阻两端电压,确认是否正常。
  • IO口配置错误:比如,应该配置成输入模式,结果配成了输出模式。或者内部上拉电阻没使能。
  • 外部干扰:长走线容易耦合噪声。可以在IO口加一个100pF的电容到GND,滤除高频干扰。
  • MCU引脚损坏:静电或者过压导致IO口内部损坏。可以换一个IO口试试。

排查步骤:

  1. 用万用表测量按键引脚电压,确认静态电平是否正确。
  2. 检查代码中GPIO的初始化配置,确认模式、上下拉、速度等参数。
  3. 用示波器观察按键按下时的波形,看有没有毛刺或电平不稳。
  4. 如果以上都正常,考虑更换MCU或按键。

嗯,这一章的内容就到这里。按键诊断,说白了就是细心加耐心。你只要掌握了GPIO的配置、消抖的原理、长按短按的检测方法,再结合万用表和示波器,大部分问题都能搞定。下一章,我们会聊聊更复杂的通信接口故障,准备好了吗?