当前位置:首页 > 公众号精选 > 嵌入式云IOT技术圈
[导读]鸿哥是我的一位单片机学习的启蒙老师,在此分享来自于他的一篇文章。 【92.1   独立按键的硬件电路简介。】                      上图92.1.1  独立按键电路     按键有两种驱动方式,一种是独立按键,一种是矩阵按键。1个独立按键要占用1个IO口,IO口不能共


鸿哥是我的一位单片机学习的启蒙老师,在此分享来自于他的一篇文章。

【92.1   独立按键的硬件电路简介。】


                上图92.1.1  独立按键电路

    按键有两种驱动方式,一种是独立按键,一种是矩阵按键。1个独立按键要占用1个IO口,IO口不能共用。而矩阵按键的IO口是分时片选复用的,用少量的IO口就可以驱动翻倍级别的按键数量。比如,用8个IO口只能驱动8个独立按键,但是却可以驱动16个矩阵按键(4x4)。因此,按键少的时候就用独立按键,按键多的时候就用矩阵按键。这两种按键的驱动本质是一样的,都是靠识别输入信号的下降沿(或上升沿)来识别按键的触发。
     独立按键的硬件原理基础,如上图,P2.2这个IO口,在按键K1没有被按下的时候,P2.2口因为单片机内部自带上拉电阻把电平拉高,此时P2.2口是高电平的输入状态。当按键K1被按下的时候,按键K1左右像一根导线连接到电源的负极(GND),直接把原来P2.2口的电平拉低,此时P2.2口变成了低电平的输入状态。编写按键驱动程序,就是要识别这个电平从高到低的过程,这个过程也叫下降沿。多说一句,51单片机的P1,P2,P3口是内部自带上拉电阻的,而P0口是内部没有上拉电阻的,需要外接上拉电阻。除此之外,很多单片机内部其实都没有上拉电阻的,因此,建议大家在做独立按键电路的时候,养成一个习惯,凡是按键输入状态都外接上拉电阻。
     识别按键的下降沿触发有四大要素:自锁,消抖,非阻塞,清零式滤波
