接上文:普中STM32F103ZET6开发攻略(一)-CSDN博客
各位看官老爷们,点击关注不迷路哟。你的点赞、收藏,一键三连,是我持续更新的动力哟!!!
目录
接上文:普中STM32F103ZET6开发攻略(一)-CSDN博客
2.6.2 如何修改程序,实现不同按键组合的功能(如同时按下两个按键)?
2.6.4 按键扫描可以用中断方式替代轮询方式吗?两种方式各有什么优缺点?
2.GPIO端口实验_按键控制:
2.1 实验目的:
-
熟悉STM32F10x微控制器的GPIO口结构和基本操作
-
掌握STM32标准库函数对GPIO输入和输出的配置方法
-
学会使用按键输入控制LED和蜂鸣器的工作状态
-
理解按键消抖原理并实现稳定的按键检测
2.2 实验环境:
-
开发板:STM32F103ZET6
-
IDE:Keil MDK 5 /Visual Studio
-
调试工具:CMSIS-DAP
-
电路连接:
-
LED0 接 PB5 引脚
-
LED1 接 PE5 引脚
-
KEY0 接 PE4 引脚
-
KEY1 接 PE3 引脚
-
KEY2 接 PE2 引脚
-
WK_UP 接 PA0 引脚
-
2.3 实验原理
1. 按键输入原理
STM32的GPIO可配置为输入模式,用于检测外部按键状态。按键输入模式包括: (1) 上拉输入:默认高电平,按下按键后为低电平 (2) 下拉输入:默认低电平,按下按键后为高电平 本实验中KEY0、KEY1、KEY2采用上拉输入方式,KEY_UP采用下拉输入方式。
模式分类 | 具体模式 | 核心特点 | 典型应用 |
---|---|---|---|
输入模式 | 浮空输入(GPIO_Mode_IN_FLOATING) | 无内部上下拉,电平由外部决定 | 外部信号采集(需外部上下拉) |
上拉输入(GPIO_Mode_IPU) | 内部上拉,默认高电平 | 按键输入(低电平有效) | |
下拉输入(GPIO_Mode_IPD) | 内部下拉,默认低电平 | 按键输入(高电平有效) | |
模拟输入(GPIO_Mode_AIN) | 连接 ADC,禁用数字输入功能 | ADC 模数转换 | |
输出模式 | 开漏输出(GPIO_Mode_Out_OD) | 需外部上拉,支持线与逻辑 | I2C 总线、电平转换 |
推挽输出(GPIO_Mode_Out_PP) | 直接输出高低电平,驱动能力强 | LED 控制、普通数字信号 | |
开漏复用功能()(GPIO_Mode_AF_OD) | 外设驱动,需外部上拉 | SPI/I2C 外设输出 | |
推挽复用功能(GPIO_Mode_AF_PP) | 外设驱动,直接输出高低电平 | USART/CAN 外设输出 |
2. 按键消抖技术 机械按键在按下或释放时会产生抖动,导致一次按键操作被多次检测。消抖的常用方法有: (1) 延时消抖:检测到按键状态变化后,延时一段时间再次检测确认 (2) 连续采样消抖:连续多次采样按键状态,确认状态稳定才视为有效
3. LED和蜂鸣器控制原理
本实验板上的LED采用共阳极连接方式,GPIO输出低电平时LED点亮;蜂鸣器则是GPIO输出高电平时发声。
2.4 实验思路
2.4.1 硬件电路
本实验使用STM32F10x开发板上的按键、LED灯和蜂鸣器进行测试。LED1连接到PB5,LED2连接到PE5,KEY_UP连接到PA0(下拉输入),KEY0连接到PE4,KEY1连接到PE3,KEY2连接到PE2(上拉输入),蜂鸣器连接到PB8,接线图如下:
由上图可知:KEY0、KEY1、KEY2采用上拉输入方式,KEY_UP采用下拉输入方式。
为了完成实验目的:
我们需要在项目工程中,在实验一的基础上新建两个“库函数”:key.c+beep.c
由于实验二的实验目的与实验一的不一样所以Led头、源文件需要重新写。
2.5 实验代码
2.5.1 LED头、源
led.h
#ifndef __LED_H #define __LED_H #include "stm32f10x.h" #define LED0_GPIO_PORT GPIOB #define LED0_GPIO_PIN GPIO_Pin_5 //LED0 #define LED1_GPIO_PORT GPIOE #define LED1_GPIO_PIN GPIO_Pin_5 //LED1 void LED_Init(void); void LED0_TOGGLE(void); void LED1_ON(void); void LED1_OFF(void); #endif
led.c
#include "led.h" void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; // 开启 GPIOB 和 GPIOE 的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE); //配置PB5(LED0) GPIO_InitStructure.GPIO_Pin = LED0_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LED0_GPIO_PORT, &GPIO_InitStructure); //配置PE5(LED1) GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure); //低电平输出 GPIO_ResetBits(LED0_GPIO_PORT, LED0_GPIO_PIN); GPIO_ResetBits(LED1_GPIO_PORT, LED1_GPIO_PIN); } void LED0_TOGGLE(void) { LED0_GPIO_PORT->ODR ^= LED0_GPIO_PIN; } void LED1_ON(void) { GPIO_ResetBits(LED1_GPIO_PORT, LED1_GPIO_PIN); } void LED1_OFF(void) { GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN); }
2.5.2 key头、源
key.h
#ifndef __KEY_H #define __KEY_H #include "stm32f10x.h" // GPIO 输入宏 #define PAin(n) (GPIO_ReadInputDataBit(GPIOA, (1 << (n)))) #define PEin(n) (GPIO_ReadInputDataBit(GPIOE, (1 << (n)))) // 按键 GPIO 宏定义 #define KEY0_PIN GPIO_Pin_4 #define KEY1_PIN GPIO_Pin_3 #define KEY2_PIN GPIO_Pin_2 #define KEY_UP_PIN GPIO_Pin_0 #define KEY_PORT GPIOE #define KEY_UP_PORT GPIOA #define KEY_UP PAin(0) #define KEY0 PEin(4) #define KEY1 PEin(3) #define KEY2 PEin(2) // 返回值宏定义 #define KEY_UP_PRESS 1 #define KEY0_PRESS 2 #define KEY1_PRESS 3 #define KEY2_PRESS 4 void KEY_Init(void); u8 KEY_Scan(u8 mode); #endif
key.c
#include "key.h" #include "delay.h" void KEY_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOE, ENABLE); // KEY_UP: 下拉输入 GPIO_InitStructure.GPIO_Pin = KEY_UP_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; GPIO_Init(KEY_UP_PORT, &GPIO_InitStructure); // KEY0, KEY1, KEY2: 上拉输入 GPIO_InitStructure.GPIO_Pin = KEY0_PIN | KEY1_PIN | KEY2_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(KEY_PORT, &GPIO_InitStructure); } u8 KEY_Scan(u8 mode) { static u8 key_up = 1; if (mode) key_up = 1; if (key_up && (KEY_UP || !KEY0 || !KEY1 || !KEY2)) { Delay_ms(10); // 消抖 key_up = 0; if (KEY_UP) return KEY_UP_PRESS; if (!KEY0) return KEY0_PRESS; if (!KEY1) return KEY1_PRESS; if (!KEY2) return KEY2_PRESS; } else if (!KEY_UP && KEY0 && KEY1 && KEY2) { key_up = 1; } return 0; }
2.5.3 beep头、源
bepp.h
#ifndef __BEEP_H #define __BEEP_H #include "stm32f10x.h" #define BEEP_GPIO_PORT GPIOB #define BEEP_GPIO_PIN GPIO_Pin_8 void BEEP_Init(void); void BEEP_ON(void); void BEEP_OFF(void); #endif
beep.c
#include "beep.h" void BEEP_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //初始化各项参数: GPIO_InitStructure.GPIO_Pin = BEEP_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(BEEP_GPIO_PORT, &GPIO_InitStructure); GPIO_ResetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN); // 默认关闭蜂鸣器 } void BEEP_OFF(void) { GPIO_ResetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN); } void BEEP_ON(void) { GPIO_SetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN); }
实验现象:
DS0指示灯会不断闪烁(200ms翻转一次)
按下KEY_UP键,DS1指示灯点亮
按下KEY2键,DS1指示灯熄灭
按下KEY1键,蜂鸣器发声
按下KEY0键,蜂鸣器停止发声
2.6 实验思考和拓展
2.6.1 如何改进按键消抖方法,使其更加可靠?
传统的延时消抖存在阻塞问题,可通过以下方式优化:
改进方案:
-
双阈值检测:同时设置按下阈值和释放阈值(如按下 > 20ms,释放 > 15ms)
-
状态机设计:将按键状态分为 "未按下 - 抖动 - 已按下 - 释放抖动" 等状态
-
定时器扫描:使用硬件定时器进行定时检测,避免阻塞主程序
-
滤波算法:采用一阶 RC 滤波或滑动平均滤波处理按键信号
2.6.2 如何修改程序,实现不同按键组合的功能(如同时按下两个按键)?
实现思路:
-
定义组合键映射表:将按键组合与功能函数关联
-
扫描所有按键状态:记录每个按键的独立状态
-
状态比对:检测是否符合预定义的组合模式
例如: // 定义按键掩码 #define KEY1_MASK (1 << 0) #define KEY2_MASK (1 << 1) #define KEY3_MASK (1 << 2) // 组合键处理函数 void HandleKeyCombination(uint8_t key_mask) { switch(key_mask) { case KEY1_MASK: // 单按KEY1 Function1(); break; case KEY2_MASK: // 单按KEY2 Function2(); break; case KEY1_MASK|KEY2_MASK: // 同时按KEY1+KEY2 Function3(); break; case KEY1_MASK|KEY3_MASK: // 同时按KEY1+KEY3 Function4(); break; // 其他组合... } } // 主循环中调用 void MainLoop(void) { uint8_t key_mask = 0; if(IS_KEY1_PRESSED) key_mask |= KEY1_MASK; if(IS_KEY2_PRESSED) key_mask |= KEY2_MASK; if(IS_KEY3_PRESSED) key_mask |= KEY3_MASK; HandleKeyCombination(key_mask); }
2.6.3 如何实现按键长按与短按的区分,并执行不同功能?
实现方案:
-
定时器计时:按下按键时启动定时器,释放时停止
-
时间阈值判断:超过长按阈值(如 1 秒)为长按,否则为短按
-
状态标志:使用标志位区分长按 / 短按回调
2.6.4 按键扫描可以用中断方式替代轮询方式吗?两种方式各有什么优缺点?
中断方式:
-
优点:实时响应、不占用 CPU 资源、适合低功耗设计
-
缺点:需处理中断优先级、可能受噪声干扰、抖动处理复杂
-
适用场景:需要立即响应的场合(如紧急停止按键)
轮询方式:
-
优点:实现简单、可靠性高、可批量处理多个按键
-
缺点:占用 CPU 资源、响应不及时(取决于扫描周期)
-
适用场景:按键数量较多、对响应时间要求不高的场合
推荐方案:
-
使用中断检测按键动作,进入中断后启动定时器消抖
-
消抖完成后在定时器回调函数中处理按键事件
-
既保证实时性,又避免抖动干扰
2.7 注意事项
(1) 按键检测时需要注意输入模式的选择(上拉或下拉)和实际电平变化方向
(2) 按键消抖是保证按键检测可靠性的关键
(3) 使用标准库函数时,需要注意头文件的包含和依赖关系
(4) GPIO操作前必须先使能对应的外设时钟
(5) 避免在主循环中设置过长的延时,以免影响按键响应速度
文章有写的不当的地方,欢迎在评论区中指正修改。如果感觉文章实用对你有帮助,欢迎点赞收藏和关注,你的点赞关注就是我动力,大家一起学习进步。
有不懂的可以在评论区里提出来哟,博主看见后会及时回答的。