明德扬论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

微信扫一扫,快捷登录!

查看: 68|回复: 0

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

[复制链接]
发表于 2020-7-29 10:00:06 | 显示全部楼层 |阅读模式

马上注册,看完整文章,学更多FPGA知识。

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
【上板现象】

按键控制数字时钟在点拨板的上板现象


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



【设计教程】

至简设计系列_矩阵按键检测

--作者:肖肖肖


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 系统结构框图
系统结构框图如下图一所示:
01.JPG


图一
1.1.4模块功能

矩阵键盘模块实现功能
1、将外来异步信号打两拍处理,将异步信号同步化。
2、实现20ms按键消抖功能。
3、实现矩阵键盘的按键检测功能,并输出有效按键信号。

数码管显示模块实现功能
1、  对接收到的按键数据进行译码并显示。

1.1.5顶层信号
  
信号名
  
I/O
位宽
定义
  
clk
  
I
1
系统工作时钟 50M
  
rst_n
  
I
1
系统复位信号,低电平有效
  
Key_col
  
I
4
4位矩阵键盘列信号,最高位表示矩阵键盘往右数第四列,默认高电平
  
Key_row
  
O
4
4位矩阵键盘行信号,最高位表示矩阵键盘往下数第四行
  
Segment
  
O
8
8位数码管段选信号
  
Seg_sel
  
O
2
2位数码管位选信号

1.1.6参考代码

下面是使用工程的顶层代码:

  1. module top_KeyScanCheck(
  2.     clk         ,
  3.     rst_n       ,
  4.     key_col     ,

  5.     key_row     ,
  6.     segment     ,
  7.     seg_sel     
  8.     );

  9.     parameter      DATA_W =        4;

  10.     input               clk    ;
  11.     input               rst_n  ;
  12.     input   [3:0]       key_col;
  13.    
  14.     output  [3:0]       key_row;
  15.     output  [7:0]       segment;
  16.     output  [1:0]       seg_sel;

  17.     wire     [3:0]       key_row;
  18.     wire     [7:0]       segment;
  19.     wire     [1:0]       seg_sel;

  20.     wire    [DATA_W-1:0]  key_out;
  21.     wire                  key_vld;
  22.     wire    [DATA_W-1:0]  segment_data;

  23. key_scan    u1(
  24.    .clk        (   clk                 ),
  25.    .rst_n      (   rst_n               ),
  26.    .key_col    (   key_col             ),
  27.    .key_row    (   key_row             ),
  28.    .key_out    (   key_out             ),
  29.    .key_vld    (   key_vld             )
  30.     );


  31. seg_disp    u2(
  32.    .clk               (  clk             ),
  33.    .rst_n             (  rst_n           ),
  34.    .data_in           (  key_out           ),
  35.    .key_en            (  key_vld           ),
  36.    .segment           (  segment           ),
  37.    .seg_sel           (  seg_sel           )
  38. );

  39.     endmodule
复制代码



1.2 矩阵键盘模块设计

1.2.1接口信号
  
信号名
  
I/O
位宽
定义
  
clk
  
I
1
系统工作时钟 50M
  
rst_n
  
I
1
系统复位信号,低电平有效
  
key_col
  
I
4
4位矩阵键盘列信号,最高位表示矩阵键盘往右数第四列,默认高电平
  
key_row
  
O
4
4位矩阵键盘行信号,最高位表示矩阵键盘往下数第四行
  
key_out
  
O
4
矩阵按键数据
  
key_vld
  
O
1
按键有效指示信号
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个相交点。
如下图所示:
02.png