“自锁”,按键一旦进入到低电平,就要“自锁”起来,避免不断触发按键,只有当按键被松开变成高电平的时候,才及时“解锁”为下一次触发做准备。
“消抖”,按键是一个机械触点器件,在接触的瞬间必然存在微观上的机械抖动,反馈到电平的瞬间就是“高,低,高,低...”这种不稳定的电平状态是一种干扰,但是,按键一旦按下去稳定了之后,这种状态就消失,电平就一直保持稳定的低电平。消抖的本质就是滤波,要把这种接触的瞬间抖动过滤掉,避免按键的“一按多触发”。
“非阻塞”,在处理消抖的时候,必须用到延时,如果此时用阻塞的delay延时就会影响其它任务的运行效率,因此,用非阻塞的定时延时更加有优越性。
“清零式滤波”,在消抖的时候,有两种境界,第一种境界是判断两次电平的状态,中间插入“固定的时间”延时,这种方法前后一共判断了两次,第一次是识别到低电平就进入延时的状态,第二次是延时后再确认一次是否继续是低电平的状态,这种方法的不足是,“固定的时间”全凭经验值,但是不同的按键它们的抖动时间长度是不同的,除此之外,前后才判断了两次,在软件的抗干扰能力上也弱了很多,“密码等级”不够高。第二种境界就是“清零式滤波”,“清零式滤波”非常巧妙,抗扰能力超强,它能自动过滤不同按键的“抖动时间”,然后再进入一个“稳定时间”的“N次识别判断”,更加巧妙的是,在“抖动时间”和“稳定时间”两者时间内,只要发现一次是高电平的干扰,就马上自动清零计时器,重新开始计时。“稳定时间”一般取20ms到30ms之间,而“抖动时间”是隐藏的,在代码上并没有直接描写出来,但是却无形地融入了代码之中,只有慢慢体会才能发现它的存在。
     具体的代码如下,实现的功能是按一次K1或者K2按键,就触发一次蜂鸣器鸣叫。

 1#include "REG52.H"   2  3#define KEY_VOICE_TIME   50 //按键触发后发出的声音长度   4#define KEY_FILTER_TIME  25 //按键滤波的“稳定时间”25ms  5  6void T0_time();  7void SystemInitial(void) ;  8void Delay(unsigned long u32DelayTime) ;  9void PeripheralInitial(void) ;  10  11void BeepOpen(void);  12void BeepClose(void);  13void VoiceScan(void);  14void KeyScan(void); //按键识别的驱动函数,放在定时中断里  15void KeyTask(void); //按键任务函数,放在主函数内  16  17sbit P3_4=P3^4;  18sbit KEY_INPUT1=P2^2; //K1按键识别的输入口。  19sbit KEY_INPUT2=P2^1; //K2按键识别的输入口。  20  21volatile unsigned char vGu8BeepTimerFlag=0;  22volatile unsigned int vGu16BeepTimerCnt=0;  23  24volatile unsigned char vGu8KeySec=0; //按键的触发序号,全局变量意味着是其它函数的接口。  25  26void main()  27{  28SystemInitial();  29Delay(10000);  30PeripheralInitial();  31 while(1)  32{  33 KeyTask(); //按键任务函数  34 }  35}  36  37void T0_time() interrupt 1  38{  39VoiceScan();  40KeyScan(); //按键识别的驱动函数  41  42TH0=0xfc;  43TL0=0x66;  44}  45  46  47void SystemInitial(void)  48{  49TMOD=0x01;  50TH0=0xfc;  51TL0=0x66;  52EA=1;  53ET0=1;  54TR0=1;  55}  56  57void Delay(unsigned long u32DelayTime)  58{  59 for(;u32DelayTime>0;u32DelayTime--);  60}  61  62void PeripheralInitial(void)  63{  64  65}  66  67void BeepOpen(void)  68{  69P3_4=0;  70}  71  72void BeepClose(void)  73{  74P3_4=1;  75}  76  77void VoiceScan(void)  78{  79  80 static unsigned char Su8Lock=0;  81  82if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)  83 {  84 if(0==Su8Lock)  85 {  86 Su8Lock=1;  87BeepOpen();  88 }  89 else  90{  91  92 vGu16BeepTimerCnt--;  93  94 if(0==vGu16BeepTimerCnt)  95 {  96 Su8Lock=0;  97BeepClose();  98 }  99 100} 101 } 102} 103 104/* 注释一: 105* 独立按键扫描的详细过程,以按键K1为例,如下: 106* 第一步:平时没有按键被触发时,按键的自锁标志,去抖动延时计数器一直被清零。 107* 第二步:一旦有按键被按下,去抖动延时计数器开始在定时中断函数里累加,在还没累加到 108*         阀值KEY_FILTER_TIME时,如果在这期间由于受外界干扰或者按键抖动,而使 109*         IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16KeyCnt1清零了,这个过程 110*         非常巧妙,非常有效地去除瞬间的杂波干扰。以后凡是用到开关感应器的时候, 111*         都可以用类似这样的方法去干扰。 112* 第三步:如果按键按下的时间达到阀值KEY_FILTER_TIME时,则触发按键,把编号vGu8KeySec赋值。 113*         同时,马上把自锁标志Su8KeyLock1置1,防止按住按键不松手后一直触发。 114* 第四步:等按键松开后,自锁标志Su8KeyLock1及时清零(解锁),为下一次自锁做准备。 115* 第五步:以上整个过程,就是识别按键IO口下降沿触发的过程。 116*/ 117void KeyScan(void) //此函数放在定时中断里每1ms扫描一次 118{ 119 static unsigned char Su8KeyLock1; //1号按键的自锁 120 static unsigned int Su16KeyCnt1; //1号按键的计时器 121 static unsigned char Su8KeyLock2; //2号按键的自锁 122 static unsigned int Su16KeyCnt2; //2号按键的计时器 123 124 //1号按键 125 if(0!=KEY_INPUT1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 126 { 127 Su8KeyLock1=0; //按键解锁 128 Su16KeyCnt1=0; //按键去抖动延时计数器清零,此行非常巧妙,是全场的亮点。  129 } 130 else if(0==Su8KeyLock1)//有按键按下,且是第一次被按下。这行很多初学者有疑问,请看专题分析。 131 { 132 Su16KeyCnt1++; //累加定时中断次数 133 if(Su16KeyCnt1>=KEY_FILTER_TIME) //滤波的“稳定时间”KEY_FILTER_TIME,长度是25ms。 134 { 135 Su8KeyLock1=1; //按键的自锁,避免一直触发 136 vGu8KeySec=1; //触发1号键 137 } 138 } 139 140 //2号按键 141 if(0!=KEY_INPUT2) 142 { 143 Su8KeyLock2=0; 144 Su16KeyCnt2=0; 145 } 146 else if(0==Su8KeyLock2) 147 { 148 Su16KeyCnt2++; 149 if(Su16KeyCnt2>=KEY_FILTER_TIME) 150 { 151 Su8KeyLock2=1; 152 vGu8KeySec=2; //触发2号键 153 } 154 } 155 156 157} 158 159void KeyTask(void) //按键任务函数,放在主函数内 160{ 161if(0==vGu8KeySec) 162{ 163return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码 164} 165 166switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码 167{ 168 case 1: //1号按键 169 170 vGu8BeepTimerFlag=0; 171vGu16BeepTimerCnt=KEY_VOICE_TIME; //触发按键后,发出固定长度的声音 172 vGu8BeepTimerFlag=1; 173vGu8KeySec=0; //响应按键服务处理程序后,按键编号必须清零,避免一致触发 174break; 175 176 case 2: //2号按键 177 178 vGu8BeepTimerFlag=0; 179vGu16BeepTimerCnt=KEY_VOICE_TIME; //触发按键后,发出固定长度的声音 180 vGu8BeepTimerFlag=1; 181vGu8KeySec=0; //响应按键服务处理程序后,按键编号必须清零,避免一致触发 182break; 183 184} 185}

