嵌入式开发原创教程嵌入式入门 02
GPIO 输入输出:按键控制 LED 与 20ms 消抖
从原理图确认有效电平,完成按键输入、LED 输出、非阻塞消抖和按键事件处理。

实验目标
- 让一个 LED 受按键控制;
- 理解上拉、下拉和有效电平;
- 不用长时间 delay 实现消抖;
- 区分“按键按住”和“按一下触发一次”。
一、先看硬件
常见接法是按键一端接 GND,另一端接 GPIO,并启用上拉电阻。这样:
- 松开时读到高电平;
- 按下时读到低电平;
- 有效状态为低电平。
如果你的板子有外部上拉,CubeMX 中可以不开启内部上拉;如果没有,请开启 GPIO_PULLUP。
假设:
#define LED_GPIO_Port GPIOC
#define LED_Pin GPIO_PIN_13
#define KEY_GPIO_Port GPIOA
#define KEY_Pin GPIO_PIN_0二、CubeMX 配置
- PC13 配置为 GPIO_Output、Push Pull、Low;
- PA0 配置为 GPIO_Input;
- PA0 的 Pull 设置为 Pull-up;
- 生成代码。
三、可用的非阻塞消抖
在 main.c 的 USER CODE 区域加入:
/* USER CODE BEGIN PV */
static GPIO_PinState key_last_raw = GPIO_PIN_SET;
static GPIO_PinState key_stable = GPIO_PIN_SET;
static uint32_t key_changed_at = 0;
static uint8_t key_pressed_event = 0;
static void key_scan(void)
{
GPIO_PinState raw = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);
if (raw != key_last_raw)
{
key_last_raw = raw;
key_changed_at = HAL_GetTick();
}
if ((HAL_GetTick() - key_changed_at) >= 20U &&
raw != key_stable)
{
key_stable = raw;
if (key_stable == GPIO_PIN_RESET)
{
key_pressed_event = 1U;
}
}
}
/* USER CODE END PV */主循环:
while (1)
{
key_scan();
if (key_pressed_event)
{
key_pressed_event = 0U;
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
HAL_Delay(5);
}这段代码的含义:
- 原始电平变化时重新计时;
- 连续稳定 20ms 才认可新状态;
- 只有从松开变成按下时产生一次事件;
- 主循环不会被 20ms 完全阻塞。
四、实验现象
每按一次按键,LED 翻转一次。按住按键不放,LED 不应持续快速闪烁。
五、常见问题
按键一直触发
先检查有效电平。如果按下时读到高电平,需要把消抖代码中的 GPIO_PIN_RESET 改成 GPIO_PIN_SET。
按键偶尔触发多次
可能是按键抖动时间较长或接线松动。把 20ms 改成 30-50ms 再测试。
串口打印影响按键
不要在中断中打印,也不要在主循环中使用很长的 HAL_Delay。先保证主循环每 5-10ms 运行一次按键扫描。
六、进阶练习
- 增加长按 2 秒触发另一个功能;
- 用两个按键分别控制 LED 开关;
- 把按键扫描封装成 button.c / button.h;
- 用串口输出按键事件,而不是在中断里打印。


