IT技术之家

首页 > 硬件开发

硬件开发

基于FPGA的两位按键控制LED数码管加减计数实验_伊藤诚诚诚诚_fpga按键控制数码管加减

发布时间:2022-10-24 16:58:45 硬件开发 0次 标签:fpga开发
两位按键控制LED数码管加减计数实验...

两位按键控制LED数码管加减计数实验

????????这是一篇拖了一个多月的文章,主要是基于FPGA利用按键消抖原理与动态数码管驱动原理相结合,来实现一个利用两位按键来控制数码管实现0-99的加法计数或者减法计数功能。

1.1 简介

????????本文使用的开发板的LED数码管是采用共阳极连接,关于如何进行驱动,可以搜索相关动态数码管扫描实验,这边不进行过多的复述了。

1.2 实验任务

????????本章的实验任务是设计一个两位数码管显示0-99的加减法计数,主要功能是数码管显示数值范围0~99,按下KEY0增1;按下KEY1减1;长按KEY0计数不断增加;长按KEY1计数不断减少。

1.3 软件设计

根据实验任务我们需要画出本次实验的系统模块框图,如下图所示:

如上图所示,可知主要由按键消抖模块,计数器模块以及动态数码管驱动模块组成。

程序中各个模块端口及信号连接如图所示:

顶层模块(top_key_seg)例化了以下三个模块:按键消抖模块(key_debounce)、按键计数器模块(key_cnt)以及数码管动态驱动模块(seg_led)。?

顶层代码如下:

1  module top_seg_led(
2      input            sys_clk  ,         //时钟
3      input            sys_rst_n,         //复位(低电平有效)
4  	   input	[1:0]   key      ,         //两个按键key[0]: add  key[1]:sub
5          
6          
7      output   [1:0]   seg_sel   ,        // 数码管位选信号
8      output   [7:0]   seg_led            // 数码管段选信号
9  );
10 
11 //wire define
12 wire            key_value0;             //按键0有效
13 wire            key_value1;             //按键1有效
14 wire            key_flag0;              //按键0有效的标志位
15 wire            key_flag1;              //按键1有效的标志位
16 
17 wire    [19:0]  data;                   //数码管显示的数值
18 wire            en;                     //数码管使能
19 
20 
21 //*****************************************************
22 //**                    main code
23 //*****************************************************
24 
25 //按键消抖加法模块
26 key_debounce u_add_key_debounce(
27     .sys_clk        (sys_clk),			
28     .sys_rst_n      (sys_rst_n),		
29     
30     .key            (key[0]),			
31     .key_flag       (key_flag0),		
32     .key_value      (key_value0)		
33 );
34 
35 //按键消抖减法模块
36 key_debounce u_sub_key_debounce1(
37     .sys_clk        (sys_clk),			
38     .sys_rst_n      (sys_rst_n),	    
39     
40     .key            (key[1]),		    
41     .key_flag       (key_flag1),		
42     .key_value      (key_value1)		
43 );
44 
45 //计数器模块,产生数码管需要显示的数据
46 key_cnt u_key_cnt(
47     .sys_clk       (sys_clk  ),         
48     .sys_rst_n     (sys_rst_n),         
49 	.key_flag0     (key_flag0),		    
50     .key_value0    (key_value0),		
51 	.key_flag1     (key_flag1),		    
52     .key_value1    (key_value1),		
53 	 
54     .data          (data     ),       	
55     .en            (en       )      	
56 
57 );
58 
59 //数码管动态显示驱动模块
60 seg_led u_seg_led(
61     .sys_clk       (sys_clk  ),     
62     .sys_rst_n     (sys_rst_n),     
63 
64     .data          (data     ),         
65     .en            (en       ),         
66            
67     .seg_sel       (seg_sel  ),         
68     .seg_led       (seg_led  )          
69 );
70 
71 endmodule

????????顶层模块主要完成对子模块的例化,并且实现各模块之间的信号的交互。按键消抖模块的输出连接计数器模块,计数器模块的输出data和en传递给动态数码管驱动模块,最后将数据从数码管中显示出来。

按键消抖模块的代码如下所示:

