写在前面的话
在使用按键的时候,如果按键不多的话,我们可以直接让按键与FPGA相连接,但是如果按键比较多的时候,如果还继续使用直接让按键与FPGA相连接的话,会大量增加FPGA端口的消耗,为了减少FPGA端口的消耗,我们可以把按键设计成矩阵的形式。接下来,梦翼师兄将和大家一起学习扫描键盘的电路原理以及驱动方式。
项目需求
设计4*4矩阵键盘按键扫描模块,正确解析按键值。
矩阵键盘的原理
由上图可以知道,矩阵键盘的行row(行)与col(列)的交点,都是通过一个按键相连接。传统的一个按键一个端口的方法,若要实现16个按键,则需要16个端口,而现在这个矩阵键盘的设计,16个按键,仅仅需要8个端口,如果使用16个端口来做矩阵键盘的话,可以识别64个按键,端口的利用率远远比传统的设计高好多,所以如果需要的按键少的话,可以选择传统的按键设计,如果需要的按键比较多的话,可以采用这种矩阵键盘的设计。而我们现在就以扫描法为例来介绍矩阵键盘的工作原理。
首先col(列)是FPGA给矩阵键盘输出的扫描信号,而row(行)是矩阵键盘反馈给FPGA的输入信号,用于检测哪一个按键被按下,示意图如下:
如上图所示,FPGA给出扫描信号COL[3:0],COL = 4’b0111,等下一个时钟周期COL = 4’b1011,再等下一个时钟周期COL = 4’b1101,再等下一个时钟周期COL = 4’b1110,再等下一个时钟周期COL = 4’b0111,COL就是这样不断循环,给矩阵键盘一个低电平有效的扫描信号,当FPGA给矩阵键盘COL扫描信号的同时,FPGA也要在检测矩阵键盘给FPGA的的反馈信号ROW,举个例子,假若矩阵键盘中的9号按键被按下了:
当COL = 4’b0111,ROW = 4’b1111;
当COL = 4’b1011,ROW = 4’b1111;
当COL = 4’b1101,ROW = 4’b1011;
当COL = 4’b1110,ROW = 4’b1111;
有人问,为什么当COL = 4’b1101的时候,ROW = 4’b1011呢?我们现在就以矩阵键盘的电路来分析一下这个原因,如上图所示:
当9号按键被按下的时候,9号按键的电路就会被导通,扫描电路COL开始扫描,当扫描到COL[1]的时候,由于9号按键的电路被导通了,COL[1]的电压等于ROW[2]的电压,所以会出现当COL = 4’b1101的时候ROW = 4’b1011(扫描信号的频率大概1K左右)。
通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。
抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒(按键按下的时间一般都会大于20ms)。键抖动会引起一次按键被误读多次。为确保CPU对按键的一次闭合仅作一次处理,必须去除按键抖动。在按键闭合稳定时读取按键的电平状态,并且必须判别到按键释放稳定后再作处理。
然后我们就可以利用这些现象,来设计一个识别按键的电路。
架构设计
根据原理分析,我们设计出架构图如下:
模块功能介绍
模块名 |
功能描述 |
key_scan |
检测按键值 |
顶层模块端口描述
端口名 |
端口说明 |
clk |
系统时钟输入 |
rst_n |
系统复位 |
row |
矩阵键盘的行线 |
data |
按键值 |
flag |
按键值有效(尖峰脉冲) |
col |
矩阵键盘的列线 |
代码解释
Key_scan模块代码
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function: 检测出键盘矩阵的按键值 *****************************************************/ 000 module key_scan ( 001 clk, //系统时钟输入 002 rst_n, //系统复位 003 row,//矩阵键盘的行线 004 flag,//输出值有效标志(尖峰脉冲) 005 data,//按键值 006 col//矩阵键盘的列线 007 ); 008 //系统输入 009 input clk;//系统时钟输入 010 input rst_n;//系统复位 011 input [3:0] row;//矩阵键盘的行线 012 //系统输出 013 output reg flag;//输出值有效标志(尖峰脉冲) 014 output reg [3:0] data;//按键值 015 output reg [3:0] col;//矩阵键盘的列线 016 017 reg clk_1K;//1K的时钟 018 reg [20:0] count;//计数器 019 020 always @ (posedge clk or negedge rst_n) 021 begin 022 if (!rst_n) 023 begin 024 clk_1K <= 1; 025 count <= 0; 026 end 027 else 028 if (count < 24999) // 50000分频,得出1K的时钟 029 count <= count + 1; 030 else 031 begin 032 count <= 0; 033 clk_1K <= ~clk_1K; 034 end 035 end 036 037 reg [4:0] cnt_time;//按键按下的时间 038 reg [1:0] state;//状态寄存器 039 reg [7:0] row_col;//按键对应的行列值 040 041 always @ (posedge clk_1K or negedge rst_n) 042 begin 043 if (!rst_n)//复位时,将中间寄存器和输出置0 044 begin 045 flag <= 0; 046 state <= 0; 047 cnt_time <= 0; 048 row_col <= 0; 049 col <= 4‘b0000; 050 end 051 else 052 begin 053 case (state) 054 0 : begin 055 if (row != 4‘b1111)//当有按键按下时,开始计数,只有 056 begin //一直按下20ms才会被当做有效的按键 057 if (cnt_time < 19) 058 cnt_time <= cnt_time + 1; 059 else 060 begin 061 cnt_time <= 0; 062 state <= 1; 063 col <= 4‘b1110;//扫描的初始值 064 end 065 end 066 else 067 cnt_time <= 0; 068 end 069 070 1 : begin 071 if (row!=4‘b1111) 072 begin 073 row_col <= {row,col};//当检测出来时,把行列线的值存起来 074 flag <= 1; //拉高有效标志 075 state <= 2; 076 col <= 4‘b0000;//用于判断按键是否抬起来 077 end 078 else 079 begin 080 col <= {col[2:0],col[3]};//没有检测出来时,换成下一列 081 end //扫描 082 end 083 084 2 : begin 085 if (row == 4‘b1111)//当按键释放20ms以后才会被当做释放 086 begin //跳转到0状态进行新的按键值的检测 087 if (cnt_time < 19) 088 begin 089 cnt_time <= cnt_time + 1; 090 flag <= 0; 091 end 092 else 093 begin 094 cnt_time <= 0; 095 state <= 0; 096 col <= 4‘b0000; 097 end 098 end 099 else 100 begin 101 cnt_time <= 0; 102 flag <= 0; 103 end 104 end 105 106 default : state <= 0; 107 endcase 108 end 109 end 110 111 always @ (*) 112 begin 113 if(!rst_n) 114 begin 115 data =0; 116 end 117 else 118 begin 119 case(row_col) 120 8‘b1110_1110: data =0; 121 8‘b1110_1101: data =1; //每一个按键的位置被行线和列线唯一确定 122 8‘b1110_1011: data =2; //根据行线和列线的值给出对应的按键值 123 8‘b1110_0111: data =3; 124 8‘b1101_1110: data =4; 125 8‘b1101_1101: data =5; 126 8‘b1101_1011: data =6; 127 8‘b1101_0111: data =7; 128 8‘b1011_1110: data =8; 129 8‘b1011_1101: data =9; 130 8‘b1011_1011: data =10; 131 8‘b1011_0111: data =11; 132 8‘b0111_1110: data =12; 133 8‘b0111_1101: data =13; 134 8‘b0111_1011: data =14; 135 8‘b0111_0111: data =15; 136 default : data = 0; 137 endcase 138 end 139 end 140 141 endmodule |
测试代码
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function:矩阵键盘测试代码 *****************************************************/ 000 `timescale 1ns/1ps 001 002 module key_scan_tb; 003 //系统输入 004 reg clk;//系统时钟输入 005 reg rst_n;//系统复位 006 reg [3:0] row;//矩阵键盘的行线 007 //系统输出 008 wire flag;//输出值有效标志(尖峰脉冲) 009 wire [3:0] data;//按键值 010 wire [3:0] col;//矩阵键盘的列线 011 012 initial 013 begin 014 clk=0; 015 rst_n=0; 016 # 1000.1 rst_n=1; 017 end 018 019 always #10 clk=~clk;//50M的时钟 020 021 reg [4:0] pnumber;//按键值 022 023 initial 024 begin 025 pnumber=16;//无按键按下 026 # 6000000 pnumber=1; 027 # 3000000 pnumber=16; 028 # 6000000 pnumber=1; 029 # 3000000 pnumber=16; 030 # 6000000 pnumber=1;//模仿了一段抖动 031 # 3000000 pnumber=16; 032 # 6000000 pnumber=1; 033 # 3000000 pnumber=16; 034 # 6000000 pnumber=1; 035 # 21000000 036 pnumber=1;//按键“1”按下了21ms 037 # 3000000 pnumber=16; 038 # 6000000 pnumber=1; 039 # 3000000 pnumber=16; 040 # 6000000 pnumber=1;//模仿释放时的抖动 041 # 3000000 pnumber=16; 042 # 6000000 pnumber=1; 043 # 3000000 pnumber=16; 044 # 6000000 pnumber=1; 045 # 3000000 pnumber=16; 046 # 60000000 047 pnumber=2; 048 # 3000000 pnumber=16; 049 # 6000000 pnumber=2; 050 # 3000000 pnumber=16; 051 # 6000000 pnumber=2; 052 # 3000000 pnumber=16;//按下时的抖动 053 # 6000000 pnumber=2; 054 # 21000000 055 pnumber=2;//按下21ms 056 # 3000000 pnumber=16; 057 # 6000000 pnumber=2; 058 # 3000000 pnumber=16; 059 # 6000000 pnumber=2; 060 # 3000000 pnumber=16;//释放时的抖动 061 # 6000000 pnumber=2; 062 # 3000000 pnumber=16; 063 # 6000000 pnumber=2; 064 # 3000000 pnumber=16; 065 end 066 067 //当有按键按下时,行线和列线的变化 068 always @(*) 069 case (pnumber) 070 0: row = {1‘b1,1‘b1,1‘b1,col[0]}; 071 1: row = {1‘b1,1‘b1,1‘b1,col[1]}; 072 2: row = {1‘b1,1‘b1,1‘b1,col[2]}; 073 3: row = {1‘b1,1‘b1,1‘b1,col[3]}; 074 4: row = {1‘b1,1‘b1,col[0],1‘b1}; 075 5: row = {1‘b1,1‘b1,col[1],1‘b1}; 076 6: row = {1‘b1,1‘b1,col[2],1‘b1}; 077 7: row = {1‘b1,1‘b1,col[3],1‘b1}; 078 8: row = {1‘b1,col[0],1‘b1,1‘b1}; 079 9: row = {1‘b1,col[1],1‘b1,1‘b1}; 080 10: row = {1‘b1,col[2],1‘b1,1‘b1}; 081 11: row = {1‘b1,col[3],1‘b1,1‘b1}; 082 12: row = {col[0],1‘b1,1‘b1,1‘b1}; 083 13: row = {col[1],1‘b1,1‘b1,1‘b1}; 084 14: row = {col[2],1‘b1,1‘b1,1‘b1}; 085 15: row = {col[3],1‘b1,1‘b1,1‘b1}; 086 16: row = 4‘b1111; 087 default: row = 4‘b1111; 088 endcase 089 090 //实例化key_scan模块 091 key_scan key_scan ( 092 .clk(clk), //系统时钟输入 093 .rst_n(rst_n), //系统复位 094 .row(row),//矩阵键盘的行线 095 .flag(flag),//输出值有效标志(尖峰脉冲) 096 .data(data),//按键值 097 .col(col)//矩阵键盘的列线 098 ); 099 100 endmodule |
在测试模块的中的68行至88行,描述了矩阵键盘的响应方式。假如:“5”被按下,“5”处在row[1]和col[1]的位置,只有当col[1]为低电平时,row[1]才能检测到低电平,并且row=4’b1101唯一确定了按键的位置。
在测试中,模拟了数字“1”以及数字“2”按下以及释放时的抖动。
仿真分析
从波形中,我们可以看出:按键稳定前,pnumber有一段抖动,稳定之后,data变成了按键值,释放时pnumber又有一段抖动,两段抖动data都没有发生改变。
原文地址:https://www.cnblogs.com/mengyi1989/p/11521086.html