韦东山stm32hal库--定时器喂狗模型按键消抖原理+实操详细步骤

news2024/11/29 16:36:38

一.定时器按键消抖的原理:

按键消抖的原因:

image-20241128221138526

当我们按下按键的后, 端口从高电平变成低电平, 理想的情况是, 按下, 只发生一次中断, 中断程序只记录一个数据.

但是我们使用的是金属弹片, 实际的情况就是如上图所示, 可能会发生多次中断,难道我们要记录3/4次数据吗?

答:按键按下的时候, 会有机械振动, 这个震荡也会引起单片机io口变化, 从而实现多次触发按键中断,实际我们只需要一次按键中断, 通过科学家的研究, 按下按键到按键平稳, 这个时长是10ms

处理方法(1)

所以我们一般情况下, 是当检测到按键电平变化的时候, 延时10ms后, 然后才真正判断, 按键是否真正按下, 从而把这个按键震荡周期躲了过去

分析第一种方法

​ 第一种延时躲避按键震荡周期的方法, 通过强行让单片机死循环10ms, 来进行实现的。有一个弊端就是延时的这10ms,我们是做不了事情的, 单片机只能傻傻的停留在那里, 如果我们多按下几次, 那系统不就卡爆了。一般按键的事件, 都不是特别紧急的,开发者开发系统的时候, 留给用户的按键, 都是保证绝对安全的, 所以按键的优先级不能太高。程序的运行应该留给更重要并且能够保证系统安全的功能。

这个时候再来看, 我们按下按键程序竟然卡死了10ms, 这个不是很严重的事情吗? 所以我们要节省这10ms, 必须用定时器来做,意思就是系统该干什么就干什么, 我买了一个秒表,按下按键后开始计时, 到点了(震荡周期过了)提醒系统处理按键中断就行了。

处理方法(2)定时器优化

现实因素:

我们按下按键, 不可避免的会产生, 按键机械振动

实际需求:

当按下按键, 按键平稳的时候, 我们才认定是按下了按键, 我们就进入中断去处理。

分析因素和需求之间的困难

因素:按键振动的不可避免

需求: 按键平稳才处理按键事件

困难: 常用方法解决的弊端(ctrl 加鼠标左键,快速跳转)

确定真正软件层面的需求

了解了这中间的矛盾, 我们仔细分析按键的整个过程

image-20241128095639305

我们需要的是按键平稳的时候, 再处理按键, 所以如果我们精准的找到最后一次按键按下的时机,那么不就是可以了吗?

所以现在我们的需求就变成了,如何精准的找到,按键彻底按下的时机,也就是按键按下到按键彻底闭合, 这之间虽然会产生按键振动,也就是上图所示的①②③④, 这些电平都会被我们的单片机捕捉到,识别到io口电平变化,但这只是蜻蜓点水,不是真正的按下, 我们要的是稳稳地幸福。

解决方案

(1)逃避法(ctrl 加鼠标左键,快速跳转)
(2)按键中断+定时器实时扫描法(喂狗模型)

通过了解方案(1), 我们不能进行逃避, 所以要精准的识别到最后一次按键,然后触发事件就行了.

那如何判断, 最后一次按键,就是最后一次呢?

观察最后一次按键抖动的特点:

最后一次按键按下后, 就是彻底的闭合了,因为按键抖动的时长最长是10ms,所以按键按下,然后电平保持稳定10ms,就可以认定是此次按键事件触发。

但是按键按下的信息都一样, 都是电平变化, 不管电平变化间隔的时间长短,都会触发if(电平变化){代表按键按下}

所以我们就需要, 在每一次按键按下后, 都开始计时10ms,

按键识别分成两种情况:

(1)这个按键是抖动

虽然是抖动, 我们一视同仁, 也开始用定时器计时从零计时10ms,然后还没来得及计时到 10ms

