前言

本文基于 STM32F103C8T6 最小系统完成工程:4 路独立按键采集、软件消抖处理、0.96 寸 I2C OLED 实时展示按键按下 / 松开状态。内容从硬件接线、GPIO 底层原理、按键消抖、位运算状态存储、模块化程序结构到设备故障排查全覆盖,适合单片机新手入门 GPIO 输入实验,接线、逻辑讲解通俗易懂,可直接照搬操作。

视频讲解:STM32F103C8T6驱动四路独立按键+OLED状态显示完整教程(硬件原理+代码逻辑+故障排查)https://www.bilibili.com/video/BV1vTuB6sE9i/?share_source=copy_web&vd_source=91b967b41a17a5686913521545a8c3fb

一、工程整体实现效果

完整工作流程: 手指按下 / 松开 K1~K4 → 按键内部机械触点通断改变引脚电平 → 单片机读取 PB12-PB15 引脚电压 → 延时 20ms 二次采样消除抖动干扰 → 将四路按键状态整合为一组二进制位图数据 → OLED 屏幕同步刷新,显示每个按键 UP(松开)/DOWN(按下)

核心概括:独立按键把按压动作转化为高低电平,STM32 读取引脚电平并过滤机械抖动,最后将四路按键状态通过 OLED 可视化展示。

二、硬件接线对照表

主控芯片:STM32F103C8T6 OLED 采用软件 I2C 驱动,全部按键为低电平有效接法:

器件 STM32 引脚 接线说明
K1 按键 PB12 按键另一端接 GND,低电平有效
K2 按键 PB13 按键另一端接 GND,低电平有效
K3 按键 PB14 按键另一端接 GND,低电平有效
K4 按键 PB15 按键另一端接 GND,低电平有效
OLED 时钟 SCL PB8 软件 I2C 通信时钟线
OLED 数据 SDA PB9 软件 I2C 通信数据线
OLED 供电 VCC 3.3V 禁止接入 5V,容易烧毁屏幕
OLED 地线 GND GND 单片机与屏幕必须共地,否则通信异常

2.1 按键硬件电路原理(以 K1 为例)

单片机内部自带 3.3V 电源,搭配片内集成上拉电阻连接至 PB12 引脚;PB12 引脚引出后接轻触按键一端,按键另一端直接接地。

2.2 开启内部上拉的作用

如果 GPIO 引脚悬空无高低电平输入,外界电磁干扰会让电平随机跳动,无法稳定识别按键。程序将 PB12~PB15 配置为内部上拉输入模式:

  1. 按键松开:内部上拉电阻拉高引脚,电平为高电平 1;
  2. 按键按下:引脚通过按键直通 GND,电平变为低电平 0。 优势:不用额外焊接外接上拉电阻,简化硬件接线,降低新手接线失误概率。

2.3 独立按键定义、优缺点

每个按键单独占用一路 GPIO 输入引脚,不存在行列复用,因此叫做独立按键。 优点:电路简单,程序逻辑直观,支持单键、多键同时识别,适合 GPIO 输入入门学习; 缺点:按键数量多会大量占用单片机 IO 口,超过 8 个按键推荐使用矩阵键盘。

三、独立按键完整工作逻辑

按键内部是金属机械触点,松开时触点断开,按下时触点闭合,本电路触点闭合后会直接把 GPIO 引脚拉至 0V。

完整工作步骤:

  1. 人为按压或松开按键;
  2. 按键内部金属触点断开 / 闭合,PB12~PB15 引脚电平切换;
  3. STM32 读取 GPIO 端口输入寄存器,获取当前引脚电平;
  4. 延时 20ms 后再次读取电平,完成软件消抖;
  5. 根据稳定电平生成四路按键的状态位图;
  6. OLED 屏幕刷新,分别展示每路按键 UP/DOWN 状态。

单路按键电平对应关系(K1)

K1 实际状态 触点状态 PB12 电平 程序判断结果 OLED 显示内容
松开 断开 高电平 1 未按下 K1 PB12: UP
按下 闭合 低电平 0 已按下 K1 PB12: DOWN

