STM32 零基础可移植教程 04:按键输入,为什么按下去读到的是 0 或 1

STM32 零基础可移植教程 04:按键输入,为什么按下去读到的是 0 或 1

前面两篇,我们都是在控制输出:LED 亮灭、蜂鸣器响不响。

这一篇开始做输入。

输入里最适合新手上手的就是按键。它看起来也很简单:按下去,程序读一下,然后做动作。但真正写的时候,很多人会卡在这些地方:

  • CubeMX 里 GPIO 到底选 Input 还是 EXTI

  • 按键为什么有时候按下读到 0,有时候按下读到 1;

  • Pull-up、Pull-down、No pull 到底怎么选;

  • HAL_GPIO_ReadPin() 返回的值怎么判断;

  • 换一块开发板,按键代码为什么反了。

这篇只做一个目标:

bash 复制代码
用轮询方式读取一个按键:按下时 LED 亮,松开时 LED 灭

按键消抖、短按/长按、外部中断,我们后面单独讲。这一篇先把"读到电平并判断按下/松开"这件事讲透。

本篇目标

最终现象:

bash 复制代码
按住按键,LED 亮
松开按键,LED 灭

本篇用到的外设:

bash 复制代码
GPIO Input
GPIO Output

本篇跑通标准:

  • Keil 编译通过;

  • 程序能下载到开发板;

  • 按键按下和松开时,LED 状态能跟着变化;

  • 能说清楚自己的按键是"按下为低电平"还是"按下为高电平"。

本篇暂时不处理按键抖动。你可能会看到按键边沿不够干净,但"按住亮、松开灭"应该是稳定可见的。

准备工作

你需要准备:

|

项目

|

说明

|

| --- | --- |

|

STM32 开发板

|

任意 STM32 开发板

|

|

下载器

|

ST-LINK/V2 或板载 ST-LINK

|

|

LED

|

板载 LED 或外接 LED,沿用第 02 篇即可

|

|

按键

|

板载按键或外接独立按键模块

|

|

原理图

|

最好找到 LED 和按键对应的原理图

|

如果你的开发板自带用户按键,优先用板载按键。常见名称可能叫:

bash 复制代码
KEY
KEY0
WK_UP
USER Button
BUTTON

不同开发板的按键引脚不一样,不要死记。一定以你的原理图为准。

硬件连接

按键最常见的连接方式有两种。

第一种:按键一端接 GPIO,一端接 GND。

bash 复制代码
GPIO ---- 按键 ---- GND

这种情况下,通常要给 GPIO 开上拉:

bash 复制代码
松开:GPIO 被上拉到高电平,读到 1
按下:GPIO 被接到 GND,读到 0

所以这种按键是:

bash 复制代码
低电平有效

第二种:按键一端接 GPIO,一端接 3.3V。

bash 复制代码
GPIO ---- 按键 ---- 3.3V

这种情况下,通常要给 GPIO 开下拉:

bash 复制代码
松开:GPIO 被下拉到低电平,读到 0
按下:GPIO 被接到 3.3V,读到 1

所以这种按键是:

bash 复制代码
高电平有效

这就是为什么有的教程里写"按下等于 0",有的教程里写"按下等于 1"。不是谁写错了,而是硬件接法不一样。

该电路是一个典型的按键输入检测电路,主要用于单片机或数字逻辑芯片的GPIO口输入。

电路中将PA0引脚通过一个4.7kΩ的上拉电阻R15连接到3.3V电源,确保在按键未按下时PA0保持稳定的高电平状态。

同时,PA0与按键之间串联了一个1kΩ的电阻,用以限制电容放电电流,保护GPIO口。

按键KEY1采用低电平有效的方式工作。

当按键按下时,PA0通过1kΩ电阻和按键开关被拉低至GND,此时PA0读取为低电平(0);释放按键后,PA0恢复为高电平(1)。为了抑制按键抖动和外部高频干扰,电路中还加入了一个104(100nF)的滤波电容C56,与电阻共同构成RC滤波网络,提高信号稳定性。

CubeMX 配置步骤

1. 复制上一篇工程

建议从上一篇蜂鸣器工程或第 02 篇 LED 工程复制一份,改名为:

bash 复制代码
04_key_input

如果你保留 LED 代码,这一篇可以直接复用 app_led.h/.c

如果你想从干净工程开始,也可以只配置一个 LED 输出和一个按键输入。

2. 配置 LED 输出引脚

LED 的配置和第 02 篇一样:

  1. 找到 LED 对应 GPIO;

  2. 设置为 GPIO_Output

  3. User Label 填 LED

  4. 根据有效电平设置初始输出电平。

