说起按键,这个大家应该不会陌生,因为我们每天都接触的啊,至少我们每天都用手机,我们发短信,打电话都会通过按键输入信息,虽然有的手机按键是“坚硬的”按键,有的按键是“软软的”按键,但这些统统称为“物理按键”,就如图中座机的按键和键盘的按键亦是如此;但还有一种“虚拟的”按键,就如手机中触摸屏的按键,这是一种软件虚拟出来的按键,但它的功能和真实的物理按键并没有什么两样。
但话说回来,按键究竟有什么玄机呢?按键是我们根据自己的意愿向机器设备发送指令的地方,它其实涉及到了物理信号到电信号的转变,如下图所示,物理信号指的是我们用手指接触按键并给它压力,电信号则是按键受到压力后变低使电路中的两个点连通了,我们所有的电子设备都是基于电路而工作的。当电路检测到A和B两点接通了,就认为按键按下了。
但是如果我们使用16个按键呢?就得有16根线与按键相连,或者说有16个通路。但是这样就有些耗费资源,如果实际上我们没有足够的通路,那又该怎么办呢?这时采用的方法就是矩阵按键。
可以看出同样是16个按键,但这时只连接了8根线,比16路独立按键节省了一半的线。为什么矩阵按键8根线也能控制16个键呢?这是因为它采用了行列分别控制,对于4×4的矩阵按键来说,就是4行和4列,每一行控制4个,每一列也控制4个,而对于其中任何一个按键来说,由它所在的行和列分别控制。
但对我们来说,有一个好消息,还有一个坏消息。好消息是我们的连线变少了,坏消息是,我们检测按键的过程变复杂了。通常,我们只要知道某个按键两边的点连通了,我们就认为按键按下了,对于独立按键,16个按键对应独立的16对的点,我们只要一一检测就好了,比如右端全给0,左端检测到0就可以了,如果有多个按键同时按下也能检测出来。但是对于矩阵按键,如果我们列全给0的话,假设某行为0,我们并不能判断出是这一行的那个列被按下了,所以只能一列一列的排查,就是只能一列为0,其余列的输入不能改变行的状态,比如1。当然,虽然稍微麻烦了一点,我们还是能克服的,毕竟它的优势是显而易见的。
一般情况下,我们检测按键是通过CPU读取相应连线的值来实现的。我们的手机里也有CPU,电脑里也有CPU,而我们所熟知的安卓系统,苹果系统,Windows系统等都是运行在CPU里的,这些“系统”都是CPU里运行的程序。当然,CPU也可以通过变成来读取按键连线上的值。
那么,问题来了。CPU是怎么读取按键连线上的值的呢?当然是人在教它通过一定的方法步骤来最终识别按键连线上的值的。 但每个人的做事方式不一样,CPU的最终识别方式也就不一样,换句话说,就是CPU上的程序运行方式不一样。但这只是说CPU做事的步骤不一样,就好如做面条,有的人先加水,再加葱花,再加油,有的人先加油,再加葱花,再加水,但最终出来都是香喷喷的面条。所以,所有的CPU都是按照一定的步骤来做事的。
现在,就以矩阵按键来说,CPU要想控制它,就需要一个流程,我们就需要根据这个流程告诉CPU遇到什么事情该怎么做,就如救援机器人一样,检测到生命体就实施救援,没有检测到就返回原地并发送消息。而矩阵按键,我们也可以根据当前的情况来进行相应的操作,比如下面这个流程图。
根据这个流程,CPU就可以根据步骤处理按键的事了。这个步骤可以说是很详细的。
首先,我们使用的手机,电脑,电视机遥控开关其实都有一个默认状态,那就是大多数时候,我们并不操作它,它处于一个空闲状态。这个例子也是,我们假设按键默认处于空闲状态,也就是没有被按下的状态。同时,我们假设行1、2、3、4也是默认状态,均为1。同时,CPU默认给列A、B、C、D一个0,那么如果有按键被按下,被接通的两点就会出现一根线一端为1,而另一端为0的情况,这时为1的一端也会变成0(这个规律可以总结为:一根线两端同时为0,则两端同时为0;两端同时为1,则两端同时为1;一端为0,一端为1,则两端仍然均为0)。那么CPU就会去检测了,如果行1、2、3、4不是默认的全为1,必然是有按键按下了。但这时它并不能判断出,究竟按下的是哪一个按键。
有按键按下了,就好说了。先让CPU把这个按键的值保存一下,保存很重要。这里又考虑了另外一个细节,就是防抖动。举个例子,你用遥控给电视机选台,如果你手很冷,手一抖,连按了好几下,那么本来你想选的台就直接被跳过了。我们设计的时候得防止这件事,那就是CPU一旦检测到按下按键了,就会延时一段时间,在这一段时间内无论手都还是什么的都会被屏蔽,专业术语就叫“按键滤波”。不过术语什么的,并不重要。而保存按键的值就会避免后续的抖动而导致最初的值丢失。
CPU中有一个称为计数器的东西,其实也就是定时器来实现延时。你可以用一个电信号来启动定时器,定时器走到规定的时间后也会输出一个电信号,这样一旦我们发现按键被按下了,就启动定时器;定时器走到规定的时间就关掉定时器。如果延时一段时间后,行1、2、3、4仍不是默认的全为1,那CPU相信你是真正的想按这个按键,而不是不小心碰了一下什么的。
CPU知道你已经按下了按键,那么接下来它要弄清楚你按的是那个按键了。如果它检测到某一行为0,那么它并不知道哪一列被按下了,因为任一列被按下都会是这一行变为0,怎么办?一列一列的试。它首先只将第一列变为0,然后检测行有没有按键被按下,有的话,就说明这一列有按键被按下了。然后它再将第二列变为0,检测按键有没有按下,如果有,那说明这一列按键被按下了。如果这一列被按下,就将这一列的标志标记为1(与这一列为0并不冲突,只是标志)。以此类推,每一列都要试上一遍。
试完了,下一步就是输出结果。如果应用比如计算器,一般都是一次只能按一个键,不能同时按两个键。所以这个例子也采用了这样的机制,如果同时按两个键则无效。那么结果就可以表示成xxxx_xxxx
的形式。其中前面的4位表示4行,后面的4位,表示4列,如1110_0001
则表示第“0行0列”被按下了。当然这只是一种标记,你也可以写成“1110_1110”表示第“0行0列”被按下了。我们已前一种方式为例,因为我们说过,只能一次按一个键才生效,所以同时按两个或多个键都无效。这就要求我们检测是不是只按了一个键,如果只按了一个键,那么行xxxx
不可能出现多于1个的0,所以xxxx
各位的和应等于3,而列呢,不可能出现多余1个的1,也就是列的和只能等于1。满足这个条件,我们就说我们检测到有效的按键被按下,就可以输出一个检测成功的标志并输出结果。如果不满足这个条件,说明一次可能按下了多个键,这不符合我们的预设,我们可以忽略它,那么结果就是不产生检测成功标志,以及按键的检测结果仍保留原先的值,而不会更新。
或许有的应用需要使用组合键,如PC的键盘,这时就可以将需要组合的按键定义进去。其实也可以增加组合键的机制,不过流程上可能就稍微复杂一些了。一般是应用需要什么,我们就增加什么,这种思想叫做“面向应用”。那如果将所有的按键组合都囊括进去,估计得耗费大量的资源,CPU估计会吃不消,且流程会相当复杂,很少人会那么干。
得到结果了,一般你就不会再按着键盘不放了,你要松开手了。CPU也会随时检测你有没有松开手,如过检测到行xxxx
为1111
,那表示你确实松开手了,但CPU还会延时一段时间,考察你是不是真的松开手,会不会有再按回去的可能,这种过程术语叫做“释放滤波”。还有一种可能就是你按着不放,CPU检测到了这种行为就会一直延时,如果计数器达到时间后,你仍不松手,CPU会再一次输出检测成功标志,如果一开始就检测成功了,CPU的输出结果会一直保持直到按下一个按键,如果一开始是多个按键的组合,CPU的输出结果会继续维持上一次的输出结果。
最终CPU延时了一段时间后,发现你确实释放了按键。CPU就会继续检测行的值,如果没有按键按下,就会跳到“空闲状态”,如果有按键按下就会跳到上一状态。
整个过程,总结下来就是:
- 按键空闲,时刻检测行状态,如果检测到了按键被按下,那么
- 延时,直到延时结束,然后
- 检测按键是否按下,否,跳到步骤1,是,检测是否是第0列按键按下引起的
- 如果检测到是第0列按下引起的,将第0列标志置1,否则,标志仍为0
- 检测是否是第1列引起的,如果是,将第1列标志置1,否则标志仍未0
- 检测是否是第2列引起的,如果是,将第2列标志置1,否则标志仍未0
- 检测是否是第3列引起的,如果是,将第3列标志置1,否则标志仍未0
- 判断是否只有一个按键按下,如果是,输出检测成功标志,并输出结果,否则
不输出检测成功标志,结果维持不变 - 检测按键是否被释放,是,延时一段时间(释放延时),否,等待(等待延
时,也可只等待就行,不必加计数器设定延时) - 检测到确实被释放了,并且延时完成,于是
- 继续检测是否有按键按下,有,说明按键被释放时有抖动,需跳到上一步继续
延时,否则说明按键进入空闲状态,跳到步骤1
这就是整个流程了,这里设定了等待释放的过程中加入等待计数器,达到指定延时后,不论是否按了一个键或是两个键都会输出检测成功标志,这一点也可以不加入这样的计数器,指定延时后,无需输出检测成功标志。不过,只要按照当初的需求就好。
然后,根据CPU的控制流程设计好控制程序后,我们还要对它验证。验证的方法就是给施加相应的信号,看看CPU的反应。但是我们加信号要符合实际的应用场景,比如一上电,系统的时钟clk
就会自动运行(所谓系统时钟,就好比人类人类生活的时间,人类做任务规划,也是基于时间,而电子系统亦是如此,它这一步做什么,下一步做什么都是基于一个时钟的)。然后系统的“复位”信号rst_n
一开始为低(复位信号其实就类似于人类的出生,刚进入世间,大脑完全归零,不懂任何事物,而电子系统也是如此,刚进入工作时,会将内部所有的控制逻辑归零,或者叫清空,为下一步运行做准备)。以上两个信号为电子设计领域的常识了,可以说每个CPU必有此信号。
而根据我们的设计要求,键盘的行信号都是默认为1(如果行未选通,由行寄存器,也就是内部数据寄存器提供此信号,同时受行选通信号控制,所以如果行如果被选通时,由列信号提供行信号),所谓行选通
row_sel
,其实也类似于一个开关,行的信号通过开关选择。由图中的信号图可知,row_sel
为0,未选通。
列信号
col
与按键成功标志key_flag
,按键默认值key_value
均为CPU输出,在这种情况下,它们的默认输出均为0,符合我们的设计要求。
等了50ms后,我按下了第0行0列的按键,可以看出计数器1开始启动,计数器开始计数,状态也由原来的00000000001
变化到00000000010
。但是由于按键有抖动,所以按键的值总是在0
和1
之间跳变。我们需要等待一会儿,等到按键的值变稳定,至于等多长时间,则由计数器说的算,等到它计数完成,按键的值也就稳定了。
我们可以看到计数器cnt1记满数后会重新变为0,而记满标志
cnt_done1
会变为1,并持续1个时钟周期。得知记满标志变为1后,计数器启动信号en_cnt1
会变为0,状态也由00000000010
变为00000000100
,进而开始逐列扫描,看看到底是那一列按键被按下,可以看出只有第0列被按下会使行为1110
,其余列按下行始终为1111
。且此时状态变化到了00001000000
。
接下来状态变到00010000000
,开始输出结果了,同时变化到状态00100000000
。此时内部寄存器key_flag_r
被拉高,同时启动连按计数器en_cnt2
,表示此时按键还被按着,而基于此输出信号key_flag
也被拉高(基于key_flag_r
,所以延迟一拍)。因为按键并未释放,所以CPU就一直等着按键释放。
然后检测到
row
变为1,说明按键被释放了,而这里我的检测标志key_flag
应该清零了(图中未清零)。然后状态转到01000000000
,同时释放延迟计数en_cnt1
启动,长按延迟计数en_cnt2
终止。释放延时计数开始从0计数。
等到计数完成,我们会看到计数值归0。然后
en_cnt1
归0,计数终止。状态变为10000000000
,这个状态读取row
的值,若无按键按下,那么状态重新回到初始状态00000000001
。
如此,则验证了这个程序的正确性。
不过我却多了深一层的思考:你看,电子世界靠着时钟一步一步照着人类给予的指令做着自己的事情,而观看我们人类自己,是依照着谁的指令一步步做事的呢?
注:除本文前两幅图片源于Pixabay和Unsplash,其余均为原创。
参考源码:FPGA矩阵键盘设计 提取码:1234
Congratulations @lucienyoung! You have completed the following achievement on the Hive blockchain and have been rewarded with new badge(s) :
Your next payout target is 100 HP.
The unit is Hive Power equivalent because your rewards can be split into HP and HBD
You can view your badges on your board and compare yourself to others in the Ranking
If you no longer want to receive notifications, reply to this comment with the word
STOP
Support the HiveBuzz project. Vote for our proposal!
你的文章太技术性了,虽然看不懂,但点个赞
ヽ(*。>Д<)o゜
谢谢😁