前言
本文以初学者角度切入,详细剖析按键检测原理,实现按键短按、短按抬起、首次长按、持续长按次数、长按抬起功能;
目录
前言
波形图分析
抖动原因
为什么要消抖
如何消抖
原理图分析
程序设计思路
代码实践
按键配置
按键检测
实验结果
留下反思
波形图分析
如图为按键按下→释放过程的波形图
可以从图中看到,实际波形在按下与释放时都有一段杂乱的波形,期间存在着多次高电平与低电平,这就是抖动。
抖动原因
机械原因:由于按键内部构造常为弹簧、金属弹片等接触器件,当按键按下或释放时,这些器件的反弹、震动等原因导致瞬时的接触和断开,从而产生抖动;
电气原因:电路噪声、环境干扰、电容充放电等因素;
为什么要消抖
为了确保按键信号的稳定性和可靠性。由于上述机械原因与电气原因,软件在读取到一个有效电平时,是无法百分百分辨出是真实的按键按下/释放还是由于抖动、干扰等造成的。如果不进行消抖会导致在一次按键按下释放过程中出现判断为多次的按键短按与释放,这是对系统功能有破坏性与不可靠性的。
如何消抖
硬件层面:添加合适电容消除机械原因(注意电容过大会影响按键响应时间,过小则作用不佳)。添加上拉电阻抵抗电气原因;
软件层面:在一段时间内,对引脚电平进行多次判断,如果每次都满足了触发条件则认为是有效的;
原理图分析
图一为最简单的按键原理图
优点:简单,降低了成本和电路设计的复杂性;
缺点:没有硬件消抖,且抗干扰能力差;
图二为加了上拉电阻的按键原理图
优点:与图一方式比较,提高了抗干扰能力;
缺点:没有硬件消抖;
图三为加了上拉电阻与电容的按键原理图
优点:与图一二方式比较,有了抗干扰能力与硬件消抖;
缺点:提高了成本和电路设计的复杂性;
程序设计思路
当检测电平有按键按下时,等待一段时间(即抖动时间)之后,再次检测电平是否按下,如果仍然检测为有按键按下则认为有效。检测释放也同理,下图为大致流程图,省略了释放的消抖以便阅读。
代码实践
详细代码工程链接
按键配置
#include "stm32f10x.h"
#define KEY_GPIO_PORT GPIOA /* 按键引脚端口号 */
#define KEY_GPIO_PIN GPIO_Pin_0 /* 按键引脚编号 */
#define KEY_GPIO_CLK_ENABLE() RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE) /* GPIO时钟使能 */
/*按键初始化*/
void Key_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*GPIO时钟使能*/
KEY_GPIO_CLK_ENABLE();
/*GPIO配置*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* 上拉输入 */
GPIO_InitStructure.GPIO_Pin = KEY_GPIO_PIN;
GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStructure);
}
详细代码工程链接
按键检测
#include "stdio.h"
/* 按键检测 */
void KeyCheck(void)
{
unsigned char tempDownCount = 0; /* 按下计数 */
unsigned char tempUpCount = 0; /* 释放计数 */
unsigned char tempLongFlag = 0; /* 长按标志 */
unsigned int tempContinueCount = 0; /* 持续长按次数 */
if(0 == GPIO_ReadInputDataBit(KEY_GPIO_PORT, KEY_GPIO_PIN)) /* 读取按键电平有效 */
{
Delay_ms(20); /* 按下阶段消抖(合理范围10~50ms) */
if(0 == GPIO_ReadInputDataBit(KEY_GPIO_PORT, KEY_GPIO_PIN)) /* 再次读取按键电平仍然有效 */
{
printf("按键按下\n");
//按键短按需要干嘛。。。
/*检测长按*/
while(1)
{
if(0 == GPIO_ReadInputDataBit(KEY_GPIO_PORT, KEY_GPIO_PIN))
{
tempUpCount = 0; /* 复位释放计数 */
if(++tempDownCount >= 10)
{
tempDownCount = 0; /* 复位按下计数 */
if(0 == tempLongFlag)
{
tempLongFlag = 1;
tempContinueCount = 1;
printf("按键首次长按\n");
//按键首次长按需要干嘛。。。
}
else
{
++tempContinueCount;
printf("按键持续长按次数【%d】\n", tempContinueCount);
//按键多次持续长按需要干嘛。。。
}
}
}
/*检测释放*/
if(1 == GPIO_ReadInputDataBit(KEY_GPIO_PORT, KEY_GPIO_PIN))
{
if(++tempUpCount >= 2) /* 释放阶段消抖 */
{
if(0 == tempLongFlag)
{
printf("按键短按抬起\n\n");
//按键短按抬起需要干嘛。。。
}
else
{
printf("按键长按抬起\n\n");
//按键长按抬起需要干嘛。。。
}
return;
}
}
Delay_ms(20); /* 时间间隔 */
}
}
}
}
int main(void)
{
Delay_Init(); /* 滴答延时初始化 */
Usart1_Init(9600); /* 串口初始化 */
Key_Init(); /* 按键初始化 */
printf("按键检测测试\n");
while(1)
{
KeyCheck(); /* 按键检测 */
}
}
详细代码工程链接
实验结果
短按测试
长按测试
留下反思
细心的同学应该能发现,虽然按键检测的功能是实现了,但是当后续其他功能的加入时,如果按键一直按住不松是不是就一直在KeyCheck()里的while(1)里不出来了?这时其他功能是不是就跑不了了?那么该怎么办呢?
敬请关注“我的按键驱动编年史”专栏,里面有你想要的答案。
分享先到这里,希望能给大家带来启发与帮助。如果对内容存在疑问或想法,欢迎在评论区留言,我会积极回复大家的问题。在我的“我的按键驱动编年史”专栏中,还有一些关于按键驱动不同写法的教程,欢迎一起探讨、一起学习。
终版驱动gitee仓库:文章来源:https://www.toymoban.com/news/detail-794471.html
XxxSwitchScan_Driver: XxxSwitchScan_Driver可以简单的看作为一个C语言的按键驱动,使用简单、灵活且解耦,以面向对象思想结合状态机编写,同时适用于裸机与操作系统。最终实现响应事件有:短按/短按抬起/长按/持续长按/长按抬起/连击/单边沿触发。一开始仅为了实现按键驱动。后面把按键结合如高低电平的传感器、开关量的限位等进一步抽象为开关量的输入设备。由此我常会把项目中的开关量的输入设备通过该驱动统一管理。https://gitee.com/wx_372d4eb42f/xxx-switch-scan_-driver文章来源地址https://www.toymoban.com/news/detail-794471.html
到了这里,关于初学者思路-实现独立按键检测(以STM32为例)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!