以STM32为例,实现按键的短按和长按

以STM32为例,实现按键的短按和长按

目录

  • 以STM32为例,实现按键的短按和长按
    • 1 实现原理
    • 2 实现代码
    • 3 测试
    • 结束语

1 实现原理

简单来说就是通过设置一个定时器来定时扫描几个按键的状态,并分别记录按键按下的持续时间,通过时间的长短就可以判断出是长按还是短按。

本文硬件接线图如下:
在这里插入图片描述
在这里插入图片描述

2 实现代码

1、key.h
主要是一些按键引脚以及后面需要使用的变量定义。

#ifndef __KEY_H
#define __KEY_H	 
#include "sys.h"#define KEY1_PORT  GPIOE
#define KEY1_PIN   GPIO_Pin_4
#define KEY2_PORT  GPIOE
#define KEY2_PIN   GPIO_Pin_3
#define KEY3_PORT  GPIOE
#define KEY3_PIN   GPIO_Pin_2
#define KEY4_PORT  GPIOA
#define KEY4_PIN   GPIO_Pin_0// 按键引脚定义
typedef struct
{GPIO_TypeDef* port;          // GPIOxuint16_t      pin;           // GPIO PINxuint16_t      pressed_state; // 按键按下时的状态,0:按下时为低电平,1:按下时为高电平
}key_gpio_t;// 按键状态
typedef enum
{KEY_RELEASE,         // 释放松开KEY_CONFIRM,         // 消抖确认KEY_SHORT_PRESSED,   // 短按KEY_LONG_PRESSED,    // 长按
}key_status_t;// 按键事件
typedef enum
{EVENT_NULL,EVENT_SHORT_PRESSED,EVENT_LONG_PRESSED,
}key_event_t;typedef struct
{key_status_t current_state; // 按键当前状态uint32_t  pressed_time;     // 按下时间    key_event_t key_event;      // 按键事件
}key_param_t;uint8_t read_key_state(uint8_t index);
uint8_t key_scan(void);
void key_handle(void);
void key_timer_init(void);
void key_gpio_init(void);
void key_init(void);#endif

2、key.c
按键的实现代码,包括定时器和引脚的初始化,按键的扫描和处理函数,等等。

