【51单片机实验笔记】开关篇(一) 独立按键


前言

本节内容,我们学习一下物理按键结构控制方式按键是一种典型的输入元器件,认识了按键,我们就可以和之前的章节所学的元器件们联动起来了。


硬件介绍

在物理电路中,我们最熟知的一个词就是开关,那其实就是现实世界中按键(Key)理想模型

按键在我们日常生活中几乎无处不在:手机的音量键电脑键盘日光灯的开关电视机的电源按钮…


自锁与自复位

机械结构来说,按键分为自锁式自复位式。两者的应用场景并不相同。

  • 自锁式:按键按下未按下状态不同,对用户有引导作用。一般应用于没有明显的用户交互界面的场景,用户可以自行判断按键状态。但价格稍贵。

  • 自复位式:按键按下未按下状态相同,仅从按键本身并不能判断是否()被按下。一般应用于有明显的用户交互界面的场景,例如手机音量键,用户不用关注按键状态,只需观察屏幕反馈即可。

以上两种类型与按键电气特性无关,仅仅是为了介绍全面,多啰嗦几句。本文研究的是自复位式独立按键


按键响应事件

对于一个独立按键,它有两种状态:按下释放。这就意味着每次按键动作都有两个状态可以被观测,称为按下响应释放响应

在有些场景下,按键响应我们只希望它执行一次,例如一次点击就打开一个应用(而不是按一次就弹出来许多);在其他场景下,我们希望按住按键可以使响应连续执行,例如调节音量(否则从 0 按到 100 将相当痛苦)。这即是单次响应连续响应

还有一种情况,我希望在按下某个按键时不会受其他按键的干扰。这即为按键屏蔽问题。

因此,联系手机或键盘的按键操作,大致可以将按键事件分为以下几类:
在这里插入图片描述

  • 短按: 计算按下响应释放响应时间间隔 t,若小于设定的阈值,则视为短按事件
    1. 按下立刻执行(无论释放与否)
    2. 释放立刻执行(按下没有反应)
    3. 按下执行,释放也执行
  • 长按:大于设定的阈值,则视为长按事件
    • 单次执行:如手机关机
    • 连续执行:如调节音量
  • 双击:同个按键两次按下响应的时间间隔小于设定的阈值,则视为双击事件
    • 必是两个短按事件,如鼠标双击打开应用
  • 组合键:两个不同的按键按下响应的时间间隔小于设定的阈值,则视为组合键事件
    • 短按:如截屏
    • 长按:如强制重启

按键抖动

另外,在真实按键中,由于按键物理结构的限制,必存在机械振动,俗称按键抖动。如何防抖,什么时候防抖是关键所在。一般可分为硬件防抖软件防抖两种。

在这里插入图片描述

  • 硬件防抖:在稳定性要求高的场景,应当优先考虑。比软件防抖稳定,但会增加成本
    • 低通滤波电路:在按键两端并联电容,由于电容两端电压不能突变,必定存在一个电容充放电的时间常数,从而滤去抖动。
      在这里插入图片描述

    • RS触发器:利用RS触发器来吸收按键的抖动。一旦有键按下,触发器立即翻转,按键的抖动便不会再对输出产生影响,按键释放时也一样。

  • 软件防抖:一般的按键抖动时间5~10ms,而人不太可能以大于100Hz的频率去按键。所以通过延时可以消除抖动带来的影响,也不会过滤掉人的正常操作

按键抖动是一种不可消除机械振动形式,客观存在。其影响是,按一次可能会导致MCU执行多次抖动时间依赖于按键机械结构设计,越优质的结构材料抖动时间越短。需要强调的是,无论是硬件还是软件防抖都不能过滤人为的误触(时间量级不一样)。

为防止误触,一般将比较重要的功能以长按双击组合键的形式设置。

工业或是军事场景中,按键响应除了要满足高度实时的要求,还必须高度可靠最有效的方法即检测多次例如,在10ms内检测10次按键状态,当全部有效时,才视为按键按下,而普通的软件延时仅仅检测了2次,显然可靠性不及前者。定时器章节,我们将利用这种思想实现最佳按键检测


原理图分析

独立按键

观察独立按键,共有四个引脚,其中两组间距较短,而另两组间距较长间距长的两组引脚之间是连接在一起的而短间距引脚之间初始状态是断开的,当按键被按下时,四个引脚被接通,可视作一根导线
在这里插入图片描述
一般情况下,独立按键只需要接两个脚即可使用。其中一个引脚接地,另一个引脚由单片机IO口控制