【92.2   专题分析:else if(0==Su8KeyLock1)。】

疑问:

 1if(0!=KEY_INPUT1)  2 {  3 Su8KeyLock1=0;  4 Su16KeyCnt1=0;  5 }  6 else if(0==Su8KeyLock1)//有按键按下,且是第一次被按下。为什么?为什么?为什么?  7 {  8 Su16KeyCnt1++;  9 if(Su16KeyCnt1>KEY_FILTER_TIME) 10 { 11 Su8KeyLock1=1; 12 vGu8KeySec=1; 13 } 14 }

 解答:
       首先,我们要明白C语言的语法中,

1if(条件1) 2{ 3 4} 5else if(条件2) 6{ 7 8}

以上语句是一对组合语句,不能分开来看。当(条件1)成立的时候,它是绝对不会判断(条件2)的。当(条件1)不成立的时候,才会判断(条件2)。
       回到刚才的问题,当程序执行到(条件2) else if(0==Su8KeyLock1)的时候,就已经默认了(条件1) if(0!=KEY_INPUT1)不成立,这个条件不成立,就意味着0==KEY_INPUT1,也就是有按键被按下,因此,这里的else if(0==Su8KeyLock1)等效于else if(0==Su8KeyLock1&&0==KEY_INPUT1),而Su8KeyLock1是一个自锁标志位,一旦按键被触发后,这个标志位会变1,防止按键按住不松手的时候不断触发按键。这样,按键只能按一次触发一次,松开手后再按一次,又触发一次。
【92.3   专题分析:if(0!=KEY_INPUT1)。】
       疑问:为什么不用if(1==KEY_INPUT1)而用if(0!=KEY_INPUT1)?
       解答:其实两者在功能上是完全等效的,在这里都可以用。之所以本教程优先选用后者if(0!=KEY_INPUT1),是因为考虑到了代码在不同单片机平台上的可移植性和兼容性。很多32位的单片机提供的是库函数,库函数返回的按键状态是一个字节变量来表示,当被按下的时候是0,但是,当没有按下的时候并不一定等于1,而是一个“非0”的数值。
【92.4   专题分析:把KeyScan函数放在定时器中断里。】
       疑问:为什么把KeyScan函数放在定时器中断里?
       解答:中断函数里放的函数或者代码越少越好,但是KeyScan函数是特殊的函数,是涉及到IO口输入信号的滤波,滤波就涉及到时间的及时性与均匀性,放在定时中断函数里更加能保证时间的一致性。比如,蜂鸣器驱动,动态数码管驱动,按键扫描驱动,我个人都习惯放在定时中断函数里。