如果你已经从第 02 篇工程复制过来,这一步通常已经配置好了。

3. 配置按键输入引脚

找到按键对应的 GPIO 引脚。

假设你的按键接在 PA0,就在 CubeMX 里点击 PA0,选择:

bash 复制代码
GPIO_Input

如果你的按键接在 PB12PC0PE4,就配置对应引脚。

这一篇先不用外部中断,所以不要选 GPIO_EXTI。我们先用最直观的轮询方式读取按键。

4. 给按键引脚起名字

进入:

bash 复制代码
System Core -> GPIO

找到按键引脚,把 User Label 改成:

bash 复制代码
KEY1

这样 CubeMX 生成代码后,会在 main.h 里生成类似:

bash 复制代码
#define KEY1_Pin GPIO_PIN_0
#define KEY1_GPIO_Port GPIOA

后面应用代码就使用 KEY1_PinKEY1_GPIO_Port,不把具体引脚写死。

5. 设置 Pull-up / Pull-down

这是按键输入最容易配错的地方。

如果你的按键是:

bash 复制代码
GPIO ---- 按键 ---- GND

就配置:

bash 复制代码
GPIO Pull-up/Pull-down: Pull-up

如果你的按键是:

bash 复制代码
GPIO ---- 按键 ---- 3.3V

就配置:

bash 复制代码
GPIO Pull-up/Pull-down: Pull-down

如果开发板原理图里已经有外部上拉或外部下拉电阻,也可以选择 No pull-up and no pull-down,但新手阶段建议按原理图确认,不要猜。

6. 生成 Keil 工程

配置完成后点击:

bash 复制代码
GENERATE CODE

打开 Keil 工程,先编译一次确认没有错误。

Keil 工程生成和编译

打开 Keil 后,先编译:

bash 复制代码
Build / F7

确认输出里没有错误:

bash 复制代码
0 Error(s)

如果这里报错,先不要怀疑按键逻辑。优先检查:

  • CubeMX 是否重新生成成功;

  • app_led.c 是否还在工程里;

  • 新增的 app_key.c 后面是否加入工程;

  • KEY 的 User Label 是否写对。

完整代码

这一篇我们继续使用应用层封装。

新增两个文件:

bash 复制代码
Core/Inc/app_key.h
Core/Src/app_key.c

如果你复用第 02 篇 LED 代码,还需要保留:

bash 复制代码
Core/Inc/app_led.h
Core/Src/app_led.c

1. 新建 Core/Inc/app_key.h

Core/Inc 目录下新建:

bash 复制代码
app_key.h

写入下面代码:

bash 复制代码
#ifndef APP_KEY_H
#define APP_KEY_H

#include "main.h"

typedefenum
{
    APP_KEY_RELEASED = 0,
    APP_KEY_PRESSED = 1
} App_KeyState;

void App_Key_Init(void);
App_KeyState App_Key_Read(void);

#endif

这里没有直接返回 GPIO_PinState,而是自己定义了:

bash 复制代码
APP_KEY_RELEASED
APP_KEY_PRESSED

这样代码读起来更接近业务含义。

2. 新建 Core/Src/app_key.c

Core/Src 目录下新建:

bash 复制代码
app_key.c

写入下面代码:

bash 复制代码
#include "app_key.h"

/*
 * KEY_GPIO_Port and KEY_Pin are generated by CubeMX in main.h.
 * Set the key pin User Label to KEY in CubeMX.
 */
#ifndef KEY_GPIO_Port
#error "KEY_GPIO_Port is not defined. Set the key pin User Label to KEY in CubeMX."
#endif

#ifndef KEY_Pin
#error "KEY_Pin is not defined. Set the key pin User Label to KEY in CubeMX."
#endif

/*
 * Many key circuits are active-low:
 * released -> GPIO_PIN_SET
 * pressed  -> GPIO_PIN_RESET
 *
 * If your key is active-high, change this macro to GPIO_PIN_SET.
 */
#ifndef APP_KEY_PRESSED_LEVEL
#define APP_KEY_PRESSED_LEVEL  GPIO_PIN_RESET
#endif

void App_Key_Init(void)
{
}

App_KeyState App_Key_Read(void)
{
    GPIO_PinState level = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);

    if (level == APP_KEY_PRESSED_LEVEL)
    {
        return APP_KEY_PRESSED;
    }

    return APP_KEY_RELEASED;
}

这里最关键的是:

bash 复制代码
#define APP_KEY_PRESSED_LEVEL  GPIO_PIN_RESET