下次抖动就触发了, 然后就把这个计时抛弃了, 再次刷新定时器从头开始计时,判断下次的抖动是否是最后一次按键

(2)最后一次按键来临

通过过滤上面的抖动按键(每次新的按键来临,并且这个按键定时器计时不超过10ms),终于等到你(最后一次稳稳地幸福),那么我们还是开始计时, 此时定时器就计时到了10ms,然后我们就去处理按键事件就行了。

二.代码执行方案

测试真的存在按键抖动问题

1.首先解压打开0602_key_isr_oled.7z,然后改名为 0603_key_timer

0602工程

密码:5bpw

image-20241128143947105

2.打开工程

image-20241128144016652

3.找到中断函数

image-20241128144158455

4.当发生按键中断的时候, 按键被调用

image-20241128144310580

5.我们会看到中断调用了回调函数, 我们接着f12进入

image-20241128144343053

6.这里就是我们之前自己写的中断回调函数,之前我们是点亮led,现在我们开始记录按键次数, 看看是否存在按键抖动现象

int g_key_cnt = 0;
void HAL_GPIO_EXTI_Callback()
{
    g_key_cnt++;
    
}    
image-20241128144609622

7.我们现在开始测试, 那现在我们就按下按键, 查看这个g_key的值, 怎么显示呢? 我们就用OLED函数吧, 我们用之前的OLED函数

image-20241128144913684

8.复制初始化函数, 拿到main函数里面

image-20241128145006417 image-20241128145114198

9.先显示 一串字符串, 表明这个是我们的按键次数, 复制示例代码里面的函数

image-20241128145258402

image-20241128145344355

10.然后在while循环里面, 重复的进行, 显示按键次数

image-20241128145500410

11.编译运行, 发现没有oled函数头文件, 所以我们把路径, 加入到环境变量里面(只指定目录即可)

image-20241128150800051

12.然后我们烧录, 按下按键, 观察现象(我们只是按下松开一次, 数值就增加了好多次)

定时器解决按键抖动问题

1.我们进入启动文件, 来定位到我们的滴答定时器计数中断,然后f12进入

image-20241128151126074

2.每一毫秒运行一次

image-20241128151501397

3.f12接着进入, 我们再看看, 他真的是只增加一个计数值而已

image-20241128151547431

4.那么如何获得计数值呢?

image-20241128151650190

5.当我们按下或者松开按键的时候, 我们回调函数被调用

image-20241128151809055

6.每当有按键按下(即使这个按键是按键抖动), 我们都要去刷新修改定时器的计数值, 从而实现上文所说的(精确的检测到最后一次按键按下)

image-20241128152436131

7.当最后一次按键按下的时候, 然后等待10ms, 定时器超时, 我们就要进行按键事件的处理

image-20241128152746338

8.什么是timer, 所以我们就需要在main.c里面声明一个结构体.

我们想一下, 我们计时需要哪些变量, 我们把他们放在一个结构体里面就行了

① 超时时间: 在我们SysTick里面, 我们每次过1ms, 都会增加一个计数值,

uint32_t timeout; //当前uwTick + 某一个数值

② 想做的通用一点的话, 来个参数

void *arg;

③ 调用什么函数

void (*func)(void *);

9.我们在main.c里面定义一个结构体

struct soft_timer{
    uint32_t timeout;
    void * args;
    void (*func)(void *);
};
image-20241128161125704

10.我们再根据这个结构体,来定义一个按键的结构体:

第一个超时时间,我们对0取反,就相当于一个巨大的数

第二个我们定义成引脚吧, 先设置成NULL

第三个,就是我们按键事件触发,调用的函数, 我们还没有写, 先欠着

struct soft_timer key_timer = {~0, NULL, key_timeout_func};
image-20241128161250984

11.之前我们发生按键操作的时候, 我们是修改led, 现在是来修改这个结构体里面的超时时间, 也就是我们之前说的, 按键来了(不管这个按键是抖动还是最后一次按键), 我们都进行刷新定时器计数(也就是结构体里面的超时时间)

