我在高职教STM32——GPIO入门之按键输入(2)

        大家好,我是老耿,高职青椒一枚,一直从事单片机、嵌入式、物联网等课程的教学。对于高职的学生层次,同行应该都懂的,老师在课堂上教学几乎是没什么成就感的。正因如此,才有了借助 CSDN 平台寻求认同感和成就感的想法。在这里,我准备陆续把自己花了很多心思的教学设计分享出来,主要面向广大师生朋友,单片机老鸟就略过吧。欢迎点赞+关注,各位的支持是本人持续输出的动力,多谢多谢!

        前面,我们介绍了STM32的IO口作为输出的使用,这一章,我们将向大家介绍如何使用IO口作为输入。在本章中,我们将利用开发板上的按键来控制LED的亮灭。通过本章的学习,我们将明白按键的电路原理,了解按键消抖是怎么回事,巩固GPIO的初始化配置,学习GPIO端口输入函数等知识。

【学习目标】

  1. 了解按键防抖、锁存的方法
  2. 巩固GPIO初始化的过程,独立完成代码编写
  3. 理解按键单击、双击、长按的程序算法

        按键是初学嵌入式的第一类输入器件,入门不难,但是一旦按法多样化(单击/双击/长按),或是结合其他被控器件,就需要用上中断、定时器、状态机等知识,难度也就上来了。本章还是基于GPIO输入电平的传统方法来按键,计划分两个部分,本文是第二部分。

三、按键的单击/双击/长按

        电子产品上的轻触按键,除了单击,双击和长按也是比较普遍的输入方式,这样可以在同一个按键上实现更多的控制效果,鼠标就是典型的例子。上一节的程序仅能检测单击这个动作,这一节,我们就来学习一下如何通过一定算法,把这三种动作都能检测出来。

3.1 编程要点

        我们来分析三种动作的按键IO口电平变化,如图7所示,看看各自有何特征。首先,无论何种动作,都是从按下这个动作开始的。接下来,就是要看按下的时长了,如果超过了长按标准(图中的S3,如2s),那么毫无疑问肯定是长按了,也就排除了单击和双击。因此,从逻辑上来看,判断出长按所需的条件是最少的。

        其次,如果按下的时长小于S3,也就是说没到长按标准就松开了,那么就只能是单击或双击其中一种可能。这时,看的就是松开的时长了,如果超过了两次单击间隔标准(图中的D1,如250ms),那么就不是双击了,只能是单击。反之,就是双击。所以,单击或双击的判断依据就变成了“按下时长小于S3 且 松开时长是否小于D1”这样的双重条件了。

        最后,还有一种可能,那就是无动作。当然,发生这种情况的条件是没有按下这个动作发生。这种情况其实是程序的初态,或者说是完成一次按键动作后应该回归的状态。至此,本程序的目的就是要通过一系列条件判断得到“单击/双击/长按/无动作”其中一种结果。

图7 单击/双击/长按对应的IO口电平变化

3.2 代码剖析

        本实验的硬件电路和工程文件清单跟上一个实验一样,但文件中的代码发生了较大变化,因此我们将上一个实验的工程文件另存一份再进行编写,这样不会混淆。需要说明的是,这里为了节省篇幅和排版需要,一些与之前重复的代码做了省略,阅读时请注意,完整的源码请阅读本实验配套的工程。

1. key.h文件源码

        如代码清单4所示,这个头文件了增加了很多跟按键动作有关的宏,其实就是把每个按键的每一种可能都起好名字,编好数字。当然,还有一个用来扫描按键动作的函数声明 Key_Scan(),其代码也是本实验的重点。

//-------------------------------------------------------
// 代码清单4:补充与完善后的key.h
//-------------------------------------------------------
 
#ifndef  _KEY_H_
#define  _KEY_H_
 
#include "stm32f10x.h"

//-------------------------------------------------------
// 与按键状态和动作有关的宏定义
//-------------------------------------------------------
#define  KEY_DOWN		0
#define  KEY_UP			1

#define  KEY1_DOWN		10
#define  KEY1_UP		11
#define  KEY1_DOUBLE	12
#define  KEY1_DOWNLONG	13

#define  KEY2_DOWN		20
#define  KEY2_UP		21
#define  KEY2_DOUBLE	22
#define  KEY2_DOWNLONG	23

#define  KEY3_DOWN		30
#define  KEY3_UP		31
#define  KEY3_DOUBLE	32
#define  KEY3_DOWNLONG	33

#define  KEY4_DOWN		40
#define  KEY4_UP		41
#define  KEY4_DOUBLE	42
#define  KEY4_DOWNLONG	43

