STM32框架之按键扫描新思路
STM32框架之按键扫描新思路
- 引入
- 代码展示
- 思路分析
我们学习了定时器实现毫秒级/秒级任务框架,这期我们基于任务框架学习按键扫描新思路。
引入
在按键扫描的过程中,最重要的一步就是按键消抖,解决的方法最简单粗暴的就是先扫描一次按键状态,判断按键按下后,延时,再次判断按键状态。就像这样:
if(1-HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)) //判断按键0是否按下{HAL_Delay(20); //延时消抖 if(1-HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)) //再次判断按键0是否按下 {while(1-HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)) //等待按键0松开{}Key_Num = 1; //赋值按键值}}
这是最笨的方法,当然还可以
多次调用按键扫描函数,当按键按下的时候开始计数/计时。当数字/时间到达一定值时,判断按键按下,当有一次扫描到按键为按下时,计数/计时清零,代码如下:
int Key_Scan(void)
{int Key_Num = 0; //定义按键值int temp = 0; //定义临时变量static int Key0_Count=0,Key1_Count=0,Key2_Count=0,WKUP_Count=0 ; //定义不同按键的按键值if(1-HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)) temp = 1; //当某一个按键按下是,临时变量值为对应的按键值加一if(1-HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)) temp = 2; //加一是因为temp为0时无法进入switch中,所以案件之都加一if(1-HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)) temp = 3; //if(HAL_GPIO_ReadPin(WKUP_GPIO_Port,WKUP_Pin)) temp = 4;switch(temp){case 1: Key0_Count++; Key1_Count=0;Key2_Count=0;WKUP_Count=0 ; //通过temp变量的值,将对应按键计数值加一break; //同时将其他按键计数值清零,case 2: Key1_Count++; Key0_Count=0;Key2_Count=0;WKUP_Count=0 ; break;case 3: Key2_Count++; Key0_Count=0;Key1_Count=0;WKUP_Count=0 ;break;case 4: WKUP_Count++; Key0_Count=0;Key1_Count=0;Key2_Count=0 ;break;default: Key0_Count=0; Key1_Count=0;Key2_Count=0;WKUP_Count=0 ;break;}if(Key0_Count >= 500) //当temp到达一定值时,则可判断按键按下{Key_Num = 1; //按键按下,将所有的按键计数值清零Key0_Count=0; Key1_Count=0;Key2_Count=0;WKUP_Count=0 ;}if(Key1_Count >= 500){Key_Num = 2;Key0_Count=0; Key1_Count=0;Key2_Count=0;WKUP_Count=0 ;}if(Key2_Count >= 500){Key_Num = 3;Key0_Count=0; Key1_Count=0;Key2_Count=0;WKUP_Count=0 ;}if(WKUP_Count >= 500){Key_Num = 4;Key0_Count=0; Key1_Count=0;Key2_Count=0;WKUP_Count=0 ;}return Key_Num; //返回按键值,当没有按键按下,或者存在抖动时,返回值为0
这种方法相对于第一种,逻辑上更严谨,但是不好控制计数值。并且多次进入函数,占用大量CPU资源,所以我们引入第三种方法,规定时间间隔,进行扫描,通过数组判断结果。代码如下:
代码展示
- main.c
/* USER CODE BEGIN PFP */
void Proc2msTask(void) //2ms任务
{static uint8_t i=0;if(Get_2ms_Flag() == 1) //获取2ms标志位{Clear_2ms_Flag(); //清除2ms标志位//2mstask codei++;if(i==4) //1s扫描一处按键状态{i=0;Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task); //扫描Key0状态Key_One_Scan(Key_Name_Key1,Key1_Up_Task,Key1_Down_Task); //扫描Key1状态Key_One_Scan(Key_Name_Key2,Key2_Up_Task,Key2_Down_Task); //扫描Key2状态Key_One_Scan(Key_Name_WKUP,WWKUP_Up_Task,WKUP_Down_Task); //扫描WKUP状态}}
}void Proc1sTask(void) //1s任务
{if(Get_1s_Flag() == 1) //获取1s标志位{Clear_1s_Flag(); //清除1s标志位//1stask code
// HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);}
}
/* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 *//* USER CODE END 0 *//*** @brief The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_TIM10_Init();/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim10);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){Proc2msTask(); //调用2ms任务Proc1sTask(); //调用1s任务/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}
- Key.c
/* USER CODE BEGIN 2 */
void Key0_Down_Task(void)
{HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET); //按下亮灯
}
void Key0_Up_Task(void)
{HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET); //松开关闭
}
void Key1_Down_Task(void)
{HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET); //按下亮灯
}
void Key1_Up_Task(void)
{HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET); //松开关闭
}
void Key2_Down_Task(void)
{HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET); //按下亮灯
}
void Key2_Up_Task(void)
{HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET); //松开关闭
}
void WKUP_Down_Task(void)
{HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_RESET); //按下亮灯
}
void WWKUP_Up_Task(void)
{HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_SET); //松开关闭
}void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void))
{static uint8_t Key_Val[Key_Name_Max]; //按键值的存放位置static uint8_t Key_Flag[Key_Name_Max]; //KEY0~2为0时表示按下,为1表示松开,WKUP反之Key_Val[KeyName] = Key_Val[KeyName] <<1; //每次扫描完,将上一次扫描的结果左移保存switch(KeyName){case Key_Name_Key0: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)); //读取Key0按键值break;case Key_Name_Key1: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)); //读取Key1按键值break;case Key_Name_Key2: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin)); //读取Key2按键值break;case Key_Name_WKUP: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin)); //读取WKUP按键值break; default:break;}if(KeyName == Key_Name_WKUP) //WKUP的电路图与其他按键不同,所以需要特殊处理{//WKUP特殊情况//当按键标志为1(松开)是,判断是否按下,WKUP按下时为0xffif(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 1){(*OnKeyOneDown)();Key_Flag[KeyName] = 0;}//当按键标志位为0(按下),判断按键是否松开,WKUP松开时为0x00if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 0){(*OnKeyOneUp)();Key_Flag[KeyName] = 1;} }else //Key0~2按键逻辑判断{//Key0~2常规判断//当按键标志为1(松开)是,判断是否按下if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 1){(*OnKeyOneDown)();Key_Flag[KeyName] = 0;}//当按键标志位为0(按下),判断按键是否松开if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 0){(*OnKeyOneUp)();Key_Flag[KeyName] = 1;} }}
/* USER CODE END 2 */
- Key.h
/* Includes ------------------------------------------------------------------*/
#include "main.h"/* USER CODE BEGIN Includes *//* USER CODE END Includes *//* USER CODE BEGIN Private defines */
typedef enum{Key_Name_Key0 = 0,Key_Name_Key1,Key_Name_Key2,Key_Name_WKUP,Key_Name_Max}EnumKeyOneName;/* USER CODE END Private defines */void MX_GPIO_Init(void);/* USER CODE BEGIN Prototypes */
void Key0_Down_Task(void);void Key0_Up_Task(void);void Key1_Down_Task(void);void Key1_Up_Task(void);void Key2_Down_Task(void);void Key2_Up_Task(void);void WKUP_Down_Task(void);void WWKUP_Up_Task(void);void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void));
/* USER CODE END Prototypes */#ifdef __cplusplus
}
#endif
#endif /*__ GPIO_H__ */
思路分析
按键按下的过程中,难免会有抖动,但是抖动的时间通常在10~20ms之间,而按键按下的过程往往会持续100ms以上,所以我们可以每10ms扫描一次按键状态,如果连续八次都是按下,则认为按键已经按下,想要的执行认为即可。反之,如果八次都是松开,则认为按键松开了,也可以执行按键松开的相关任务。
具体细节,大家看代码吧,这个只是大体思路,代码还有很多精彩的地方。
相关文章:
STM32框架之按键扫描新思路
STM32框架之按键扫描新思路 引入代码展示思路分析 我们学习了定时器实现毫秒级/秒级任务框架,这期我们基于任务框架学习按键扫描新思路。 引入 在按键扫描的过程中,最重要的一步就是按键消抖,解决的方法最简单粗暴的就是先扫描一次按键状态&…...
完美解决k8s master节点无法ping node节点中的IP或Service NodePort的IP
1、问题一 使用搭建好了K8S集群,先是node节点加入k8s集群时,用的内网IP,导致master节点无法操作node节点中的pod(这里的不能操作,指定是无法查看node节点中pod的日志、启动描述、无法进入pod内部,即 kubec…...
弗洛伊德算法(C++)
目录 介绍: 代码: 结果: 介绍: 弗洛伊德算法(Floyd algorithm)也称为Floyd-Warshall算法,是一种用于求解所有节点对之间的最短路径的动态规划算法。它使用了一个二维数组来存储所有节点…...
相对定位、绝对定位、固定定位、绝对定位堆叠顺序
相对定位:相对自己本身进行偏移 CSS语法: position: relative;/*相对自己进行定位*/ top: 10px;/*距离上边*/ left: 10px;/*距离左边*/ 演示图: 绝对定位:默认以浏览器进行定位。如果想依照父盒子定位,需要在父盒子…...
px4+vio实现无人机室内定位
文章主要讲述px4 如何利用vins_fusion里程计数据实现在室内定位功能。 文章基于以下软、硬件展开。 硬件软件机载电脑: Intel NUC系统:Ubuntu 20.04相机: Intel Realsense D435iros:noetic飞控:Pixhawk 2.4.8固件&am…...
享元模式 rust和java的实现
文章目录 享元模式介绍实现javarust实现代码 rust仓库rust仓库 享元模式 享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应…...
XmlElement注解在Java的数组属性上,以产生多个相同的XML元素
例如,下面这段XML数据,有多个data元素,并且它们级别相同: <?xml version"1.0" encoding"UTF-8"?><request><reqtype>05</reqtype><secret>test</secret><body><userid&…...
SQLServer 数字加千分位 用FORMAT函数强转不管多大位数
问题 CONVERT ( money, CONVERT ( money, CAST ( round( FTP_AMOUNT, 2 ) AS NUMERIC ( 20, 2 ) ) ) 1 ) AS FTP_AMOUNT用的money函数 结果空间不足,无法将 money 值转换为 varchar。 可以强转 select FORMAT(CAST ( round( ‘-8926143870680.62000000’, 2 ) AS N…...
说说mvc和mvvm的区别和联系
mvvm 与mvc mvvm 与mvcmvc和mvvm的区别和联系 举例说明mvvm与mvc MVC是一种用于构建应用程序的架构模式,它也将应用程序的逻辑和界面分离。它由三个主要组件组成: 模型(Model):表示应用程序的数据和业务逻辑。视图&a…...
linux rsyslog综合实战2
本次我们通过rsyslog服务将A节点服务器上的两个(E.g:多个日志也可以)日志(Path:/var/log/245-1.log、245-2.log)实时同步到B节点服务器目录下(Path:/opt/rsyslog/245) 1.rsyslog架构 2.环境信息 环境信息 HostnameIpAddressOS versionModuleNotersyslog1192.168.10.245CentOS…...
AcWing 4. 多重背包问题 I 学习笔记
有 N� 种物品和一个容量是 V� 的背包。 第 i� 种物品最多有 si�� 件,每件体积是 vi��,价值是 wi��。 求解将哪些物品装入背包,可使物…...
解决selenium使用chrome下载文件(如pdf)时,反而打开浏览器的预览界面
文章目录 解决方法完整的配置 解决方法 在初始化浏览器的时候,添加以下配置即可: option webdriver.ChromeOptions()prefs {"profile.managed_default_content_settings.images": 2, # 禁止加载图片# permissions.default.stylesheet: 2, …...
2024年山东省职业院校技能大赛中职组“网络安全”赛项竞赛试题-C
2024年山东省职业院校技能大赛中职组 “网络安全”赛项竞赛试题-C 一、竞赛时间 总计:360分钟 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 A、B模块 A-1 登录安全加固 180分钟 200分 A-2 本地安全策略设置 A-3 流量完整性保护 A-4 …...
基于Python实现用于实时监控和分析 MySQL 服务器的性能指标和相关信息工具源码
MySQL命令行监控工具 - mysqlstat 介绍 mysqlstat 是一个命令行工具,用于实时监控和分析 MySQL 服务器的性能指标和相关信息。 它可以帮助 DBA(数据库管理员)和开发人员定位和解决数据库性能问题。 以下是 mysqlstat 工具的主要功能&#…...
Android 10-13鼠标右键返回功能适配
Android 10-13鼠标右键返回功能适配 文章目录 Android 10-13鼠标右键返回功能适配一、前言二、鼠标右键适配修改1、Android 10 以及更低版本2、Android11 或者更高版本三、总结1、鼠标右键返回功能修改主要代码2、标右键返回修改代码系统源码搜索3、其他 一、前言 Android 原生…...
51单片机/STM32F103/STM32F407学习1_点亮LED灯
目录: 基础知识单片机从0实现单片机GPIO介绍 参考连接: 野火霸天虎教程 https://doc.embedfire.com/products/link/zh/latest/mcu/stm32/ebf_stm32f407_batianhu_v1_v2/download/stm32f407_batianhu_v1_v2.html x.1 基础知识 x.1.1 指针中的取地址&a…...
(Transfer Learning)迁移学习在IMDB上训练情感分析模型
1. 背景 有些场景下,开始的时候数据量很小,如果我们用一个几千条数据训练一个全新的深度机器学习的文本分类模型,效果不会很好。这个时候你有两种选择,1.用传统的机器学习训练,2.利用迁移学习在一个预训练的模型上训练…...
蓝桥杯每日一题2023.11.20
题目描述 “蓝桥杯”练习系统 (lanqiao.cn) 题目分析 方法一:暴力枚举,如果说数字不在正确的位置上也就意味着这个数必须要改变,进行改变记录即可 #include<bits/stdc.h> using namespace std; const int N 2e5 10; int n, a[N], …...
【迅搜02】究竟什么是搜索引擎?正式介绍XunSearch
究竟什么是搜索引擎?正式介绍XunSearch 啥?还要单独讲一下啥是搜索引擎?不就是百度、Google嘛,这玩意天天用,还轮的到你来说? 额,好吧,虽然大家天天都在用,但是我发现&am…...
【Sql】sql server还原数据库的时候,提示:因为数据库正在使用,所以无法获得对数据库的独占访问权。
【问题描述】 sql server 还数据库的时候,提示失败。 点击左下角进度位置,可以得到详细信息: 因为数据库正在使用,所以无法获得对数据库的独占访问权。 【解决方法】 针对数据库先后执行下述语句,获得独占访问权后&a…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
