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

相关文章

8、运算符

1、运算符相关概念 运算符&#xff1a; 具有一定运算规则的符号 运算符分类&#xff1a; 按照功能分&#xff1a; 赋值运算符 算术运算符 关系运算符 逻辑运算符 其他运算符 位运算符 按照操作数分&#xff1a; 单目运算符 双目运算符 三目运算符 表达式 具有一定意义的式子&…

【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…

算法训练营day08(字符串01:反转字符串,反转字符串2,替换数字,反转字符串里的单词,右旋转字符串)

第四章 字符串part01今日任务 ● 344.反转字符串 ● 541. 反转字符串II ● 卡码网&#xff1a;54.替换数字 ● 151.翻转字符串里的单词 ● 卡码网&#xff1a;55.右旋转字符串详细布置 344.反转字符串 建议&#xff1a; 本题是字符串基础题目&#xff0c;就是考察 reverse 函数…

Java中三种常用布局方式

引言 在Java Swing和JavaFX中&#xff0c;布局管理器&#xff08;Layout Managers&#xff09;用于控制组件&#xff08;如按钮、文本框等&#xff09;在容器&#xff08;如窗口、面板等&#xff09;内的位置和大小。下面介绍Java Swing中常用的三种布局方式&#xff1a; 1. Fl…

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

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

Leetcode(快慢指针习题思路总结,持续更新。。。)

这种模式&#xff0c;有一个非常出门的名字&#xff0c;叫龟兔赛跑。这种算法的两个指针的在数组上&#xff08;或是链表上&#xff0c;序列上&#xff09;的移动速度不一样。快的一个指针肯定会追上慢的一个&#xff08;可以想象成跑道上面跑得快的人套圈跑得慢的人&#xff0…

基于时间维度优化“开源 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. 示例代…

二刷代码随想录第15天

513. 找树左下角的值 找到深度最大的点&#xff0c;遍历方式左边节点在右边节点前面&#xff0c;找到就返回&#xff0c;一定就是最左下角的值了 class Solution { public:int max_depth -1;int result 0;int findBottomLeftValue(TreeNode* root) {traversal(root, 0);ret…

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的值&#…

Python plotly库介绍

一、引言 在数据可视化领域&#xff0c;Python提供了众多强大的库。其中&#xff0c;plotly是一个功能强大、交互式的可视化库&#xff0c;可以创建各种类型的图表&#xff0c;包括线图、散点图、柱状图、饼图、3D图表等。它不仅提供了美观的可视化效果&#xff0c;还支持交互式…

中国高铁、中兴通讯和中国 装备制造业的发展中能分别获得哪些启示

题目 【2015 年国考省级以上第四题】阅读“给定资料 4”,谈谈你从中国高铁、中兴通讯和中国装备制造业的发展中能分别获得哪些启示。(20 分) 要求&#xff1a;(1)紧扣材料,重点突出&#xff1b;(2)观点明确,表述有条理&#xff1b;(3)不超过 500 字。 材料 材料4&#xff1a…

Java全栈开发:宠物医院管理系统项目实战

Java全栈开发:宠物医院管理系统项目实战 项目介绍 本文将介绍一个基于Spring Boot + Vue.js的宠物医院管理系统的开发过程。该系统主要用于帮助宠物医院管理日常运营,包括患者管理、预约挂号、处方开具等功能。 技术栈 后端技术 Spring Boot 2.7.xSpring SecurityMyBatis…

BERT的中文问答系统36-2

为了使聊天机器人在生成答案时不依赖于特定的训练数据集&#xff0c;我引入其他方法来生成答案。例如&#xff0c;可以使用预训练的语言模型&#xff08;如BERT&#xff09;直接生成答案&#xff0c;或者使用搜索引擎来获取答案。以下BERT的中文问答系统36-1改进后的代码 1.引入…

升级智享 AI 直播三代:领航原生直播驶向自动化运营新航道

在瞬息万变的数字商业世界&#xff0c;直播行业恰似一艘破浪前行的巨轮&#xff0c;原生直播作为初始 “航船”&#xff0c;在历经风雨后&#xff0c;终于迎来智享 AI 直播三代这股强劲 “东风”&#xff0c;校准航向&#xff0c;开启自动化运营的全新航道&#xff0c;驶向一片…

鸿蒙多线程应用-taskPool

并发模型 并发模型是用来实现不同应用场景中并发任务的编程模型&#xff0c;常见的并发模型分为基于内存共享的并发模型和基于消息通信的并发模型。 Actor并发模型作为基于消息通信并发模型的典型代表&#xff0c;不需要开发者去面对锁带来的一系列复杂偶发的问题&#xff0c;同…

JavaScript实用工具lodash库

Lodash中文文档: Lodash 简介 | Lodash中文文档 | Lodash中文网 Lodash是一个功能强大、易于使用的JavaScript实用工具库&#xff0c;它提供了丰富的函数和工具&#xff0c;能够方便地处理集合、字符串、数值、函数等多种数据类型。通过使用Lodash&#xff0c;开发者可以大幅…

数据结构-最短路径问题

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