赞
踩
题目:多功能按键设计。利用一个I/O口,接一个按键,实现3功能操作:单击 + 双击 + 长按。 ============================================================================ 用户基本操作定义: 1。短按操作:按键按下,按下时间<1s,属于一次短按操作 2。长按操作:按键按下,按下时间>1s,属于一次长按操作 在正常0.5s内无按键操作为启始按键扫描条件下,扫描按键将产生以下3种按键事件: 1。长按事件:任何1次出现的长按操作都属于长按事件 2。单击事件:1次短按操作后,间隔0.5内没有短按操作 3。双击事件:2次短按操作间隔时间<0.5s,则2次短按操作为1次双击事件,且2次短按都取消 特别操作情况定义: 1。短按操作和长按操作间隔<0.5s,以及,长按操作和短按操作间隔<0.5s,均不产生双击事件 2。连续n次(n为奇数)短按操作,且间隔均<0.5s,产生(n-1)/2次双击事件+1次单击事件 3。连续n次(n为偶数)短按操作,且间隔均<0.5s,产生n/2次双击事件 对按键操作者的建议: 由于按键的多功能性质,建议操作者每次在单击/长按/双击按键事件发生后,隔0.5s后再进行下一次的按键操作。因为在特别操作情况下,程序是保证按定义进行判断和处理的,主要是怕操作者自己记不清楚导致操作失误。 对软件设计者的要求: 1。应该全面进行分析,给出严格定义和判断条件,如上所示。如果自己都不清楚,你的设计出的系统就不稳定,不可靠。 2。在1的基础上,编写出符合要求的程序,并进行全面测试。 /*============= 低层按键(I/0)扫描函数,即低层按键设备驱动,只返回无键、短按和长按。具体双击不在此处判断。参考本人教材的例9-1,稍微有变化。教材中为连_发。 ===============*/ #define key_input PIND.7 // 按键输入口 #define N_key 0 //无键 #define S_key 1 //单键 #define D_key 2 //双键 #define L_key 3 //长键 #define key_state_0 0 #define key_state_1 1 #define key_state_2 2 unsigned char key_driver(void) { static unsigned char key_state = key_state_0, key_time = 0; unsigned char key_press, key_return = N_key; key_press = key_input; // 读按键I/O电平 switch (key_state) { case key_state_0: // 按键初始态 if (!key_press) key_state = key_state_1; // 键被按下,状态转换到按键消抖和确认状态 break; case key_state_1: // 按键消抖与确认态 if (!key_press) { key_time = 0; // key_state = key_state_2; // 按键仍然处于按下,消抖完成,状态转换到按下键时间的计时状态,但返回的还是无键事件 } else key_state = key_state_0; // 按键已抬起,转换到按键初始态。此处完成和实现软件消抖,其实按键的按下和释放都在此消抖的。 break; case key_state_2: if(key_press) { key_return = S_key; // 此时按键释放,说明是产生一次短操作,回送S_key key_state = key_state_0; // 转换到按键初始态 } else if (++key_time >= 100) // 继续按下,计时加10ms(10ms为本函数循环执行间隔) { key_return = L_key; // 按下时间>1000ms,此按键为长按操作,返回长键事件 key_state = key_state_3; // 转换到等待按键释放状态 } break; case key_state_3: // 等待按键释放状态,此状态只返回无按键事件 if (key_press) key_state = key_state_0; //按键已释放,转换到按键初始态 break; } return key_return; } /*============= 中间层按键处理函数,调用低层函数一次,处理双击事件的判断,返回上层正确的无键、单键、双键、长键4个按键事件。 本函数由上层循环调用,间隔10ms ===============*/ unsigned char key_read(void) { static unsigned char key_m = key_state_0, key_time_1 = 0; unsigned char key_return = N_key,key_temp; key_temp = key_driver(); switch(key_m) { case key_state_0: if (key_temp == S_key ) { key_time_1 = 0; // 第1次单击,不返回,到下个状态判断后面是否出现双击 key_m = key_state_1; } else key_return = key_temp; // 对于无键、长键,返回原事件 break; case key_state_1: if (key_temp == S_key) // 又一次单击(间隔肯定<500ms) { key_return = D_key; // 返回双击键事件,回初始状态 key_m = key_state_0; } else { // 这里500ms内肯定读到的都是无键事件,因为长键>1000ms,在1s前低层返回的都是无键 if(++key_time_1 >= 50) { key_return = S_key; // 500ms内没有再次出现单键事件,返回上一次的单键事件 key_m = key_state_0; // 返回初始状态 } } break; } return key_return; } 下面,根据程序分析按键事件的反映时间: 1。对于长键,按下超过1s马上响应,反映最快 2。对于双键,第2次按键释放后马上得到反映。 3。对于单键,释放后延时拖后500ms才能响应,反映最慢。这个与需要判断后面是否有双击操作有关,只能这样。实际应用中,可以调整两次单击间隔时间定义,比如为300ms,这样单击的响应回快一点,单按键操作人员需要加快按键的操作过程。如果产品是针对老年人的,这个时间不易太短,因为年纪大的人,反映和动作都比较慢。 当然,上面两段可以合在一起。我这样做的目的,是为了可以方便的扩展为N击(当然,需要做修改)。可是最底层的就是最基本的操作处理短按和长按,不用改动的。至于双击,还是N击,在中间层处理。这就是程序设计中分层结构的优点。 测试代码环境如下: interrupt [TIM0_COMP] void timer0_comp_isr(void) // 定时器10ms中断服务 { time_10ms_ok = 1; } main(viod) { ......... while { if (time_10ms_ok) //每10ms执行一次, { time_10ms_ok =0; key = key_read(); //《====== 10ms一次调用按键中间层函数,根据返回键值,点亮不同的LED灯,全面测试按键操作是否正常 if (key == L_key) ........//点亮A_LED,关闭B_LED和C_LED else if(key == D_key) ........//点亮B_LED,关闭A_LED和C_LED else if(key == S_key) ........//点亮C_LED,关闭A_LED和B_LED } } }
https://blog.csdn.net/su_fei_ma_su/article/details/105091193?utm_medium=distribute.pc_relevant.none-task-blog-title-11&spm=1001.2101.3001.4242
4个独立按键中用到3个,
keys5用于切换对时分秒等状态,keys2是减小数值,keys3是增加数值
同时可以判断按键的"短按,长按,连发"等功能
小于2秒视为短按,
大于2秒视为长按,
在长按状态下每0.2秒自动连发一次, 这样对时的时候就不用按N次了
欢迎一起交流,qq 102351263 验证码 iteye
程序分很多个文件 ,Keil uVision4 打包
#include "MY51.H" #include "keyScan.h" #include "smg.h" #include "myClock.h" void show(); //数码管显示 extern s8 shi; extern s8 fen; extern s8 miao; extern u8 changeTimeFlag; extern u8 timeMultipleFlag; void main() { startT0(10,100); //开T0启定时器 10毫秒*100=1秒 while(1) { show(); } } void T0_Work() //T0定时器调用的工作函数 { u8 key_stateValue; u8* pKeyValue; *pKeyValue=0; key_stateValue=read_key(pKeyValue); if(timeMultipleFlag) //到1秒了 { timeMultipleFlag=0; //标志清零 clock(); //走时,秒++ } if( (return_keyPressed==key_stateValue)&&(*pKeyValue==KEYS5_VALUE) ) { //短按keyS5时改变对时状态 changeTimeState(); //改变changeTimeFlag的3种状态,分别修改时或分或秒 } if((return_keyPressed==key_stateValue)||(key_stateValue&return_keyAuto) ) { //短按s2或s3可加减响应数值,长按keyS2或keyS3时每0.1秒加减一次数值 if(changeTimeFlag) //changeTimeFlag不为0时,允许修改 { if(KEYS2_VALUE == *pKeyValue) { changeTime(TRUE); //KEYS2,秒++ } if(KEYS3_VALUE == *pKeyValue) { changeTime(FALSE); //KEYS3,秒-- } } } } void show() //显示时钟 { u8 oneWela,twoWela,threeWela,foreWela,fiveWela,sixWela; //oneWela是最左边的数码管 sixWela =miao%10; fiveWela=miao/10; foreWela=fen%10; threeWela=fen/10; twoWela=shi%10; oneWela=shi/10; displaySMG(oneWela,twoWela,threeWela,foreWela,fiveWela,sixWela,0xf5); //0xf5是小数点的位置 }
#ifndef _MY51_H #define _MY51_H #include <reg52.h> #include <math.h> #include <intrins.h> #include "mytype.h" #define high 1 //高电平 #define low 0 //低电平 #define led P1 //灯总线控制 sbit led0=P1^0; //8个led灯,阴极送低电平点亮 sbit led1=P1^1; sbit led2=P1^2; sbit led3=P1^3; sbit led4=P1^4; sbit led5=P1^5; sbit led6=P1^6; sbit led7=P1^7; sbit lcdEN=P3^4; //液晶通讯使能端en,高脉冲有效 sbit lcdRS=P3^5; //液晶第4脚,RS,低电平是指令模式,高电平是数据模式 //sbit lcdR/W //液晶第5脚,低电平是写入模式,因为我们只写不读,所以接地 sbit csda=P3^2; //DAC0832模数转换cs口 sbit adwr=P3^6; //ADC0804这个同DAC0832 sbit dawr=P3^6; sbit adrd=P3^7; //ADC0804 sbit beep=P2^3; //蜂鸣器 void delayms(u16 ms); void T0_Work(); void startT0(u32 ms,u16 t_multiple); /// / #endif
#include "MY51.h" u8 TH0Cout=0 ; //初值 u8 TL0Cout=0 ; u16 T0IntCout=0; //中断计数 u16 timeMultiple=0; //中断复用时间的倍数 u8 timeMultipleFlag=0; //中断时间复用置位标志 void delayms(u16 ms) //软延时函数 { u16 i,j; for(i=ms;i>0;i--) { for(j=113;j>0;j--) {} } } //开启定时器,定时完成后需要手动关闭TR0,否则将循环定时 //参数一是定时的毫秒数,参数二是定时的倍率数(定时复用) void startT0(u32 ms,u16 t_multiple) //定时器初始化设定 { u32 N=11059.2*ms/12; //定时器总计数值 TH0Cout =(65536-N)/256; //装入计时值零头计数初值 TL0Cout =(65536-N)%256; timeMultiple=t_multiple; TMOD=TMOD | 0x01; //设置定时器0的工作方式为1 EA =OPEN; //打开总中断 ET0=OPEN; //打开定时器中断 TH0=TH0Cout; //定时器装入初值 TL0=TL0Cout; TR0=START; //启动定时器 } /* 方法二,此方法用于长时间的定时,以利于减少中断次数,减小误差 void startT0(u32 one_ms,u16 two_multiple) { u32 N=11059.2*one_ms/12; //定时器总计数值 TH0Cout =(65536-N%65536)/256; //装入计时值零头计数初值 TL0Cout =(65536-N%65536)%256; T0IntCountAll=(N-1)/65536+1; //总中断次数 T0IntCountAll2=T0IntCountAll*two_multiple; TMOD=TMOD | 0x01; //设置定时器0的工作方式为1 EA =OPEN; //打开总中断 ET0=OPEN; //打开定时器中断 TH0=TH0Cout; //定时器装入初值 TL0=TL0Cout; TR0=START; //启动定时器 }*/ void T0_times() interrupt 1 //T0定时器中断函数 { TH0=TH0Cout; TL0=TL0Cout; T0IntCout++; if(T0IntCout==timeMultiple) //复用定时器 { T0IntCout=0; //中断次数清零,重新计时 timeMultipleFlag=1; } T0_Work(); //调用工作函数 }
#ifndef _MYTYPE_H #define _MYTYPE_H / typedef float f32 ; typedef double d64 ; typedef float const fc32 ; typedef double const dc64 ; typedef volatile float vf32 ; typedef volatile double vd64 ; //typedef volatile float const vfc32 ; //typedef volatile double const vdc64 ; // typedef signed long s32; typedef signed short s16; typedef signed char s8; typedef signed long const sc32; /* Read Only */ typedef signed short const sc16; /* Read Only */ typedef signed char const sc8; /* Read Only */ typedef volatile signed long vs32; typedef volatile signed short vs16; typedef volatile signed char vs8; //typedef volatile signed long const vsc32; /* Read Only */ //typedef volatile signed short const vsc16; /* Read Only */ //typedef volatile signed char const vsc8; /* Read Only */ typedef unsigned long u32; typedef unsigned short u16; typedef unsigned char u8; typedef unsigned long const uc32; /* Read Only */ typedef unsigned short const uc16; /* Read Only */ typedef unsigned char const uc8; /* Read Only */ typedef volatile unsigned long vu32; typedef volatile unsigned short vu16; typedef volatile unsigned char vu8; //typedef volatile unsigned long const vuc32; /* Read Only */ //typedef volatile unsigned short const vuc16; /* Read Only */ //typedef volatile unsigned char const vuc8; /* Read Only */ typedef enum {FALSE = 0, TRUE = !FALSE} bool; typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus; typedef enum {DISABLE = 0, ENABLE = !DISABLE} FunctionalState; typedef enum {CLOSE = 0, OPEN = !CLOSE} OPEN_CLOSE; typedef enum {GND = 0, VCC = !GND} GND_VCC; typedef enum {NO = 0, YES = !NO} YES_NO; typedef enum {STOP = 0, START = !STOP} START_STOP; #define U8_MAX ((u8)255) #define S8_MAX ((s8)127) #define S8_MIN ((s8)-128) #define U16_MAX ((u16)65535u) #define S16_MAX ((s16)32767) #define S16_MIN ((s16)-32768) #define U32_MAX ((u32)4294967295uL) #define S32_MAX ((s32)2147483647) #define S32_MIN ((s32)-2147483648) #endif
#ifndef _KEYSACN_H #define _KEYSACN_H #include <reg52.h> #include "mytype.h" #define state_keyUp 0 //初始状态,未按键 #define state_keyDown 1 //键被按下 #define state_keyLong 2 //长按 #define state_keyTime 3 //按键计时态 #define return_keyUp 0x00 //初始状态 #define return_keyPressed 0x01 //键被按过,普通按键 #define return_keyLong 0x02 //长按 #define return_keyAuto 0x04 //自动连发 #define key_down 0 //按下 #define key_up 0xf0 //未按时的key有效位键值 #define key_longTimes 200 //10ms一次,200次即2秒,定义长按的判定时间 #define key_autoTimes 20 //连发时间定义,20*10=200,200毫秒发一次 sbit keyS2=P3^4; //4个独立按键 sbit keyS3=P3^5; sbit keyS4=P3^6; sbit keyS5=P3^7; #define KEYS2_VALUE 0xe0 //keyS2 按下 #define KEYS3_VALUE 0xd0 //keyS3 按下 #define KEYS4_VALUE 0xb0 //keyS4 按下 #define KEYS5_VALUE 0x70 //keyS5 按下 //void KeyInit(void); //初始化,io口未复用时可省略此步 static u8 getKey(void); //获取P口的连接key的io值,其他io位屏蔽为0 u8 read_key(u8* pKeyValue); //返回按键的各种状态,pKeyValue保存键值 #endif
#include "keyScan.h" #include <reg52.h> /*按键初始化,若io没有复用的话可以省略此步骤 void KeyInit(void) { keyS2 = 1 ; keyS3 = 1 ; keyS4 = 1 ; keyS5 = 1 ; //即P3|=0xf0; }*/ static u8 getKey(void) //获取P3口值 { if(key_down == keyS2) { return KEYS2_VALUE ; } if(key_down == keyS3 ) { return KEYS3_VALUE ; } if(key_down == keyS4 ) { return KEYS4_VALUE ; } if(key_down == keyS5 ) { return KEYS5_VALUE ; } return key_up ; //0xf0 没有任何按键 } //函数每10ms被调用一次,而我们弹性按键过程时一般都20ms以上 //所以每次按键至少调用本函数2次 u8 read_key(u8* pKeyValue) { static u8 s_u8keyState=0; //未按,普通短按,长按,连发等状态 static u16 s_u16keyTimeCounts=0; //在计时状态的计数器 static u8 s_u8LastKey = key_up ; //保存按键释放时的P3口数据 u8 keyTemp=0; //键对应io口的电平 s8 key_return=0; //函数返回值 keyTemp=key_up & getKey(); //提取所有的key对应的io口 switch(s_u8keyState) //这里检测到的是先前的状态 { case state_keyUp: //如果先前是初始态,即无动作 { if(key_up!=keyTemp) //如果键被按下 { s_u8keyState=state_keyDown; //更新键的状态,普通被按下 } } break; case state_keyDown: //如果先前是被按着的 { if(key_up!=keyTemp) //如果现在还被按着 { s_u8keyState=state_keyTime; //转换到计时态 s_u16keyTimeCounts=0; s_u8LastKey = keyTemp; //保存键值 } else { s_u8keyState=state_keyUp; //键没被按着,回初始态,说明是干扰 } } break; case state_keyTime: //如果先前已经转换到计时态(值为3) { //如果真的是手动按键,必然进入本代码块,并且会多次进入 if(key_up==keyTemp) //如果未按键 { s_u8keyState=state_keyUp; key_return=return_keyPressed; //返回1,一次完整的普通按键 //程序进入这个语句块,说明已经有2次以上10ms的中断,等于已经消抖 //那么此时检测到按键被释放,说明是一次普通短按 } else //在计时态,检测到键还被按着 { if(++s_u16keyTimeCounts>key_longTimes) //时间达到2秒 { s_u8keyState=state_keyLong; //进入长按状态 s_u16keyTimeCounts=0; //计数器清空,便于进入连发重新计数 key_return=return_keyLong; //返回state_keyLong } //代码中,在2秒内如果我们一直按着key的话,返回值只会是0,不会识别为短按或长按的 } } break; case state_keyLong: //在长按状态检测连发 ,每0.2秒发一次 { if(key_up==keyTemp) { s_u8keyState=state_keyUp; } else //按键时间超过2秒时 { if(++s_u16keyTimeCounts>key_autoTimes)//10*20=200ms { s_u16keyTimeCounts=0; key_return=return_keyAuto; //每0.2秒返回值的第2位置位(1<<2) }//连发的时候,肯定也伴随着长按 } key_return |= return_keyLong; //0x02是肯定的,0x04|0x02是可能的 } break; default: break; } *pKeyValue = s_u8LastKey ; //返回键值 return key_return; }
#ifndef _51SMG_H_ #define _51SMG_H_ #include <reg52.h> #include "mytype.h" sbit dula =P2^6; //段选锁存器控制 控制笔段 sbit wela =P2^7; //位选锁存器控制 控制位置 #define dark 0x11 //在段中,0x11是第17号元素,为0是低电平,数码管不亮 #define dotDark 0xff //小数点全暗时 void displaySMG(u8 one,u8 two,u8 three,u8 four,u8 five,u8 six,u8 dot); //数码管显示函数 #endif
#include "smg.h" #include "my51.h" u8 code table[]= { //0~F外加小数点和空输出的数码管编码 0x3f , 0x06 , 0x5b , 0x4f , // 0 1 2 3 0x66 , 0x6d , 0x7d , 0x07 , // 4 5 6 7 0x7f , 0x6f , 0x77 , 0x7c , // 8 9 A B 0x39 , 0x5e , 0x79 , 0x71 , // C D E F 0x80 , 0x00 ,0x40 // . 空 负号 空时是第0x11号也就是第17号元素 }; u8 code dotTable[]={ //小数点位置 0xff , //全暗 0xfe , 0xfd , 0xfb , //1 2 3 0xf7 , 0xef , 0xdf //4 5 6 }; //数码管显示 void displaySMG(u8 oneWela,u8 twoWela,u8 threeWela,u8 fourWela,u8 fiveWela,u8 sixWela,u8 dot) { //控制6位数码管显示函数,不显示的位用参数dark,保留ADC0804的片选信号 u8 csadState=0x80&P0; //提取最高位,即ADC0804的片选信号 u8 tempP0=((csadState==0)?0x7f:0xff); //数码管位选初始信号,阴极全置高电平 P0=tempP0; //0x7f表示数码管不亮,同时ADC0804片选有效 wela=1; //注:wela和dula上电默认为1 P0=tempP0; wela=0; P0=0; //由于数码管是共阴极的,阳极送低电平,灯不亮,防止灯误亮 dula=1; P0=0; dula=0; //段选数据清空并锁定 //oneWela { //消除叠影,数码管阴极置高电平,并锁存 P0=tempP0; wela=1; P0=tempP0; wela=0; } P0=0; //低电平送到数码管阳极,避免数码管误亮 dula=1; P0=table[oneWela]|((0x01&dot)?0x00:0x80); //送段数据,叠加小数点的显示 dula=0; P0=tempP0; //送位数据前关闭所有显示,并保持csad信号 wela=1; P0=tempP0 & 0xfe; //0111 1110最高位是AD片选,低6位是数码管位选,低电平有效 wela=0; delayms(1); /twoWela { //消除叠影 P0=tempP0; wela=1; P0=tempP0; wela=0; } P0=0; dula=1; P0=table[twoWela]|((0x02&dot)?0x00:0x80); dula=0; P0=tempP0; wela=1; P0=tempP0 & 0xfd; //0111 1101 wela=0; delayms(1); /threeWela { //消除叠影 P0=tempP0; wela=1; P0=tempP0; wela=0; } P0=0; dula=1; P0=table[threeWela]|((0x04&dot)?0x00:0x80); dula=0; P0=tempP0; wela=1; P0=tempP0 & 0xfb; //0111 1011 wela=0; delayms(1); /fourWela { //消除叠影 P0=tempP0; wela=1; P0=tempP0; wela=0; } P0=0; dula=1; P0=table[fourWela]|((0x08&dot)?0x00:0x80); dula=0; P0=tempP0; wela=1; P0=tempP0 & 0xf7; //0111 0111 wela=0; delayms(1); /fiveWela { //消除叠影 P0=tempP0; wela=1; P0=tempP0; wela=0; } P0=0; dula=1; P0=table[fiveWela]|((0x10&dot)?0x00:0x80); dula=0; P0=tempP0; wela=1; P0=tempP0 & 0xef; //0110 1111 wela=0; delayms(1); /sixWela { //消除叠影 P0=tempP0; wela=1; P0=tempP0; wela=0; } P0=0; dula=1; P0=table[sixWela]|((0x20&dot)?0x00:0x80); dula=0; P0=tempP0; wela=1; P0=tempP0 & 0xdf; //0101 1111 wela=0; delayms(1); }
#ifndef _MYCLOCK_H #define _MYCLOCK_H #include "mytype.h" #include "my51.h" void clock(void); //走时 void changeTimeState(void); //改变对时状态 void changeTime(bool add_or_sub); //修改时间,true为增加,false为减少 #endif
#include "myClock.h" u8 changeTimeFlag=0; s8 shi=22; //对时 s8 fen=45; s8 miao=0; void clock(void) { if(!changeTimeFlag) //不在对时状态 { miao++; if(miao>59) { miao=0; fen++; } if(fen>59) { fen=0; shi++; } if(shi>23) { shi=0; } } } void changeTimeState(void) //在满足条件时改变对时状态,时或分或秒,同时改变指示灯 { changeTimeFlag=(++changeTimeFlag)%4; switch(changeTimeFlag) { case 0: { led=0xff; } break; case 1: { led=0xff; led7=0; } break; case 2: { led=0xff; led5=0; } break; case 3: { led=0xff; led3=0; } break; default: break; } } void changeTime(bool add_or_sub) //修改时分秒 { if(add_or_sub) { switch(changeTimeFlag) { case 1: { shi++; if(shi>23) { shi=0; } } break; case 2: { fen++; if(fen>59) { fen=0; } } break; case 3: { miao++; if(miao>59) { miao=0; } } break; default: break; } } else { switch(changeTimeFlag) { case 1: { shi--; if(shi<0) { shi=23; } } break; case 2: { fen--; if(fen<0) { fen=59; } } break; case 3: { miao--; if(miao<0) { miao=59; } } break; default: break; } } }