#define  KEY_NONE		255

#define  KEYDOWN_LONG_TIME	200		//长按标准,单位为10ms

//-------------------------------------------------------
// 必要的宏定义
//-------------------------------------------------------
#define  KEY1_PIN     GPIO_Pin_13
#define  KEY2_PIN     GPIO_Pin_11
#define  KEY3_PIN     GPIO_Pin_12
#define  KEY4_PIN     GPIO_Pin_2
 
//-------------------------------------------------------
// 库函数操作宏定义
//-------------------------------------------------------
#define  READ_KEY1	GPIO_ReadInputDataBit(GPIOC, KEY1_PIN)
#define  READ_KEY2	GPIO_ReadInputDataBit(GPIOC, KEY2_PIN)
#define  READ_KEY3	GPIO_ReadInputDataBit(GPIOC, KEY3_PIN)
#define  READ_KEY4	GPIO_ReadInputDataBit(GPIOD, KEY4_PIN)
 
//--------------------------------------------------------
// 函数声明
//--------------------------------------------------------
void Key_Init(void);	//按键初始化函数
u8 Key_Scan(void);	    //按键扫描函数	
 
#endif
 

2. key.c文件源码

        我们主要对该文件中的 Key_Scan() 函数源码进行剖析,如代码清单5所示,请大家结合上面的编程要点来阅读源码。

/**
************************************************************
* 代码清单5:在key.c中补充的按键扫描函数
* 函数名称:Key_Scan
* 函数功能:按键IO口扫描
* 入口参数:无
* 返回参数:单击/双击/长按/无动作对应编号
* 说    明:
************************************************************
*/
u8 Key_Scan(void)
{
	u8 downCount = 0, upCount = 0;	//按下和松开的计时变量,单位10ms
	u8 clickDoubleFlag = 0;			//单/双击标志,0为单击,1为双击

	/*------------------- 以下是检测过程 ----------------------*/
	if(READ_KEY1 == KEY_DOWN)
	{
		delay_ms(10);			    //10ms消抖
		if(READ_KEY1 == KEY_DOWN)	//确认按下,开始检测
		{
			while(READ_KEY1==KEY_DOWN && downCount<KEYDOWN_LONG_TIME)
			{	//提前松开按键或按下超过规定时长都会退出循环
				downCount++;	//按下期间计数值累加
				delay_ms(10);	//累加1次的时长
			}
			if(downCount>=KEYDOWN_LONG_TIME)	//说明前面是长按导致的退出
			{
				while(READ_KEY1 == KEY_DOWN);	//等待按键松开
				return KEY1_DOWNLONG;		    //返回长按结果
			}
			else	//说明前面是短按导致的退出,则进行单/双击检测
			{
				for(upCount=0; upCount<25; upCount++)
				{	//在松开期间检测是否有二次按下
					delay_ms(10);
					if(READ_KEY1 == KEY_DOWN)	//有二次按下
					{
						clickDoubleFlag = 1;		    //置双击标志
						while(READ_KEY1 == KEY_DOWN);	//等待按键松开
						return KEY1_DOUBLE;			    //返回双击结果
					}
				}
				if(clickDoubleFlag == 0)	//退出循环发现仍为单击标志
					return KEY1_DOWN;		//返回单击结果
			}
		}
	}
		
	/*---------------------- 扫描按键2/3/4的过程同上 ----------------------*/
	
	return KEY_NONE;	//无按下动作或扫描完成,返回无动作结果
}

        以上代码给出了扫描KEY1按键的过程,由于判断的条件较多且相互嵌套,理解起来是有一点难度的,大家阅读时可以借助Keil的代码收缩和展开的功能(如图8所示),先理清总体上的逻辑关系,再逐层展开仔细阅读,体会编程思路在代码层面上的实现。

图8 Keil中的代码收缩和展开

3. main.c文件源码

        主程序比较简单,如代码清单6所示,主循环中不断根据按键扫描函数的返回值来控制LED的亮灭。单击KEY1,改变红灯状态;双击KEY1,改变绿灯状态;长按KEY1,改变黄灯状态。

/**
  ******************************************************************************
  * 代码清单7:main.c
  * 应用:按键单击/双击/长按控制LED
  * 平台:麒麟座开发板V3.2
  * 作者:老耿
  * 日期:yyyy-mm-dd
  * 修改:无
  ******************************************************************************
  */  

//必要的头文件
#include "delay.h"
#include "key.h"
#include "led.h"

