STM32F103C8T6驱动四路独立按键+OLED状态显示完整教程(硬件原理+代码逻辑+故障排查)
前言
本文基于 STM32F103C8T6 最小系统完成工程:4 路独立按键采集、软件消抖处理、0.96 寸 I2C OLED 实时展示按键按下 / 松开状态。内容从硬件接线、GPIO 底层原理、按键消抖、位运算状态存储、模块化程序结构到设备故障排查全覆盖,适合单片机新手入门 GPIO 输入实验,接线、逻辑讲解通俗易懂,可直接照搬操作。
一、工程整体实现效果
完整工作流程: 手指按下 / 松开 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;
- 按键按下:引脚通过按键直通 GND,电平变为低电平 0。 优势:不用额外焊接外接上拉电阻,简化硬件接线,降低新手接线失误概率。
2.3 独立按键定义、优缺点
每个按键单独占用一路 GPIO 输入引脚,不存在行列复用,因此叫做独立按键。 优点:电路简单,程序逻辑直观,支持单键、多键同时识别,适合 GPIO 输入入门学习; 缺点:按键数量多会大量占用单片机 IO 口,超过 8 个按键推荐使用矩阵键盘。
三、独立按键完整工作逻辑
按键内部是金属机械触点,松开时触点断开,按下时触点闭合,本电路触点闭合后会直接把 GPIO 引脚拉至 0V。
完整工作步骤:
- 人为按压或松开按键;
- 按键内部金属触点断开 / 闭合,PB12~PB15 引脚电平切换;
- STM32 读取 GPIO 端口输入寄存器,获取当前引脚电平;
- 延时 20ms 后再次读取电平,完成软件消抖;
- 根据稳定电平生成四路按键的状态位图;
- 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 消抖处理完整逻辑
- 第一次读取按键原始电平;
- 对比当前电平与上一次稳定状态,如果一致,直接返回原有按键状态;
- 如果电平发生变化,延时 20ms 跳过抖动区间;
- 延时结束后第二次读取按键电平;
- 对比两次采样电平,数值完全相同则更新稳定按键状态;两次数值不一致则舍弃本次变化,保留原有状态。
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 模块化文件分工
- 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 - 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; } - 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
故障原因:
- 按键接线错误,GPIO 引脚直接接地短路;
- 四脚轻触按键接线选错同侧引脚(同侧两脚内部导通,需要接对角引脚);
- PB12~PB15 引脚被其他程序、外设重新配置为输出或复用功能。 排查方法:断开所有按键导线,依靠内部上拉,引脚自动变为高电平,屏幕全部显示 UP;若故障不变,检查引脚复用配置代码。
故障 2:按下按键屏幕无变化,始终 UP
故障原因:
- 按键接地端断路,没有连接到 GND;
- 面包板松动、导线接触不良;
- 代码引脚定义和实物接线不匹配。 排查方法:万用表测量引脚电压,松开按键电压接近 3.3V,按下后电压接近 0V,以此判断硬件通路是否正常。
故障 3:按键状态无规律快速跳变,识别不稳定
故障原因:
- 单片机、OLED 地线连接不牢固,引入干扰;
- 按键信号线过长,容易受外界电磁干扰;
- 消抖代码缺失,或者消抖延时设置太短。 解决办法:加固地线连接,适当增大消抖延时宏的数值。
故障 4:OLED 屏幕完全无显示文字
故障原因:
- OLED 供电异常,单片机和屏幕没有共地;
- SCL、SDA 接线颠倒,未对应 PB8、PB9 引脚。 补充说明:按键 PB12~PB15 和 OLED PB8、PB9 引脚资源互不冲突,两者可以同时正常工作。
故障 5:引脚复用冲突注意事项
PB12、PB13、PB14、PB15 属于复用功能 IO,本工程固定用作普通输入引脚,代码中不能同时开启占用这几组引脚的外设,否则按键采集失效。
更多推荐



所有评论(0)