韦东山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)

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

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

相关文章

【Linux网络编程】第二弹---Socket编程入门指南:从IP、端口号到传输层协议及编程接口全解析

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【Linux网络编程】 目录 1、Socket 编程预备 1.1、理解源 IP 和目的 IP 1.2、认识端口号 1.2.1、端口号范围划分 1.2.2、理解 &q…

如何借助AI生成PPT,让创作轻松又高效

PPT是现代职场中不可或缺的表达工具&#xff0c;但同时也可能是令人抓狂的时间杀手。几页幻灯片的制作&#xff0c;常常需要花费数小时调整字体、配色与排版。AI的飞速发展为我们带来了革新——AI生成PPT的技术不仅让制作流程大大简化&#xff0c;还重新定义了效率与创意的关系…

基于时间维度优化“开源 AI 智能名片 S2B2C 商城小程序”运营策略:提升触达与转化效能

摘要&#xff1a; 随着数字化商业生态的蓬勃发展&#xff0c;“开源 AI 智能名片 S2B2C 商城小程序”融合前沿技术与创新商业模式&#xff0c;为企业营销与业务拓展带来新机遇。本文聚焦于用户时间场景维度&#xff0c;深入剖析如何依据不同时段用户行为特征&#xff0c;精准适…

【消息序列】详解(8):探秘物联网中设备广播服务

目录 一、概述 1.1. 定义与特点 1.2. 工作原理 1.3. 应用场景 1.4. 技术优势 二、截断寻呼&#xff08;Truncated Page&#xff09;流程 2.1. 截断寻呼的流程 2.2. 示例代码 2.3. 注意事项 三、无连接外围广播过程 3.1. 设备 A 启动无连接外围设备广播 3.2. 示例代…

vue3的prop

- 父组件需要传多个值给子组件 把值放对象&#xff0c;通过v-bind传整个对象 父组件 <script setup> import BlogPost from ./BlogPost.vue import { reactive } from vue; // 要传给子组件的所有值&#xff0c;用reactive包了该对象后&#xff0c;父组件的值变了&#…

Ubuntu下的Doxygen+VScode实现C/C++接口文档自动生成

Ubuntu下的DoxygenVScode实现C/C接口文档自动生成 1、 Doxygen简介 Doxygen 是一个由 C 编写的、开源的、跨平台的文档生成系统。最初主要用于生成 C 库的 API 文档&#xff0c;但目前又添加了对 C、C#、Java、Python、Fortran、PHP 等语言的支持。其从源代码中提取注释&…

uniapp强制修改radio-group内单选组件的状态方法

在uniapp开发中&#xff0c;需要在radio-group内部切换时做判断&#xff0c;提醒客户是否要变换radio的值&#xff0c;但是大家知道radio是单选组件&#xff0c;往往你点击后&#xff0c;是不能再修改状态的&#xff0c;就算你在点击后做判断&#xff0c;修改current的值&#…

数据结构-最短路径问题

一.问题分类 二.无权图单源最短路算法 dist[]数组记录的是个个顶点到源点的距离这个数组的下标表示顶点 源点到自己的距离是0,dist[s]0 path[]数组记录的是这个顶点的前驱&#xff0c;可以同过这个数组找到源点到个个顶点的距离 代码如下 void Unweighted(MGraph Graph, Ver…

Vue.js 实现用户注册功能

在本篇博客中&#xff0c;我们将通过一个简单的例子来展示如何使用 Vue.js 来实现一个用户注册功能。我们将创建一个包含用户名、邮箱和密码输入的表单&#xff0c;并在用户点击“创建账号”按钮时进行简单的验证。 完整代码 <!DOCTYPE html> <html lang"en&q…

【Java 学习】面向程序的三大特性:封装、继承、多态

引言 1. 封装1.1 什么是封装呢&#xff1f;1.2 访问限定符1.3 使用封装 2. 继承2.1 为什么要有继承&#xff1f;2.2 继承的概念2.3 继承的语法2.4 访问父类成员2.4.1 子类中访问父类成员的变量2.4.2 访问父类的成员方法 2.5 super关键字2.6 子类的构造方法 3. 多态3.1 多态的概…

impala入门与实践

