ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
【97.1 按住不松手的连续均匀触发。】 ![](https://img.kancloud.cn/a2/3d/a23df87ac21f61d2182864f67461b009_359x103.png) 上图97.1.1 独立按键电路 ![](https://img.kancloud.cn/73/34/7334d9d189f0de190e15939b9fff75d9_214x279.png) 上图97.1.2 灌入式驱动8个LED ![](https://img.kancloud.cn/89/70/8970513a066fe0726b2997dcb0329ce0_194x190.png) 上图97.1.3 有源蜂鸣器电路 在电脑上删除某个文件某行文字的时候,单击一次“退格按键\[Backspace\]”,就删除一个文字,如果按住“退格按键\[Backspace\]”不松手,就会“连续均匀”的触发“删除”的功能,自动逐个把整行文字删除清空,这就是“按住不松手的连续均匀触发”应用案例之一。除此之外,在很多需要人机交互的项目中都有这样的功能,为了快速加减某个数值,按住某个按键不松手,某个数值有节奏地快速往上加或者快速往下减。这种“按住不松手连续均匀触发”的按键识别,在程序上有“3个时间”需要留意,第1个是按键单击的“滤波”时间,第2个是按键“从单击进入连击”的间隔时间(此时间是“单击”与“连击”的分界线),第3个是按键“连击”的间隔时间, 本节例程实现的功能如下:(1)8个受按键控制的跑马灯在某一时刻只有1个LED亮,每触发一次K1按键,“亮的LED”就“往左边跑一步”;相反,每触发一次K2按键,“亮的LED”就“往右边跑一步”。如果按住K1或者K2不松手就连续触发,“亮的LED”就“连续跑”,一直跑到左边或者右边的尽头。(2)按键每“单击”一次蜂鸣器就鸣叫一次,但是,当按键“从单击进入连击”后,蜂鸣器就不鸣叫。 \#include "REG52.H" \#define KEY\_VOICE\_TIME 50 \#define KEY\_SHORT\_TIME 25 //按键单击的“滤波”时间25ms \#define KEY\_ENTER\_CONTINUITY\_TIME 300 //按键“从单击进入连击”的间隔时间300ms \#define KEY\_CONTINUITY\_TIME 80 //按键“连击”的间隔时间80ms \#define BUS\_P0 P0 //8个LED灯一一对应单片机的P0口总线 void T0\_time(); void SystemInitial(void) ; void Delay(unsigned long u32DelayTime) ; void PeripheralInitial(void) ; void BeepOpen(void); void BeepClose(void); void VoiceScan(void); void KeyScan(void); void KeyTask(void); void DisplayTask(void); //显示的任务函数(LED显示状态) sbit P3\_4=P3^4; //蜂鸣器 sbit KEY\_INPUT1=P2^2; //K1按键识别的输入口。 sbit KEY\_INPUT2=P2^1; //K2按键识别的输入口。 volatile unsigned char vGu8BeepTimerFlag=0; volatile unsigned int vGu16BeepTimerCnt=0; unsigned char Gu8LedStatus=0; //LED灯的状态 unsigned char Gu8DisplayUpdate=1; //显示的刷新标志 volatile unsigned char vGu8KeySec=0; //按键的触发序号 volatile unsigned char vGu8ShieldVoiceFlag=0; //屏蔽声音的标志 void main() { SystemInitial(); Delay(10000); PeripheralInitial(); while(1) { KeyTask(); //按键的任务函数 DisplayTask(); //显示的任务函数(LED显示状态) } } /\* 注释一: \* Gu8DisplayUpdate这类“显示刷新变量”在“显示框架”里是很常见的,而且屡用屡爽。 \* 目的是,既能及时刷新显示,又能避免主函数“不断去执行显示代码”而影响程序效率。 \*/ void DisplayTask(void) //显示的任务函数(LED显示状态) { if(1==Gu8DisplayUpdate) //需要刷新一次显示 { Gu8DisplayUpdate=0; //及时清零,避免主函数“不断去执行显示代码”而影响程序效率 //Gu8LedStatus是左移的位数,范围(0至7),决定了跑马灯的显示状态。 BUS\_P0=~(1<<Gu8LedStatus); //“左移<<”之后的“取反~”,因为LED电路是灌入式驱动方式。 } } /\* 注释二: \* 按键“连续均匀触发”的识别过程: \* 第一步:平时只要K1没有被按下,按键的自锁标志Su8KeyLock1、去抖动延时计数器Su16KeyCnt1、 \* 连击计数器Su16KeyContinuityCnt1,一直被清零。 \* 第二步:一旦K1按键被按下,去抖动延时计数器Su16KeyCnt1开始在定时中断函数里累加,在还没 \* 累加到阀值KEY\_SHORT\_TIME时,如果在这期间由于受外界干扰或者按键抖动, \* 而使IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16KeyCnt1清零, \* 这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。 \* 第三步:如果K1按键按下的时间超过了阀值KEY\_SHORT\_TIME,则触发一次“单击”, 同时,马上把自锁 \* 标志Su8KeyLock1置1防止按住按键不松手后一直触发,并且把计数器Su16KeyCnt1清零为了下 \* 一步用来累加“从单击进入连击的间隔时间1000ms”。如果此时还没有松手,直到发现按下的时 \* 间超过“从单击进入连击的间隔时间”阀值KEY\_ENTER\_CONTINUITY\_TIME时,从此进入“连击” \* 的模式,连击计数器Su16KeyContinuityCnt1开始累加,每到达一次阀值 \* KEY\_CONTINUITY\_TIME就触发1次按键,为了屏蔽按键声音及时把vGu8ShieldVoiceFlag也置1, \* 同时,Su16KeyContinuityCnt1马上清零为继续连击作准备。 \* 第四步:等K1按键松手后,自锁标志Su8KeyLock1、去抖动延时计数器Su16KeyCnt1、 \* 连击计数器Su16KeyContinuityCnt1,及时清零,为下一次按键触发做准备。 \*/ void KeyScan(void) //此函数放在定时中断里每1ms扫描一次 { static unsigned char Su8KeyLock1; static unsigned int Su16KeyCnt1; static unsigned int Su16KeyContinuityCnt1; //连击计数器 static unsigned char Su8KeyLock2; static unsigned int Su16KeyCnt2; static unsigned int Su16KeyContinuityCnt2; //连击计数器 //K1按键 if(0!=KEY\_INPUT1)//单个K1按键没有按下,及时清零一些标志。 { Su8KeyLock1=0; //按键解锁 Su16KeyCnt1=0; //去抖动延时计数器清零,此行非常巧妙,是全场的亮点。 Su16KeyContinuityCnt1=0; //连击计数器 } else if(0==Su8KeyLock1)//单个按键K1被按下 { Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms if(Su16KeyCnt1>=KEY\_SHORT\_TIME) //按键的“滤波”时间25ms { Su8KeyLock1=1; //“自锁” vGu8KeySec=1; //触发一次K1按键 Su16KeyCnt1=0; //清零,为了下一步用来累加“从单击进入连击的间隔时间300ms” } } else if(Su16KeyCnt1<=KEY\_ENTER\_CONTINUITY\_TIME)//按住不松手累加到300ms { Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms } else //按住累加到300ms后仍然不放手,这个时候进入有节奏的连续触发 { Su16KeyContinuityCnt1++; //连击计数器开始累加,每一次累加额度是1ms if(Su16KeyContinuityCnt1>=KEY\_CONTINUITY\_TIME) //按住没松手,每0.08秒就触发一次 { Su16KeyContinuityCnt1=0; //清零,为了继续连击。 vGu8KeySec=1; //触发一次K1按键 vGu8ShieldVoiceFlag=1; //把当前按键触发的声音屏蔽掉 } } //K2按键 if(0!=KEY\_INPUT2) { Su8KeyLock2=0; Su16KeyCnt2=0; Su16KeyContinuityCnt2=0; } else if(0==Su8KeyLock2) { Su16KeyCnt2++; if(Su16KeyCnt2>=KEY\_SHORT\_TIME) { Su8KeyLock2=1; vGu8KeySec=2; //触发一次K2按键 Su16KeyCnt2=0; } } else if(Su16KeyCnt2<=KEY\_ENTER\_CONTINUITY\_TIME) { Su16KeyCnt2++; } else { Su16KeyContinuityCnt2++; if(Su16KeyContinuityCnt2>=KEY\_CONTINUITY\_TIME) { Su16KeyContinuityCnt2=0; vGu8KeySec=2; //触发一次K2按键 vGu8ShieldVoiceFlag=1; //把当前按键触发的声音屏蔽掉 } } } void KeyTask(void) //按键任务函数,放在主函数内 { if(0==vGu8KeySec) { return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码 } switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码 { case 1: //K1触发的任务 if(Gu8LedStatus>0) { Gu8LedStatus--; //控制LED“往左边跑” Gu8DisplayUpdate=1; //刷新显示 } if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽 { vGu8BeepTimerFlag=0; vGu16BeepTimerCnt=KEY\_VOICE\_TIME; //发出“嘀”一声 vGu8BeepTimerFlag=1; } vGu8ShieldVoiceFlag=0; //及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。 vGu8KeySec=0; //响应按键服务处理程序后,按键编号必须清零,避免一直触发 break; case 2: //K2触发的任务 if(Gu8LedStatus<7) { Gu8LedStatus++; //控制LED“往右边跑” Gu8DisplayUpdate=1; //刷新显示 } if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽 { vGu8BeepTimerFlag=0; vGu16BeepTimerCnt=KEY\_VOICE\_TIME; //发出“嘀”一声 vGu8BeepTimerFlag=1; } vGu8ShieldVoiceFlag=0; //及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。 vGu8KeySec=0; //响应按键服务处理程序后,按键编号必须清零,避免一直触发 break; } } void T0\_time() interrupt 1 { VoiceScan(); KeyScan(); //按键识别的驱动函数 TH0=0xfc; TL0=0x66; } void SystemInitial(void) { TMOD=0x01; TH0=0xfc; TL0=0x66; EA=1; ET0=1; TR0=1; } void Delay(unsigned long u32DelayTime) { for(;u32DelayTime>0;u32DelayTime--); } void PeripheralInitial(void) { } void BeepOpen(void) { P3\_4=0; } void BeepClose(void) { P3\_4=1; } void VoiceScan(void) { static unsigned char Su8Lock=0; if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0) { if(0==Su8Lock) { Su8Lock=1; BeepOpen(); } else { vGu16BeepTimerCnt--; if(0==vGu16BeepTimerCnt) { Su8Lock=0; BeepClose(); } } } }