【92.5   专题分析:if(0==vGu8KeySec)return。】
       疑问:if(0==vGu8KeySec)return是不是多此一举?
       解答:在KeyTask函数这里,if(0==vGu8KeySec)return这行代码删掉,对程序功能是没有影响的,这里之所以多插入这行判断语句,是因为,当按键多达几十个的时候,避免主函数每次进入KeyTask函数,都挨个扫描判断switch的状态进行多次判断,如果增加了这行if(0==vGu8KeySec)return代码,就可以直接退出省事,在理论上感觉更加运行高效。其实,不同单片机不同的C编译器可能对switch语句的翻译不一样,因此,这里的是不是更加高效我不敢保证。但是可以保证的是,加了这行代码也没有其它副作用。


长期商务合作服务:




免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

在电力电子和电气工程领域,直流电源的稳定性与纯净度对于整个系统的正常运行至关重要。然而,由于电源线路中的干扰和噪声,直流电源中常常混入交流成分,这严重影响了电源的质量。因此,如何有效地滤波直流电源,消除其中的交流干扰,成...

关键字: 直流电源 滤波

在这篇文章中,小编将为大家带来电容的相关报道。如果你对本文即将要讲解的内容存在一定兴趣,不妨继续往下阅读哦。

关键字: 电容 去耦 滤波

电容,作为电子学中的一个基本概念和关键元件,广泛应用于各种电路和设备中。它的主要功能是储存电荷并在电路中起到滤波、耦合、调谐等作用。那么,电容的工作原理是什么呢?本文将从电容的基本结构、电荷储存机制、电场作用以及实际应用...

关键字: 电容 滤波 电子学

有源滤波器能够实时检测电网中由非线性负载产生的电流波形,并动态生成反向谐波电流以补偿负载谐波电流,具有响应速度快、滤波范围广、滤波效率高、不受系统参数影响以及体积小等优点。

关键字: 有源滤波器 滤波 谐波电流

带滤波的直流电源驱动是指在使用直流电源为设备提供电力时,为了减小电源中的噪声和干扰,采用滤波技术对电源进行滤波处理。通过滤波处理,可以减小电源中的高频噪声、电磁干扰等对设备性能的影响,从而提高设备的稳定性和可靠性。

关键字: 滤波 直流电源

暨2022中国新增长数字化先锋榜和2022优选解决方案·金如意奖 北京2022年12月26日 /美通社/ -- 近日,《哈佛商业评论》中文版2022新增长大会成功举办并发布五大榜单。睡眠健康创导者瑞思迈凭...

关键字: 数字化 MIDDOT OS 阻塞

上篇文章本来想写BUCK输出电容的计算的,但是看到好多电子同行理解都比较深刻,理论基础都非常扎实,我就改变了想法,转而写了一篇关于续流二极管参数的短文,所以如果对理论计算感兴趣的话,还是优先阅读同行的文章吧,如果我觉得时...

关键字: BUCK 电容 滤波

滤波在几乎所有通信系统中都扮演着重要的角色,因为去除噪声和失真会增加信道容量。设计一个只通过所需频率的滤波器是相当容易的。然而,在实际的物理滤波器实现中,通过滤波器会损失所需的信号功率。这种信号损失会为模数转换器(ADC...

关键字: 滤波 ADC 噪声

摘要:针对现有数据中心机房智能巡检系统复杂、作业效率低等问题,提出了一种基于设备指示灯轮廓及颜色识别的视觉巡检系统。首先对采集的视频图像进行颜色空间转换及二值化处理,然后选取合适的滤波方式对二值化图像进行去噪,最后利用霍...

关键字: 智能巡检 轮廓提取 滤波

摘要:通过对空气悬浮高速离心鼓风机全流道进行三维数值模拟,对其外特性曲线与测试试验数据进行了对比,得到的外特性参数与测试试验数据一致:对内部压力、速度、温度场进行分析,并得到了鼓风机的喘振及阻塞曲线,结果显示,该鼓风机内...

关键字: 高速离心鼓风机 喘振 阻塞
关闭
关闭