明德扬吴老师 发表于 2020-7-29 10:00:06

【每周FPGA案例】至简设计系列_矩阵按键检测

【上板现象】

按键控制数字时钟在点拨板的上板现象
https://www.bilibili.com/video/BV1Af4y117H4?p=15

按键控制数字时钟在实现箱的上板现象

https://www.bilibili.com/video/BV1Af4y117H4?p=16

【设计教程】

至简设计系列_矩阵按键检测
--作者:肖肖肖

1.1 总体设计

1.1.1 概述

在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式。在矩阵式键盘中,每条水平线和垂直线在交叉处不直接连通,而是通过一个按键加以连接。这样,一个端口(如P1口)就可以构成4*4=16个按键,比之直接将端口线用于键盘多出了一倍,而且线数越多,区别越明显,比如再多加一条线就可以构成20键的键盘,而直接用端口线则只能多出一键(9键)。由此可见,在需要的键数比较多时,采用矩阵法来做键盘是合理的。
1.1.2 设计目标

完成矩阵键盘的扫描检测程序,具体功能要求如下:
1.       运用逐行扫描的方法进行按键监测; 2.       每行扫描的时间不少于 20ms,滤除抖动; 3.       检测到有按键按下之后,消抖时间20ms;4.       输出信号 key_vld 持续一拍即可; 5.       输出信号key_out表示16 个按键,并在数码管上显示对应数值;
1.1.3 系统结构框图系统结构框图如下图一所示:

图一1.1.4模块功能

矩阵键盘模块实现功能
1、将外来异步信号打两拍处理,将异步信号同步化。2、实现20ms按键消抖功能。3、实现矩阵键盘的按键检测功能,并输出有效按键信号。
数码管显示模块实现功能
1、对接收到的按键数据进行译码并显示。
1.1.5顶层信号
信号名I/O位宽定义
clkI1系统工作时钟 50M
rst_nI1系统复位信号,低电平有效
Key_colI44位矩阵键盘列信号,最高位表示矩阵键盘往右数第四列,默认高电平
Key_rowO44位矩阵键盘行信号,最高位表示矩阵键盘往下数第四行
SegmentO88位数码管段选信号
Seg_selO22位数码管位选信号

1.1.6参考代码

下面是使用工程的顶层代码:
module top_KeyScanCheck(
    clk         ,
    rst_n       ,
    key_col   ,

    key_row   ,
    segment   ,
    seg_sel   
    );

    parameter      DATA_W =      4;

    input               clk    ;
    input               rst_n;
    input          key_col;
   
    output       key_row;
    output       segment;
    output       seg_sel;

    wire          key_row;
    wire          segment;
    wire          seg_sel;

    wire    key_out;
    wire                  key_vld;
    wire    segment_data;

key_scan    u1(
   .clk      (   clk               ),
   .rst_n      (   rst_n               ),
   .key_col    (   key_col             ),
   .key_row    (   key_row             ),
   .key_out    (   key_out             ),
   .key_vld    (   key_vld             )
    );


seg_disp    u2(
   .clk               (clk             ),
   .rst_n             (rst_n         ),
   .data_in         (key_out         ),
   .key_en            (key_vld         ),
   .segment         (segment         ),
   .seg_sel         (seg_sel         )
);

    endmodule



1.2 矩阵键盘模块设计

1.2.1接口信号
信号名I/O位宽定义
clkI1系统工作时钟 50M
rst_nI1系统复位信号,低电平有效
key_colI44位矩阵键盘列信号,最高位表示矩阵键盘往右数第四列,默认高电平
key_rowO44位矩阵键盘行信号,最高位表示矩阵键盘往下数第四行
key_outO4矩阵按键数据
key_vldO1按键有效指示信号
1.2.2 设计思路

