【连载】 FPGA Verilog HDL 系列实例--------矩阵键盘接口
Verilog HDL 之 矩阵键盘接口
1、矩阵键盘的原理
  矩阵键盘又叫行列式键盘。用带IO口的线组成行列结构,按键设置在行列的交点上。例如用4×4的行列式结构可以构成16个键的键盘。这样,当按键数量平方增长时,I/O口只是线性增长,这样就可以节省I/O口。矩阵键盘的原理图如图1.1所示:
         
图1.1 矩阵键盘的原理图
  按键设置在行列线交叉点,行列线分别连接到按键开关的两端。列线通过上拉电阻接3.3V电压,即列线的输出被钳位到高电平状态。
  判断键盘中有无按键按下式通过行线送入扫描线好然后从列线读取状态得到的。其方法是依次给行线送低电平,检查列线的输入。如果列线全是高电平,则代表低电平信号所在的行中无按键按下;如果列线有输入为低电平,则代表低电平信号所在的行和出现低电平的列的交点处有按键按下。
2、一个完整的键盘控制程序应解决以下任务:
  (1)检测有无按键按下
  (2)有键按下,在无硬件去抖得情况下,应有软件延时除去抖动影响
  (3)键扫描程序
  (4)将键编码转换成相应建值
  整个设计程序包括三个模块:时钟分频、键盘扫描和键译码转换。为了显示,还必须在顶层添加显示部分。
3、时钟分频
  由于使用的外部时钟频率为50MHz,这个频率对扫描来说太高,所以这里需要一个分频器来分得适合键盘扫描使用的频率。
scan_clk.v 
 1 //-------------------------------------------------------------------------------------------------
 2 // File        : scan_clk.v
 3 // Generated   : 2011-07-20
 4 // Author      : wangliang
 5 //-------------------------------------------------------------------------------------------------
 6 `timescale 1 ns / 1 ps
 7 
 8 module scan_clk ( clkout ,clk ,rst );
 9     
10 input rst ;
11 input clk ;
12 wire clk ;
13 
14 output clkout ;
15 
16 reg clkout_r ; 
17 
18 parameter  period= 200000;    
19 //parameter  period= 10;
20 reg [31:0] cnt;  
21 
22 always @( posedge clk or negedge rst)                  //分频50Hz
23     begin 
24         if ( !rst )
25         begin
26             cnt <= {31{1'b0}}  ;
27             clkout_r <= 0 ;    
28             end        
29         else begin 
30             cnt<= cnt+1;
31         if (cnt    == (period >> 1) - 1)                   //设定周期时间的一半
32                 clkout_r <= #1 1'b1;
33         else if (cnt == period - 1)                    //设定的周期时间
34             begin 
35                 clkout_r <= #1 1'b0;
36                  cnt <= #1 'b0;      
37             end
38          
39         end
40      end
41 assign clkout = clkout_r ;    
42     
43 endmodule
  注:从上面可以看出,我们经过分频得到50HZ的时钟,是为下一模块键盘扫描提供的时钟,之所以是50HZ,而不是其他的数值,是因为,人在按键的时候,其停留时间大概是20ms左右,键盘扫描时,会发送固定的数值码,本例中是循环的发送4种数值,下面会提及到,50HZ转化为时间是0.02S=20ms。图1.2是分频模块。
        
图1.2 分频模块
4、键盘扫描
  键盘扫描电路是用于产生keydrv3~ keydrv0 信号,其变化顺序是1110→1101→1011→0111→1110…周而复始地扫描。其停留时间大慨在20ms。更短的时间没有必要,因为人为按键的时间大慨为20ms,不可能产生有更快的动作;另外,更短的停留时间还容易采集到抖动信号,会干扰判断。而太长的时间容易丢失某些较快的按键动作。图1.3是键盘扫描模块。
key_scan.v 
 1 //-------------------------------------------------------------------------------------------------
 2 // File        : key_scan.v
 3 // Generated   : 2011-07-20
 4 // Author      : wangliang
 5 //-------------------------------------------------------------------------------------------------
 6 `timescale 1 ns / 1 ps
 7 
 8 module key_scan ( clk ,keydrv ,rst );
 9 
10 input clk ;    
11 input rst ;
12 wire clk ;
13 
14 output [3:0] keydrv ;
15 wire [3:0] keydrv ;
16 
17 parameter s1 = 4'b1110;
18 parameter s2 = 4'b1101;
19 parameter s3 = 4'b1011;
20 parameter s4 = 4'b0111;     
21 
22 reg [3:0]current_state;
23 reg [3:0]next_state;
24 
25 always @ ( posedge clk or negedge rst )
26     
27     begin 
28         if ( !rst )
29             current_state <= s1    ;
30         else    current_state <= next_state       ;
31         end
32         
33 always      @ ( current_state )
34     begin
35         case  ( current_state )
36             s1: next_state <=s2;     
37             s2: next_state <=s3;
38             s3: next_state <=s4;
39             s4: next_state <=s1;
40             default:  next_state <=s1;
41     endcase
42     end
43     
44 assign    keydrv =  current_state      ;
45     
46 endmodule
        
图1.3 键盘扫描模块
5、键译码转换
  
  键盘译码电路是从keydrv3~keydrv0和keyin3~keyin0信号中译码出按键值的电路。clk是全局时钟,由外部晶振提供。clk在系统的频率是最高的,其他的时钟由分频产生。Keydrv表示键盘扫描信号,keyin为键盘输入信号,keyvalue为键值。其外部接口如图1.4所示。
