接上文:普中STM32F103ZET6开发攻略(一)-CSDN博客

各位看官老爷们,点击关注不迷路哟。你的点赞、收藏,一键三连,是我持续更新的动力哟!!!

目录

接上文:普中STM32F103ZET6开发攻略(一)-CSDN博客

2.GPIO端口实验_按键控制:

2.1 实验目的:

2.2 实验环境:

2.3 实验原理

2.4 实验思路

2.4.1 硬件电路

2.5 实验代码

2.5.1 LED头、源

2.5.2 key头、源

2.5.3 beep头、源

2.6 实验思考和拓展

2.6.1 如何改进按键消抖方法,使其更加可靠?

2.6.2 如何修改程序,实现不同按键组合的功能(如同时按下两个按键)?

2.6.3 如何实现按键长按与短按的区分,并执行不同功能?

2.6.4 按键扫描可以用中断方式替代轮询方式吗?两种方式各有什么优缺点?

2.7 注意事项


2.GPIO端口实验_按键控制

2.1 实验目的:

  1. 熟悉STM32F10x微控制器的GPIO口结构和基本操作

  2. 掌握STM32标准库函数对GPIO输入和输出的配置方法

  3. 学会使用按键输入控制LED和蜂鸣器的工作状态

  4. 理解按键消抖原理并实现稳定的按键检测

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 如何改进按键消抖方法,使其更加可靠?

传统的延时消抖存在阻塞问题,可通过以下方式优化:

改进方案

  1. 双阈值检测:同时设置按下阈值和释放阈值(如按下 > 20ms,释放 > 15ms)

  2. 状态机设计:将按键状态分为 "未按下 - 抖动 - 已按下 - 释放抖动" 等状态

  3. 定时器扫描:使用硬件定时器进行定时检测,避免阻塞主程序

  4. 滤波算法:采用一阶 RC 滤波或滑动平均滤波处理按键信号

2.6.2 如何修改程序,实现不同按键组合的功能(如同时按下两个按键)?

实现思路

  1. 定义组合键映射表:将按键组合与功能函数关联

  2. 扫描所有按键状态:记录每个按键的独立状态

  3. 状态比对:检测是否符合预定义的组合模式

例如:
// 定义按键掩码
#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:          // 单按KEY1Function1(); break;case KEY2_MASK:          // 单按KEY2Function2(); break;case KEY1_MASK|KEY2_MASK: // 同时按KEY1+KEY2Function3(); break;case KEY1_MASK|KEY3_MASK: // 同时按KEY1+KEY3Function4(); 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. 时间阈值判断:超过长按阈值(如 1 秒)为长按,否则为短按

  3. 状态标志:使用标志位区分长按 / 短按回调

2.6.4 按键扫描可以用中断方式替代轮询方式吗?两种方式各有什么优缺点?

中断方式

  • 优点:实时响应、不占用 CPU 资源、适合低功耗设计

  • 缺点:需处理中断优先级、可能受噪声干扰、抖动处理复杂

  • 适用场景:需要立即响应的场合(如紧急停止按键)

轮询方式

  • 优点:实现简单、可靠性高、可批量处理多个按键

  • 缺点:占用 CPU 资源、响应不及时(取决于扫描周期)

  • 适用场景:按键数量较多、对响应时间要求不高的场合

推荐方案

  • 使用中断检测按键动作,进入中断后启动定时器消抖

  • 消抖完成后在定时器回调函数中处理按键事件

  • 既保证实时性,又避免抖动干扰

2.7 注意事项

(1) 按键检测时需要注意输入模式的选择(上拉或下拉)和实际电平变化方向

(2) 按键消抖是保证按键检测可靠性的关键

(3) 使用标准库函数时,需要注意头文件的包含和依赖关系

(4) GPIO操作前必须先使能对应的外设时钟

(5) 避免在主循环中设置过长的延时,以免影响按键响应速度

文章有写的不当的地方,欢迎在评论区中指正修改。如果感觉文章实用对你有帮助,欢迎点赞收藏和关注,你的点赞关注就是我动力,大家一起学习进步。

