一.基础 》1(独立按键检测 2(矩阵按键检测 3(定时器扫描矩阵按键 4(按键综合
二.进阶 》1(状态机按键综合 2(矩阵按键底层 3(独立按键底层 4(独立按键使用实例 5(矩阵按键使用实例
·进阶部分用到的算法参考新型的按键扫描程序,仅三行程序 (amobbs.com 阿莫电子技术论坛),结尾附有原理图,所有程序均在开发板成功测试,不建议花过多的时间在基础部分,在使用矩阵按键的时候,一定要将P34公共端跳线帽拔掉,否则第四列按键会收到干扰!
1)立按附键的检测
===========================================================================================
#include <STC15F2K60S2.H>
unsigned char key_Init()
{
unsigned char keynum = 0;
if(P30==0){Delay1ms(20);while(P30==0);Delay1ms(20);keynum = 1;}
if(P31==0){Delay1ms(20);while(P30==0);Delay1ms(20);keynum = 2;}
if(P32==0){Delay1ms(20);while(P30==0);Delay1ms(20);keynum = 3;}
if(P33==0){Delay1ms(20);while(P30==0);Delay1ms(20);keynum = 4;}
return keynum;
}
//简单的按键消抖:当按键按下的一瞬间延时20ms,按键不松开则陷入while循环,松开的一瞬间延时20ms
===========================================================================================
2)矩阵按键4*4普通扫描检测
=========================================================================================
#include <STC15F2K60S2.H>
#include <ALL.H>
unsigned char matrix_key() //逐列逐行扫描,先扫描列在扫描行
{
unsigned char keynum = 0;
P44 = 0;P42 = 1;P35 = 1;P34 = 1;
if(P30==0){Delay1ms(20);while(P30==0);Delay1ms(20);keynum = 1;}
if(P31==0){Delay1ms(20);while(P31==0);Delay1ms(20);keynum = 5;}
if(P32==0){Delay1ms(20);while(P32==0);Delay1ms(20);keynum = 9;}
if(P33==0){Delay1ms(20);while(P33==0);Delay1ms(20);keynum = 13;}
P44 = 1;P42 = 0;P35 = 1;P34 = 1;
if(P30==0){Delay1ms(20);while(P30==0);Delay1ms(20);keynum = 2;}
if(P31==0){Delay1ms(20);while(P31==0);Delay1ms(20);keynum = 6;}
if(P32==0){Delay1ms(20);while(P32==0);Delay1ms(20);keynum = 10;}
if(P33==0){Delay1ms(20);while(P33==0);Delay1ms(20);keynum = 14;}
P44 = 1;P42 = 1;P35 = 0;P34 = 1;
if(P30==0){Delay1ms(20);while(P30==0);Delay1ms(20);keynum = 3;}
if(P31==0){Delay1ms(20);while(P31==0);Delay1ms(20);keynum = 7;}
if(P32==0){Delay1ms(20);while(P32==0);Delay1ms(20);keynum = 11;}
if(P33==0){Delay1ms(20);while(P33==0);Delay1ms(20);keynum = 15;}
P44 = 1;P42 = 1;P35 = 1;P34 = 0;
if(P30==0){Delay1ms(20);while(P30==0);Delay1ms(20);keynum = 4;}
if(P31==0){Delay1ms(20);while(P31==0);Delay1ms(20);keynum = 8;}
if(P32==0){Delay1ms(20);while(P32==0);Delay1ms(20);keynum = 12;}
if(P33==0){Delay1ms(20);while(P33==0);Delay1ms(20);keynum = 16;}
return keynum;
}
=========================================================================================
3)定时器扫描独立按键(跳线帽选择独立按键)
中断每20ms调用一次扫描函数key_loop();相当于实现了消抖;按键按下长按无反应,松开的时刻才会有;
============================================main.c=======================================
//定时器扫描按键与数码管
#include <STC15F2K60S2.H>
#include "ALL.H"
unsigned char keys;
void main()
{
Timer0_Init();
switch_138(5);P0 &= 0XAF;
while(1)
{
keys = key_set();
if(keys){nixie_set(1,keys);}
}
}
void Timer0_Rutine() interrupt 1
{
static unsigned char c1=0,c2=0;
c1++;c2++;
if(c1==2){nixie_loop();c1=0;}
if(c2==20){key_loop();c2=0; }
}
===============================================key.c=====================================
#include <STC15F2K60S2.H>
sbit K1 = P3^0; sbit K2 = P3^1; sbit K3 = P3^2; sbit K4 = P3^3;
unsigned char keynum;
unsigned char key_set()
{
unsigned char cash=0;//中转
cash = keynum;
keynum = 0;
return cash;
}
unsigned char key()//在中断中每隔20ms调用一次,相当于进行了消抖
{
unsigned char num=0;
if(K1==0){num = 1;} if(K2==0){num = 2;} if(K3==0){num = 3;} if(K4==0){num = 4;}
return num;
}
void key_loop()//松手检测,放中断中,if上一刻按键按下和现在按键没有按下,返回对应值
{
static unsigned char lastdata,nowdata;
lastdata = nowdata;nowdata = key();
if(lastdata==1&&nowdata==0){keynum = 1;}
if(lastdata==2&&nowdata==0){keynum = 2;}
if(lastdata==3&&nowdata==0){keynum = 3;}
if(lastdata==4&&nowdata==0){keynum = 4;}
}
=========================================================================================
4)按键综合 按键的长按、短按、双击检测
若要调整单击双击或长按灵敏度,调整COUNT2、COUNT3的数值即可。实现功能:单击NUM+1,双击归中NUM=50,长按两秒及以上NUM清零
//==================================================主函数==============================
#include <STC15F2K60S2.H>
#include "MAIN.H"
unsigned char COUNT1;
unsigned int COUNT2;
extern char NUM,KEY_FLAG,STATE;
extern int COUNT3;
void main()
{
TIMER1_INIT();
while(1)
{
NIXIE_SET(1,NUM/10);NIXIE_SET(2,NUM%10);
SCAN_KEY();
}
}
void Timer1() interrupt 3
{
COUNT1++;if(COUNT1==2){COUNT1=0;NIXIE_LOOP();}
if(KEY_FLAG){COUNT2++;}
if(STATE){COUNT3++;}
}
//========================================================key.c=========================
#include <STC15F2K60S2.H>
#include "MAIN.H"
sbit S1=P3^0;
unsigned char KEY_FLAG,NUM,STATE=1;
unsigned int COUNT3;
extern int COUNT2;
void DELAY1MS(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms)
{
i = 12;j = 169;
do
{
while (--j);
} while (--i);
xms--;
}
}
void SCAN_KEY()
{
if(S1==0)
{
DELAY1MS(10);
if(S1==0){COUNT2=0;KEY_FLAG=1;}
while(S1==0){NIXIE_SET(1,NUM/10);NIXIE_SET(2,NUM%10);}KEY_FLAG=0;
if(COUNT2<200)
{
STATE++;
if(COUNT2<200&&COUNT3>250){NUM++;NUM%=100;COUNT3=0;STATE=1;}//单击
if(STATE==2&&COUNT2<250){NUM=50;STATE=1;COUNT3=0;}//双击
}
if(COUNT2>2000){NUM=0;}//长按
}
}
//=======================================================================================
1) 状态机——按键综合(单双击,长按)
@annotation :适用于单个按键检测,本程序实现单双击及长按检测的功能,其中单双击标志建立后只执行一次,长按标志建立后若仍在按下状态则持续返回长按标志。这里把持续返回长按标志单独拿出来建立了一个状态,方便大家理解。以下程序中出现到的其它模块可以参考我的其它文章。
/****************************************************************************************
* @data :2024/2/15/23:50
* @pattern :IO
* @IRC: 12MHZ
* @MCU :STC15F2K60S2
******************************************************************************************/
#include "main.h"
sbit K1 = P3^0;
//============================================================
//@brief :state_0(按键按下检测), state_1(确认按键按下并完成消抖),state_2(长按逻辑检测),state_3(长按释放检测),state_4(单双击检测),state_5(单双击检测);
typedef enum
{state_0 = 0,state_1,state_2,state_3,state_4,state_5} states;
//@brief :N_KEY(返回无标志),S_KEY(单击标志),D_KEY(双击标志),L_KEY(长按标志)
typedef enum
{N_KEY = 5,S_KEY,L_KEY,D_KEY} flags;
//@brief :key_error_time(消抖时间设定3*10 = 30ms),key_long_time(长按时间设定),key_double_time(双击间隔设定)
typedef enum
{key_error_time = 2, key_long_time = 100, key_double_time = 20} times;
//============================================================
u8 key_drive(void)
{
static u8 key_time = 0,key_return = 0,state = 0; //keytime(计时变量) ,key_return(返回间接变量), state(状态变量)
key_return = N_KEY; //初始化key_return;
switch(state) //选择状态,每调用一次Switch,以下的case中只会执行一个
{
case 0: //仅检测是否按下
{
if(!K1){key_time = 0;state = state_1;} //如果按下,清零计时变量
else {state = state_0;key_return = N_KEY;} //没有按下,返回无标志
}break;
case 1: //状态1,用于完成消抖和确认按键按下
{
if(!K1){key_time++; if(key_time>=key_error_time){key_time=0; state = state_2;}} //计时变量值大于设定抖动值,确认按键按下
else{key_time=0; state = state_0;} //抖动
}break;
case 2: //状态2,用于长按的判断,先不返回长按标志
{
if(!K1){key_time++; if(key_time>=key_long_time){key_time=0; state = state_3;}} //如果计时变量值大于设定长按值,判定长按,先不返回长按标志,进入长按释放检测逻辑
else {state = state_4;} //else ,进入状态4判断单双击
}break;
case 3: //状态3,长按释放逻辑检测,把这个状态独立出来的目的主要是为了长按状态下持续触发,松手停止触发,若只想触发一次,直接在上个状态返回长按标志即可
{
if(!K1)
{
key_return = L_KEY;
}
else {key_time=0;state = state_0;} //长按释放,回到状态0,等待新的响应
}break;
case 4: //单双击检测,能进入到状态4就说明了前面已经出发了一次单击
{ key_time++;
if(key_time<=key_double_time) {if(!K1){key_return=D_KEY; key_time = 0; state = state_5;}} // 在设定的双击间隔时间内若按键再次被触发,被判定为双击 ,进入按键5等待按键释放
else{key_return = S_KEY; key_time=0; state = state_5;} //超过这个时间,被判定为单击,进入状态5等待按键释放
}break;
case 5: //按键释放检测
{
key_time++;
if(key_time>=key_error_time) //释放消抖
{
if(K1){state = state_0;}
}
}break;
default: //预料之外的情况,直接重启程序
{
state = state_0;
}break;
}
return key_return;
}
//按键综合示例代码
#include "MAIN.H"
u8 Key_flag, KeyValue, NUM;
void main()
{
Delay1ms(100); //开机延时,使电路达到稳定状态
SYS_Init();
Nixie_Cartoon(); //开机动画
while(1)
{
if(Key_flag)
{ Key_flag = 0;
KeyValue = key_drive();
switch(KeyValue)
{
case 5:{;}break; //按键空闲状态
case 6:{NUM++;}break; //按键单击状态
case 7:{NUM++;NUM%=100;Display_Set(1,NUM/100,0); Display_Set(2,NUM%100/10,0); Display_Set(3,NUM%10,0);}break; //按键长按状态
case 8:{NUM = 0;}break; //按键双击状态
}
}
Display_Set(1,NUM/100,0); Display_Set(2,NUM%100/10,0); Display_Set(3,NUM%10,0);
}
}
void T0() interrupt 1
{
static u8 Tube_Count;
static u8 Key_Count;
Tube_Count++; if(Tube_Count==2){Tube_Count = 0;Display_Loop();}
Key_Count++;if(Key_Count==10){Key_Count=0;Key_flag=1;}
}
2)算法——矩阵按键底层驱动
#include "main.h"
typedef enum{S7_S19=0X01,S6_S18=0X02,S5_S17=0X04,S4_S16=0X08}keys;
u8 data rd[12] = 0;
void KEY_SCAN()
{ /*采用定列扫行,为避免冲突,公共端跳线帽必须拔掉。20ms调用一次实现消抖*/
static u8 i = 0;
P4 = P3 = 0XFF; P3 =~ (0X80 >> i); P44 = P37; P42 = P36;
rd[0+3*i] = P3 ^ 0XFF;
rd[1+3*i] = rd[0+3*i] & (rd[0+3*i] ^ rd[2+3*i]);
rd[2+3*i] = rd[0+3*i];
i++; i%=4;
}
3)算法——独立按键底层驱动
#include "main.h"
/******************************************************************************************/
typedef enum{S4 = 0X08,S5 = 0X04,S6 =0X02,S7 = 0X01}keys;
u8 data rd[3]=0;
/***************************************************************************************/
void KEY_SCAN()
{ P3 &= 0XEF;
rd[0] = P3 ^ 0XFF;
rd[1] = rd[0] & (rd[0] ^ rd[2]);
rd[2] = rd[0];
}
/***************************************************************************************/
4)算法——独立按键使用实例
#include "main.h"
/******************************************************************************************/
typedef enum{S4 = 0X08,S5 = 0X04,S6 =0X02,S7 = 0X01}keys;
u8 data rd[3]=0;
u8 data press_cnt = 0; //按下次数
bit data s_flag = 0,d_flag = 0,l_flag = 0,w_flag = 0;/*单、双、长、误标志位*/
u8 data NUM = 0;
/******************************************************************************************/
void KEY_SCAN()
{ P3 &= 0XEF;
rd[0] = P3 ^ 0XFF;
rd[1] = rd[0] & (rd[0] ^ rd[2]);
rd[2] = rd[0];
}
/******************************************************************************************/
/*基于释放检测对单个按键附加单击,双击,长按三种功能*/
void KEY_CONFIG()
{ static u8 s_cnt = 0, l_cnt = 0;/*计时变量*/
if(rd[1]&S4){press_cnt ++;}
if(rd[2] & S4) l_cnt ++;
if(!(rd[2] & S4))/*完全释放*/
{ if(press_cnt != 0)
{ s_cnt ++;
/*双击*/if(s_cnt <= 30 && press_cnt == 2){ press_cnt = 0; s_cnt = 0; d_flag = 1; }
/*单击*/else if(s_cnt >= 15 && press_cnt != 2){ press_cnt = 0; s_cnt = 0; if(w_flag){s_flag =0;w_flag = 0;}else s_flag = 1;}//w_flag是告诉释放检测,我正在执行长按
/*长按*/if(l_cnt >= 75){l_cnt = 0; l_flag = 1; w_flag = 1; }
else l_cnt = 0;
}
}
}
5)算法,矩阵按键使用实例同独立按键使用方法