我们首先传入此时的滴答计数器的数值 , 再传入10ms这个参数, 把超时时间设置成 10ms后,

后面我们定时器里面就每毫秒查询这个 超时时间和滴答定时器的数值, 如果到达,则代表按键平稳是最后一次按键, 如果没达到, 就会被下次按键刷新

image-20241128163408522
image-20241128163440611
mod_timer(&key_timer, 10);

12.我们按键中断函数里面, 已经设置好了超时时间, 那么检测是否超时的任务,就交给定时器中断了, 我们进入此定时器中断函数,进行设置检测是否超时函数

extern void check_timer(void);//声明一下这是外部函数
check_timer();	//每毫秒都调用检测是否超时, 从而实现按键抖动过滤

image-20241128165028123

13.下面我们来完善这些代码

mod_timer(&key_timer, 10);//修改超时时间

void mod_timer(struct soft_timer *pTimer, uint32_t timeout)
{
 pTimer->timeout = HAL_GetTick() + timeout;
}    

超时时间等于 10ms, 我们传入的是当前的按键结构体, 和设置的超时时长(10ms)

我们结构体的超时时间, 需要和定时器的滴答计数器对比,

所以我们的超时时间 = 当前Tick时间 + 超时时长

image-20241128170306372

也就是 pTimer->timeout = HAL_GetTick() + timeout;

image-20241128170112347

14.接下来我们写check_timer();

image-20241128170732504
void check_timer(void)
{
    if(key_timer.timeout <= HAL_GetTick())
    {
        key_timer.func(key_timer.args);
    }
}    

我们定时器, 一直在检测, 滴答定时器是否达到超时时间, 在按下按键的时候, 刚开始会产生抖动, 也就是我们还没达到超时时间, 就又触发按键中断, 那么按键中断里面的

mod_timer(&key_timer, 10); 函数,就会刷新超时时间, 从而使定时器无法达到超时时间

也就进入不了 if(key_timer.timeout <= HAL_GetTick())

只有最后一次按键触发,电平稳定,也就是达到超时时间, 才能够触发我们的按键处理事件

也就是 我们调用了结构体里面的按键函数事件 key_timer.func(key_timer.args);

15.定时器通过调用 check_timer() 函数检测uwTick是否达到超时时间, 如果达到, 就代表着是最后一次按键事件, 我们就调用结构体里的key_timer.func(key_timer.args);

我们在这里处理的就是 , 对按键计数值加一

一次按键中断, 累加一次, 并不会重复触发中断,从而实现了消抖

当然, 需要注意的一点是, 我们一次按键事件触发后, 在key处理函数中, 要记得清除超时时间, 因为定时器是一直工作的, 要么按键事件处理完后,我们就把key_timer.timeout设置成一个很大的值.定时器就不会再超时触发按键事件了, 直到下次按键再次按下, 重新设置超时时间.

image-20241128200123621

void key_timeout_func(void *args);

void key_timeout_func(void *args)
{
    g_key_cnt++;
    key_timer.timeout = ~0;
}    

16.烧录运行, 发现按键按下一次, 触发一次

测试成功的工程

1732801940901 (1)

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

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

相关文章

雨云服务器搭建docker且用docker部署kali服务器教程

雨云 - 新一代云服务提供商 介绍 大家好今天教大家如何使用雨云的服务器安装docker并且用docker搭建kali服务器&#xff0c;实现大家做黑客的梦。 性价比比较高的云服务器提供参考&#xff1a;雨云 - 新一代云服务提供商 优惠码&#xff1a;MzkxODI4 什么是kali Kali L…

SQL进阶——JOIN操作详解