在这里插入图片描述
开发板上独立按键的硬件原理图如上。


软件实现

注:本节暂不讨论长按和双击事件,在后续章节中通过定时器实现。

单键单次与连续响应

实验效果:四个独立按键,分别控制四个LED的开关,实现不同的按键效果数码管显示按键编号

  • 按键1按下LED6亮灭状态转变。按键1松开,不执行操作。(按下响应
  • 按键2按下,不执行操作。按键2松开LED7亮灭状态转变。(松开响应
  • 按键3按下LED8亮灭状态转变。按键3松开LED8亮灭状态转变。(按下松开均响应
  • 按键4按下数码管显示数值持续增加按键4松开数码管清零。(连续响应

delay.h

#ifndef _DELAY_H_
#define _DELAY_H_

#include <REGX52.H>


#define false 0
#define true 1

typedef unsigned char u8;
typedef unsigned int u16;

void delay_10us(u16);
void delay_ms(u16);

#endif

delay.c

#include "delay.h"
/** 
 **  @brief    通用函数
 **  @author   QIU
 **  @date     2023.08.23
 **/

/*-------------------------------------------------------------------*/

/**
 **  @brief   延时函数(10us)
 **  @param   t:0~65535,循环一次约10us
 **  @retval  无
 **/
void delay_10us(u16 t){
    
    
	while(t--);
}


/**
 **  @brief   延时函数(ms)
 **  @param   t:0~65535,单位ms
 **  @retval  无
 **/
void delay_ms(u16 t){
    
    
	while(t--){
    
    
		delay_10us(100);
	}
}

led.h

#ifndef _LED_H_
#define _LED_H_

#include "delay.h"

#define LED_PORT P2

void led_on(u8);
void led_turn(u8);
void led_stream(u16);
void led_run(u16);

#endif

led.c

#include "led.h"

/** 
 **  @brief    LED控制程序
 **  @author   QIU
 **  @date     2023.08.23
 **/

/*-------------------------------------------------------------------*/


/**
 **  @brief  指定某个LED亮
 **  @param  pos: 位置(1~8)
 **  @retval 无
 **/
void led_on(u8 pos){
    
    
	LED_PORT &= ~(0x01<<(pos-1));
}


/**
 **  @brief  指定某个LED灭
 **  @param  pos: 位置(1~8)
 **  @retval 无
 **/
void led_off(u8 pos){
    
    
	LED_PORT |= 0x01<<(pos-1);
}


/**
 **  @brief   指定位置LED翻转
 **  @param   pos:1—8
 **  @retval  无
 **/
void led_turn(u8 pos){
    
    
	u8 port;
	port = (LED_PORT>>(pos-1))&0x01;
	if(port){
    
    
		led_on(pos);
	}else{
    
    
		led_off(pos);
	}
}


/**
 **  @brief   LED流水灯
 **  @param   time 延时时间
 **  @retval  无
 **/
void led_stream(u16 time){
    
    
	u8 i;
	for(i=0;i<8;i++){
    
    
		led_on(i+1);
		delay_10us(time);
	}
	
	// 全部熄灭
	for(i=0;i<8;i++){
    
    
		led_off(i+1);
	}
}


/**
 **  @brief   LED跑马灯
 **  @param   time 延时时间
 **  @retval  无
 **/
void led_run(u16 time){
    
    
	u8 i;
	for(i=0;i<8;i++){
    
    
		led_on(i+1);
		delay_10us(time);
		led_off(i+1);
	}
}

smg.h

#ifndef _SMG_H_
#define _SMG_H_

#include "delay.h"

#define SMG_PORT P0
 
// 位选引脚,与38译码器相连
sbit A1 = P2^2;
sbit A2 = P2^3;
sbit A3 = P2^4;

void smg_showChar(u8,u8);
void smg_showString(u8*, u8);

#endif

smg.c

#include "smg.h"
/** 
 **  @brief    数码管功能封装
 **  @author   QIU
 **  @date     2023.08.31
 **/

/*-------------------------------------------------------------------*/


//共阴极数码管字形码编码
u8 code smgduan[] = {
    
    0x3f,0x06,0x5b,0x4f,0x66, //0 1 2 3 4
					 0x6d,0x7d,0x07,0x7f,0x6f, //5 6 7 8 9
					 0x77,0x7c,0x58,0x5e,0x79, //A b c d E
					 0x71,0x76,0x30,0x0e,0x38, //F H I J L
					 0x54,0x3f,0x73,0x67,0x50, //n o p q r
					 0x6d,0x3e,0x3e,0x6e};     //s U v y  

					 
/**
 **  @brief   指定第几个数码管点亮,38译码器控制位选(不对外声明)
 **  @param   pos:从左至右,数码管位置 1~8
 **  @retval  无
 **/
void select_38(u8 pos){
    
    
	u8 temp_pos = 8 - pos; // 0~7
	A1 = temp_pos % 2; //高位
	temp_pos /= 2;
	A2 = temp_pos % 2; 
	temp_pos /= 2;
	A3 = temp_pos % 2; //低位
}


/**
 **  @brief   解析数据并取得相应数码管字形码编码
 **  @param   dat:想要显示的字符
 **  @retval  对应字形码编码值
 **/
u8 parse_data(u8 dat){
    
    
	switch(dat){
    
    
		case 0:
		case 1:
		case 2:
		case 3:
		case 4:
		case 5:
		case 6:
		case 7:
		case 8:
		case 9:return smgduan[dat];
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':return smgduan[dat-'0'];
		case 'a':
		case 'A':return smgduan[10];
		case 'b':
		case 'B':return smgduan[11];
		case 'c':
		case 'C':return smgduan[12];
		case 'd':
		case 'D':return smgduan[13];
		case 'e':
		case 'E':return smgduan[14];
		case 'f':
		case 'F':return smgduan[15];
		case 'h':
		case 'H':return smgduan[16];
		case 'i':
		case 'I':return smgduan[17];
		case 'j':
		case 'J':return smgduan[18];
		case 'l':
		case 'L':return smgduan[19];
		case 'n':
		case 'N':return smgduan[20];
		case 'o':
		case 'O':return smgduan[21];
		case 'p':
		case 'P':return smgduan[22];
		case 'q':
		case 'Q':return smgduan[23];
		case 'r':
		case 'R':return smgduan[24];
		case 's':
		case 'S':return smgduan[25];
		case 'u':
		case 'U':return smgduan[26];
		case 'v':
		case 'V':return smgduan[27];
		case 'y':
		case 'Y':return smgduan[28];
		default:return 0x00; //不显示
	}
}



/**
 **  @brief   显示一个字符(1字节)
 **  @param   dat:字符数据
 **  @param   pos:显示位置 1~8
 **  @retval  无
 **/
void smg_showChar(u8 dat, u8 pos){
    
    
	// 解析点亮哪一个数码管
	select_38(pos);
	// 解析数据
	SMG_PORT = parse_data(dat);
}



/**
 **  @brief   显示字符串(动态显示)
 **  @param   dat[]:字符串
 **  @param   pos:显示位置 1~8
 **  @retval  无
 **/
void smg_showString(u8 dat[], u8 pos){
    
    
	u8 i;
	// 超出部分直接截断
	for(i=0;(i<9-pos )&&(dat[i]!='\0');i++){
    
    
		smg_showChar(dat[i], pos+i);
		delay_10us(100); //延时1ms
		SMG_PORT = 0x00; //消影
	}
}

key.h

#ifndef __KEY_H__
#define __KEY_H__

#include "delay.h"

// 按键单次响应(0)或连续响应(1)开关
#define KEY1_MODE 0
#define KEY2_MODE 0
#define KEY3_MODE 0
#define KEY4_MODE 1

// 引脚定义
sbit key1 = P3^1; 
sbit key2 = P3^0;
sbit key3 = P3^2;
sbit key4 = P3^3;

// 按键状态枚举
typedef enum{
    
    
	KEY_UNPRESS = 0,
	KEY1_PRESS,
	KEY2_PRESS,
	KEY3_PRESS,
	KEY4_PRESS,
	KEY_NON_DEAL,
}Key_State;


void scan_key();
void check_key();

#endif

key.c

#include "key.h"

// 按键状态、前状态
Key_State key_state, key_pre_state;

// 按键累积值,反映按键时长
u16 num = 0;


/** 
 **  @brief    独立按键的函数封装
 **  @author   QIU
 **  @date     2023.08.31
 **/

/*-------------------------------------------------------------------*/


// 包含的头文件(按需求修改)
#include "led.h"
#include "smg.h"
#include <stdio.h>


/**
 **  @brief   按键1按下函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key1_pressed(){
    
    
	smg_showChar(1, 1);
	led_turn(6);
}

/**
 **  @brief   按键2按下函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key2_pressed(){
    
    
	;
}

/**
 **  @brief   按键3按下函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key3_pressed(){
    
    
	smg_showChar(3, 1);
	led_turn(8);
}


/**
 **  @brief   按键4按下函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key4_pressed(){
    
    
	u8 str[8];
	num++;
	sprintf(str,"%d",num/10);
	smg_showString(str, 1);
}

/**
 **  @brief   按键1松开函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key1_unpressed(){
    
    
	;
}

/**
 **  @brief   按键2松开函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key2_unpressed(){
    
    
	smg_showChar(2, 1);
	led_turn(7);
}

/**
 **  @brief   按键3松开函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key3_unpressed(){
    
    
	key3_pressed();
}

/**
 **  @brief   按键4松开函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key4_unpressed(){
    
    
	smg_showChar(0, 1);
	num = 0;
}


/*-------------------------------------------------------------------*/


/**
 **  @brief   按键松开响应
 **  @param   无
 **  @retval  无
 **/
void key_unpress(){
    
    
	switch(key_pre_state){
    
    
		case KEY_UNPRESS: break;
		case KEY1_PRESS: key1_unpressed();break;
		case KEY2_PRESS: key2_unpressed();break;
		case KEY3_PRESS: key3_unpressed();break;
		case KEY4_PRESS: key4_unpressed();break;
	}
}


/**
 **  @brief   (轮询方式)扫描独立按键,判断哪个键按下
 **  @param   无
 **  @retval  无
 **/
void scan_key(){
    
    
	static u8 flag = 1; // 开关(按下至完全松开为一轮判断)

	// 如果有按键按下
	if(flag && (!key1||!key2||!key3||!key4)){
    
    
		flag = 0; // 清零
		delay_ms(10); // 延时10ms消抖
		// key1往下屏蔽(屏蔽组合键)
		if(!key1) key_state = KEY1_PRESS; 
		else if(!key2) key_state = KEY2_PRESS;
		else if(!key3) key_state = KEY3_PRESS;
		else if(!key4) key_state = KEY4_PRESS;
		else key_state = KEY_UNPRESS;
	// 如果按键松开
	}else if(key1&&key2&&key3&&key4){
    
    
		flag = 1;
		delay_ms(10); // 延时10ms消抖,松开响应有逻辑判断时,需要加上消抖。否则可以省略。
		if(key1&&key2&&key3&&key4)key_state = KEY_UNPRESS;
	}
}


/**
 **  @brief   (轮询方式)判断按键, 进行相应处理
 **  @param    无
 **  @retval   无
 **/
void check_key(){
    
    
	switch(key_state){
    
    
		case KEY_UNPRESS:
			// 松开响应
			key_unpress();
			// 记录当前按键状态
			key_pre_state = KEY_UNPRESS;
			break;
		
		case KEY1_PRESS:
			// 记录当前按键状态
			key_pre_state = KEY1_PRESS;
			// 如果是单次响应
			if(!KEY1_MODE) key_state = KEY_NON_DEAL;
			key1_pressed();
			break;
		
		case KEY2_PRESS:
			// 记录当前按键状态
			key_pre_state = KEY2_PRESS;
			// 如果是单次响应
			if(!KEY2_MODE) key_state = KEY_NON_DEAL;
			break;
		
		case KEY3_PRESS:
			// 记录当前按键状态
			key_pre_state = KEY3_PRESS;
			// 如果是单次响应
			if(!KEY3_MODE) key_state = KEY_NON_DEAL;
			key3_pressed();
			break;
		
		case KEY4_PRESS:
			// 记录当前按键状态
			key_pre_state = KEY4_PRESS;
			if(!KEY4_MODE) key_state = KEY_NON_DEAL;
			key4_pressed();
			break;
		
		case KEY_NON_DEAL:
			// 按下不处理
			break;
	}
}

main.c

#include "key.h"
#include "smg.h"
/** 
 **  @brief    独立按键单键的单次响应与连续响应
 **            1. 按键1按下,LED6亮灭状态转变。按键1松开,不执行操作。(按下响应)
 **            2. 按键2按下,不执行操作。按键2松开,LED7亮灭状态转变。(松开响应)
 **            3. 按键3按下,LED8亮灭状态转变。按键3松开,LED8亮灭状态转变。(按下松开均响应)
 **            4. 按键4按下,数码管显示数值持续增加。按键4松开,数码管清零。(连续响应)
 **  @author   QIU
 **  @date     2023.08.31
 **/

/*-------------------------------------------------------------------*/


void main(){
    
    
	
	smg_showChar(0, 1); // 初始为0
	while(1){
    
    
		// 扫描按键
		scan_key();
		// 检查按键
		check_key();
	}
}

本例联动了之前篇章的LED数码管元器件,所以代码文件略多。

核心代码是在key.hkey.c中,主要的逻辑是:通过scan_key()函数不断轮询4个独立按键状态,一旦发生改变,延时10ms消抖,再次检测,以确定是否存在按键按下或是松开。然后判断是哪一个按键被按下,记录当前状态。此后,用check_key()轮询按键状态,并在该函数内实现各个按键所执行的逻辑

对于一些需要松开响应的场景,应当用变量key_pre_state记录前一个按键状态,以判断时哪一个键被松开

对于一些需要连续响应的场景,应当用宏定义KEYx_MODE 来设置按键的响应模式。例如开灯的时候,我们只希望在按下时执行一次,而在调节音量时,我们希望在按下时能持续执行

注意:

  • if .. else if语句具有向下屏蔽的特点。在此例中,按键1会屏蔽按键2~4的响应,即按下按键1时,按下其他的按键并不响应。
  • 按下和松开都存在抖动,但如果只需对按下事件做出响应时,松开消抖延时可以省略

组合键单次与连续响应

实验效果

  • 按键1和按键2同时按下LED切换为流水灯模式。
  • 按键2和按键3同时按下LED切换为跑马灯模式。
  • 按键3和按键4同时按下LED模式速度持续加快

key.h

#ifndef __KEY_H__
#define __KEY_H__

#include "delay.h"

// 按键单次响应(0)或连续响应(1)开关
#define KEY1_MODE 0
#define KEY2_MODE 0
#define KEY3_MODE 0
#define KEY4_MODE 1
#define KEY_1_2_MODE 0
#define KEY_2_3_MODE 0
#define KEY_3_4_MODE 1

// 引脚定义
sbit key1 = P3^1; 
sbit key2 = P3^0;
sbit key3 = P3^2;
sbit key4 = P3^3;

// 按键状态枚举
typedef enum{
    
    
	KEY_UNPRESS = 0x00,
	KEY1_PRESS = 0x01,
	KEY2_PRESS = 0x02,
	KEY3_PRESS = 0x04,
	KEY4_PRESS = 0x08,
	KEY1_2_PRESS = 0x03,
	KEY2_3_PRESS = 0x06,
	KEY3_4_PRESS = 0x0C,
	KEY_NON_DEAL = 0xff,
}Key_State;

void scan_key();
void check_key();

#endif

key.c

#include "key.h"

// 按键状态、前状态
Key_State key_now_state, key_pre_state;

// 按键累积
u16 num = 0;
// LED速度
u16 time = 10000;



/** 
 **  @brief    独立按键的函数封装
 **            1. 单键的单次或连续响应
 **            2. 组合键的单次或连续响应 
 **  @author   QIU
 **  @date     2023.08.31
 **/

/*-------------------------------------------------------------------*/

#include "led.h"
#include "smg.h"
#include <stdio.h>


/**
 **  @brief   按键1按下函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key1_pressed(){
    
    
	smg_showChar(1, 1);
	led_turn(6);
}


/**
 **  @brief   按键2按下函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key2_pressed(){
    
    
	;
}


/**
 **  @brief   按键3按下函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key3_pressed(){
    
    
	smg_showChar(3, 1);
	led_turn(8);
}


/**
 **  @brief   按键4按下函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key4_pressed(){
    
    
	u8 str[8];
	num++;
	sprintf(str,"%d",num/10);
	smg_showString(str, 1);
}


/**
 **  @brief   组合键1和2按下函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key_1_2_pressed(){
    
    
	led_stream(time);
}


/**
 **  @brief   组合键2和3按下函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key_2_3_pressed(){
    
    
	led_run(time);
}


/**
 **  @brief   组合键3和4按下函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key_3_4_pressed(){
    
    
	u8 str[8];
	time += 100;
	sprintf(str,"%u",time); // 无符号数
	smg_showString(str, 1);
}



/**
 **  @brief   按键1松开函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key1_unpressed(){
    
    
	;
}


/**
 **  @brief   按键2松开函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key2_unpressed(){
    
    
	smg_showChar(2, 1);
	led_turn(7);
}


/**
 **  @brief   按键3松开函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key3_unpressed(){
    
    
	key3_pressed();
}


/**
 **  @brief   按键4松开函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key4_unpressed(){
    
    
	smg_showChar(0, 1);
	num = 0;
}


/**
 **  @brief   按键1、2松开函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key1_2_unpressed(){
    
    
	;
}


/**
 **  @brief   按键2、3松开函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key2_3_unpressed(){
    
    
	;
}

/**
 **  @brief   按键3、4松开函数(可以按需求修改)
 **  @param   无
 **  @retval  无
 **/
void key3_4_unpressed(){
    
    
	;
}


/*-------------------------------------------------------------------*/


/**
 **  @brief   按键松开响应
 **  @param   无
 **  @retval  无
 **/
void key_unpress(){
    
    
	switch(key_pre_state){
    
    
		case KEY_UNPRESS: break;
		case KEY1_PRESS: key1_unpressed();break;
		case KEY2_PRESS: key2_unpressed();break;
		case KEY3_PRESS: key3_unpressed();break;
		case KEY4_PRESS: key4_unpressed();break;
		case KEY1_2_PRESS: key1_2_unpressed();break;
		case KEY2_3_PRESS: key2_3_unpressed();break;
		case KEY3_4_PRESS: key3_4_unpressed();break;
	}
}


/**
   *  @brief 扫描独立按键,判断哪个键按下
   *  @param  无
   *  @retval  无
   */
void scan_key(){
    
    
	static u8 flag = 1;

	// 如果有按键按下
	if(flag && (!key1||!key2||!key3||!key4)){
    
    
		flag = 0; // 清零
		delay_ms(10); // 延时10ms消抖
		delay_ms(50); // 延时50ms,组合键容许间隔
		// 获取当前所有按下的键
		if(!key1) key_now_state |= KEY1_PRESS; 
		if(!key2) key_now_state |= KEY2_PRESS; 
		if(!key3) key_now_state |= KEY3_PRESS; 
		if(!key4) key_now_state |= KEY4_PRESS; 

	// 如果按键全部松开
	}else if(key1&&key2&&key3&&key4){
    
    
		flag = 1;
		delay_ms(10); // 延时10ms消抖,松开响应有逻辑判断时,需要加上消抖。否则可以省略。
		if(key1&&key2&&key3&&key4)key_now_state = 0;
	}
}


/**
   *  @brief  判断按键, 进行相应处理
   *  @param  
   *  @retval
   */
void check_key(){
    
    
	switch(key_now_state){
    
    
		case KEY_UNPRESS:
			// 松开响应
			key_unpress();
			key_pre_state = KEY_UNPRESS;
			break;
		case KEY1_PRESS:
			key_pre_state = KEY1_PRESS;
			// 如果是单次响应
			if(!KEY1_MODE) key_now_state = KEY_NON_DEAL;
			key1_pressed();
			break;
		case KEY2_PRESS:
			key_pre_state = KEY2_PRESS;
			if(!KEY2_MODE) key_now_state = KEY_NON_DEAL;
			break;
		case KEY3_PRESS:
			key_pre_state = KEY3_PRESS;
			if(!KEY3_MODE) key_now_state = KEY_NON_DEAL;
			key3_pressed();
			break;
		case KEY4_PRESS:
			key_pre_state = KEY4_PRESS;
			if(!KEY4_MODE) key_now_state = KEY_NON_DEAL;
			key4_pressed();
			break;
		case KEY1_2_PRESS:
			key_pre_state = KEY1_2_PRESS;
			if(!KEY_1_2_MODE) key_now_state = KEY_NON_DEAL;
			key_1_2_pressed();
			break;
		case KEY2_3_PRESS:
			key_pre_state = KEY2_3_PRESS;
			if(!KEY_2_3_MODE) key_now_state = KEY_NON_DEAL;
			key_2_3_pressed();
			break;
		case KEY3_4_PRESS:
			key_pre_state = KEY3_4_PRESS;
			if(!KEY_3_4_MODE) key_now_state = KEY_NON_DEAL;
			key_3_4_pressed();
			break;
		case KEY_NON_DEAL:
			// 按下不处理
			break;
	}
}

主要更改了scan_key()的处理逻辑,可以处理组合键事件


总结

按键虽小,但细节很多,本节只是初步实现按键检测,事实上,代码实现思路还有诸多不足。在学完中断定时器后,我们将采用最佳的方式完成按键检测。继续加油吧!

猜你喜欢

转载自blog.csdn.net/m0_46500149/article/details/128406601
今日推荐