int main()
{
	delay_init();
	LED_Init();
	Key_Init();
	
	while(1)
	{
		switch(Key_Scan())
		{
			case KEY1_DOWN:     RED_TOG();    break;
			case KEY1_DOUBLE:   GREEN_TOG();  break;
			case KEY1_DOWNLONG: YELLOW_TOG(); break;
			default: break;
		}
	}
}

四、再谈延时和消抖方法

        上面的两个实验我们都是用简单的延时实现了按键的消抖。对于这种很简单的演示程序,这样写没问题,但是在实际做项目开发时,程序量往往很大,各种状态值也很多,while(1)主循环要不停的扫描各种状态值是否发生变化,及时的进行任务调度。如果程序中加了这种delay延时操作,则很可能有一件事发生了,但程序还在进行delay延时操作中,而delay结束再去检查那件事的时候,已经晚了。

        为了避免这种情况的发生,要尽量缩短while(1)循环一次所用的时间,而需要进行长时间延时的操作,必须用其他办法来处理,比如通过IO口的外部中断机制或交给专门的定时器去扫描,这些内容我们将在后续的章节讲到。这里只是先给大家强调一种编程的意识,不光是用来消抖的延时,其它任务的延时亦是如此。

(第二部分完,本文结束)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

影响LED显示屏质量的关键因素

LED电子显示屏以其环保节能的特点&#xff0c;成为现代显示技术的重要选择。然而&#xff0c;确保显示屏的质量和安全使用&#xff0c;需要考虑多个方面。本文将探讨影响LED电子显示屏质量的关键因素&#xff0c;以及在不同环境下如何预防失火现象。 材质因素 显示屏的质量首先…

排序(冒泡排序、选择排序、插入排序、希尔排序)-->深度剖析(一)

欢迎来到我的Blog&#xff0c;点击关注哦&#x1f495; 前言 排序是一种基本的数据处理操作&#xff0c;它涉及将一系列项目重新排列&#xff0c;以便按照指定的标准&#xff08;通常是数值大小&#xff09;进行排序。在C语言中&#xff0c;排序算法是用来对元素进行排序的一系…

C语言从入门到进阶(15万字总结)

前言&#xff1a; 《C语言从入门到进阶》这本书可是作者呕心沥血之作&#xff0c;建议零售价1元&#xff0c;当然这里开个玩笑。 本篇博客可是作者之前写的所有C语言笔记博客的集结&#xff0c;本篇博客不止有知识点&#xff0c;还有一部分代码练习。 有人可能会问&#xff…

“ONLYOFFICE 8.1:提升用户体验和编辑功能的全面升级”

引言 官网链接 在当今快节奏的工作环境中&#xff0c;高效地处理文档是每个职场人士必备的技能。ONLYOFFICE 桌面编辑器凭借其强大的功能和用户友好的界面&#xff0c;成为了提升文档处理效率的得力助手。本文将介绍 ONLYOFFICE 桌面编辑器的核心特性&#xff0c;并展示如何通…

PAI3D: Painting Adaptive Instance-Prior for 3D Object Detection论文讲解

PAI3D: Painting Adaptive Instance-Prior for 3D Object Detection论文讲解 1. 引言2. PAI3D框架2.1 Instance Painter2.2 Adaptive Projection Refiner2.3 Fine-granular Detection Head 3. 实验结果3.1 消融实验 1. 引言 3D目标检测对于自动驾驶来说是一个非常重要的模块&a…

鸿蒙系统——强大的分布式系统

鸿蒙相比较于传统安卓最最最主要的优势是微内核分布式操作系统&#xff0c;具有面向未来&#xff0c;跨设备无缝协作&#xff0c;数据共享的全场景体验。下面简单来感受一下鸿蒙系统的多端自由流转。 自由流转概述 场景介绍 随着全场景多设备的生活方式不断深入&#xff0c;…

background 与 background-image

相同点&#xff1a;background 与 background-image都可以用于设置背景图 区别. background既可以用于设置背景图&#xff0c; 又可以用于设置CSS样式&#xff0c;还可以用于设置背景属性。 background-image只能用于设置背景图 background能设置的背景属性&#xff0c;如下&…

学习过程中遇到的 部分问题及解决办法

1.安装build wheel时报错&#xff1a; The detected CUDA version (12.1) mismatches the version that was used to compile PyTorch (11.7). Please make sure to use the same CUDA versions. 由于cuda版本和 当前虚拟环境中的pytorch-cudatoolkit版本不同&#xff0c; 解…

数据结构历年考研真题对应知识点(数组和特殊矩阵)