1  module key_debounce(                     
2      input            sys_clk	,           //时钟
3      input            sys_rst_n,          //复位信号,低电平有效
4      
5      input            key		,           //外部按键输入
6      output reg       key_flag	,       //按键数据有效信号标志位
7  	   output reg       key_value           //按键有效值  
8      );
9  
10 //parameter define
11 //10ms计数最大值 19'd500_000
12 parameter CNT_10MS_MAX = 19'd500_000; 
13 
14 //reg define    
15 reg [18:0] cnt_10ms;		//计数器
16 reg        key_reg0 ;		//按键寄存器0
17 reg        key_reg1 ;		//按键寄存器1
18 
19 //*****************************************************
20 //**                    main code
21 //*****************************************************
22 
23 //检测按键状态
24 always @(posedge sys_clk or negedge sys_rst_n) begin 
25     if (!sys_rst_n) begin 
26         key_reg0 <= 1'b1;                //按键寄存器低电平有效
27 		key_reg1 <= 1'b1;
28         cnt_10ms <= 19'd0;
29     end
30     else begin
31 		key_reg0 <= key;				    //按键信号给寄存器0
32         key_reg1 <= key_reg0;			//寄存器0的值传送给寄存器1
33 		if(key_reg1 != key_reg0)            //寄存器key0和key1不相等,说明有按键被按下或释放
34             cnt_10ms <= CNT_10MS_MAX;    //10ms延迟计数器
35         else if(cnt_10ms > 19'd0)
36             cnt_10ms <= cnt_10ms - 19'd1;       
37     end   
38 end
39 
40 //给按键消抖后的数据赋值
41 always @(posedge sys_clk or negedge sys_rst_n) begin 
42     if (!sys_rst_n) begin 
43         key_flag  <= 1'b0;
44         key_value <= 1'b1;             
45     end
46     else begin
47         if(cnt_10ms == 19'd1) begin   //当计数器递减到1时,说明按键稳定状态维持了10ms
48             key_flag  <= 1'b1;        //此时消抖过程结束,给出一个时钟周期的标志信号
49             key_value <= key_reg1;    //并寄存此时按键的值
50         end
51         else begin
52             key_flag  <= 1'b0;
53             key_value <= key_value; 
54         end  
55     end   
56 end
57   
58 endmodule 

按键消抖模块从第33行开始不断检测按键有没有被按下,当检测到按键被按下赋给10ms寄存器一个10ms的最大值,然后进行逐一递减,当10ms计数器减到1时,说明按键已经进入稳定状态,按键消抖完成,然后输出一个按键消抖标志位信号同时将按键的有效值记录下来。

按键计数器模块代码如下:

1  module key_cnt(              
2      input               sys_clk  	,	// 时钟信号
3      input               sys_rst_n	,	// 复位信号
4  	input               key_flag0	,	//按键数据有效信号标志位0
5  	input               key_value0	,	//按键消抖后的数据 key0: add
6  	input               key_flag1	,	//按键数据有效信号标志位1
7  	input               key_value1	,	//按键消抖后的数据 key1:sub
8      
9      output   reg [19:0] data 	    ,	// 6个数码管要显示的数值
10     output   reg        en   			// 数码管使能信号
11 );
12 
13 //parameter define
14 //100ms计数最大值 23'd5_000_000
15 parameter CNT_1S_MAX = 26'd50_000_000;
16 parameter CNT_100MS_MAX = 25'd5_000_000;
17 
18 //wire define
19 reg	[25:0]	cnt_1s		;               //1s长按计数器
20 reg			down_flag	;               //长按标志
21 reg	[24:0]	down_100ms	;               //100ms长按累加/减
22 reg			flag	    ;               //长按累加/减标志位
23 
24 //*****************************************************
25 //**                    main code
26 //*****************************************************
27 
28 //判断按键是否处于长按状态
29 always @ (posedge sys_clk or negedge sys_rst_n) begin
30 	if (!sys_rst_n) begin
31 		cnt_1s <= 26'd0;
32 		down_flag <= 1'd0;
33 	end
34 	else if((key_value0 == 1'd1) && (key_value1 == 1'd1)) begin			//按键未被按下时延时计数器和长按标志赋初值
35 		cnt_1s <= 26'd0;
36 		down_flag <= 1'd0;
37 	end
38 	else if( (key_value0 == 1'd0) || (key_value1 == 1'd0) ) begin		//按键按下有效时
39 		if(cnt_1s < CNT_1S_MAX) begin
40 			cnt_1s <= cnt_1s + 1'd1;
41 			down_flag <= 1'd0;
42 		end
43 		else begin																		//延时计数器计数1s时,判定为长按状态,并将长按标志拉高
44 			cnt_1s <= cnt_1s;
45 			down_flag <= 1'd1;
46 		end
47 	end
48 end
49 
50 //长按状态下每100ms,输出一个时钟周期的脉冲信号
51 always @(posedge sys_clk or negedge sys_rst_n) begin
52 	if (!sys_rst_n) begin
53 		down_100ms <= 25'd0;
54 		flag <=	1'd0;
55 	end
56 	else if(down_flag) begin							
57 		if(down_100ms < CNT_100MS_MAX) begin
58 			down_100ms <= down_100ms + 1'd1;
59 			flag	<=	1'd0;
60 		end
61 		else begin
62 			down_100ms <= 25'd0;
63 			flag <=	1'd1;
64 		end	
65 	end
66 end
67 
68 //数码管需要显示的数据,从0到99进行累加/减计算
69 always @(posedge sys_clk or negedge sys_rst_n) begin
70 	if (!sys_rst_n) begin
71 		data <= 20'b0;
72 		en <= 1'b0;
73 	end 
74 	else begin           			
75 		en <= 1'b1;               																			
76 		if ((key_value0 == 1'd0) && (key_flag0 || flag) ) begin		//按键0按下时显示数值按下时累加一次,或长按状态时每隔0.1s累加一次
77 			if(data < 20'd99) 
78 				data <= data + 1'b1;     
79 			else if (data == 20'd99)
80                 data <= 20'd0;
81             else    
82 				data <= data;
83 		end 
84 		else if((key_value1 == 1'd0) && (key_flag1 || flag)) begin	//按键1按下时显示数值按下时累减一次,或长按状态时每隔0.1s累减一次
85 			if(data > 20'd0) 
86 				data <= data - 1'b1;     
87 			else if(data == 20'd0)
88                 data <= 20'd99;
89             else    
90 				data <= data;
91 		end
92 	end 
93 end 
94 
95 endmodule 

按键计数器模块主要实现将消抖后的按键信号,进行进一步判定。在39行之中增加了一个按下超过1s判定为长按的判定,如果超过1s以上的将按键信号判定为长按,并且增加一个长按按键标志位。在长按之后的每0.1s进行对数码管计数累加/减的判定;当按下key0时数码管累加等于99的时候,让数码管数据归零然后继续进行相应的累加,当按下key1进行递减运算的时候,在等于0时,进行一个判定让数码管下一个数据等于99,就完成了本次实验所需求的功能拓展。按键计数模块最后输出一个使能信号以及一个数据信号传递给下一层的数码管驱动模块。

数码管驱动模块:

1   module seg_led(                          
2           input               sys_clk     ,   //时钟
3           input               sys_rst_n   ,   //复位
4                                               
5           input     [19:0]    data        ,   //数据       
6           input               en          ,   //使能
7                                               
8           output reg [7:0]    seg_led     ,   //段选
9           output reg [1:0]    seg_sel         //位选  
10      );
11  
12  //parameter define
13  //时钟分频 4'd10
14  parameter CLK_DIV     = 4'd10;   
15  
16  //对数码管驱动分频计数最大值 13'd5_000       
17  parameter CNT_NUM_MAX = 13'd5_000;           
18  
19  //reg define    
20  reg     [3:0]   clk_cnt ;                   //时钟分频计数器
21  reg             dri_clk ;                   //时钟分频5MHz
22  reg     [7:0]  num     ;                    //BCD数码位数
23  reg     [15:0]  cnt     ;                   //数码管驱动时钟计数器
24  reg             flag    ;                   //计数器标志位
25  reg     [2:0]   cnt_sel ;                   //数码管位选计数器
26  reg     [3:0]   num_disp;                   //数据显示值
27           
28  //wire define
29  wire [3:0]   data0;                         //个位
30  wire [3:0]   data1;                         //十位
31  wire [3:0]   data2;                         //百位
32  wire [3:0]   data3;                         //千位
33  wire [3:0]   data4;                         //万位
34  wire [3:0]   data5;                         //十万位
35  
36  //*****************************************************
37  //**                    main code
38  //*****************************************************
39  
40  assign data0 = data % 4'd10             ;   //个位
41  assign data1 = data / 4'd10 % 4'd10     ;   //十位
42  assign data2 = data / 7'd100 % 4'd10    ;   //百位
43  assign data3 = data / 10'd1_000 % 4'd10 ;   //千位
44  assign data4 = data / 14'd10_000 % 4'd10;   //万位
45  assign data5 = data / 17'd100_000       ;   //十万位
46  
47  //对系统时钟进行10分频
48  always @(posedge sys_clk or negedge sys_rst_n) begin
49     if(!sys_rst_n)begin
50         clk_cnt <= 4'd0;
51         dri_clk <= 1'b1;
52     end
53     else if(clk_cnt == CLK_DIV/2 - 1'd1) begin   //0~4为1/2个周期
54         clk_cnt <= 4'd0;
55         dri_clk <= ~dri_clk;
56     end
57     else begin
58         clk_cnt <= clk_cnt + 1'b1;
59         dri_clk <= dri_clk;     
60     end
61  end        
62  
63  //BCD数码显示    
64  always @ (posedge dri_clk or negedge sys_rst_n) begin
65      if (!sys_rst_n)
66          num <= 8'b0;
67      else begin
68          if(data1)
69              num[7:0]   <= {data1, data0};
70          else
71              if(data0)
72                  num[3:0]   <= data0;
73              else
74                  num[7:0] <= {2{4'd10}}; //0、1不显示        
75      end
76  end
77      
78  //数码管1ms计数器    
79  always @(posedge dri_clk or negedge sys_rst_n) begin
80      if(!sys_rst_n)begin
81          cnt  <= 13'd0;
82          flag <= 1'd0;
83      end
84      else if(cnt < CNT_NUM_MAX - 13'd1) begin
85          cnt  <= cnt + 13'd1;
86          flag <= 1'b0;    
87      end
88      else begin
89          cnt <= 13'd0;
90          flag<= 1'b1;
91      end
92  end    
93  
94  //位选计数器
95  always @(posedge dri_clk or negedge sys_rst_n)
96      if (!sys_rst_n)
97          cnt_sel <= 2'b0;
98      else if(flag) 
99          if(cnt_sel < 2'd2)
100             cnt_sel <= cnt_sel + 1'b1;
101         else
102             cnt_sel <= 2'b0;
103     else
104         cnt_sel <= cnt_sel;  
105 
106 //数码管的位选判断
107 always @(posedge dri_clk or negedge sys_rst_n) begin
108     if (!sys_rst_n) begin
109         seg_sel  <= 2'b11;           //位选  
110         num_disp <= 4'b0;           //数据显示         
111     end        
112     else begin
113         if(en)begin
114             case (cnt_sel)
115                 2'd0 :begin
116                     seg_sel  <= 2'b10;  
117                     num_disp <= num[3:0] ;  
118                 end
119                 2'd1 :begin
120                     seg_sel  <= 2'b01;  
121                     num_disp <= num[7:4] ;
122                 end
123                 default :begin
124                     seg_sel  <= 2'b0;
125                     num_disp <= 4'b0;
126                 end    
127             endcase
128         end    
129         else begin
130             seg_sel  <= 2'b0;          
131             num_disp <= 4'b0;
132         end
133     end
134 end  
135 
136 //数码管的数据显示  
137 always @(posedge dri_clk or negedge sys_rst_n) begin
138     if(!sys_rst_n)        
139         seg_led <= 8'hff;
140     else begin
141         case(num_disp)
142             4'd0 :seg_led <= 8'b1100_0000;            //0
143             4'd1 :seg_led <= 8'b1111_1001;            //1
144             4'd2 :seg_led <= 8'b1010_0100;            //2
145             4'd3 :seg_led <= 8'b1011_0000;            //3
146             4'd4 :seg_led <= 8'b1001_1001;            //4
147             4'd5 :seg_led <= 8'b1001_0010;            //5
148             4'd6 :seg_led <= 8'b1000_0010;            //6
149             4'd7 :seg_led <= 8'b1111_1000;            //7
150             4'd8 :seg_led <= 8'b1000_0000;            //8
151             4'd9 :seg_led <= 8'b1001_0000;            //9
152         default:        
153                   seg_led <= 8'b1111_1111;            //关闭
154         endcase
155     end
156 end
157 
158 endmodule  

数码管显示驱动部分,位选只选择了两位,如果想要进行更多位数的显示可以增加位选。其次将符号位以及小数点位省略掉了,如果需要显示小数或者负数可以将符号位以及小数点位进行添加。上面数码管的显示方式同样可以采用BCD译码器来完成,思路大致相同仅供各位参考。