独立按键单击检测(延时消抖+定时器扫描)

目录

独立按键简介

按键抖动

模块接线

延时消抖

Key.h

Key.c

定时器扫描按键代码

Key.h

Key.c

main.c

思考

MultiButton按键驱动


独立按键简介

​ 轻触按键相当于一种电子开关,按下时开关接通,松开时开关断开,实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通与断开。 ​

按键抖动

由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴随有一连串的抖动

当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动。由于单片机检测 IO口速度非常快,超过弹片抖动的频率,所以在检测按键状态时。从而对按键的判断产生一些误操作(比如按一下会产生按多下的效果),因此必须要消除抖动才能正常使用按键。要消除按键抖动的影响。抖动时间的长短由按键的机械特性决定的,一般为5ms 到10ms。

硬件消抖

本节不涉及

软件消抖

延时消抖

定时器消抖

模块接线

|--------|-------------------|
| 按键 | STM32F103C8T6 |
| KEY1 | PB11 |
| KEY2 | PB1 |

另一端接入GND

延时消抖

按键松手后判定

Key.h

cpp 复制代码
#ifndef       _KEY_H_
#define		  _KEY_H_

void  Key_Init(void);
uint8_t Key_GetNum(void);

#endif

Key.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Key.h"
#include "Delay.h"


#define KEY1   GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)
#define KEY2   GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)

void  Key_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度,输入无用
 
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

uint8_t Key_GetNum(void)
{
    uint8_t KeyNum = 0;
    if(KEY1 == RESET)
    {
        Delay_ms(20);
        while(KEY1 == RESET)
        Delay_ms(20);
        KeyNum = 1;
    }
	    if(KEY2 == RESET)
    {
        Delay_ms(20);
        while(KEY2 == RESET)
        Delay_ms(20);
        KeyNum = 2;
    }

    return KeyNum;
}

单按松手前按键触发+连按按键扫描函数来源于正点原子:

cpp 复制代码
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,KEY0按下
//2,KEY1按下
//3,KEY3按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY_UP!!
u8 KEY_Scan(u8 mode)
{	 
	static u8 key_up=1;//按键按松开标志
	if(mode)key_up=1;  //支持连按		  
	if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
	{
		delay_ms(10);//去抖动 
		key_up=0;
		if(KEY0==0)return KEY0_PRES;
		else if(KEY1==0)return KEY1_PRES;
		else if(WK_UP==1)return WKUP_PRES;
	}else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1; 	    
 	return 0;// 无按键按下
}

连按: key_up每次进入函数都为1,相当于把有关key_up的判断与赋值删除,只要有按键按下,消抖后再次判断是否按下,不等松手就返回对应按键值。

cpp 复制代码
//拆分代码
u8 KEY_Scan(void)
{	 
			  
	if(KEY0==0||KEY1==0||WK_UP==1)
	{
		delay_ms(10);//去抖动 
		key_up=0;
		if(KEY0==0)return KEY0_PRES;
		else if(KEY1==0)return KEY1_PRES;
		else if(WK_UP==1)return WKUP_PRES;
	}; 	    
 	return 0;// 无按键按下
}

只单击不连按: key_up为上一次检测按键值,上一次没有按,这一次按了才会返回对应按键值,

cpp 复制代码
u8 KEY_Scan(void)
{	 
	static u8 key_up=1;//按键按松开标志		  
	if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
	{
		delay_ms(10);//去抖动 
		key_up=0;
		if(KEY0==0)return KEY0_PRES;
		else if(KEY1==0)return KEY1_PRES;
		else if(WK_UP==1)return WKUP_PRES;
	}else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1; 	    
 	return 0;// 无按键按下
}

作为这种很简单的演示程序,我们可 以这样来写,但是实际做项目开发的时候,程序量往往很大,各种状态值也很多,while(1) 这个主循环要不停的扫描各种状态值是否有发生变化,及时的进行任务调度,如果程序中间 加了这种 delay 延时操作后,很可能某一事件发生了,但是我们程序还在进行 delay 延时操作 中,当这个事件发生完了,程序还在 delay 操作中,当我们 delay 完事再去检查的时候,已经晚了,已经检测不到那个事件了。为了避免这种情况的发生,我们要尽量缩短 while(1)循环 一次所用的时间,而需要进行长时间延时的操作,必须想其它的办法来处理。 那么消抖操作所需要的延时该怎么处理呢?

定时器扫描按键代码