/*********************************************************************************************************** @file           key.c* @author         qiyiqi* @brief          按键驱动代码* MCU:            STM32F103ZE开发板* 按键原理:       设置一个1ms定时器定时扫描几个按键的状态,并分别记录按下的持续时间,通过时间可以判断是长按还是*                 短按。    * 注意事项:       此代码只是作为一个参考例程,如果不使用STM32的标准库,移植到其他MCU或者HAL库之类的,主要修改的*                 地方在初始化函数key_init(),按键读取函数read_key_state(),定时器初始化以及中断服务函数等。*********************************************************************************************************/
#include "key.h"
#include "stdio.h"// 按键列表
key_gpio_t key_list[] =
{// 端口号,引脚号,有效电平{KEY1_PORT, KEY1_PIN, 0},  // 按下为0,松开为1{KEY2_PORT, KEY2_PIN, 0},{KEY3_PORT, KEY3_PIN, 0},  // 按下为1,松开为0{KEY4_PORT, KEY4_PIN, 1},/* 可以继续往下添加更多按键 */
};// 按键数量
#define KEY_NUM_MAX      (sizeof(key_list)/sizeof(key_list[0]))  
#define CONFIRM_TIME     20    // 消抖时间 ms
#define LONG_PRESS_TIME  2000  // 长按时间窗 ms// 按键配置
#define SHORT_RELEASE_VALID  1 // 0:短按按下时即刻生效,1:短按释放时生效,注意:如果配成0的话,长按的时候就一定会先触发短按
#define LONG_RELEASE_VALID   1 // 0:长按按下时即刻生效,1:长按释放时生效key_param_t key_param[KEY_NUM_MAX]; // 保存所有按键的状态// 读取按键状态
uint8_t read_key_state(uint8_t index)
{if(GPIO_ReadInputDataBit(key_list[index].port, key_list[index].pin) == key_list[index].pressed_state){// 按键按下return 1; }return 0;
}// 扫描单个按键状态(需要按1ms频率扫描)
uint8_t key_scan(void)
{uint8_t key_press;uint8_t index;for(index = 0; index < KEY_NUM_MAX; index++){// 根据按键列表依次扫描key_press = read_key_state(index);  // 读取按键状态switch (key_param[index].current_state)                                    {// 按键状态机case KEY_RELEASE:{// 释放状态if(key_press)                                                                                                 {// 按键按下key_param[index].current_state = KEY_CONFIRM;}else{// 按键松开key_param[index].pressed_time = 0; }break;}case KEY_CONFIRM:{// 按键消抖if(key_press){// 按键保持按下if(++key_param[index].pressed_time > CONFIRM_TIME)    // 10ms{// 完成消抖key_param[index].current_state = KEY_SHORT_PRESSED;#if (SHORT_RELEASE_VALID == 0)  // 短按按下立马生效key_param[index].key_event = EVENT_SHORT_PRESSED;  // 短按事件生效#endif}}else{// 按键松开key_param[index].current_state = KEY_RELEASE;}break;}case KEY_SHORT_PRESSED:{// 短按if(key_press){// 按键保持按下if(++key_param[index].pressed_time > LONG_PRESS_TIME)  // 2000ms{// 长按key_param[index].current_state = KEY_LONG_PRESSED;#if (LONG_RELEASE_VALID == 0)  // 长按按下立马生效key_param[index].key_event = EVENT_LONG_PRESSED;  // 长按事件生效#endif}}else   {// 按键松开key_param[index].current_state = KEY_RELEASE;#if (SHORT_RELEASE_VALID == 1)  // 短按释放才生效key_param[index].key_event = EVENT_SHORT_PRESSED;  // 短按事件生效#endif}break;}case KEY_LONG_PRESSED:{// 长按if(!key_press)          {// 按键松开key_param[index].current_state = KEY_RELEASE;#if (LONG_RELEASE_VALID == 1)  // 长按释放才生效key_param[index].key_event = EVENT_LONG_PRESSED;  // 长按事件生效#endif} break;}default:{key_param[index].current_state = KEY_RELEASE;}}}return 0;                            
}// 按键处理函数
void key_handle(void)
{uint8_t index;for (index = 0; index < KEY_NUM_MAX; index++){// 检查有无按键按下if(key_param[index].key_event != 0){// 有按键按下switch (index){case 0:{// 按键1if(key_param[index].key_event == EVENT_SHORT_PRESSED){// 短按printf("KEY1 SHORT PRESSED\n");}else if(key_param[index].key_event == EVENT_LONG_PRESSED){// 长按printf("KEY1 LONG PRESSED\n");}break;}case 1:{// 按键2if(key_param[index].key_event == EVENT_SHORT_PRESSED){// 短按printf("KEY2 SHORT PRESSED\n");}else if(key_param[index].key_event == EVENT_LONG_PRESSED){// 长按printf("KEY2 LONG PRESSED\n");}break;}case 2:{// 按键3if(key_param[index].key_event == EVENT_SHORT_PRESSED){// 短按printf("KEY3 SHORT PRESSED\n");}else if(key_param[index].key_event == EVENT_LONG_PRESSED){// 长按printf("KEY3 LONG PRESSED\n");}break;}case 3:{// 按键4if(key_param[index].key_event == EVENT_SHORT_PRESSED){// 短按printf("KEY4 SHORT PRESSED\n");}else if(key_param[index].key_event == EVENT_LONG_PRESSED){// 长按printf("KEY4 LONG PRESSED\n");}break;}default:{break;}}key_param[index].key_event = EVENT_NULL;  // 清除该事件}}
}// 定时器中断服务程序(用于定时扫描按键)
void TIM3_IRQHandler(void)
{if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  // 检查TIM3更新中断发生与否{key_scan();  // 扫描按键TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  // 清除TIMx更新中断标志 }
}// 定时器初始化(定时1ms)
void key_timer_init(void)
{TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能//定时器TIM3初始化TIM_TimeBaseStructure.TIM_Period = 1000 - 1; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值    TIM_TimeBaseStructure.TIM_Prescaler = SystemCoreClock / 1000000 - 1; //设置用来作为TIMx时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断//中断优先级NVIC设置NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器TIM_Cmd(TIM3, ENABLE);  //使能TIMx    
}// 按键引脚初始化
void key_gpio_init(void)
{GPIO_InitTypeDef  GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOE, ENABLE);    GPIO_InitStructure.GPIO_Pin = KEY1_PIN;                 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;          GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        GPIO_Init(KEY1_PORT, &GPIO_InitStructure);        GPIO_InitStructure.GPIO_Pin = KEY2_PIN;                 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;          GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        GPIO_Init(KEY2_PORT, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = KEY3_PIN;                 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;          GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        GPIO_Init(KEY3_PORT, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = KEY4_PIN;                 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;          GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        GPIO_Init(KEY4_PORT, &GPIO_InitStructure);
}// 按键初始化
void key_init(void)
{key_gpio_init();key_timer_init();
}

3、main.c
主函数入口,这里调用按键驱动的代码。

#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "key.h"int main(void)
{		delay_init();       // 延时函数初始化	  uart_init(115200);	// 串口初始化为115200key_init();         // 按键初始化while(1){	    	key_handle();}
}

3 测试

通过串口打印按键扫描的结果,可以看到每个按键都是可以实现独立的长短按功能。

按键释放时有效,log如下:
注:每个按键的长短按都独立,互不影响。
请添加图片描述
按键按下时有效,log如下:
注:长按触发之前,短按必先触发。
请添加图片描述

结束语

本文以STM32为例讲解了按键长按和短按的实现方法,当然,这只是其中一种方法,实现的方式其实还是很多。
好了,如果还有什么问题,欢迎评论区留言,谢谢!

如果觉得本文有帮助,就…你懂的。
请添加图片描述

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

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

相关文章

序列化与反序列化xml bin

1、XML XML&#xff08;可扩展标记语言&#xff09;序列化是一种将对象的数据结构转换为XML格式的过程&#xff0c;以便将其存储在文件中、通过网络传输或与其他系统进行数据交换。在C#中&#xff0c;你可以使用.NET框架提供的XmlSerializer类来执行对象到XML的序列化和XML到对…

Java中访问未初始化的对象:原理、错误与防范

在Java编程中&#xff0c;访问未初始化的对象是一种常见的错误&#xff0c;可能导致程序运行时异常。这个问题的根本原因在于Java要求对象在使用之前必须经过初始化&#xff0c;否则会存在未知的状态。本文将详细讲解Java中访问未初始化对象的原理、可能导致的错误以及如何防范…

后端杂七杂八系列篇一

后端杂七杂八系列篇一 ① MySQL选择合适的数据类型① Char与Varchar② Text与Blob ② EqualsAndHashCode(callSuper true)的作用③ mybatis-plus 相关① 主键生成策略② 使用Model实现CRUD③ Wrapper的用法① Wrapper的继承关系② 项目中最常用的warpper [LambdaQueryWrapper]…

[NISACTF 2022]level-up

[NISACTF 2022]level-up wp level 1 robots.txt 通过目录爆破&#xff0c;发现存在 robots.txt 文件&#xff08;或者说查看源码&#xff0c;源码中有 disallow 提示&#xff0c;说明存在 robots.txt 文件&#xff09;&#xff1a; dirsearch -u "http://node5.anna.n…

M3u8播放列表文件(索引格式文件)、HLS(HTTP Live Streaming)协议介绍

M3U8文件格式是为了支持HLS&#xff08;HTTP Live Streaming&#xff09;协议而开发的。HLS是Apple公司设计的一种基于HTTP的流媒体传输协议&#xff0c;用于传输音频和视频数据。 在HLS中&#xff0c;媒体内容被切分为一系列的小片段&#xff0c;每个片段都有自己的URL。这些U…

Docker入门教程(详解)

Docker容器化 一 入门 1. 引言 &#xff08;1&#xff09;单机部署 场景&#xff1a; 将多个应用部署一台服务器上。 问题 每个应用软件&#xff0c;都会消耗物理资源&#xff0c;共用计算机资源&#xff0c;彼此之间会形成竞争关系。 &#xff08;2&#xff09;多机部署 …

python django 生鲜商城管理系统

python django 生鲜商城管理系统,包含用户端和管理端 功能&#xff1a; 用户端&#xff1a;商城主页展示&#xff0c;登录&#xff0c;注册&#xff0c;用户中心&#xff0c;购物车&#xff0c;我的订单&#xff0c;购物车结算 管理端&#xff1a;登录&#xff0c;商品&…

Java 堆与栈的作用与区别

栈是运行时的单位&#xff0c;而堆是存储的单位&#xff0c;栈解决程序的运行问题&#xff0c;堆解决数据存储的问题。 一个线程对应一个线程栈&#xff0c;栈是运行单位&#xff0c;里面存储的信息都是跟当前线程相关的信息&#xff0c;包括局部变量、程序运行状态、方法返回…

SpringBoot: 通过MyBatis访问ClickHouse

一、ClickHouse中建表&#xff0c;添加数据 二、SpringBoot项目添加mybatis、clickhouse、druid相关依赖 <dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.6</version></dependency>…

SQL学习汇总

数据库将两张没有关联的表进行横向连接数据库将两张表进行横向连接&#xff08;拼接成一张表的形式显示&#xff09;_db2 两条记录如果相同就横向显示-CSDN博客 mysql统计某个字段的比例_mysql查询一行某个字段占整个字段sum的比例-CSDN博客 Spark 系列&#xff08;十二&…

opencv入门到精通——Canny边缘检测

目录 理论 OpenCV中的Canny Edge检测 附加资源 在本章中&#xff0c;我们将学习 Canny边缘检测的概念 OpenCV函数: cv.Canny() 理论 Canny Edge Detection是一种流行的边缘检测算法。它由John F. Canny发明 1.这是一个多阶段算法&#xff0c;我们将经历每个阶段。 2.降…

原生JS调用OpenAI GPT接口并实现ChatGPT逐字输出效果

效果&#xff1a; 猜你感兴趣&#xff1a;springbootvue实现ChatGPT逐字输出打字效果 附源码&#xff0c;也是小弟原创&#xff0c;感谢支持&#xff01; 没废话&#xff0c;上代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><me…

09、docker 安装nacos并配置mysql存储配置信息

docker 安装nacos并配置mysql存储配置信息 1、docker启动nacos的各种方式2、Docker安装nacos3、MySQL中新建nacos的数据库4、挂载数据or配置目录5、运行 1、docker启动nacos的各种方式 内嵌derby数据源 docker run -d \ -e PREFER_HOST_MODEhostname \ -e SPRING_DATASOURCE_…

软件测试技术复习点

1 术语含义&#xff08;故障、错误、失效、测试用例&#xff09; 故障&#xff08;Fault&#xff09;&#xff1a;故障是软件中的静态缺陷&#xff1b; 故障屏蔽&#xff1a;软件中的某个故障可能被其他一个或多个故障屏蔽&#xff1b; 错误&#xff08;Error&#xff09;&…

1 电科院FTU检测标准学习笔记-外观检查

作者简介&#xff1a; 本人从事电力系统多年&#xff0c;岗位包含研发&#xff0c;测试&#xff0c;工程等&#xff0c;具有丰富的经验 在配电自动化验收测试以及电科院测试中&#xff0c;本人全程参与&#xff0c;积累了不少现场的经验 目录 **前言****检测大纲****外观与结构…

LeetCode 2397. 被列覆盖的最多行数,状态压缩优化回溯法

一、题目 1、题目描述 给你一个下标从 0 开始、大小为 m x n 的二进制矩阵 matrix &#xff1b;另给你一个整数 numSelect&#xff0c;表示你必须从 matrix 中选择的 不同 列的数量。 如果一行中所有的 1 都被你选中的列所覆盖&#xff0c;则认为这一行被 覆盖 了。 形式上&am…

【目标检测实验系列】YOLOv5模型改进:融合混合注意力机制CBAM,关注通道和空间特征,助力模型高效涨点!(内含源代码,超详细改进代码流程)

自我介绍&#xff1a;本人硕士期间全程放养&#xff0c;目前成果:一篇北大核心CSCD录用,两篇中科院三区已见刊&#xff0c;一篇中科院四区在投。如何找创新点&#xff0c;如何放养过程厚积薄发&#xff0c;如何写中英论文&#xff0c;找期刊等等。本人后续会以自己实战经验详细…

开源协议简介和选择

软件国产化已经提到日程上了&#xff0c;先来研究一下开源协议。 引言 在追求“自由”的开源软件领域的同时不能忽视程序员的权益。为了激发程序员的创造力&#xff0c;现今世界上有超过60种的开源许可协议被开源促进组织&#xff08;Open Source Initiative&#xff09;所认可…

SpingBoot的项目实战--模拟电商【3.购物车模块】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于SpringBoot电商项目的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.功能需求 二.代码编写 …

Git(2):Git环境的安装

本教程里的git命令例子都是在Git Bash中演示的&#xff0c;会用到一些基本的linux命令&#xff0c;在此为大家提前列举&#xff1a; ls/ll 查看当前目录cat 查看文件内容touch 创建文件vi vi编辑器&#xff08;使用vi编辑器是为了方便展示效果&#xff0c;学员可以记事本、edi…