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

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

按键消抖的原因:

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)

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

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

相关文章

Web前端学习_CSS盒子模型

content padding border margin <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>CSS盒子模型</title><style></style> </head> <body> <div class"demo&quo…

将自定义 AWS S3 快照存储库连接到 Elastic Cloud

作者&#xff1a;来自 Elastic Annie Hansen, Stef Nestor 在本博客中&#xff0c;我们将介绍如何通过 Elasticsearch 的快照将我们已提交的集群数据备份到 AWS S3 存储桶中。在 Elastic Cloud&#xff08;企业版&#xff09;中&#xff0c;Elastic 在其 found-snapshots 存储…

部署 Prometheus

实验环境 IP地址服务192.168.88.10Prometheus服务端, Consul, Grafana, Node-Exporter192.168.88.77MySQL, Node-Exporter192.168.88.30Nginx&#xff0c;Node-Exporter 一、Prometheus Server 端安装和相关配置 【Prometheus1.sh】 &#xff08;1&#xff09;上传 prometh…

第29天 MCU入门

目录 MCU介绍 MCU的组成与作用 电子产品项目开发流程 硬件开发流程 常用元器件初步了解 硬件原理图与PCB板 常见电源符号和名称 电阻 电阻的分类 贴片电阻的封装说明&#xff1a; 色环电阻的计算 贴片电阻阻值计算 上拉电阻与下拉电阻 电容 电容的读数 二极管 LED 灯电路 钳位作…

汽车免拆诊断案例 | 2017款捷豹F-PACE车发动机偶尔怠速不稳

故障现象  一辆2017款捷豹F-PACE车&#xff0c;搭载2.0 L GTDi发动机&#xff0c;累计行驶里程约为16万km。车主反映&#xff0c;车辆组合仪表上发动机故障灯点亮&#xff08;图1&#xff09;&#xff0c;且发动机偶尔怠速不稳。 图1 发动机故障灯点亮 故障诊断 接车后试车…

Cobalt Strike 4.8 用户指南-第十一节 C2扩展

11.1、概述 Beacon 的 HTTP 指标由 Malleable Command and Control &#xff08;Malleable C2&#xff09; 配置文件控制。Malleable C2 配置文件是一个简单的程序&#xff0c;它指定如何转换数据并将其存储在事务中。转换和存储数据的同一程序&#xff08;向后解释&#xff0…

上传镜像docker hub登不上和docker desktop的etx4.vhdx占用空间很大等解决办法

平时使用docker一般都在Linux服务器上&#xff0c;但这次需要将镜像上传到docker hub上&#xff0c;但是服务器上一直无法登录本人的账号&#xff0c;&#xff08;这里的问题应该docker 网络配置中没有开代理的问题&#xff0c;因服务器上有其他用户使用&#xff0c;不可能直接…

[BUUCTF]ciscn_2019_n_8

题目 解题 先连接看看有什么信息 返回whats your name 没有其他信息 看程序基本信息 32位 拉到ida32查看 打开发现如下 由上述代码可知&#xff0c;需要将数组0-12装满&#xff0c;装什么都可以&#xff0c;将var[13]17才能执行system("/bin/sh") payload fro…

orangepi _全志H616

1. 全志H616简介 1.1. 为什么学&#xff1a; 学习目标依然是Linux系统&#xff0c;平台是ARM架构 蜂巢快递柜&#xff0c;配送机器人&#xff0c;这些应用场景用C51,STM32单片机无法实现 &#xff08;UI界面&#xff0c;提高用户的体验感&#xff09;第三方介入库的局限性&a…

信息收集之网站架构类型和目录扫描(一)

目录 前言 1.查看域名的基本信息 2.常见的网站架构类型 3.目录扫描 前言 最近也是到了期末周了,比较空闲,把信息收集的一些方式和思路简单总结一下,顺便学习一些新的工具和一些未接触到的知识面. 1.查看域名的基本信息 新学了一个工具,kali中的whois也可以进行查看,当然在…

消息中间件用途介绍

