在电路设计中,我们经常需要读取外部的电平信号。比如,在项目中,我们需要通过按键来输入一些数据,那么就需要检测按键是否被按下。电平分为高电平读取和低电平读取,读取高电平,需要设置IO为下拉电阻输入模式,反之,设置IO为上拉电阻输入模式。
S4按下时,单片机IO为高电平,S1-S3按下时,为低电平。我们设置S1为上拉输入模式,S4为下拉输入模式。本节使用按键实现2个功能:
S1短按一次,LED2点亮,S1再短按一次,LED2熄灭。S1长按,LED1点亮,S1再长按,LED1熄灭。这种方式可以用来实现短按调节菜单,长按保存参数。
S4短按一次,LED4点亮,S4再短按一次,LED4熄灭。S4长按且不松手,LED4闪烁。这种方式可以用来实现短按调节数字,长按快速调节数字。在下一节,我们讲解数码管数字显示时,再来实现数字慢调和快调这个功能。下图是程序的大概流程思路以及框架:
关于IO输入输出初始化,上一节讲过如何配置。这里按键设置上拉输入和下拉输入,LED设置推挽低速输出。一般来说,我们会把应用代码写在单独的一个文件里,比如按键检测就是KEY.c,然后其他文件用到的函数和宏定义,可以直接写在对应的头文件里KEY.h。在哪个文件里使用,就在该文件里include头文件即可。每个外设电路都由一组C文件和H文件组成,一般来说,在复杂的项目中,在USER.c文件里调用外设函数编写用户程序,在主函数里调用用户函数即可。下图就是一个按键的头文件。
下图列出了一些按键检测的核心代码,KEY.c部分代码:
ScanKey()函数中的if语句是用来判断键值是否变化的,如果键值变化,则执行按键处理函数keyDeal();
按键键值读取的思路:while语句每循环一次所需时间是已知的,假设是100us,按键在被按下的过程中,IO的电平是剧烈变化的,只有完全按下时,IO的电平才会稳定。因此,当检测到低电平时,开始计数,每100us检测一次,假设检测了100次都是低电平,那么就可以确定按键真的被按下了。这里千万别采用长延时,防止程序被堵塞,一直空等待。
有的人是这样设计的,当检测到低电平时,就开始延迟100ms,然后再次检测到低电平,就认为按键被按下一次。这种方式是不合理的,在这100ms延时期间,CPU什么也没干,一直在那空运行,以至于其他程序无法被执行。而我们现在采用的策略是,while循环体里一般会有一些程序要被执行,这些程序执行是需要花时间的,那么循环一次花的时间,乘以循环次数,就可以达到延迟的目的。这样做,既可以延迟,也不影响其他程序执行。下面列出一些按键检测的核心代码。
关于长按,短按,是根据不同的循环次数来区分的。当达到长循环次数时,就认为长按,在键值上加上长按标志。如果松手后,循环次数没有达到长按次数,就认为是短按。如果检测到长按,就在长按执行代码里,设置另一个变量,每循环几次就执行数字递增或者递减。这就是长按快速计数的原理。
如果长按用来实现数据保存,那么,就直接在长按执行代码里设置保存标志位或者直接保存数据。这里KEY4按键长按LED闪烁是在按键检测函数里直接执行的,而短按是在键值处理函数里执行的。之所以这么做,是因为按键长按不松手,键值一直不变,按键处理函数只会被执行一次,请看前文的ScanKey函数。KEY1长按,短按都是在键值处理函数里执行的。
在按键处理函数中,首先分离出长按标志位,然后根据键值去匹配是哪个按键被按下。在键值处理代码段里,根据长按标志位来判断执行短按操作还是长按操作。