有不懂的可以在评论区里提出来哟,博主看见后会及时回答的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/907897.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/907897.shtml
英文地址,请注明出处:http://en.pswp.cn/news/907897.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用提示词写程序(3),VSCODE+Claude3.5+deepseek开发edge扩展插件V2

edge扩展插件;筛选书签,跳转搜索,设置背景 链接: https://pan.baidu.com/s/1nfnwQXCkePRnRh5ltFyfag?pwd86se 提取码: 86se 导入解压的扩展文件夹: 导入扩展成功: edge扩展插件;筛选书签,跳转搜索,设置背景

电脑桌面便签软件哪个好?桌面好用便签备忘录推荐

在日常办公中&#xff0c;一款优秀的桌面便签工具能显著提升工作效率。面对市面上琳琅满目的选择&#xff0c;不少用户都难以抉择。如果你正在寻找一款兼具轻量化与多功能性的便签软件&#xff0c;那么集实用性与便捷性于一身的"好用便签"&#xff0c;或许就是你的理…

性能优化 - 工具篇:基准测试 JMH

文章目录 Pre引言1. JMH 简介2. JMH 执行流程详解3. 关键注解详解3.1 Warmup3.2 Measurement3.3 BenchmarkMode3.4 OutputTimeUnit3.5 Fork3.6 Threads3.7 Group 与 GroupThreads3.8 State3.9 Setup 与 TearDown3.10 Param3.11 CompilerControl 4. 示例代码与分析4.1 关键点解读…

2025年十大AI幻灯片工具深度评测与推荐

我来告诉你一个好消息。 我们已经亲自测试和对比了市面上最优秀的AI幻灯片工具&#xff0c;让你无需再为选择而烦恼。 得益于AI技术的飞速发展&#xff0c;如今你可以快速制作出美观、专业的幻灯片。 这些智能平台的功能远不止于配色美化——它们能帮你头脑风暴、梳理思路、…

雪花算法:分布式ID生成的优雅解决方案

一、雪花算法的核心机制与设计思想 雪花算法&#xff08;Snowflake&#xff09;是由Twitter开源的分布式ID生成算法&#xff0c;它通过巧妙的位运算设计&#xff0c;能够在分布式系统中快速生成全局唯一且趋势递增的ID。 1. 基本结构 雪花算法生成的是一个64位&#xff08;lo…

第1章:走进Golang

第1章&#xff1a;走进Golang 一、Golang简介 Go语言&#xff08;又称Golang&#xff09;是由Google的Robert Griesemer、Rob Pike及Ken Thompson开发的一种开源编程语言。它诞生于2007年&#xff0c;2009年11月正式开源。Go语言的设计初衷是为了在不损失应用程序性能的情况下…

Higress项目解析(二):Proxy-Wasm Go SDK

3、Proxy-Wasm Go SDK Proxy-Wasm Go SDK 依赖于 tinygo&#xff0c;同时 Proxy - Wasm Go SDK 是基于 Proxy-Wasm ABI 规范使用 Go 编程语言扩展网络代理&#xff08;例如 Envoy&#xff09;的 SDK&#xff0c;而 Proxy-Wasm ABI 定义了网络代理和在网络代理内部运行的 Wasm …

NVMe IP现状扫盲

SSD优势 与机械硬盘&#xff08;Hard Disk Driver, HDD&#xff09;相比&#xff0c;基于Flash的SSD具有更快的数据随机访问速度、更快的传输速率和更低的功耗优势&#xff0c;已经被广泛应用于各种计算领域和存储系统。SSD最初遵循为HDD设计的现有主机接口协议&#xff0c;例…

`docker commit` 和 `docker save`区别

理解 docker commit 和 docker save 之间的区别对于正确管理 Docker 镜像非常重要。让我们详细解释一下这两个命令的作用及其区别。 1. docker commit 作用&#xff1a; docker commit roop-builder roop:v1 命令的作用是基于一个正在运行的容器 roop-builder 创建一个新的镜…

Linux内核体系结构简析

