概述
两位按键控制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)。
顶层代码如下:
module top_key_led(
input sys_clk , //时钟
input sys_rst_n, //复位(低电平有效)
input [1:0] key , //两个按键key[0]: add key[1]:sub
output [5:0] sel , // 数码管位选信号
output [7:0] seg // 数码管段选信号
);
//wire define
wire key_value0; //按键0有效
wire key_value1; //按键1有效
wire key_flag0; //按键0有效的标志位
wire key_flag1; //按键1有效的标志位
wire [19:0] data; //数码管显示的数值
wire en; //数码管使能
//*****************************************************
//** main code
//*****************************************************
//按键消抖加法模块
key_debounce u_add_key_debounce(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key (key[0]),
.key_flag (key_flag0),
.key_value (key_value0)
);
//按键消抖减法模块
key_debounce u_sub_key_debounce1(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key (key[1]),
.key_flag (key_flag1),
.key_value (key_value1)
);
//计数器模块,产生数码管需要显示的数据
key_cnt u_key_cnt(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.key_flag0 (key_flag0),
.key_value0 (key_value0),
.key_flag1 (key_flag1),
.key_value1 (key_value1),
.data (data ),
.en (en )
);
//数码管模块
seg_dynamic seg_dynamic_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低有效
.data (data ), //数码管要显示的值
.point (1'b0 ), //小数点显示,高电平有效
.seg_en (1'b1 ), //数码管使能信号,高电平有效
.sign (1'b0 ), //符号位,高电平显示负号
.sel (sel ), //数码管位选信号
.seg (seg ) //数码管段选信号
);
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,就完成了本次实验所需求的功能拓展。按键计数模块最后输出一个使能信号以及一个数据信号传递给下一层的数码管驱动模块。
数码管驱动模块:
`timescale 1ns/1ns
// Author : EmbedFire
// 实验平台: 野火FPGA系列开发板
// 公司 : http://www.embedfire.com
// 论坛 : http://www.firebbs.cn
// 淘宝 : https://fire-stm32.taobao.com
module seg_dynamic
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低有效
input wire [19:0] data , //数码管要显示的值
input wire [5:0] point , //小数点显示,高电平有效
input wire seg_en , //数码管使能信号,高电平有效
input wire sign , //符号位,高电平显示负号
output reg [5:0] sel , //数码管位选信号
output reg [7:0] seg //数码管段选信号
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter CNT_MAX = 16'd49_999; //数码管刷新时间计数最大值
//wire define
wire [3:0] unit ; //个位数
wire [3:0] ten ; //十位数
wire [3:0] hun ; //百位数
wire [3:0] tho ; //千位数
wire [3:0] t_tho ; //万位数
wire [3:0] h_hun ; //十万位数
//reg define
reg [23:0] data_reg ; //待显示数据寄存器
reg [15:0] cnt_1ms ; //1ms计数器
reg flag_1ms ; //1ms标志信号
reg [2:0] cnt_sel ; //数码管位选计数器
reg [5:0] sel_reg ; //位选信号
reg [3:0] data_disp ; //当前数码管显示的数据
reg dot_disp ; //当前数码管显示的小数点
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//data_reg:控制数码管显示数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_reg <= 24'b0;
//若显示的十进制数的十万位为非零数据或需显示小数点,则六个数码管全显示
else if((h_hun) || (point[5]))
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
//若显示的十进制数的万位为非零数据或需显示小数点,则值显示在5个数码管上
//打比方我们输入的十进制数据为20’d12345,我们就让数码管显示12345而不是012345
else if(((t_tho) || (point[4])) && (sign == 1'b1))//显示负号
data_reg <= {4'd10,t_tho,tho,hun,ten,unit};//4'd10我们定义为显示负号
else if(((t_tho) || (point[4])) && (sign == 1'b0))
data_reg <= {4'd11,t_tho,tho,hun,ten,unit};//4'd11我们定义为不显示
//若显示的十进制数的千位为非零数据或需显示小数点,则值显示4个数码管
else if(((tho) || (point[3])) && (sign == 1'b1))
data_reg <= {4'd11,4'd10,tho,hun,ten,unit};
else if(((tho) || (point[3])) && (sign == 1'b0))
data_reg <= {4'd11,4'd11,tho,hun,ten,unit};
//若显示的十进制数的百位为非零数据或需显示小数点,则值显示3个数码管
else if(((hun) || (point[2])) && (sign == 1'b1))
data_reg <= {4'd11,4'd11,4'd10,hun,ten,unit};
else if(((hun) || (point[2])) && (sign == 1'b0))
data_reg <= {4'd11,4'd11,4'd11,hun,ten,unit};
//若显示的十进制数的十位为非零数据或需显示小数点,则值显示2个数码管
else if(((ten) || (point[1])) && (sign == 1'b1))
data_reg <= {4'd11,4'd11,4'd11,4'd10,ten,unit};
else if(((ten) || (point[1])) && (sign == 1'b0))
data_reg <= {4'd11,4'd11,4'd11,4'd11,ten,unit};
//若显示的十进制数的个位且需显示负号
else if(((unit) || (point[0])) && (sign == 1'b1))
data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd10,unit};
//若上面都不满足都只显示一位数码管
else
data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd11,unit};
//cnt_1ms:1ms循环计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1ms <= 16'd0;
else if(cnt_1ms == CNT_MAX)
cnt_1ms <= 16'd0;
else
cnt_1ms <= cnt_1ms + 1'b1;
//flag_1ms:1ms标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
flag_1ms <= 1'b0;
else if(cnt_1ms == CNT_MAX - 1'b1)
flag_1ms <= 1'b1;
else
flag_1ms <= 1'b0;
//cnt_sel:从0到5循环数,用于选择当前显示的数码管
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_sel <= 3'd0;
else if((cnt_sel == 3'd5) && (flag_1ms == 1'b1))
cnt_sel <= 3'd0;
else if(flag_1ms == 1'b1)
cnt_sel <= cnt_sel + 1'b1;
else
cnt_sel <= cnt_sel;
//数码管位选信号寄存器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel_reg <= 6'b000_000;
else if((cnt_sel == 3'd0) && (flag_1ms == 1'b1))
sel_reg <= 6'b000_001;
else if(flag_1ms == 1'b1)
sel_reg <= sel_reg << 1;
else
sel_reg <= sel_reg;
//控制数码管的位选信号,使六个数码管轮流显示
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_disp <= 4'b0;
else if((seg_en == 1'b1) && (flag_1ms == 1'b1))
case(cnt_sel)
3'd0: data_disp <= data_reg[3:0] ; //给第1个数码管赋个位值
3'd1: data_disp <= data_reg[7:4] ; //给第2个数码管赋十位值
3'd2: data_disp <= data_reg[11:8] ; //给第3个数码管赋百位值
3'd3: data_disp <= data_reg[15:12]; //给第4个数码管赋千位值
3'd4: data_disp <= data_reg[19:16]; //给第5个数码管赋万位值
3'd5: data_disp <= data_reg[23:20]; //给第6个数码管赋十万位值
default:data_disp <= 4'b0 ;
endcase
else
data_disp <= data_disp;
//dot_disp:小数点低电平点亮,需对小数点有效信号取反
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
dot_disp <= 1'b1;
else if(flag_1ms == 1'b1)
dot_disp <= ~point[cnt_sel];
else
dot_disp <= dot_disp;
//控制数码管段选信号,显示数字
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg <= 8'b1111_1111;
else
case(data_disp)
4'd0 : seg <= {dot_disp,7'b100_0000}; //显示数字0
4'd1 : seg <= {dot_disp,7'b111_1001}; //显示数字1
4'd2 : seg <= {dot_disp,7'b010_0100}; //显示数字2
4'd3 : seg <= {dot_disp,7'b011_0000}; //显示数字3
4'd4 : seg <= {dot_disp,7'b001_1001}; //显示数字4
4'd5 : seg <= {dot_disp,7'b001_0010}; //显示数字5
4'd6 : seg <= {dot_disp,7'b000_0010}; //显示数字6
4'd7 : seg <= {dot_disp,7'b111_1000}; //显示数字7
4'd8 : seg <= {dot_disp,7'b000_0000}; //显示数字8
4'd9 : seg <= {dot_disp,7'b001_0000}; //显示数字9
4'd10 : seg <= 8'b1011_1111 ; //显示负号
4'd11 : seg <= 8'b1111_1111 ; //不显示任何字符
default:seg <= 8'b1100_0000;
endcase
//sel:数码管位选信号赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel <= 6'b000_000;
else
sel <= sel_reg;
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//---------- bsd_8421_inst ----------
bcd_8421 bcd_8421_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低电平有效
.data (data ), //输入需要转换的数据
.unit (unit ), //个位BCD码
.ten (ten ), //十位BCD码
.hun (hun ), //百位BCD码
.tho (tho ), //千位BCD码
.t_tho (t_tho ), //万位BCD码
.h_hun (h_hun ) //十万位BCD码
);
endmodule
BCD8421模块
module bcd_8421 ( input wire sys_clk , //系统时钟,频率50MHz input wire sys_rst_n , //复位信号,低电平有效 input wire [19:0] data , //输入需要转换的数据 output reg [3:0] unit , //个位BCD码 output reg [3:0] ten , //十位BCD码 output reg [3:0] hun , //百位BCD码 output reg [3:0] tho , //千位BCD码 output reg [3:0] t_tho , //万位BCD码 output reg [3:0] h_hun //十万位BCD码 ); //********************************************************************// //******************** Parameter And Internal Signal *****************// //********************************************************************// //reg define reg [4:0] cnt_shift ; //移位判断计数器 reg [43:0] data_shift ; //移位判断数据寄存器 reg shift_flag ; //移位判断标志信号 //********************************************************************// //***************************** Main Code ****************************// //********************************************************************// //cnt_shift:从0到21循环计数 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_shift <= 5'd0; else if((cnt_shift == 5'd21) && (shift_flag == 1'b1)) cnt_shift <= 5'd0; else if(shift_flag == 1'b1) cnt_shift <= cnt_shift + 1'b1; else cnt_shift <= cnt_shift; //data_shift:计数器为0时赋初值,计数器为1~20时进行移位判断操作 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) data_shift <= 44'b0; else if(cnt_shift == 5'd0) data_shift <= {24'b0,data}; else if((cnt_shift <= 20) && (shift_flag == 1'b0)) begin data_shift[23:20] <= (data_shift[23:20] > 4) ? (data_shift[23:20] + 2'd3) : (data_shift[23:20]); data_shift[27:24] <= (data_shift[27:24] > 4) ? (data_shift[27:24] + 2'd3) : (data_shift[27:24]); data_shift[31:28] <= (data_shift[31:28] > 4) ? (data_shift[31:28] + 2'd3) : (data_shift[31:28]); data_shift[35:32] <= (data_shift[35:32] > 4) ? (data_shift[35:32] + 2'd3) : (data_shift[35:32]); data_shift[39:36] <= (data_shift[39:36] > 4) ? (data_shift[39:36] + 2'd3) : (data_shift[39:36]); data_shift[43:40] <= (data_shift[43:40] > 4) ? (data_shift[43:40] + 2'd3) : (data_shift[43:40]); end else if((cnt_shift <= 20) && (shift_flag == 1'b1)) data_shift <= data_shift << 1; else data_shift <= data_shift; //shift_flag:移位判断标志信号,用于控制移位判断的先后顺序 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) shift_flag <= 1'b0; else shift_flag <= ~shift_flag; //当计数器等于20时,移位判断操作完成,对各个位数的BCD码进行赋值 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) begin unit <= 4'b0; ten <= 4'b0; hun <= 4'b0; tho <= 4'b0; t_tho <= 4'b0; h_hun <= 4'b0; end else if(cnt_shift == 5'd21) begin unit <= data_shift[23:20]; ten <= data_shift[27:24]; hun <= data_shift[31:28]; tho <= data_shift[35:32]; t_tho <= data_shift[39:36]; h_hun <= data_shift[43:40]; end endmodule
本次修改了数码管驱动代码,不推荐各位学习使用乘除法进行驱动。主要FPGA的资源还是很宝贵的,代码已经贴出来了,如果需要源码可以私聊。代码是通用的,不过源码是vivado的。
最后
以上就是谦让期待为你收集整理的基于FPGA的两位按键控制LED数码管加减计数实验两位按键控制LED数码管加减计数实验的全部内容,希望文章能够帮你解决基于FPGA的两位按键控制LED数码管加减计数实验两位按键控制LED数码管加减计数实验所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复