第11课:STM32按键输入实验(标准库)
专栏:STM32F103入门教程
环境:Keil‑MDK + STM32F103 标准库3.5版本
在单片机项目开发中,按键是非常基础、高频使用的人机交互方式。复杂产品会采用触摸屏、APP、小程序,而入门阶段我们先学习普通物理按键读取。本节课使用 PB12 作为按键输入引脚。
一、硬件接线
按键原理

轻触按键特性:未按下时两端断开;按下之后两端导通。可以把万用表拨到蜂鸣档,自行实测验证通断。
本实验接线方案:
按键一端接开发板
3.3V,另一端接单片机PB12引脚。

GPIO总共有4种输入模式:上拉输入、下拉输入、浮空输入、模拟输入,我们分析该选用哪一种:
- 模拟输入
GPIO_Mode_AIN:专门用于ADC电压采集,直接排除。 - 浮空输入
GPIO_Mode_IN_FLOATING:芯片内部上下拉电阻全部断开。引脚电平极易受外界电磁干扰产生抖动,只适合外部电路已经自带上下拉电阻的场景,本实验不使用。 - 上拉输入
GPIO_Mode_IPU:空闲默认高电平;按键接3.3V,按下后依旧是高电平,电平不会发生变化,无法识别按键动作,不可用。 - 下拉输入
GPIO_Mode_IPD✅:引脚空闲默认低电平;按键按下接通3.3V,引脚变为高电平,可以准确识别按键按下,本节课选用该模式。
💡学习小建议:上面4种模式大家都可以修改代码实际测试一遍,实践出真知。
面包板实物接线参考:

- 3.3V电源从开发板3.3V引脚引出
- 按键的其中一个引脚接3.3V
- 按键另外一个引脚飞线连接到PB12
二、工程与程序编写
2.1 工程复制
复制上节课LED_FLASH整个工程文件夹,重命名为KEY,同时把工程文件LED_FLASH.uvprojx重命名为KEY.uvprojx,双击打开工程。
本节课我们加入串口
printf打印功能,按键按下时把信息输出到电脑串口助手,方便调试。串口原理后续课程详解,本节课先直接使用,做到会调用即可。
2.2 添加串口驱动文件
- 在工程目录
KEY\Hardware文件夹下新建子文件夹Usart。 - Keil工程,右键
Hardware分组 →Add New Item to Group 'Hardware',新建usart.c源文件。

继续新建头文件usart.h:

usart.h
#ifndef __USART_H //防止头文件重复包含,避免编译报错
#define __USART_H
#include "stm32f10x.h"
void usart1_init(uint32_t baud); //串口1初始化函数
#endif
usart.c
#include "stdio.h"
#include "usart.h"
// MicroLIB 环境下printf重定向,仅需实现fputc
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t)ch);
//等待发送数据寄存器为空,避免丢字符
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return ch;
}
void usart1_init(uint32_t baud)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//开启USART1、GPIOA外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
//PA9 USART1_TX 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//串口参数初始化
USART_InitStructure.USART_BaudRate = baud; //波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8位数据
USART_InitStructure.USART_StopBits = USART_StopBits_1; //1位停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控
USART_InitStructure.USART_Mode = USART_Mode_Tx; //仅发送模式
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE); //使能串口1
}
-
添加标准库外设文件
stm32f10x_usart.c到工程,用到哪个外设就添加对应的库文件。
-
添加头文件搜索路径,把
Hardware\Usart加入包含路径。

2.3 添加按键驱动文件
在KEY\Hardware\Gpio目录新建key.c、key.h。
key.h
#ifndef __KEY_H
#define __KEY_H
#include "stm32f10x.h"
void key_init(void); //按键GPIO初始化
uint8_t key_press_get(void); //读取按键状态:返回1代表按键按下
#endif
key.c
#include "key.h"
void key_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启GPIOB时钟
gpio_init_struct.GPIO_Pin = GPIO_Pin_12;
gpio_init_struct.GPIO_Mode = GPIO_Mode_IPD; //下拉输入模式
gpio_init_struct.GPIO_Speed = GPIO_Speed_2MHz; //输入模式低速即可
GPIO_Init(GPIOB,&gpio_init_struct);
}
//读取PB12引脚电平
uint8_t key_press_get(void)
{
return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12);
}
📖怎么知道API函数、模式宏定义?
可以查阅《STM32F10x_StdPeriph_Driver_3.5.0中文版.chm》固件库手册,也可以直接打开库源码stm32f10x_gpio.c、stm32f10x_gpio.h查看函数声明。
GPIO库部分函数声明参考:
void GPIO_DeInit(GPIO_TypeDef* GPIOx);
void GPIO_AFIODeInit(void);
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource);
//......省略其余函数
🔔初学阶段优先学会调用库函数;后续进阶再去研究底层寄存器。
2.4 main.c主函数
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include <stdio.h>
int main(void)
{
led_init();
delay_init();
usart1_init(115200); //串口波特率115200,串口助手需要保持一致
key_init();
while(1)
{
led_on();
delay_ms(1000);
led_off();
delay_ms(1000);
if(key_press_get() == 1){
printf("检测到按键按下 \r\n");
}
}
}
硬件接线提示:PA9(TX)接到WCH-LINK的RX引脚。
2.5 Keil工程配置MicroLIB
魔术棒 → Target,勾选 Use MicroLIB,printf重定向依赖该配置。

⚠️注意:MicroLIB不需要写半主机相关桩代码,只需要实现
fputc函数。
三、现象与问题分析
下载程序,打开串口助手,波特率设置115200。
现象1:LED周期闪烁,按键很难触发打印
大部分时候按下按键串口没有输出,需要一直按住按键,大约等待2秒左右才会打印一次。
原因分析
主循环里面大部分时间卡在delay_ms(1000)延时,按键检测代码执行时机极少,只有LED熄灭后的一瞬间读取按键电平。
时序说明:0~2000ms执行LED亮灭延时,仅很短的窗口执行按键判断。
这是时间简化示意图,实际检测时间不到1ms.led_on这类函数执行时间很短,没有算在里面。
现象2:去掉LED延时,循环只检测按键
while(1)
{
if(key_press_get() == 1){
printf("检测到按键按下 \r\n");
}
}
实际效果:按一次按键,串口疯狂输出大量打印信息。

原因:单片机运行速度极快;人手按下按键持续几百毫秒,在这段时间内程序会循环成千上万次,就会重复多次检测到高电平。
现象3:简单加上延时消抖
while(1)
{
delay_ms(200);
if(key_press_get() == 1){
printf("检测到按键按下 \r\n");
}
}
简单测试下可以做到按一次打印一次。
❗该写法只适合简单测试项目,实际产品中不推荐。
delay_ms是阻塞延时,会卡住整个系统,其他任务无法运行。
✨下一节课我们学习外部中断方式实现按键检测,解决轮询阻塞的痛点。
实物效果图

串口助手接收效果:

资料下载
参考资料网盘:
链接: https://pan.baidu.com/s/1BrH0JbUY6z7j7gAihTa4Dw
提取码: qnws
本节课完整工程例程网盘:
链接: https://pan.baidu.com/s/1rl6EIqi9m6kQ1JFjuSqINg
提取码: bumc



7313

被折叠的 条评论
为什么被折叠?