K2、K3、K4 工作逻辑完全一致,仅对应 PB13、PB14、PB15 引脚区分。

四、GPIO 输入配置与电平读取逻辑

4.1 按键初始化配置说明

初始化函数 KEY_Init 中,首先开启 GPIOB 外设时钟,随后将 PB12、PB13、PB14、PB15 统一设置为内部上拉输入模式 GPIO_Mode_IPU。 参数释义:I 代表 Input 输入模式,PU 代表 Pull Up 内部上拉开启。

4.2 引脚电平读取判断原理

程序一次性读取 GPIOB 整个端口的输入电平,再通过按位与运算单独提取单根引脚状态。 以 K1 判断为例:将端口读取值与 PB12 引脚掩码做与运算,运算结果等于 0 时,代表 PB12 为低电平,判定按键按下。

五、软件消抖原理(20ms 延时复检法)

5.1 什么是按键抖动

机械轻触按键在按下、松开的瞬间,内部金属触点会在几毫秒内反复接触、断开,电平持续来回跳变。如果不做处理,单次按压按键会被单片机识别成多次触发,功能出现错乱。

5.2 消抖处理完整逻辑

  1. 第一次读取按键原始电平;
  2. 对比当前电平与上一次稳定状态,如果一致,直接返回原有按键状态;
  3. 如果电平发生变化,延时 20ms 跳过抖动区间;
  4. 延时结束后第二次读取按键电平;
  5. 对比两次采样电平,数值完全相同则更新稳定按键状态;两次数值不一致则舍弃本次变化,保留原有状态。

5.3 消抖延时参数说明

20ms 是教学实验通用标准消抖时长: 延时过短,无法完全过滤抖动,按键状态频繁乱跳; 延时过长,按键按压后屏幕反应迟缓,操作体验差; 可修改宏定义 KEY_DEBOUNCE_MS 自定义消抖时间。

六、按键状态位图(位运算实操案例)

工程没有使用 4 个独立变量分别存储按键状态,仅使用 1 个 8 位变量的低 4 位保存四路按键信息,节省内存,同时支持识别多按键同时按下。

6.1 按键状态宏定义对照表

宏名称 数值 二进制低四位 对应按键状态
KEY_NONE 0x00 0000 无按键按下
KEY1_PRESSED 0x01 0001 K1 按下
KEY2_PRESSED 0x02 0010 K2 按下
KEY3_PRESSED 0x04 0100 K3 按下
KEY4_PRESSED 0x08 1000 K4 按下

6.2 多按键组合与判断方法

举例:K1 和 K3 同时按下,两个状态宏做按位或运算,0001 | 0100 = 0101,对应数值 0x05。 程序中使用按位与判断单键是否按下:将总状态变量与对应按键掩码做与运算,结果不为 0 则代表该按键处于按下状态。

七、工程文件结构与 OLED 显示逻辑