行扫描法原理开发板上为 4*4 矩阵键盘:默认 4 条列线上来高电平,4 条行线默认接高电平。列线 KEY_C1 ~ KEY_C4分别接有4个上拉电阻到正电源 +3.3 V,并把列线 KEY_C1~KEY_C4设置为输入线,行线KEY_R1~KEY_R4设置为输出线。4根行线和4根列线形成16个相交点。如下图所示:
确认矩阵键盘上哪个按键被按下有多同方法,其中行扫描法又称为逐行(或列)扫描查询法,是一种最常用的按键识别方法。1.       判断键盘中有无键按下:将全部行线KEY_R1~KEY_R4 置低电平,然后检测列线 KEY_C1~KEY_C4 的状态。只要有一列的电平为低,则表示键盘中有键被按下,而且闭合的键位于低电平线与4根行线相交叉的4 个按键之中。若所有列线均为高电平,则键盘中无键按下。 2.       判断闭合键所在的位置:在确认有键按下后,即可进入确定具体闭合键的过程。其方法是:依次将行线置为低电平,即在置某根行线为低电平时,其它线为高电平。在确定某根行线位置为低电平后,再逐行检测各列线的电平状态。若某列为低,则该列线与置为低电平的行线交叉处的按键就是闭合的按键。打拍操作输入的key_col是异步信号,所以要进行打两拍操作,将异步信号key_col同步化,并防止亚稳态。设计代码如下:input            key_col   ;
   
reg            key_col_ff0      ;
reg            key_col_ff1      ;
   