确认矩阵键盘上哪个按键被按下有多同方法,其中行扫描法又称为逐行(或列)扫描查询法,是一种最常用的按键识别方法。
1.       判断键盘中有无键按下:将全部行线KEY_R1~KEY_R4 置低电平,然后检测列线 KEY_C1~KEY_C4 的状态。只要有一列的电平为低,则表示键盘中有键被按下,而且闭合的键位于低电平线与4根行线相交叉的4 个按键之中。若所有列线均为高电平,则键盘中无键按下。
2.       判断闭合键所在的位置:在确认有键按下后,即可进入确定具体闭合键的过程。其方法是:依次将行线置为低电平,即在置某根行线为低电平时,其它线为高电平。在确定某根行线位置为低电平后,再逐行检测各列线的电平状态。若某列为低,则该列线与置为低电平的行线交叉处的按键就是闭合的按键。
打拍操作
输入的key_col是异步信号,所以要进行打两拍操作,将异步信号key_col同步化,并防止亚稳态。
设计代码如下:
  1.   input   [3:0]           key_col     ;
  2.    
  3.   reg     [3:0]           key_col_ff0      ;
  4.   reg     [3:0]           key_col_ff1      ;
  5.    
  6.   always  @(posedge clk or negedge rst_n)begin
  7.       if(rst_n==1'b0)begin
  8.           key_col_ff0 <= 4'b1111;
  9.           key_col_ff1 <= 4'b1111;
  10.      end
  11.      else begin
  12.          key_col_ff0 <= key_col    ;
  13.          key_col_ff1 <= key_col_ff0;
  14.      end
  15. end
复制代码




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

由于按键按下去的时间一般都会大于20ms,为了达到不管按键按下多久,都视为按下一次的效果,提出以下计数器架构,如下图所示:
04.JPG


消抖计数器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参考代码

  1. module key_scan(
  2.     clk    ,
  3.     rst_n  ,
  4.     key_col,
  5.     key_row,
  6.     key_out,
  7.     key_vld
  8.     );

  9.     parameter       DATA_W      =   4           ;
  10.     parameter       TIME_20MS   =   1_000_000   ;

  11.     input                   clk         ;
  12.     input                   rst_n       ;
  13.     input   [3:0]           key_col     ;

  14.     output  [3:0]           key_row     ;
  15.     output                  key_vld     ;
  16.     output  [DATA_W-1:0]    key_out     ;

  17.     reg     [3:0]           key_row     ;
  18.     reg                     key_vld     ;
  19.     reg     [DATA_W-1:0]    key_out     ;

  20.     reg     [3:0]           key_col_ff0 ;
  21.     reg     [3:0]           key_col_ff1 ;


  22. always  @(posedge clk or negedge rst_n)begin
  23.     if(rst_n==1'b0)begin
  24.         key_col_ff0 <= 4'b1111;
  25.         key_col_ff1 <= 4'b1111;
  26.     end
  27.     else begin
  28.         key_col_ff0 <= key_col    ;
  29.         key_col_ff1 <= key_col_ff0;
  30.     end
  31. end

  32. reg         key_col_check;
  33. always  @(posedge clk or negedge rst_n)begin
  34.     if(rst_n==1'b0)begin
  35.         key_col_check <= 1'b0;
  36.     end
  37.     else if(key_col_ff1 !=4'hf && end_shake)begin
  38.         key_col_check <= 1'b1;
  39.     end
  40.     else if(key_col_ff1==4'hf)begin
  41.         key_col_check <= 1'b0;
  42.     end
  43. end

  44. reg [ 21:0]  shake     ;
  45. wire        add_shake ;
  46. wire        end_shake ;
  47. always @(posedge clk or negedge rst_n) begin
  48.     if (rst_n==0) begin
  49.         shake <= 0;
  50.     end
  51.     else if(add_shake) begin
  52.         if(end_shake)
  53.             shake <= 0;
  54.         else
  55.             shake <= shake+1 ;
  56.    end
  57. end
  58. assign add_shake = key_col_ff1 !=4'hf || key_row_check==1;
  59. assign end_shake = add_shake  && shake == TIME_20MS-1 ;



  60. reg     [1:0]   key_col_get     ;
  61. always  @(posedge clk or negedge rst_n)begin
  62.     if(rst_n==1'b0)begin
  63.         key_col_get <= 0;
  64.     end
  65.     else if(key_col_check) begin
  66.         if(key_col_ff1==4'b1110)
  67.             key_col_get <= 0;
  68.         else if(key_col_ff1==4'b1101)
  69.             key_col_get <= 1;
  70.         else if(key_col_ff1==4'b1011)
  71.             key_col_get <= 2;
  72.         else if(key_col_ff1==4'b0111)
  73.             key_col_get <= 3;
  74.     end
  75. end

  76. reg             key_row_check       ;
  77. always  @(posedge clk or negedge rst_n)begin
  78.     if(rst_n==1'b0)begin
  79.         key_row_check <= 0;
  80.     end
  81.     else if(key_col_check)begin
  82.         key_row_check <= 1;
  83.     end
  84.     else if(key_vld)begin
  85.         key_row_check <= 0;
  86.     end
  87. end

  88. reg   [1:0]         row_index        ;
  89. wire                add_row_index    ;
  90. wire                end_row_index    ;
  91. always @(posedge clk or negedge rst_n) begin
  92.     if (rst_n==0) begin
  93.         row_index <= 0;
  94.     end
  95.     else if(add_row_index) begin
  96.         if(end_row_index)
  97.             row_index <= 0;
  98.         else
  99.             row_index <= row_index+1 ;
  100.    end
  101. end
  102. assign add_row_index = key_row_check && end_shake;
  103. assign end_row_index = add_row_index  && row_index == 4-1 ;



  104. always  @(posedge clk or negedge rst_n)begin
  105.     if(rst_n==1'b0)begin
  106.         key_row = 4'b0;
  107.     end
  108.     else if(key_row_check)begin
  109.         key_row = ~(4'b0001 << row_index);
  110.     end
  111.     else begin
  112.         key_row = 4'b0;
  113.     end
  114. end



  115. always  @(posedge clk or negedge rst_n)begin
  116.     if(rst_n==1'b0)begin
  117.         key_vld <= 1'b0;
  118.     end
  119.     else if(key_row_check && key_col_ff1[key_col_get]==1'b0 && key_col_check==0 )begin
  120.         key_vld <= 1'b1;
  121.     end
  122.     else begin
  123.         key_vld <= 1'b0;
  124.     end
  125. end


  126. always  @(posedge clk or negedge rst_n)begin
  127.     if(rst_n==1'b0)begin
  128.         key_out <= 4'd0;
  129.     end
  130.     else if(key_vld)begin
  131.         key_out <= {row_index,key_col_get};   
  132.     end
  133.     else begin
  134.         key_out <= 4'd0;
  135.     end
  136. end

  137. endmodul
复制代码



1.3 数码管显示模块设计1.3.1接口信号
  
信号名
  
I/O
位宽
定义
  
clk
  
I
1
系统工作时钟 50M
  
rst_n
  
I
1
系统复位信号,低电平有效
  
data_in
  
I
4
矩阵按键数据
  
key_en
  
I
1
按键有效指示信号
  
segment
  
O
8
8位数码管段选信号
  
seg_sel
  
O
2
2位数码管位选信号


1.3.2设计思路
在前面的案例中已经有数码管显示的介绍,所以这里不在过多介绍,详细介绍请看下方链接:

1.3.3参考代码
  1. module seg_disp(
  2.     clk         ,
  3.     rst_n       ,
  4.     data_in     ,
  5.     key_en      ,
  6.     segment     ,
  7.     seg_sel      
  8. );

  9. parameter   ZERO           =   8'b0000_0011          ;
  10. parameter   ONE            =   8'b1001_1111          ;
  11. parameter   TWO            =   8'b0010_0101          ;
  12. parameter   THREE          =   8'b0000_1101          ;
  13. parameter   FOUR           =   8'b1001_1001          ;
  14. parameter   FIVE           =   8'b0100_1001          ;
  15. parameter   SIX            =   8'b0100_0001          ;
  16. parameter   SEVEN          =   8'b0001_1111          ;
  17. parameter   EIGHT          =   8'b0000_0001          ;
  18. parameter   NINE           =   8'b0000_1001          ;

  19. input                 clk             ;         
  20. input                 rst_n           ;   
  21. input    [4:0]        data_in         ;
  22. input                 key_en          ;
  23. output   [7:0 ]       segment         ;
  24. output   [1:0 ]       seg_sel         ;

  25. reg      [7:0 ]       segment         ;
  26. reg      [1:0 ]       seg_sel         ;
  27. reg      [10:0]       delay           ;
  28. reg      [1:0 ]       delay_time      ;
  29. wire                  add_delay_time  ;
  30. wire                  end_delay_time  ;
  31. wire                  add_delay       ;
  32. wire                  end_delay       ;
  33. reg     [4:0 ]        segment_tmp     ;




  34. always @(posedge clk or negedge rst_n) begin
  35.     if (rst_n==0) begin
  36.         delay <= 0;
  37.     end
  38.     else if(add_delay) begin
  39.         if(end_delay)
  40.             delay <= 0;
  41.         else
  42.             delay <= delay+1 ;
  43.    end
  44. end
  45. assign add_delay = 1;
  46. assign end_delay = add_delay  && delay == 2000-1 ;



  47. always @(posedge clk or negedge rst_n) begin
  48.     if (rst_n==0) begin
  49.         delay_time <= 0;
  50.     end
  51.     else if(add_delay_time) begin
  52.         if(end_delay_time)
  53.             delay_time <= 0;
  54.         else
  55.             delay_time <= delay_time+1 ;
  56.    end
  57. end
  58. assign add_delay_time = end_delay;
  59. assign end_delay_time = add_delay_time  && delay_time == 2-1 ;


  60. reg     [3:0]       segment_data;
  61. always  @(posedge clk or negedge rst_n)begin
  62.     if(rst_n==1'b0)begin
  63.         segment_data <= 4'd0;
  64.     end
  65.     else if(key_en)begin
  66.         segment_data <= data_in;
  67.     end
  68. end

  69. always  @(posedge clk or negedge rst_n)begin
  70.     if(rst_n==1'b0)begin
  71.         segment_tmp <= 4'd0;
  72.     end
  73.     else if(add_delay_time  && delay_time == 1-1)begin
  74.         segment_tmp <= (segment_data+1)%10;
  75.     end
  76.     else if(end_delay_time)begin
  77.         segment_tmp <= (segment_data+1)/10;
  78.     end
  79. end


  80. always  @(posedge clk or negedge rst_n)begin
  81.     if(rst_n==1'b0)begin
  82.         segment <= ZERO;
  83.     end
  84.     else begin
  85.         case(segment_tmp)
  86.             4'd0:segment <= ZERO;
  87.             4'd1:segment <= ONE  ;
  88.             4'd2:segment <= TWO  ;
  89.             4'd3:segment <= THREE;
  90.             4'd4:segment <= FOUR ;
  91.             4'd5:segment <= FIVE ;
  92.             4'd6:segment <= SIX  ;
  93.             4'd7:segment <= SEVEN;
  94.             4'd8:segment <= EIGHT;
  95.             4'd9:segment <= NINE ;
  96.             default:begin
  97.                 segment <= segment;
  98.             end
  99.         endcase
  100.     end
  101. end


  102. always  @(posedge clk or negedge rst_n)begin
  103.     if(rst_n==1'b0)begin
  104.         seg_sel <= 2'b11;
  105.     end
  106.     else begin
  107.         seg_sel <= ~(2'b1<<delay_time);
  108.     end
  109. end


  110. endmodule
复制代码


1.4 效果和总结

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



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

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

【设计教程下载】

至简设计系列_矩阵按键检测.pdf (1 MB, 下载次数: 1)
1 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香! 鲜花榜单
FPGA视频课程  培训班 FPGA学习资料
吴老师 18022857217(微信同号) Q1241003385
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|MDYBBS ( 粤ICP备16061416号-1

GMT+8, 2020-8-6 05:37 , Processed in 0.535332 second(s), 13 queries , File On.

Powered by Discuz! X3.4

本论坛由广州健飞通信有限公司所有

© 2001-2019 Comsenz Inc.

快速回复 返回顶部 返回列表