• 进阶项目(11) 矩阵键盘程序设计讲解


    写在前面的话

    在使用按键的时候,如果按键不多的话,我们可以直接让按键与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都没有发生改变。

     

  • 相关阅读:
    [C# 基础知识系列]专题六:泛型基础篇——为什么引入泛型
    [C#基础知识系列]专题十七:深入理解动态类型
    [C# 基础知识系列]专题九: 深入理解泛型可变性
    C#网络编程系列文章索引
    [C#基础知识系列]专题十:全面解析可空类型
    [C# 基础知识系列]专题十一:匿名方法解析
    [C# 基础知识系列]专题十六:Linq介绍
    VSTO之旅系列(一):VSTO入门
    [C# 网络编程系列]专题七:UDP编程补充——UDP广播程序的实现
    [C# 网络编程系列]专题四:自定义Web浏览器
  • 原文地址:https://www.cnblogs.com/mengyi1989/p/11521086.html
Copyright © 2020-2023  润新知