以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,一经查实,立即删除!

相关文章

后端杂七杂八系列篇一

后端杂七杂八系列篇一 ① 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…

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;商品&…

SpringBoot: 通过MyBatis访问ClickHouse

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

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…

使用异构图学习破解推荐系统 - 第 1 部分

Lokesh Sharma – Medium 一、说明 所以&#xff0c;这是独家新闻&#xff1a;异质图拥有一个充满潜力的世界&#xff0c;而常规图却无法做到这一点。传统的同构图很难处理不同关系和边类型的复杂性。现在是大炮的时候了——先进的架构可以解决具有多种边缘和关系类型的数据集的…

Nacos学习思维导图

一、服务注册 参考文档&#xff1a;http://www.bryh.cn/a/118936.html https://blog.csdn.net/Saintmm/article/details/121981184 二、服务续约 参考文档&#xff1a;http://www.bryh.cn/a/118936.html https://blog.csdn.net/Saintmm/article/details/121981184 三、服务…

清风数学建模排版

Overview 链接&#xff1a;https://pan.baidu.com/s/11QBw3zBFNicwQWvWCfW1Gg?pwdepnz 提取码&#xff1a;epnz Latex 范文排版练习 b站刘海洋latex工作室&#xff0c;待还愿 Word基础 Word VBA&#xff0c;待还愿 fnF4&#xff1a;重复上一步操作 ctrlY&#xff1a;恢…

DispatcherServlet请求处理流程

前言 DispatcherServlet 是 Spring MVC 的核心类&#xff0c;它本质是一个 Servlet&#xff0c;负责接管 HTTP 请求并把它分发给对应的处理器处理&#xff0c;最后处理响应结果渲染页面。 DispatcherServlet 本身并不复杂&#xff0c;它提供了一个模板方法doDispatch()来处理请…

进阶学习——Linux系统中重点‘进程’

目录 一、程序和进程的关系 1.程序 2.进程 2.1线程 2.2协程 3.进程与线程的区别 4.总结 4.1延伸 5.进程使用内存的问题 5.1内存泄漏——Memory Leak 5.2内存溢出——Memory Overflow 5.3内存不足——OOM&#xff08;out of memory&#xff09; 5.4进程使用内存出现…