7.1 模块化文件分工

  1. KEY.h 定义四路按键状态宏,声明按键初始化、读取状态对外函数;
    #ifndef __KEY_H
    #define __KEY_H
    
    #include "sys.h"
    
    /*
     * KEY_ReadState() 返回值的状态位定义。
     * 某一位为 1 表示对应按键按下,为 0 表示对应按键松开。
     * 多个按键可以同时按下,各状态位之间可使用按位或组合。
     */
    #define KEY_NONE             ((u8)0x00)    /* 没有按键按下 */
    #define KEY1_PRESSED         ((u8)0x01)    /* K1 按下,对应 PB12 */
    #define KEY2_PRESSED         ((u8)0x02)    /* K2 按下,对应 PB13 */
    #define KEY3_PRESSED         ((u8)0x04)    /* K3 按下,对应 PB14 */
    #define KEY4_PRESSED         ((u8)0x08)    /* K4 按下,对应 PB15 */
    
    /* 初始化 PB12~PB15 为内部上拉输入。 */
    void KEY_Init(void);
    
    /* 读取经过软件消抖处理后的按键状态。 */
    u8 KEY_ReadState(void);
    
    #endif
    
  2. KEY.c 实现引脚初始化、读取原始引脚电平、带消抖稳定状态读取功能;
    #include "KEY.h"
    #include "delay.h"
    
    #define KEY_ALL_PINS         (GPIO_Pin_12 | GPIO_Pin_13 | \
                                  GPIO_Pin_14 | GPIO_Pin_15)
    #define KEY_DEBOUNCE_MS      20U    /* 按键消抖时间,单位:毫秒 */
    
    /* 读取未经消抖处理的原始按键状态。 */
    static u8 KEY_ReadRawState(void);
    
    /*
     * 按键硬件连接说明:
     * K1、K2、K3、K4 的一端统一接地,另一端分别连接 PB12~PB15。
     * GPIO 使用内部上拉输入模式,因此按键松开时读取为高电平,
     * 按键按下时读取为低电平,即按键为低电平有效。
     */
    void KEY_Init(void)
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    
    	/* 使能 GPIOB 外设时钟。 */
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    	/* 将四个按键引脚一次性配置为内部上拉输入。 */
    	GPIO_InitStructure.GPIO_Pin = KEY_ALL_PINS;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);
    }
    
    /*
     * 读取按键状态并进行软件消抖。
     * 当原始状态与上一次稳定状态不同时,延时 20 ms 后再次采样;
     * 两次采样结果相同才更新稳定状态,从而过滤机械按键抖动。
     * 返回值为位图,因此能够同时表示多个按键的按下状态。
     */
    u8 KEY_ReadState(void)
    {
    	static u8 stable_state = KEY_NONE;
    	u8 first_sample;
    	u8 second_sample;
    
    	first_sample = KEY_ReadRawState();
    	if(first_sample != stable_state)
    	{
    		delay_ms(KEY_DEBOUNCE_MS);
    		second_sample = KEY_ReadRawState();
    		if(first_sample == second_sample)
    		{
    			stable_state = second_sample;
    		}
    	}
    
    	return stable_state;
    }
    
    static u8 KEY_ReadRawState(void)
    {
    	u16 input_state;
    	u8 key_state = KEY_NONE;
    
    	/* 一次读取 GPIOB 输入寄存器,保证四个按键来自同一次采样。 */
    	input_state = GPIO_ReadInputData(GPIOB);
    
    	/* 输入为低电平时,设置对应的“按下”状态位。 */
    	if((input_state & GPIO_Pin_12) == 0U)
    	{
    		key_state |= KEY1_PRESSED;
    	}
    	if((input_state & GPIO_Pin_13) == 0U)
    	{
    		key_state |= KEY2_PRESSED;
    	}
    	if((input_state & GPIO_Pin_14) == 0U)
    	{
    		key_state |= KEY3_PRESSED;
    	}
    	if((input_state & GPIO_Pin_15) == 0U)
    	{
    		key_state |= KEY4_PRESSED;
    	}
    
    	return key_state;
    }
    
  3. main.c 封装屏幕显示函数,解析按键位图数据,写入 OLED 显示缓存。
    /*
     * Four-key teaching demonstration for STM32F103C8T6
     * K1/K2/K3/K4 -> PB12/PB13/PB14/PB15 (active low)
     * OLED SCL/SDA -> PB8/PB9
     */
    #include "delay.h"
    #include "sys.h"
    #include "oled.h"
    #include "KEY.h"
    
    #define KEY_POLL_INTERVAL_MS  10U
    
    static void KEY_ShowState(u8 key_state);
    static void KEY_ShowOneLine(u8 y, u8 key_mask, u8 *released_text,
    							 u8 *pressed_text, u8 key_state);
    
    int main(void)
    {
    	u8 key_state;
    	u8 last_key_state = 0xFFU;
    
    	delay_init();
    	NVIC_Configuration();
    	OLED_Init();
    	KEY_Init();
    	OLED_ColorTurn(0);
    	OLED_DisplayTurn(0);
    
    	while(1)
    	{
    		key_state = KEY_ReadState();
    
    		/* Refresh only when a key changes, preventing visible OLED flicker. */
    		if(key_state != last_key_state)
    		{
    			KEY_ShowState(key_state);
    			last_key_state = key_state;
    		}
    
    		delay_ms(KEY_POLL_INTERVAL_MS);
    	}
    }
    
    static void KEY_ShowState(u8 key_state)
    {
    	OLED_Clear();
    	OLED_ShowString(0, 0, (u8 *)"4-KEY DEMO", 16);
    	KEY_ShowOneLine(16, KEY1_PRESSED, (u8 *)"K1 PB12: UP",
    					(u8 *)"K1 PB12: DOWN", key_state);
    	KEY_ShowOneLine(28, KEY2_PRESSED, (u8 *)"K2 PB13: UP",
    					(u8 *)"K2 PB13: DOWN", key_state);
    	KEY_ShowOneLine(40, KEY3_PRESSED, (u8 *)"K3 PB14: UP",
    					(u8 *)"K3 PB14: DOWN", key_state);
    	KEY_ShowOneLine(52, KEY4_PRESSED, (u8 *)"K4 PB15: UP",
    					(u8 *)"K4 PB15: DOWN", key_state);
    
    	OLED_Refresh();
    }
    
    static void KEY_ShowOneLine(u8 y, u8 key_mask, u8 *released_text,
    							 u8 *pressed_text, u8 key_state)
    {
    	if((key_state & key_mask) != 0U)
    	{
    		OLED_ShowString(0, y, pressed_text, 12);
    	}
    	else
    	{
    		OLED_ShowString(0, y, released_text, 12);
    	}
    }
    

