矩阵键盘设计.docx
《矩阵键盘设计.docx》由会员分享,可在线阅读,更多相关《矩阵键盘设计.docx(190页珍藏版)》请在冰豆网上搜索。
![矩阵键盘设计.docx](https://file1.bdocx.com/fileroot1/2022-12/18/c97faa8f-80f2-4055-b714-cba276c27696/c97faa8f-80f2-4055-b714-cba276c276961.gif)
矩阵键盘设计
周次
第七周
备注
章节
名称
项目四、矩阵键盘设计
教学
方式
理论课(√)上机课()实验课(√)
教学
方法
讲授法
时间
安排
4课时
教学
目标
1、掌握按键消抖原理及其实现方法。
2、掌握矩阵键盘扫描电路原理及其实现方法。
3、掌握矩阵键盘扫描程序的不同设计方法。
教学
重点
1、同步整形法与计数法进行按键防抖的原理及其实现方法。
2、矩阵键盘行扫描法的实现。
教学
难点
1、矩阵键盘行扫描法的设计重点。
2、按键防抖和行扫描法的综合设计。
板书
安排
下划线的内容为板书。
任务1按键消抖电路原理与实现
外部按键在闭合与释放瞬间,输入信号会有毛刺,如果不进行消抖处理,系统就会将这些毛刺误以为是用户的输入,导致系统的误操作。
毛刺的出现和按键过程的抖动有关,而这种抖动是不可避免的,因此有必要设计专门的按键消抖电路以消除毛刺的影响,减少系统误操作。
本节利用同步整形法和计数消抖法分别实现该电路。
一、同步整形法
同步整形电路也可以用于消除毛刺引起的系统误操作。
只要毛刺不出现在时钟的上升沿处,电路就不会把它当做一次有效的输入,因此不输出高电平脉冲。
由于毛刺持续的时间一般较短(所以才叫毛刺),因此只要时钟信号的周期足够大,毛刺出现在其上升沿处的概率就非常小。
另一方面,正常输入信号的持续时间只要足够一个时钟周期(起始位置可以随机),就能正常被识别为输入信号而在sout输出一个脉冲。
从而实现消除毛刺的目的。
综上,为了有效识别毛刺应该尽量增大时钟信号的周期,时毛刺出现在时钟沿的概率尽量减小;另外,为了识别正常的输入,应保证正常输入的持续时间足够一个时钟周期,但是不能由于时钟周期太大而降低识别正常输入信号的灵敏度。
因此,实际应用中,该时钟周期可以取10—1000Hz。
若时钟周期取为100Hz,这就要求按键时间至少0.01s,且无论按键持续多长时间都被识别为只输入一次。
二、计数法
计数法实现消抖的原理是对异步输入信号进行检测,当输入为高电平时对其计数,该高电平只有在保持一段时间不改变(计数器达到一定值),才确认它为有效值;若持续时间不够计数器达到一定值,则判其无效并复位计数器。
【例程-1】计数器实现按键消抖
LIBRARYIEEE;
USEIEEE.STD_LOGIC_1164.ALL;
ENTITYantitwitterIS
PORT(clk:
INSTD_LOGIC;
sin:
INSTD_LOGIC;
sout:
OUTSTD_LOGIC);
ENDantitwitter;
ARCHITECTUREbehOFantitwitterIS
CONSTANTn:
INTEGER:
=7;
SIGNALcnt:
INTEGERRANGE0TOn;
BEGIN
PROCESS(clk)
BEGIN
IF(clk'EVENTANDclk='1')THEN
IF(sin='0')THENsout<='0';cnt<=0;
ELSE
IF(cnt=n)THEN--计数器n达到预先设定数值,说明按键保持了一段较长时间
sout<='1';--信号有效
cnt<=0;--计数器清零
ELSE
cnt<=cnt+1;
ENDIF;
ENDIF;
ENDIF;
ENDPROCESS;
ENDbeh;
由上例的仿真波形图可知,当输入sin持续时间为时钟周期的8倍以上时才被认为是有效输入,且多个连续的有效输入被认为是同一次。
当输入信号持续时间不足时,被判无效,且计数器复位。
而当输入还不满一个时钟周期时,根本就不能被电路识别(计数器不工作)。
为了提高正常输入和毛刺的辨别能力,可以增大计数器的最大值,并减少时钟信号的周期。
例如,取N为63,则正常输入被有效识别的时间为时钟周期的64倍,即按键的持续时间至少为64倍的时钟周期。
因此该程序中时钟周期不能取太大,而应该越小越好,但又不能太小以至将毛刺也识别为正常输入。
任务2矩阵键盘基础知识
一、矩阵键盘基础知识
在许多数字系统中,经常采用按键作为系统的输入方式之一,为系统提供数据输入或者命令输入。
当案件数目较多时,把每一个按键连接到键盘矩阵中行和列的交叉点,如下图所示,一个4*4行列结构可构成有16个按键的矩阵。
开发板上设计了行列式矩阵编码键盘,规模为4*4,可作为外部输入。
采用矩阵编码键盘可以减少对I/O口的占用。
开发板上的矩阵编码键盘有4条行线,4条列线。
行线和列线的交叉处不直接连接,而是通过一个按键加以连接。
这样就构成4*4=16个按键输入,比之直接将信号线用于键盘多出了一倍,而且线数越多,区别越明显,比如再多加一条线就可以构成20键的键盘,而直接用端口线则只能多出一键(9键)。
由此可见,在需要的键数比较多时,采用矩阵法来做键盘是合理的。
矩阵式结构的键盘显然比直接法要复杂一些,识别也要复杂一些,如图列线通过电阻接正电源,并将行线所接的FPGA、CPLD的I/O口作为输出端,而列线所接的I/O口则作为输入。
这样,当按键没有按下时,所有的输出端都是高电平,代表无键按下。
行线输出是低电平,一旦有键按下,则输入线就会被拉低,这样,通过读入输入线的状态就可得知是否有键按下了。
二、行扫描法基础知识
行扫描法又称为逐行(或列)扫描查询法,是一种最常用的按键识别方法。
第一种(列扫描):
1、判断键盘中有无键按下:
将全部行线Y0-Y3置低电平,然后检测列线的状态。
只要有一列的电平为低,则表示键盘中有键被按下,而且闭合的键位于低电平线与4根行线相交叉的4个按键之中。
若所有列线均为高电平,则键盘中无键按下。
2、判断闭合键所在的位置:
在确认有键按下后,即可进入确定具体闭合键的过程。
其方法是:
依次将行线置为低电平,即在置某根行线为低电平时,其它线为高电平。
在确定某根行线位置为低电平后,再逐行检测各列线的电平状态。
若某列为低,则该列线与置为低电平的行线交叉处的按键就是闭合的按键。
第二种:
实验例程中采用(行扫描)
1、等待按键并识别按键位置:
按一定的频率用低电平循环扫描行线Y0-Y3,同时检测列线的状态,一旦判断有一列为低则表示有键被按下,停止扫描并保持当前行线的状态,再读取列线的状态从而得到当前按键的键码。
2、等待按键弹起:
检测到各列线都变成高点平后,重新开始扫描过程,等待下一次按键。
【例程-2】向用户介绍矩阵键盘扫描实现的方法,没有考虑去抖和判断键弹起的问题;把相应的键值显示在数码管上。
设计重点:
1、如何让行线扫描循环起来。
2、如何读取那个键被按下。
libraryIEEE;
useIEEE.STD_LOGIC_1164.ALL;
useIEEE.STD_LOGIC_ARITH.ALL;
useIEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITYKEYSCANIS
PORT(
clk:
INstd_logic;
rst:
INstd_logic;
row:
OUTstd_logic_vector(3DOWNTO0);--行线
column:
INstd_logic_vector(3DOWNTO0);--列线
dataout:
OUTstd_logic_vector(7DOWNTO0);--数码管显示数据
en:
OUTstd_logic);--数码管显示使能
ENDKEYSCAN;
ARCHITECTUREarchOFKEYSCANIS
SIGNALdiv_cnt:
std_logic_vector(24downto0);--分频计数器
SIGNALscan_key:
std_logic_vector(3DOWNTO0);--扫描码寄存器,其实就是行值row
SIGNALkey_code:
std_logic_vector(3DOWNTO0);--保存行扫描码
SIGNALdataout_tmp:
std_logic_vector(7DOWNTO0);--dataout的寄存器
BEGIN
row<=scan_key;
dataout<=dataout_tmp;
en<='0';
PROCESS(clk,rst)--分频计数器,此处的分频为非50%分频
BEGIN
IF(NOTrst='1')THEN
div_cnt<="0000000";
ELSIF(clk'EVENTANDclk='1')THEN
div_cnt<=div_cnt+1;
ENDIF;
ENDPROCESS;
PROCESS(div_cnt(20downto19))--此进程由计数器的19和20位的值来决定行线扫描的循环值
BEGIN
CASEdiv_cnt(20downto19)IS
WHEN"00"=>scan_key<="1110";--判断第一行是否有键按下
WHEN"01"=>scan_key<="1101";--判断第二行是否有键按下
WHEN"10"=>scan_key<="1011";--判断第三行是否有键按下
WHEN"11"=>scan_key<="0111";--判断第四行是否有键按下
ENDCASE;
ENDPROCESS;
PROCESS(clk,rst)--此进程用于读取那个键被按下
BEGIN
IF(NOTrst='1')THEN
key_code<="0000";
ELSIF(clk'EVENTANDclk='1')THEN
CASEscan_keyIS--检测何处有键按下
WHEN"1110"=>--当行扫描第一行时
CASEcolumnIS
WHEN"1110"=>--如果第一列为低电平,那么第一行和第一列相交键被按下
key_code<="0000";--“0”键被按下
WHEN"1101"=>--如果第二列为低电平,那么第一行和第二列相交键被按下
key_code<="0001";--“1”键被按下
WHEN"1011"=>--如果第三列为低电平,那么第一行和第三列相交键被按下
key_code<="0010";--“2”键被按下
WHEN"0111"=>--如果第四列为低电平,那么第一行和第四列相交键被按下
key_code<="0011";--“3”键被按下
WHENOTHERS=>
NULL;
ENDCASE;
WHEN"1101"=>--当行扫描第二行时
CASEcolumnIS
WHEN"1110"=>--如果第一列为低电平,那么第二行和第一列相交键被按下
key_code<="0100";--“4”键被按下
WHEN"1101"=>
key_code<="0101";
WHEN"1011"=>
key_code<="0110";
WHEN"0111"=>
key_code<="0111";
WHENOTHERS=>
NULL;
ENDCASE;
WHEN"1011"=>
CASEcolumnIS
WHEN"1110"=>
key_code<="1000";
WHEN"1101"=>
key_code<="1001";
WHEN"1011"=>
key_code<="1010";
WHEN"0111"=>
key_code<="1011";
WHENOTHERS=>
NULL;
ENDCASE;
WHEN"0111"=>
CASEcolumnIS
WHEN"1110"=>
key_code<="1100";
WHEN"1101"=>
key_code<="1101";
WHEN"1011"=>
key_code<="1110";
WHEN"0111"=>
key_code<="1111";
WHENOTHERS=>
NULL;
ENDCASE;
WHENOTHERS=>
key_code<="1111";
ENDCASE;
ENDIF;
ENDPROCESS;
-----显示键值
PROCESS(key_code)
BEGIN
CASEkey_codeIS
WHEN"0000"=>
dataout_tmp<="00000011";--当“0”键被按下,数码管显示0
WHEN"0001"=>
dataout_tmp<="10011111";--当“1”键被按下,数码管显示1
WHEN"0010"=>
dataout_tmp<="00100101";--当“2”键被按下,数码管显示2
WHEN"0011"=>
dataout_tmp<="00001101";
WHEN"0100"=>
dataout_tmp<="10011001";
WHEN"0101"=>
dataout_tmp<="01001001";
WHEN"0110"=>
dataout_tmp<="01000001";
WHEN"0111"=>
dataout_tmp<="00011111";
WHEN"1000"=>
dataout_tmp<="00000001";
WHEN"1001"=>
dataout_tmp<="00011001";
WHEN"1010"=>
dataout_tmp<="00010001";
WHEN"1011"=>
dataout_tmp<="11000001";
WHEN"1100"=>
dataout_tmp<="01100011";
WHEN"1101"=>
dataout_tmp<="10000101";
WHEN"1110"=>
dataout_tmp<="01100001";
WHEN"1111"=>
dataout_tmp<="01110001";
WHENOTHERS=>
NULL;
ENDCASE;
ENDPROCESS;
ENDarch;
任务3矩阵键盘扫描程序
【例程-3】矩阵键盘扫描程序
LIbraryIEEE;
USEIEEE.STD_LOGIC_1164.ALL;
ENTITYkeyscanningIS
PORT(clk,oe:
INSTD_LOGIC;
colum:
INSTD_LOGIC_VECTOR(3DOWNTO0);--列码
row:
OUTSTD_LOGIC_VECTOR(3DOWNTO0);--行码
d_out:
OUTSTD_LOGIC_VECTOR(3DOWNTO0);--编码输出(按下键所代表的数值)
d_avail:
OUTSTD_LOGIC);--数据有效信号
ENDkeyscanning;
ARCHITECTUREbhvOFkeyscanningIS
SIGNALfreeze:
STD_LOGIC;--数据有效信号的内部寄存器
SIGNALd:
STD_LOGIC_VECTOR(3DOWNTO0);--保存最后形成的值
BEGIN
PROCESS(clk)
VARIABLEr_counter:
STD_LOGIC_VECTOR(3DOWNTO0);--行扫描码循环计数器,用于形成行扫描信号
BEGIN
IF(clk='1'ANDclk'EVENT)THEN
IFfreeze='0'THEN--freeze='0'才进行状态转换,否则保存当前值不变
CASEr_counterIS--环形计数器,用于让四行扫描循环起来,状态转换
WHEN"1110"=>r_counter:
="1101";--当第一行被扫描,那么接下来扫描第二行,状态转换
WHEN"1101"=>r_counter:
="1011";--当第二行被扫描,那么接下来扫描第三行,状态转换
WHEN"1011"=>r_counter:
="0111";--当第三行被扫描,那么接下来扫描第四行,状态转换
WHEN"0111"=>r_counter:
="1110";--当第四行被扫描,那么接下来扫描第一行,状态转换
WHENOTHERS=>r_counter:
="1110";
ENDCASE;
ENDIF;
d_avail<=freeze;
ENDIF;
row<=r_counter;--将行扫描值输出
CASEr_counterIS--此行行扫描值决定最后数值的高两位
WHEN"1110"=>d(3DOWNTO2)<="00";--第一行被扫描,其最后值加0,可能值为(0、1、2、3)
WHEN"1101"=>d(3DOWNTO2)<="01";--第二行被扫描,其最后值加4,可能值为(4、5、6、7)
WHEN"1011"=>d(3DOWNTO2)<="10";--第三行被扫描,其最后值加8,可能值为(8、9、10、11)
WHEN"0111"=>d(3DOWNTO2)<="11";--第四行被扫描,其最后值加12,可能值为(12、13、14、15)
WHENOTHERS=>d(3DOWNTO2)<="00";
ENDCASE;
CASEcolumIS--此列值决定最后数值的低两位
WHEN"1110"=>d(1DOWNTO0)<="00";freeze<='1';
WHEN"1101"=>d(1DOWNTO0)<="01";freeze<='1';
WHEN"1011"=>d(1DOWNTO0)<="10";freeze<='1';
WHEN"0111"=>d(1DOWNTO0)<="11";freeze<='1';
WHENOTHERS=>d(1DOWNTO0)<="00";freeze<='0';
ENDCASE;
IF(freeze='1'ANDoe='1')THEN
d_out<=d;
ELSEd_out<="ZZZZ";
ENDIF;
ENDPROCESS;
ENDbhv;
注意:
1、四位环形计数器
此处利用CASE语句描述了四位移位寄存器型计数器,即环形计数器,用于产生四个扫描序列。
此种计数器常用于不规则数据之间的循环变化。
本例中四个状态中每次仅有一位为低电平,没有办法采用规则的加减乘除方法使其循环。
同时本例为了保证环形计数器能自启动,对于1110、1101、1011、0111之外的任何状态,均令其次态为1110,它利用下列语句实现:
WHENOTHERS=>r_counter:
=“1110”;
当检测到有按键按下时,freeze=‘1’,环形计数器保持当前状态不变,直到此键释放。
这一功能是通过IF-THEN语句实现的,即如果freeze=‘0’不成立,则不执行描述环形计数器的CASE语句。
由于IF-THEN语句是不完整条件语句,电路中会自动引入锁存器,保持原状态不变。
2、行编码和列编码
每个键代表行号和列号的唯一组合,通过对列和行进行编号,并把行和列编号的二进制数形成四位二进制数表示相对应的各个按键。
由程序可见,对行和列的编码是通过两组CASE语句实现的,其区别在于CASE与IS之间的表达式不同,环形计数器的值决定行编码的值,所检测到反映各列状态的column值决定列编码的值。
行编码决定按键数字的高两位,列编码决定按键数字的低两位。
3、多键被同时按下的处理结果分析
注意到由于环形计数器正常工作后,只会在1110、1101、1011、0111这四个状态之间循环,故在正常工作状态下,不会出现同时有两行被扫描的情况。
但有可能出现同时两个按键被按下的情况,譬如在同一行中同时多个按键被按下,则在列编码的CASE语句的将会执行下面的语句:
WHENOTHERS=>d(1downto0)<=“00”;freeze<=’0’;
因此,进程结束时,d_out<=“ZZZZ”,输出数据端为高阻态,输出数据有效信号d_avail为0。
而当不同行有多个键被同时按下时,最先扫描的一行具有输出编码优先权。
【例程-4】4×5矩阵键盘扫描程序设计
1、底层模块设计
键盘防抖动源程序设计如下:
libraryieee;
useieee.std_logic_1164.all;
useieee.std_logic_unsigned.all;
libraryaltera;
usealtera.maxplus2.all;
entitydebouncingis
port(d_in,clk:
instd_logic;
d_out:
outstd_logic);
end;
architectureartofdebouncingis
signalvcc,inv_d:
std_logic;
signald1,d0,q1,q0:
std_logic;
begin
vcc<='1';
inv_d<=notd_in;
dff1:
dffportmap(d=>d_in,q=>q0,clk=>clk,clrn=>vcc,prn=>vcc);
dff2:
dffportmap(d=>q0,q=>q1,clk=>clk,clrn=>vcc,prn=>vcc);
process(clk)
begin
if(clk'eventandclk='1')then
if(q0='1'andq1='1')then
d_out<='1';
elsif(q0='0'andq1='0')then
d_out<='0';
endif;
endif;
endprocess;
endart;
2、顶层模块设计
4×5矩阵键盘的源程序如下:
libraryieee;
useieee.std_logic_1164.all;
useieee.std_logic_arith.all;
useieee.std_logic_unsigned.all;
libraryaltera;
usealtera.maxplus2.all;
entitykey45is
port
(
clk_in:
instd_logic;--系统原始时钟脉冲
row_in:
instd_logic_vector(3downto0);--行输入
column_scan:
outstd_logic_vector(4downto0);--列扫描
out_value:
outstd_logic_vector(4downto0);--数字输出
bcd:
outstd_logic_v