目录 3.4数组和特殊矩阵 3.4.2数组的存储结构 【二维数组按行优先存储的下标对应关系(2021)】 3.4.3特殊矩阵的压缩存储 【对称矩阵压缩存储的下标对应关系(2018、2020)】 【上三角矩阵采用行优先存储的应用(2011)】 【三对角矩阵压缩存储的下标对应关系(2016)】 3.4.…

【AIGC】《AI-Generated Content (AIGC): A Survey》

文章目录 相关概念What is AI-generated content?Necessary conditions of AIGCHow can AI make the content better?The industrial chain of AIGCAdvantages of large-scale pre-trained modelsGeneration of smart textPros of AIGCCons of AIGCAIGC and Metaverse 挑战潜…

【Vue】Vue.js中常见的几种语法

在 Vue.js 中&#xff0c;主要的语法可以分为以下几种&#xff1a; 插值语法 (Interpolation) 使用双大括号 {{ }} 进行文本插值。 示例&#xff1a; {{ message }} 指令语法 (Directives) 指令是特殊的标记&#xff0c;用于告诉Vue框架如何操作DOM。Vue提供了多种内置指…

算法基础-----【动态规划】

动态规划(待完善) 动规五部曲分别为&#xff1a; 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式&#xff08;状态转移公式&#xff09;dp数组如何初始化确定遍历顺序举例推导dp数组、 动态规划的核心就是递归剪枝&#xff08;存储键值&#xff0c;…

有人物联的串口服务器USR-TCP232-410S基本测试通信和使用方案(485串口和232串口)

1.将 410S(USR-TCP232-410S&#xff0c;简称 410S 下同)的串口通过串口线(或USB 转串口线)与计算机相连接&#xff0c;通过网线将 410S 的网口 PC 的网口相连接&#xff0c;检测硬件连接无错误后&#xff0c;接入我们配送的电源适配器&#xff0c;给 410S 供电。观察指示灯状态…

Python面试宝典第1题:两数之和

题目 给定一个整数数组 nums 和一个目标值 target&#xff0c;找出数组中和为目标值的两个数的索引。可以假设每个输入只对应唯一的答案&#xff0c;且同样的元素不能被重复利用。比如&#xff1a;给定 nums [2, 7, 11, 15] 和 target 9&#xff0c;返回 [0, 1]&#xff0c;因…

《数据仓库与数据挖掘》 总复习

试卷组成 第一章图 第二章图 第三章图 第四章图 第五章图 第六章图 第九章图 第一章 DW与DM概述 &#xff08;特点、特性&#xff09; DB到DW 主要特征 &#xff08;1&#xff09;数据太多&#xff0c;信息贫乏&#xff08;Data Rich&#xff0c; Information Poor)。 &a…

计算机网络 —— 路由协议:RIP、OSPF、BGP、MPLS

路由协议 1. 定义2. IGP2.1 RIP2.2 OSPF 3. BGP4. MPLS 1. 定义 互联网中需要通过路由将数据发送至目标主机。 路由器根据**路由控制表(RoutingTable)**转发数据包&#xff0c;它根据所收到的数据包中目标主机的IP地址与路由控制表的比较得出下一个应该接收的路由器。 &…

HarmonyOS ArkUi ArkWeb加载不出网页问题踩坑

使用 使用还是比较简单的&#xff0c;直接贴代码了 别忘了配置网络权限 Entry Component struct WebPage {State isAttachController: boolean falseState url: string State title: string Prop controller: web_webview.WebviewController new web_webview.WebviewCont…

【opencv - C++ - Ubuntu】putText 显示中文最快方法

话不多说&#xff0c;直接上代码 #include <iostream> #include <opencv2/opencv.hpp> #include <opencv2/freetype.hpp>using namespace std; using namespace cv;int main(void) {Mat image(1000, 1800, CV_8UC3, Scalar(200,162,33));Ptr<freetype::F…

一篇大模型 Agent 工具使用全面研究综述

使用大型语言模型&#xff08;LLMs&#xff09;进行工具学习已成为增强LLMs能力以解决高度复杂问题的一个有希望的范式。尽管这一领域受到越来越多的关注和快速发展&#xff0c;但现有的文献仍然分散&#xff0c;缺乏系统性的组织&#xff0c;为新来者设置了进入障碍。因此对LL…

Gemma 2大模型:性能更优,效率更高

当地时间6月27日&#xff0c;谷歌正式发布了在一个月前的I/O开发者大会上预告过的Gemma 2大模型。这款新模型相较于第一代Gemma模型&#xff0c;在性能和推理效率上都有了显著的提升&#xff0c;为AI领域带来了新的突破。 据谷歌介绍&#xff0c;Gemma 2模型包括9B和27B两种参…