1.Linux内核 1.1 Linux内核的任务 从技术层面讲&#xff0c;内核是硬件和软件之间的一个中间层&#xff0c;作用是将应用层序的请求传递给硬件&#xff0c;并充当底层驱动程序&#xff0c;对系统中的各种设备和组件进行寻址。从应用程序的角度讲&#xff0c;应用程序与硬件没有…

python爬虫:Ruia的详细使用(一个基于asyncio和aiohttp的异步爬虫框架)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Ruia概述1.1 Ruia介绍1.2 Ruia特点1.3 安装Ruia1.4 使用案例二、基本使用2.1 Request 请求2.2 Response - 响应2.3 Item - 数据提取2.4 Field 提取数据2.5 Spider - 爬虫类2.6 Middleware - 中间件三、高级功能3.1 …

网络攻防技术二:密码学分析

文章目录 一、传统密码分析方法1、根据明文、密文等信息的掌握情况分类 2、从密码分析途径分类二、密码旁路分析1、概念2、旁路分析方法三、现代密码系统1、对称密码&#xff08;单密钥&#xff09;2、公开密码&#xff08;成对密钥&#xff09; 四、典型对称密码&#xff08;单…

Linux --TCP协议实现简单的网络通信(中英翻译)

一、什么是TCP协议 1.1 、TCP是传输层的协议&#xff0c;TCP需要连接&#xff0c;TCP是一种可靠性传输协议&#xff0c;TCP是面向字节流的传输协议&#xff1b; 二、TCPserver端的搭建 2.1、我们最终好实现的效果是 客户端在任何时候都能连接到服务端&#xff0c;然后向服务…

pc端小卡片功能-原生JavaScript金融信息与节日日历

代码如下 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>金融信息与节日日历</title><…

C语言——获取变量所在地址(uint8和uint32的区别)

前言&#xff1a; 1.使用uint8 *的原因 在C语言中&#xff0c;获取或操作一个4字节地址&#xff08;指针&#xff09;时使用uint8_t*&#xff08;即unsigned char*&#xff09;而不是uint32_t*&#xff0c;主要基于以下关键原因&#xff1a; 1.1. 避免违反严格别名规则&…

Python----目标检测(《YOLOv3:AnIncrementalImprovement》和YOLO-V3的原理与网络结构)

一、《YOLOv3:AnIncrementalImprovement》 1.1、基本信息 标题&#xff1a;YOLOv3: An Incremental Improvement 作者&#xff1a;Joseph Redmon, Ali Farhadi 机构&#xff1a;华盛顿大学&#xff08;University of Washington&#xff09; 发表时间&#xff1a;2018年 代…

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Form Wave(表单label波动效果)

&#x1f4c5; 我们继续 50 个小项目挑战&#xff01;—— FormWave组件 仓库地址&#xff1a;https://github.com/SunACong/50-vue-projects 项目预览地址&#xff1a;https://50-vue-projects.vercel.app/ &#x1f3af; 组件目标 构建一个美观、动态的登录表单&#xff0…

【数据结构】--二叉树--堆(上)

一、树的概念和结构 概念&#xff1a; 树是一种非线性的数据结构&#xff0c;他是由n(n>0)个有限结点组成一个具有层次关系的集合。其叫做树&#xff0c;是因为他倒过来看就和一棵树差不多&#xff0c;其实际上是根在上&#xff0c;树枝在下的。 树的特点&#xff1a; 1…

linux有效裁剪视频的方式(基于ffmpeg,不改变分辨率,帧率,视频质量,不需要三方软件)

就是在Linux上使用OBS Studio录制一个讲座或者其他视频&#xff0c;可能总有些时候会多录制一段时间&#xff0c;但是如果使用剪映或者PR这样的工具在导出的时候总需要烦恼导出的格式和参数&#xff0c;比如剪映就不支持mkv格式的导出&#xff0c;导出成mp4格式的视频就会变得很…

SystemVerilog—Interface语法(一)

SystemVerilog中的接口&#xff08;interface&#xff09;是一种用于封装多模块间通信信号和协议的复合结构&#xff0c;可显著提升代码复用性和维护效率。其核心语法和功能如下&#xff1a; 一、接口的基本定义 1. 声明语法 接口通过interface关键字定义&#xff0c;支持信…