为了检测按键按下和松开,必须记录按键当前状态和上一次状态。如果当前状态为高电平,上一次状态为低电平,则检测到按键按下后松手;如果当前状态为低电平,上一次为高电平,则检测到按键按下。由于短按按下一次只需返回一次按键值,因此只需检测按键松开动作,即上一次状态低电平,当前状态高电平。

定时1ms(根据项目和按键抖动情况更改,不要频繁进入中断)进入中断,中断服务函数里扫描按键上一次状态低电平,当前状态高电平就认为按下了。主函数获取按键值后就清除按键按下标志位,等待下一次中断扫描更新按键按下标志位。

单击不支持连按

Key.h

cpp 复制代码
#ifndef       _KEY_H_
#define		  _KEY_H_

void  Key_Init(void);

uint8_t Key_GetNum(void);
void Key_Scan(void);

#endif

Key.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Key.h"

uint8_t Key_KeyNum;

#define KEY1   GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)
#define KEY2   GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)
void  Key_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度,输入无用
 
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

uint8_t Key_GetNum(void)
{
    uint8_t temp;
    temp = Key_KeyNum;
    Key_KeyNum = 0;
    return temp;
}

static uint8_t Key_Get(void)
{
    uint8_t Key_Check = 0;
    if(KEY1 == RESET){ Key_Check = 1;}
    if(KEY2 == RESET){ Key_Check = 2;}
    return  Key_Check;
}

void Key_Scan(void)
{
    static uint8_t LastNum, KeyNum;
    LastNum = KeyNum;
    KeyNum = Key_Get();

    if(LastNum == 1 && KeyNum == 0)
    {
        Key_KeyNum = 1;
    }
    if(LastNum == 2 && KeyNum == 0)
    {
        Key_KeyNum = 2;
    }
}

main.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Key.h"
#include "Timer.h"

int8_t KeyNum, Num;
int main(void)
{
	OLED_Init();
	Key_Init();
	Timer_Init();

	OLED_ShowString(1, 1, "Num:");

	while (1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			Num++;
		}
		if(KeyNum == 2)
		{
			Num--;
		}
		OLED_ShowSignedNum(1, 5, Num,3);
	}
}

void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		Key_Scan();
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

思考

由于定时器每1ms进入一次中断,对于具有IIC和One-Wire通信等具有严格时序要求的项目来说,容易使通信被干扰。 在通信前关闭中断,在通信完成后打开中断。根据此种方法,可以在通过中断提高MCU工作效率的同时,使中断不干扰具有严格时序的通信(如I2C,One-Wire,SPI等)

MultiButton按键驱动

事件 说明
PRESS_DOWN 按键按下,每次按下都触发
PRESS_UP 按键弹起,每次松开都触发
PRESS_REPEAT 重复按下触发,变量repeat计数连击次数
SINGLE_CLICK 单击按键事件
DOUBLE_CLICK 双击按键事件
LONG_PRESS_START 达到长按时间阈值时触发一次
LONG_PRESS_HOLD 长按期间一直触发

开源链接

0x1abin/MultiButton: Button driver for embedded system (github.com)

使用教程(包含一个回调函数检测多个按键方法):

MultiButton | 一个小巧简单易用的事件驱动型按键驱动模块 - 知乎 (zhihu.com)

cpp 复制代码
//头文件中可根据需要修改参数
//According to your need to modify the constants.
#define TICKS_INTERVAL    5	//ms
#define DEBOUNCE_TICKS    3	//MAX 7 (0 ~ 7)
#define SHORT_TICKS       (300 /TICKS_INTERVAL)
#define LONG_TICKS        (1000 /TICKS_INTERVAL)
相关推荐
爱米的前端小笔记3 分钟前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘
lantiandianzi1 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
文弱书生6561 小时前
输出比较简介
stm32
哔哥哔特商务网1 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式1 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件
电子科技圈2 小时前
IAR与鸿轩科技共同推进汽车未来
科技·嵌入式硬件·mcu·汽车
寒笙LED2 小时前
C++详细笔记(六)string库
开发语言·c++·笔记
岳不谢3 小时前
VPN技术-VPN简介学习笔记
网络·笔记·学习·华为
东芝、铠侠总代136100683933 小时前
浅谈TLP184小型平面光耦
单片机·嵌入式硬件·物联网·平面
lantiandianzi3 小时前
基于单片机中医药柜管理系统的设计
单片机·嵌入式硬件