7.2 OLED 屏幕显示效果

全部按键松开时屏幕显示:

4-KEY DEMO
K1 PB12: UP
K2 PB13: UP
K3 PB14: UP
K4 PB15: UP

单独按下 K2,对应行实时更新为:

K2 PB13: DOWN

7.3 屏幕刷新优化逻辑

主循环每 10ms 读取一次按键状态,只有按键状态发生改变时,才重新绘制、刷新 OLED 屏幕,减少 I2C 通信次数,避免屏幕闪烁,降低单片机资源占用。

八、常见故障现象与排查步骤

故障 1:未按按键,屏幕一直显示 DOWN

故障原因:

  1. 按键接线错误,GPIO 引脚直接接地短路;
  2. 四脚轻触按键接线选错同侧引脚(同侧两脚内部导通,需要接对角引脚);
  3. PB12~PB15 引脚被其他程序、外设重新配置为输出或复用功能。 排查方法:断开所有按键导线,依靠内部上拉,引脚自动变为高电平,屏幕全部显示 UP;若故障不变,检查引脚复用配置代码。

故障 2:按下按键屏幕无变化,始终 UP

故障原因:

  1. 按键接地端断路,没有连接到 GND;
  2. 面包板松动、导线接触不良;
  3. 代码引脚定义和实物接线不匹配。 排查方法:万用表测量引脚电压,松开按键电压接近 3.3V,按下后电压接近 0V,以此判断硬件通路是否正常。

故障 3:按键状态无规律快速跳变,识别不稳定

故障原因:

  1. 单片机、OLED 地线连接不牢固,引入干扰;
  2. 按键信号线过长,容易受外界电磁干扰;
  3. 消抖代码缺失,或者消抖延时设置太短。 解决办法:加固地线连接,适当增大消抖延时宏的数值。

故障 4:OLED 屏幕完全无显示文字

故障原因:

  1. OLED 供电异常,单片机和屏幕没有共地;
  2. SCL、SDA 接线颠倒,未对应 PB8、PB9 引脚。 补充说明:按键 PB12~PB15 和 OLED PB8、PB9 引脚资源互不冲突,两者可以同时正常工作。

故障 5:引脚复用冲突注意事项

PB12、PB13、PB14、PB15 属于复用功能 IO,本工程固定用作普通输入引脚,代码中不能同时开启占用这几组引脚的外设,否则按键采集失效。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