它默认认为按键是低电平有效,也就是按下读到 0。

如果你的按键是高电平有效,就改成:

bash 复制代码
#define APP_KEY_PRESSED_LEVEL  GPIO_PIN_SET

注意,这一篇暂时没有消抖。App_Key_Read() 只是读取当前电平并判断按下/松开。

3. 把 app_key.c 加入 Keil 工程

在 Keil 工程树里右键:

bash 复制代码
Application/User/Core

选择:

bash 复制代码
Add Existing Files to Group 'Application/User/Core'

然后添加:

bash 复制代码
Core/Src/app_key.c

如果你从干净工程开始,还要确认 app_led.c 也已经加入 Keil 工程。

如果忘了添加 .c 文件,可能会报:

bash 复制代码
undefined symbol App_Key_Read

这不是函数声明错了,而是 app_key.c 没有参与编译。

main.c 调用方式

这篇我们用按键控制 LED。

按下时 LED 亮,松开时 LED 灭。

1. 在 Includes 区域添加头文件

找到:

bash 复制代码
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */

改成:

bash 复制代码
/* USER CODE BEGIN Includes */
#include "app_led.h"
#include "app_key.h"
/* USER CODE END Includes */

2. 在初始化区域调用初始化函数

确保 MX_GPIO_Init() 已经在前面执行:

bash 复制代码
MX_GPIO_Init();

然后在 USER CODE BEGIN 2 里添加:

bash 复制代码
/* USER CODE BEGIN 2 */
App_LED_Init();
App_Key_Init();
/* USER CODE END 2 */

App_Key_Init() 现在是空函数,看起来好像没用。

但保留它有一个好处:后面如果你要给按键加状态机、消抖变量、长按计时,就有一个统一初始化入口。

3. 在 while 循环里读取按键

找到:

bash 复制代码
while (1)
{
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
  /* USER CODE END 3 */
}

改成:

bash 复制代码
while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
if (App_Key_Read() == APP_KEY_PRESSED)
  {
      App_LED_On();
  }
else
  {
      App_LED_Off();
  }

  HAL_Delay(10);
/* USER CODE END 3 */
}

这里的 HAL_Delay(10) 只是让循环不要跑得太快。

注意,它不是完整消抖。真正的按键消抖,我们下一篇单独写。

编译、下载和验证

代码加完后,先编译:

bash 复制代码
Build / F7

如果没有错误,再下载:

bash 复制代码
Download

下载后观察现象:

bash 复制代码
按住按键 -> LED 亮
松开按键 -> LED 灭

如果 Keil 下载后程序没有自动运行,但你按复位键后能运行,那就先以复位后现象为准。这个问题前面已经遇到过,它不是按键代码问题。

移植到其他板子的修改点

这篇的移植点主要有 6 个。

|

要改的地方

|

为什么要改

|

在哪里改

|

| --- | --- | --- |

|

按键引脚

|

不同板子的按键接到不同 GPIO

|

CubeMX Pinout 页面

|

|

User Label

|

代码依赖 KEY_PinKEY_GPIO_Port

|

CubeMX GPIO 页面,标签设为 KEY

|

|

上拉/下拉

|

按键接 GND 还是接 3.3V 不同

|

CubeMX GPIO Pull-up/Pull-down

|

|

按下有效电平

|

有些按下读 0,有些按下读 1

| app_key.c

里的 APP_KEY_PRESSED_LEVEL

|

|

LED 引脚和有效电平

|

LED 只是用来显示按键状态

|

沿用第 02 篇的 LED 配置

|

|

是否有外部电阻

|

有外部上拉/下拉时内部 Pull 可不同

|

看开发板原理图

|

换板子的推荐顺序:

  1. 看原理图,确认按键接到哪个 MCU 引脚;

  2. 看按键另一端接 GND 还是 3.3V;

  3. 在 CubeMX 里把该引脚设置成 GPIO_Input

  4. User Label 填 KEY

  5. 根据原理图选择 Pull-up 或 Pull-down;

  6. 根据按下电平修改 APP_KEY_PRESSED_LEVEL

  7. 编译下载,用 LED 观察按键状态。

常见问题排查

1. 按下按键没反应

|

优先检查

|

具体方法

|

| --- | --- |

|

按键引脚是否选错

|

回原理图确认按键接到哪个 GPIO

|

|

User Label 是否为 KEY

|

打开 main.h 看有没有 KEY_PinKEY_GPIO_Port

|

|

上拉/下拉是否选对

|

按键接 GND 通常上拉;按键接 3.3V 通常下拉