在数据库设计中&#xff0c;数据通常存储在多个表中。为了从这些表中获取相关的信息&#xff0c;我们需要使用JOIN操作。JOIN操作允许我们通过某种关系&#xff08;如相同的列&#xff09;将多张表的数据结合起来。它是SQL中非常重要的操作&#xff0c;广泛应用于实际开发中。本…

分析JHTDB数据库的Channel5200数据集的数据(SciServer服务器)

代码来自https://github.com/idies/pyJHTDB/blob/master/examples/channel.ipynb %matplotlib inline import numpy as np import math import random import pyJHTDB import matplotlib.pyplot as plt import time as ttN 3 T pyJHTDB.dbinfo.channel5200[time][-1] time …

数据分析:彩票中奖号码分析与预测

预测双色球彩票的中奖号码是一个典型的随机事件&#xff0c;因为每个号码的出现概率是独立的&#xff0c;且历史数据并不能直接用于预测未来的开奖结果。然而&#xff0c;我们可以通过统计分析来了解号码的分布规律&#xff0c;从而提供一些可能的参考。 样例数据【点击下载】…

详细分析 npm run build 基本知识 | 不同环境不同命令

目录 前言1. 基本知识2. 构建逻辑 前言 关于部署服务器的知识推荐阅读&#xff1a;npm run build部署到云服务器中的Nginx&#xff08;图文配置&#xff09; 1. 基本知识 npm run 是 npm 的一个命令&#xff0c;用于运行 package.json 中定义的脚本&#xff0c;可以通过 “s…

Jpype调用jar包

需求描述 ​   公司要求使用python对接口做自动化测试&#xff0c;接口的实现是Java&#xff0c;部分接口需要做加解密&#xff0c;因此需要使用python来调用jar包来将明文加密成密文&#xff0c;然后通过http请求访问接口进行测试。 如何实现 1.安装Jpype ​   首先我…

Realtek网卡MAC刷新工具PG8168.exe Version:2.34.0.4使用说明

本刷新工具虽然文件名叫PG8168.EXE&#xff0c;但不是只有RTL8168可用&#xff0c;是这一个系列的产品都可以使用。实验证明RTL8111也可以使用。 用法&#xff1a; PG8168 [/h][/?][/b][/c HexOffsetHexValue][/d NICNumber][/l][/r][/w][/v] [/# NICNumber] [/nodeidHexNOD…

【Unity】Unity编辑器扩展,替代预制体上重复拖拽赋值

今天做游戏时有个需求&#xff0c;游戏中需要给不同年份不同月份的奖牌制定不一样的非规则形状&#xff0c;其中形状为100个像素组成的不同图形&#xff0c;并且按照从1-100路径一个个解锁&#xff0c;所以需要全部手动放置。但是手动放置好后&#xff0c;发现再一个个挂到脚本…

c语言的qsort函数理解与使用

介绍&#xff1a;qsort 函数是 C 标准库中用于排序的快速排序算法函数。它的用法非常灵活&#xff0c;可以对任意类型的元素进行排序&#xff0c;只要提供了比较函数即可。 qsort 函数原型及参数解释&#xff1a; void qsort ( void* base, //指向要排序的数组的首元素…

【力扣】125. 验证回文串

问题描述 思路详情 本题目的重点是对java中字符串的各种API用法的掌握理解 1.首先只保留字母和数字 1.1可以使用正则表达式1.2 Character.isLetterOrDight(ch) &#xff0c;但是这个只能单个字符判断2.将大写字母全部小写3.验证是否为回文串 代码 通过正则表达式 &#xff…

JavaEE---计算机是如何工作的?

