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

目录

独立按键简介

按键抖动

模块接线

延时消抖

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)
相关推荐
枯无穷肉2 小时前
stm32制作CAN适配器4--WinUsb的使用
stm32·单片机·嵌入式硬件
星河梦瑾2 小时前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
不过四级不改名6772 小时前
基于HAL库的stm32的can收发实验
stm32·单片机·嵌入式硬件
嵌入式科普3 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
嵌入式大圣3 小时前
单片机UDP数据透传
单片机·嵌入式硬件·udp
云山工作室3 小时前
基于单片机的视力保护及身姿矫正器设计(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·毕设
嵌入式-老费3 小时前
基于海思soc的智能产品开发(mcu读保护的设置)
单片机·嵌入式硬件
Hejjon4 小时前
SpringBoot 整合 SQLite 数据库
笔记
长潇若雪4 小时前
《类和对象:基础原理全解析(上篇)》
开发语言·c++·经验分享·类和对象
qq_397562315 小时前
MPU6050 , 设置内部低通滤波器,对于输出数据的影响。(简单实验)
单片机