always@(posedge clk or negedge rst_n)begin
      if(rst_n==1'b0)begin
          key_col_ff0 <= 4'b1111;
          key_col_ff1 <= 4'b1111;
   end
   else begin
         key_col_ff0 <= key_col    ;
         key_col_ff1 <= key_col_ff0;
   end
end




按键消抖对于按键和触摸屏等机械设备来说,都存在一个固有问题,那就是“抖动”,按键从最初接通到稳定接通要经过数毫秒,其间可能发生多次“接通-断开”这样的毛刺。如果不进行处理,会使系统识别到抖动信号而进行不必要的反应,导致模块功能不正常,为了避免这种现象的产生,需要进行按键消抖的操作。软件方法消抖,即检测出键闭合后执行一个延时程序,抖动时间的长短由按键的机械特性决定,一般为5ms~20ms,让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认按下按键操作有效。当检测到按键释放后,也要给5ms~20ms的延时,待后沿抖动消失后才能转入该键的处理程序。
由于按键按下去的时间一般都会大于20ms,为了达到不管按键按下多久,都视为按下一次的效果,提出以下计数器架构,如下图所示:

消抖计数器shake_cnt:用于计算20ms的时间,加一条件为key_col_ff1 != 4'hf || key_row_check==1,表示当某个按键按下或者进行行扫描时就开始计数;数到1,000,000下,表示数到20ms就结束。行扫描计数器row_index:用于区分扫描的行,加一条件为key_row_check && end_shake_cnt,表示当处于行扫描状态并且每行消抖20ms后,开始扫描下一行;数到4下,表示4行按键扫描完了。按键:表示有无按键按下,没被按下时为高电平,按下后为低电平。行扫描指示信号key_row_check:该信号为高电平,指示当前处于行扫描状态。矩阵键盘列信号key_col_ff1:4bit位宽的矩阵键盘列信号,最高位表示矩阵键盘往右数第四列,默认信号为key_col_ff1 = 4'hf,否则表示该信号低电平对应位的列有按键按下。
1.2.3参考代码

module key_scan(
    clk    ,
    rst_n,
    key_col,
    key_row,
    key_out,
    key_vld
    );

    parameter       DATA_W      =   4         ;
    parameter       TIME_20MS   =   1_000_000   ;

    input                   clk         ;
    input                   rst_n       ;
    input            key_col   ;

    output         key_row   ;
    output                  key_vld   ;
    output    key_out   ;

    reg            key_row   ;
    reg                     key_vld   ;
    reg       key_out   ;

    reg            key_col_ff0 ;
    reg            key_col_ff1 ;


always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_col_ff0 <= 4'b1111;
      key_col_ff1 <= 4'b1111;
    end
    else begin
      key_col_ff0 <= key_col    ;
      key_col_ff1 <= key_col_ff0;
    end
end

reg         key_col_check;
always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_col_check <= 1'b0;
    end
    else if(key_col_ff1 !=4'hf && end_shake)begin
      key_col_check <= 1'b1;
    end
    else if(key_col_ff1==4'hf)begin
      key_col_check <= 1'b0;
    end
end

reg [ 21:0]shake   ;
wire      add_shake ;
wire      end_shake ;
always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      shake <= 0;
    end
    else if(add_shake) begin
      if(end_shake)
            shake <= 0;
      else
            shake <= shake+1 ;
   end
end
assign add_shake = key_col_ff1 !=4'hf || key_row_check==1;
assign end_shake = add_shake&& shake == TIME_20MS-1 ;



reg      key_col_get   ;
always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_col_get <= 0;
    end
    else if(key_col_check) begin
      if(key_col_ff1==4'b1110)
            key_col_get <= 0;
      else if(key_col_ff1==4'b1101)
            key_col_get <= 1;
      else if(key_col_ff1==4'b1011)
            key_col_get <= 2;
      else if(key_col_ff1==4'b0111)
            key_col_get <= 3;
    end
end

reg             key_row_check       ;
always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_row_check <= 0;
    end
    else if(key_col_check)begin
      key_row_check <= 1;
    end
    else if(key_vld)begin
      key_row_check <= 0;
    end
end

reg            row_index      ;
wire                add_row_index    ;
wire                end_row_index    ;
always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      row_index <= 0;
    end
    else if(add_row_index) begin
      if(end_row_index)
            row_index <= 0;
      else
            row_index <= row_index+1 ;
   end
end
assign add_row_index = key_row_check && end_shake;
assign end_row_index = add_row_index&& row_index == 4-1 ;



always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_row = 4'b0;
    end
    else if(key_row_check)begin
      key_row = ~(4'b0001 << row_index);
    end
    else begin
      key_row = 4'b0;
    end
end



always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_vld <= 1'b0;
    end
    else if(key_row_check && key_col_ff1==1'b0 && key_col_check==0 )begin
      key_vld <= 1'b1;
    end
    else begin
      key_vld <= 1'b0;
    end
end


always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_out <= 4'd0;
    end
    else if(key_vld)begin
      key_out <= {row_index,key_col_get};   
    end
    else begin
      key_out <= 4'd0;
    end
end

endmodul


1.3 数码管显示模块设计1.3.1接口信号
信号名I/O位宽定义
clkI1系统工作时钟 50M
rst_nI1系统复位信号,低电平有效
data_inI4矩阵按键数据
key_enI1按键有效指示信号
segmentO88位数码管段选信号
seg_selO22位数码管位选信号


1.3.2设计思路在前面的案例中已经有数码管显示的介绍,所以这里不在过多介绍,详细介绍请看下方链接:http://fpgabbs.com/forum.php?mod=viewthread&tid=399
1.3.3参考代码
module seg_disp(
    clk         ,
    rst_n       ,
    data_in   ,
    key_en      ,
    segment   ,
    seg_sel      
);

parameter   ZERO         =   8'b0000_0011          ;
parameter   ONE            =   8'b1001_1111          ;
parameter   TWO            =   8'b0010_0101          ;
parameter   THREE          =   8'b0000_1101          ;
parameter   FOUR         =   8'b1001_1001          ;
parameter   FIVE         =   8'b0100_1001          ;
parameter   SIX            =   8'b0100_0001          ;
parameter   SEVEN          =   8'b0001_1111          ;
parameter   EIGHT          =   8'b0000_0001          ;
parameter   NINE         =   8'b0000_1001          ;

input               clk             ;         
input               rst_n         ;   
input          data_in         ;
input               key_en          ;
output          segment         ;
output          seg_sel         ;

reg             segment         ;
reg             seg_sel         ;
reg             delay         ;
reg             delay_time      ;
wire                  add_delay_time;
wire                  end_delay_time;
wire                  add_delay       ;
wire                  end_delay       ;
reg         segment_tmp   ;




always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      delay <= 0;
    end
    else if(add_delay) begin
      if(end_delay)
            delay <= 0;
      else
            delay <= delay+1 ;
   end
end
assign add_delay = 1;
assign end_delay = add_delay&& delay == 2000-1 ;



always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      delay_time <= 0;
    end
    else if(add_delay_time) begin
      if(end_delay_time)
            delay_time <= 0;
      else
            delay_time <= delay_time+1 ;
   end
end
assign add_delay_time = end_delay;
assign end_delay_time = add_delay_time&& delay_time == 2-1 ;


reg          segment_data;
always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      segment_data <= 4'd0;
    end
    else if(key_en)begin
      segment_data <= data_in;
    end
end

always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      segment_tmp <= 4'd0;
    end
    else if(add_delay_time&& delay_time == 1-1)begin
      segment_tmp <= (segment_data+1)%10;
    end
    else if(end_delay_time)begin
      segment_tmp <= (segment_data+1)/10;
    end
end


always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      segment <= ZERO;
    end
    else begin
      case(segment_tmp)
            4'd0:segment <= ZERO;
            4'd1:segment <= ONE;
            4'd2:segment <= TWO;
            4'd3:segment <= THREE;
            4'd4:segment <= FOUR ;
            4'd5:segment <= FIVE ;
            4'd6:segment <= SIX;
            4'd7:segment <= SEVEN;
            4'd8:segment <= EIGHT;
            4'd9:segment <= NINE ;
            default:begin
                segment <= segment;
            end
      endcase
    end
end


always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      seg_sel <= 2'b11;
    end
    else begin
      seg_sel <= ~(2'b1<<delay_time);
    end
end


endmodule

1.4 效果和总结

下图是该工程在db603开发板上的现象——按下按键s7


下图是该工程在ms980试验箱上的现象——按下按键s13

由于该项目的上板现象是按下矩阵按键,数码管显示对应的按键号,想观看完整现象的朋友可以看一下现象演示的视频。感兴趣的朋友也可以访问明德扬论坛(http://www.fpgabbs.cn/)进行FPGA相关工程设计学习,也可以看一下我们往期的文章:《基于FPGA的密码锁设计》《波形相位频率可调DDS信号发生器》《基于FPGA的曼彻斯特编码解码设计》《基于FPGA的出租车计费系统》《数电基础与Verilog设计》《基于FPGA的频率、电压测量》《基于FPGA的汉明码编码解码设计》《关于锁存器问题的讨论》《阻塞赋值与非阻塞赋值》《参数例化时自动计算位宽的解决办法》1.5 公司简介明德扬是一家专注于FPGA领域的专业性公司,公司主要业务包括开发板、教育培训、项目承接、人才服务等多个方向。点拨开发板——学习FPGA的入门之选。
MP801开发板——千兆网、ADDA、大容量SDRAM等,学习和项目需求一步到位。网络培训班——不管时间和空间,明德扬随时在你身边,助你快速学习FPGA。周末培训班——明天的你会感激现在的努力进取,升职加薪明德扬来助你。就业培训班——七大企业级项目实训,获得丰富的项目经验,高薪就业。专题课程——高手修炼课:提升设计能力;实用调试技巧课:提升定位和解决问题能力;FIFO架构设计课:助你快速成为架构设计师;时序约束、数字信号处理、PCIE、综合项目实践课等你来选。项目承接——承接企业FPGA研发项目。人才服务——提供人才推荐、人才代培、人才派遣等服务。
【设计教程下载】


【设计视频教程】
https://www.bilibili.com/video/BV1Af4y117H4?p=14

【工程源码】




明德扬娄老师 发表于 2021-1-12 18:02:59


dextero11 发表于 2021-6-28 13:02:49

明德扬娄老师 发表于 2021-1-12 18:02


加1为了显示在数码管上的数字从1开始,更符合阅读习惯!:$
页: [1]
查看完整版本: 【每周FPGA案例】至简设计系列_矩阵按键检测