1. 解耦&#xff08;Decoupling&#xff09;&#xff1a; • 消息中间件能够将消息的生产者&#xff08;Producer&#xff09;和消费者&#xff08;Consumer&#xff09;分离开来&#xff0c;使它们不必直接相互依赖。这种设计降低了系统的耦合度&#xff0c;提升了系统的可扩展…

【Maven】Nexus私服

6. Maven的私服 6.1 什么是私服 Maven 私服是一种特殊的远程仓库&#xff0c;它是架设在局域网内的仓库服务&#xff0c;用来代理位于外部的远程仓库&#xff08;中央仓库、其他远程公共仓库&#xff09;。一些无法从外部仓库下载到的构件&#xff0c;如项目组其他人员开发的…

学习ASP.NET Core的身份认证(基于Cookie的身份认证3)

用户通过验证后调用HttpContext.SignInAsync函数将用户的身份信息保存在认证Cookie中,以便后续的请求可以验证用户的身份,该函数原型如下所示&#xff0c;其中properties参数的主要属性已在前篇文章中学习&#xff0c;本文学习scheme和principal的意义及用法。 public static …

【mac】终端左边太长处理,自定义显示名称(terminal路径显示特别长)

1、打开终端 2、步骤 &#xff08;1&#xff09;修改~/.zshrc文件 nano ~/.zshrc&#xff08;2&#xff09;添加或修改PS1&#xff0c;我是自定义了名字为“macminiPro” export PS1"macminiPro$ "&#xff08;3&#xff09;使用 nano: Ctrl o &#xff08;字母…

uniapp关闭sourceMap的生成,提高编译、生产打包速度

警告信息&#xff1a;[警告⚠] packageF\components\mpvue-echarts\echarts.min.js 文件体积超过 500KB&#xff0c;已跳过压缩以及 ES6 转 ES5 的处理&#xff0c;手机端使用过大的js库影响性能。 遇到问题&#xff1a;由于微信小程序引入了mpvue-echarts\echarts.min.js&…

PyTorch 模型转换为 ONNX 格式

PyTorch 模型转换为 ONNX 格式 在深度学习领域&#xff0c;模型的可移植性和可解释性是非常重要的。本文将介绍如何使用 PyTorch 训练一个简单的卷积神经网络&#xff08;CNN&#xff09;来分类 MNIST 数据集&#xff0c;并将训练好的模型转换为 ONNX 格式。我们还将讨论 PTH …

Three.js 和其他 WebGL 库 对比

在WebGL开发中&#xff0c;Three.js是一个非常流行的库&#xff0c;它简化了3D图形的创建和渲染过程。然而&#xff0c;市场上还有许多其他的WebGL库&#xff0c;如 Babylon.js、PlayCanvas、PIXI.js 和 Cesium&#xff0c;它们也有各自的特点和优势。本文将对Three.js 与这些常…

通过 JNI 实现 Java 与 Rust 的 Channel 消息传递

做纯粹的自己。“你要搞清楚自己人生的剧本——不是父母的续集&#xff0c;不是子女的前传&#xff0c;更不是朋友的外篇。对待生命你不妨再大胆一点&#xff0c;因为你好歹要失去它。如果这世上真有奇迹&#xff0c;那只是努力的另一个名字”。 一、crossbeam_channel 参考 cr…

摆脱复杂配置!使用MusicGPT部署你的私人AI音乐生成环境

文章目录 前言1. 本地部署2. 使用方法介绍3. 内网穿透工具下载安装4. 配置公网地址5. 配置固定公网地址 前言 今天给大家分享一个超酷的技能&#xff1a;如何在你的Windows电脑上快速部署一款文字生成音乐的AI创作服务——MusicGPT&#xff0c;并且通过cpolar内网穿透工具&…

挑战用React封装100个组件【001】

项目地址 https://github.com/hismeyy/react-component-100 组件描述 组件适用于需要展示图文信息的场景&#xff0c;比如产品介绍、用户卡片或任何带有标题、描述和可选图片的内容展示 样式展示 代码展示 InfoCard.tsx import ./InfoCard.cssinterface InfoCardProps {ti…