|

|

程序是否运行

|

下载后按复位键,看 LED 是否受控

|

| app_key.c

是否加入工程

|

Keil 工程树里确认有 app_key.c

|

2. 逻辑反了:松开亮,按下灭

这通常说明按键有效电平写反了。

打开 app_key.c,找到:

bash 复制代码
#define APP_KEY_PRESSED_LEVEL  GPIO_PIN_RESET

如果你的按键按下读到高电平,改成:

bash 复制代码
#define APP_KEY_PRESSED_LEVEL  GPIO_PIN_SET

也可能是 LED 的有效电平配置反了。可以先用第 02 篇的 LED 程序确认 LED 的 On/Off 没问题,再排查按键。

3. KEY_GPIO_Port is not defined

说明 CubeMX 没有生成:

bash 复制代码
KEY_GPIO_Port
KEY_Pin

Core/Inc/main.h 看一下。如果你看到的是:

bash 复制代码
#define KEY0_Pin ...
#define KEY0_GPIO_Port ...

那说明 User Label 不是 KEY

解决方法:

  1. 回 CubeMX;

  2. 找到按键 GPIO;

  3. 把 User Label 改成 KEY

  4. 重新 Generate Code;

  5. 回 Keil 编译。

4. undefined symbol App_Key_Read

通常是 app_key.c 没有加入 Keil 工程。

解决方法:

  1. 右键 Application/User/Core

  2. 选择 Add Existing Files to Group

  3. 添加 Core/Src/app_key.c

  4. 重新编译。

5. 按键偶尔乱跳

这是机械按键的常见现象,叫抖动。

你按下一次,电平可能不是干干净净地从 1 变 0,而是在几毫秒内跳几下。

本篇只是让你先读到按键电平,所以没有处理抖动。下一篇我们会专门写:

bash 复制代码
STM32 按键消抖:为什么按一次会触发好几次

6. 按键一直显示按下

优先检查:

  • Pull-up/Pull-down 是否选错;

  • 按键模块是否接反;

  • GPIO 是否被配置成了输出;

  • 外部按键模块是否已经自带上拉/下拉;

  • APP_KEY_PRESSED_LEVEL 是否写反。

如果有万用表,可以直接量按键 GPIO 对 GND 的电压:

bash 复制代码
松开时电压是多少
按下时电压是多少

这比猜代码快得多。

本篇小结

这一篇我们完成了最基础的 GPIO 输入:读取按键状态,并用 LED 显示出来。

你现在至少应该知道:

  • GPIO 输入不是只写代码,先要看按键硬件接法;

  • 按键接 GND 时通常按下读 0,接 3.3V 时通常按下读 1;

  • CubeMX 里按键引脚要配置成 GPIO_Input

  • Pull-up 和 Pull-down 要跟硬件接法配套;

  • User Label 填 KEY 后,代码里可以使用 KEY_PinKEY_GPIO_Port

  • APP_KEY_PRESSED_LEVEL 是按键移植时最关键的宏;

  • 本篇没有消抖,按键抖动下一篇单独处理。

下一篇我们就接着讲:

STM32 按键消抖:为什么按一次会触发好几次。

这一篇跑通以后,你就已经具备了最基础的"输入控制输出"能力:读一个按键,控制一个 LED。后面做菜单、模式切换、参数设置,都是从这个基础上长出来的。

相关推荐
三佛科技-187366133977 小时前
BP8522D贴片SOP7,5V150mA高集成度无VCC电容降压型恒压芯片解析
单片机·嵌入式硬件
csg11077 小时前
MSP430F149驱动T8650北斗模块实现短报文通信实战
单片机·嵌入式硬件·物联网·自动化
hoiii1878 小时前
基于STM32的音频播放系统,实现SD卡读取音频文件PWM输出播放
stm32·嵌入式硬件·音视频
Deitymoon8 小时前
STM32——软件IIC显示字符
stm32·单片机·嵌入式硬件
百万老师9 小时前
自然语言编程时代,如何零基础学习掌握嵌入式编程
c语言·单片机·嵌入式硬件·学习·ai全流程闭环开发
efangfd9 小时前
TXS0104 和 TXB0104 的 IO 驱动电流对比
单片机·嵌入式硬件
进击切图仔10 小时前
python 工程使用 .env getenv 安全加载环境变量(备忘)
chrome·python·安全
gihigo199810 小时前
STM32F407 Modbus RTU主站例程
stm32·单片机·嵌入式硬件
程序员差不多先生10 小时前
HisparkStudio有哪些开发功能?
嵌入式硬件