1.impala基本介绍 impala是cloudera提供的一款高效率的sql查询工具&#xff0c;提供实时的查询效果&#xff0c;官方测试性能比hive快10到100倍&#xff0c;其sql查询比sparkSQL还要更加快速&#xff0c;号称是当前大数据领域最快的查询sql工具。impala是参照谷歌的新三篇论文…

结构方程模型(SEM)入门到精通:lavaan VS piecewiseSEM、全局估计/局域估计;潜变量分析、复合变量分析、贝叶斯SEM在生态学领域应用

目录 第一章 夯实基础 R/Rstudio简介及入门 第二章 结构方程模型&#xff08;SEM&#xff09;介绍 第三章 R语言SEM分析入门&#xff1a;lavaan VS piecewiseSEM 第四章 SEM全局估计&#xff08;lavaan&#xff09;在生态学领域高阶应用 第五章 SEM潜变量分析在生态学领域…

小米PC电脑手机互联互通,小米妙享,小米电脑管家,老款小米笔记本怎么使用,其他品牌笔记本怎么使用,一分钟教会你

说在前面 之前我们体验过妙享中心&#xff0c;里面就有互联互通的全部能力&#xff0c;现在有了小米电脑管家&#xff0c;老款的笔记本竟然用不了&#xff0c;也可以理解&#xff0c;毕竟老款笔记本做系统研发的时候没有预留适配的文件补丁&#xff0c;至于其他品牌的winPC小米…

python爬虫案例——猫眼电影数据抓取之字体解密,多套字体文件解密方法(20)

文章目录 1、任务目标2、网站分析3、代码编写1、任务目标 目标网站:猫眼电影(https://www.maoyan.com/films?showType=2) 要求:抓取该网站下,所有即将上映电影的预约人数,保证能够获取到实时更新的内容;如下: 2、网站分析 进入目标网站,打开开发者模式,经过分析,我…

一分钟食用前端测试框架Jest

安装 其实食用Jest是很简单的,我们只需要安装Jest即可 npm install --save-dev jestyarn add --dev jestpnpm add --save-dev jest ESmodule 本身来说,Jest是不支持Esmodule的,他支持CommonJS,我们需要Babel改一下 npm i --save-dev babel-jest babel/core babel/preset-env …

从 App Search 到 Elasticsearch — 挖掘搜索的未来

作者&#xff1a;来自 Elastic Nick Chow App Search 将在 9.0 版本中停用&#xff0c;但 Elasticsearch 拥有你构建强大的 AI 搜索体验所需的一切。以下是你需要了解的内容。 生成式人工智能的最新进展正在改变用户行为&#xff0c;激励开发人员创造更具活力、更直观、更引人入…

若依框架部署在网站一个子目录下(/admin)问题(

部署在子目录下首先修改vue.config.js文件&#xff1a; 问题一&#xff1a;登陆之后跳转到了404页面问题&#xff0c;解决办法如下&#xff1a; src/router/index.js 把404页面直接变成了首页&#xff08;大佬有啥优雅的解决办法求告知&#xff09; 问题二&#xff1a;退出登录…

【贪心算法第六弹——334.递增的三元子序列(easy)】

目录 1.题目解析 题目来源 测试用例 2.算法原理 3.实战代码 代码解析 本题属于最长递增子序列的简化版本&#xff0c;只需要判断能不能组成三位的递增子序列即可&#xff0c;建议先去看博主的另一篇博客可以更好的理解本篇博客&#xff1a;300.最长递增子序列 1.题目解析…

《TCP/IP网络编程》学习笔记 | Chapter 16:关于 I/O 流分离的其他内容

《TCP/IP网络编程》学习笔记 | Chapter 16&#xff1a;关于 I/O 流分离的其他内容 《TCP/IP网络编程》学习笔记 | Chapter 16&#xff1a;关于 I/O 流分离的其他内容分离 I/O 流2 次 I/O 流分离分离「流」的好处「流」分离带来的 EOF 问题 文件描述符的的复制和半关闭终止「流」…

LeetCode数组题

参考链接 代码随想录 讲解视频链接 数组题 1、(两数之和)给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。你可以假设每种输入只会对应一个答案&#xff0c;并且你不能使用…