1.了解冯诺依曼体系结构 2.CPU的核心概念,CPU的两个重要指标(核心数和频率) 3.CPU执行指令的流程(指令表,一条一条指令,取指令,解析指令,执行指令) 4.操作系统核心概念(管理硬件,给软件提供稳定的运行环境) 5.进程的概念(运行起来的程序和可执行文件的区别) 6.进程的管理(…

gitee:创建仓库,存入本地文件至仓库

一、git下载 git:下载与安装-CSDN博客https://blog.csdn.net/weixin_46001736/article/details/144107485?sharetypeblogdetail&sharerId144107485&sharereferPC&sharesourceweixin_46001736&spm1011.2480.3001.8118 二、创建仓库 1、主页面->右上角新增…

Flink 安装与入门:开启流式计算新时代

在当今大数据蓬勃发展的时代&#xff0c;数据处理的时效性愈发关键。传统基于先存储再批量处理的数据方式&#xff0c;在面对诸如网站实时监控、异常日志即时分析等场景时&#xff0c;显得力不从心。随着 5G、物联网等技术的兴起&#xff0c;海量数据如潮水般涌来&#xff0c;且…

使用 Jina Embeddings v2 在 Elasticsearch 中进行后期分块

作者&#xff1a;来自 Elastic Gustavo Llermaly 在 Elasticsearch 中使用 Jina Embeddings v2 模型并探索长上下文嵌入模型的优缺点。 在本文中&#xff0c;我们将配置和使用 jina-embeddings-v2&#xff0c;这是第一个开源 8K 上下文长度嵌入模型&#xff0c;首先使用 semant…

XTuner 微调个人小助手认知 -- 书生大模型实训营第4期基础岛第五关

目录 基础任务 任务要求 算力要求 环境配置与数据准备 使用 conda 先构建一个 Python-3.10 的虚拟环境 安装 XTuner 验证安装 修改提供的数据 创建一个新的文件夹用于存储微调数据 ​编辑 创建修改脚本 执行脚本 查看数据 训练启动 复制模型 修改 Config 启动…

使用vcpkg自动链接tinyxml2时莫名链接其他库(例如boost)

使用vcpkg自动链接tinyxml2时莫名链接其他库&#xff08;例如boost&#xff09; vcpkg的自动链接功能非常方便&#xff0c;但在某些情况下会出现过度链接的问题。 链接错误症状 以tinyxml2为例&#xff0c;程序中调用tinyxml2的函数后&#xff0c;若vcpkg中同时存在opencv和…

06_数据类型

数据类型 数据类型分类 JavaScript 语言的每一个值,都属于某一种数据类型。JavaScript 的数据类型,共有六种。(ES6 又新增了第七种 Symbol 类型的值和第八种 BigInt类型,当前课程暂不涉及) 据类型分类 原始类型(基础类型) var age = 20, var name = 尚学堂"; var le…

GitLab 使用过程中常见问题及解决方案

开发人员常见问题及解决方案 合并请求被拒绝 原因&#xff1a;代码质量问题、安全漏洞或流水线失败。解决方案&#xff1a; 使用 Code Quality 工具检查代码质量。查看流水线日志&#xff0c;修复单元测试、编译错误或扫描问题。优化静态分析&#xff08;SAST&#xff09;结果&…

网络空间安全之一个WH的超前沿全栈技术深入学习之路(13-2)白帽必经之路——如何用Metasploit 渗透到她的心才不会让我释怀

欢迎各位彦祖与热巴畅游本人专栏与博客 你的三连是我最大的动力 以下图片仅代表专栏特色 [点击箭头指向的专栏名即可闪现] 专栏跑道一 ➡️网络空间安全——全栈前沿技术持续深入学习 专栏跑道二 ➡️ 24 Network Security -LJS ​ ​ ​ 专栏跑道三 ➡️ MYSQL REDIS Advan…

机器学习6_支持向量机_算法流程

最大化&#xff1a; 限制条件&#xff1a; &#xff08;1&#xff09; &#xff08;2&#xff09; 如何求解这个对偶问题&#xff0c;同时基于对偶问题给出支持向量机算法的统一流程。 (核函数) 只要知道核函数&#xff0c;就可以求个这个最优化的对偶问题。 求解了这个对偶…