View Code 
  1 //-------------------------------------------------------------------------------------------------
  2 // File        : top.v
  3 // Generated   : 2011-07-20
  4 // Author      : wangliang
  5 //-------------------------------------------------------------------------------------------------
  6 `timescale 1 ns / 1 ps
  7     
  8 module top ( KEYO ,KEYI ,clk ,Y , rst);
  9 
 10 input    [3:0]    KEYO ;               //与原理图一致,是键盘输出端口给FPGA
 11 input            clk ;
 12 input            rst ;
 13 
 14 output    [3:0]    KEYI ;                //与原理图一致,是FPGA输出给 键盘    
 15 output    [7:0]    Y ;
 16 
 17 wire            keypress;
 18 wire            scanclk;
 19 wire    [7:0]    temp ;
 20 wire    [3:0]    keydrv ;    
 21    
 22 reg        [7:0]    temp_r ;
 23 reg        [7:0]    Y_r;   
 24 reg        [4:0]    keyvalue ; 
 25     
 26 reg        [3:0]    scankey_o;    
 27 reg        [3:0]    scankey_i;
 28 wire            dis;
 29 reg                dis_pre;    
 30 
 31 assign dis = &KEYO ;
 32 
 33 scan_clk key_clk(      
 34     .clk ( clk),
 35     .clkout ( scanclk) ,
 36     .rst ( rst )
 37     ); 
 38 
 39 key_scan key_scan( 
 40     .clk ( scanclk ) ,
 41     .keydrv (keydrv) ,
 42     .rst ( rst )
 43     );
 44 
 45 always @ ( posedge clk or negedge rst ) begin
 46     if ( rst==1'b0 ) begin
 47         scankey_o <= 4'b0 ; 
 48         scankey_i <= 4'b0 ;
 49         dis_pre <= dis;
 50     end else if ( clk ==1'b1 ) begin 
 51         dis_pre <= dis;
 52         if ( (dis == 1'b0)&&(dis_pre==1'b1) )  begin
 53             scankey_o <= keydrv    ;  
 54             scankey_i <= KEYO ;
 55         end    
 56     end
 57 end
 58 
 59 assign  KEYI = keydrv;
 60 
 61 assign temp = {scankey_o,scankey_i};
 62 
 63 always @ ( posedge clk or negedge rst ) begin  
 64     if ( rst==1'b0 ) begin 
 65         temp_r <= 8'b0; 
 66     end else if ( clk == 1'b1 )
 67         temp_r <= temp ;
 68 end    
 69     
 70 always @( temp_r or rst ) begin  
 71     if ( rst==1'b0 ) begin                //译码输出
 72         keyvalue <= 5'b0; 
 73     end else
 74         case ( temp_r )    
 75             8'b0111_0111 :  keyvalue <= 5'hb;        //无用,仅作为复位
 76             8'b1110_1110 :  keyvalue <= 5'h7;                
 77             8'b1110_1101 :  keyvalue <= 5'h8;                 
 78             8'b1110_1011 :  keyvalue <= 5'h9;                
 79             8'b1101_1110 :  keyvalue <= 5'h4;                
 80             8'b1101_1101 :  keyvalue <= 5'h5;                
 81             8'b1101_1011 :  keyvalue <= 5'h6;                
 82             8'b1011_1110 :  keyvalue <= 5'h1;                
 83             8'b1011_1101 :  keyvalue <= 5'h2;                
 84             8'b1011_1011 :  keyvalue <= 5'h3;                
 85             8'b0111_1101 :  keyvalue <= 5'h0;                                 
 86             8'b0111_1011 :  keyvalue <= 5'b1_0001;    //小数点
 87             default:        keyvalue <= 5'h0;             
 88         endcase    
 89 end     
 90 
 91 always @(keyvalue or rst ) begin  
 92     if ( rst==1'b0 )            //译码输出
 93         Y_r <= 8'b0000_0000;    
 94     else begin
 95         Y_r =8'b0000_0000;
 96         case (keyvalue )
 97                 5'h0: Y_r = 8'b0011_1111;         // 0
 98                 5'h1: Y_r = 8'b0000_0110;         // 1
 99                 5'h2: Y_r = 8'b0101_1011;         // 2
100                 5'h3: Y_r = 8'b0100_1111;         // 3
101                 5'h4: Y_r = 8'b0110_0110;         // 4
102                 5'h5: Y_r = 8'b0110_1101;         // 5
103                 5'h6: Y_r = 8'b0111_1101;         // 6
104                 5'h7: Y_r = 8'b0000_0111;         // 7
105                 5'h8: Y_r = 8'b0111_1111;         // 8
106                 5'h9: Y_r = 8'b0110_1111;         // 9
107                 5'b1_0001: Y_r = 8'b1000_0000;     //.
108                 default: Y_r = 8'b0000_0000;
109         endcase
110     end
111 end
112 
113 assign Y =~Y_r;
114 
115 endmodule
  注:其中第33行~第43行,是实例化上面2个时钟分频模块和键盘扫描模块。第61行的temp是根据键盘的行和键盘的列确立的一个序列。第70行~第89行是根据temp确定键值。第91行~第111行是根据键值确定数码管的显示,数码管由8位组成,最高位是小数点位。
       
图1.4 键译码转换模块
分配引脚:
按照图1.4的结构,将其对应的接口配以引脚,其中,Y[7..0]是七段数码管的引脚;clk是系统时钟;rst接复位信号;KEYO[3..0]接;KEYI[3..0]
实验结果:
  
  我们用到的就是这种标准的数字键盘,已经上面2个七段数码管中的一个。当按下数字键的时候,可以在数码管中显示出来。好了,这样就完成了键盘的实验了。就这样吧。如果还想了解其他的Verilog HDL的实验的话,可以点击【连载】 FPGA Verilog HDL 系列实例。
 
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
