本帖最后由 mengyi1989 于 2019-12-7 13:41 编辑
立即学习>>梦翼师兄的FPGA实战课程众筹
写在前面的话
在使用按键的时候,如果按键不多的话,我们可以直接让按键与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对按键的一次闭合仅作一次处理,必须去除按键抖动。在按键闭合稳定时读取按键的电平状态,并且必须判别到按键释放稳定后再作处理。
然后我们就可以利用这些现象,来设计一个识别按键的电路。
架构设计
根据原理分析,我们设计出架构图如下:
模块功能介绍
顶层模块端口描述
端口名
| 端口说明
| 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都没有发生改变。
想要与梦翼师兄交流么?想要跟其他工程交流么? 添加客服微信:mlajsw96【备注FPGA进群】
|