2. 按键输入检测与消抖

任务说明

本案例实现一个基于ARM处理器的按键输入检测程序:读取GPIO引脚上的按键电平,通过软件延时消除机械抖动,在确认按键按下后翻转LED的亮灭状态。程序采用轮询方式检测,适用于无操作系统的裸机环境。

硬件连接

ARM Cortex-M GPIOA PA0 (KEY) PA1 (LED) KEY 10k VCC 3.3V GND LED 330Ω GND 按下 → PA0 = 0 PA1 = 1 → LED亮

按键一端接GPIO PA0,另一端接地;PA0内部配置为上拉输入,按键按下时读到低电平。LED正极经限流电阻接PA1,负极接地,PA1输出高电平时LED点亮。

软件消抖原理

机械按键在按下和释放瞬间会产生约5~20ms的电平抖动。本程序采用延时消抖法:首次检测到按键电平变化后,延时10ms再次读取,若电平状态与之前一致,则确认状态稳定,视为有效按键事件。

代码实现


#include "stm32f10x.h"
#include <stdint.h>

/* 软件延时函数:约1ms @ 72MHz */
static void delay_ms(uint32_t ms) {
    for (uint32_t i = 0; i < ms * 4000; i++) {
        __NOP();
    }
}

/* GPIO初始化 */
static void gpio_init(void) {
    GPIO_InitTypeDef gpio;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    /* PA0: 上拉输入(按键) */
    gpio.GPIO_Pin   = GPIO_Pin_0;
    gpio.GPIO_Mode  = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &gpio);
    
    /* PA1: 推挽输出(LED) */
    gpio.GPIO_Pin   = GPIO_Pin_1;
    gpio.GPIO_Mode  = GPIO_Mode_Out_PP;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &gpio);
    
    GPIO_SetBits(GPIOA, GPIO_Pin_1);  /* 初始LED灭 */
}

/* 按键扫描:返回1表示一次有效按下 */
static uint8_t key_scan(void) {
    static uint8_t last_state = 1;  /* 上一次稳定状态,1=释放 */
    uint8_t current_state;
    uint8_t event = 0;
    
    current_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
    
    if (current_state != last_state) {
        delay_ms(10);  /* 消抖延时 */
        current_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
        
        if (current_state != last_state) {
            /* 状态稳定变化 */
            if (last_state == 1 && current_state == 0) {
                event = 1;  /* 检测到按下事件 */
            }
            last_state = current_state;
        }
    }
    return event;
}

int main(void) {
    gpio_init();
    
    while (1) {
        if (key_scan()) {
            /* 翻转LED状态 */
            if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1)) {
                GPIO_ResetBits(GPIOA, GPIO_Pin_1);   /* 点亮 */
            } else {
                GPIO_SetBits(GPIOA, GPIO_Pin_1);     /* 熄灭 */
            }
        }
    }
}

代码说明

key_scan()函数采用状态跟踪方式:last_state保存上一次确认的稳定电平。当检测到当前电平与上次不同时,延时10ms后重新读取;若仍不同,说明电平确实变化。仅当状态从释放(1)变为按下(0)时返回1,避免一次按压过程中重复触发。主循环中每次检测到有效按下事件就翻转LED输出。

编译与烧录


# 使用arm-none-eabi工具链编译
arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb key_led.c -o key_led.o
arm-none-eabi-gcc -T stm32f103.ld key_led.o -o key_led.elf
arm-none-eabi-objcopy -O binary key_led.elf key_led.bin

# 通过ST-Link烧录
st-flash write key_led.bin 0x08000000

需准备STM32F103最小系统板、ST-Link调试器及对应的链接脚本(startup文件与ld文件)。也可使用STM32CubeIDE等集成环境直接编译下载。

验证方法

烧录程序后,观察以下现象:

  • 初始状态LED熄灭;
  • 每次按下按键并释放,LED亮灭状态翻转一次;
  • 快速连续按键时,每次按压只翻转一次,不会出现多次抖动翻转;
  • 按住按键不放,LED状态不反复变化。

满足以上四点即判定按键消抖与LED切换功能正常。若按下时LED闪烁多次,说明消抖延时不足或延时函数不准,可适当增大delay_ms中的循环系数。