一、概述
按键控制数码管是一种常见的电子显示技术,它结合了按键输入与数码管显示的功能。在这一设计中,用户通过按下不同的按键来发送指令,这些指令随后被处理并转换为数码管上显示的数字或字符。按键通常作为输入设备,通过电路连接到微控制器(如FPGA、单片机等)的输入引脚,而数码管则作为输出设备,其显示内容由微控制器控制。
按键控制数码管的设计可以包括以下几个步骤:
-
按键输入处理:微控制器不断扫描按键的状态,当检测到按键被按下时,根据按键的编号或功能执行相应的操作。
-
指令转换:将按键的输入转换为数码管能够理解的显示指令。这通常涉及将按键编号或功能映射到特定的数字或字符编码。
-
数码管显示控制:微控制器根据转换后的显示指令,通过控制数码管的驱动电路来点亮或熄灭数码管中的不同段,从而显示出所需的数字或字符。
-
循环扫描与更新:为了保持数码管显示内容的实时性,微控制器需要不断重复上述步骤,形成一个循环扫描和更新的过程。
二、工程实现
1、基本思路构建
在本篇文章中我们以按键控制单个数码管为例讲解通过按键改变数码管显示的值的设计,基本思路如下:
在默认情况下数码管正常显示,当按下按键1时,数码管从正常显示的状态跳转到设置的状态,接着通过按键二队数码管显示的值进行一个改变,最后在按下按键1使得数码管从设置状态有重新回到正常显示状态,如此往复设计。
2、设计文件的编写
新建一个seg0.v文件
cpp
module seg0(
input clk,
input rst_n,
input [1:0] key_in,
input seg_sel,//位选
output reg [7:0] seg_dual//段选
);
localparam ZERO = 8'b1100_0000, //共阳极段码
ONE = 8'b1111_1001,
TWO = 8'b1010_0100,
THREE = 8'b1011_0000,
FOUR = 8'b1001_1001,
FIVE = 8'b1001_0010,
SIX = 8'b1000_0010,
SEVEN = 8'b1111_1000,
EIGHT = 8'b1000_0000,
NINE = 8'b1001_0000,
A = 8'b1000_1000,
b = 8'b1000_0011,
c = 8'b1100_0110,
d = 8'b1010_0001,
E = 8'b1000_0110,
f = 8'b1000_1110;
reg [26:0] cnt;
wire add_cnt;
wire end_cnt;
reg [4:0] flag;
wire add_flag;
wire end_flag;
reg seg_flag;//数码管状态切换标志位
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt<=0;
else if(add_cnt)begin
if(end_cnt)
cnt<=0;
else
cnt<=cnt+1'b1;
end
end
assign add_cnt=1'b1;
assign end_cnt=add_cnt && (cnt==50_000_000-1);
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
flag<=0;
else if(seg_flag==1 && key_in[1])
flag<=flag+1'b1;
else if(add_flag)begin
if(end_flag)
flag<=0;
else
flag<=flag+1'b1;
end
end
assign add_flag=end_cnt;
assign end_flag=add_flag && (flag==16-1);
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
seg_flag<=0;
else if(key_in[0])
seg_flag<=~seg_flag;
else
seg_flag<=seg_flag;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
seg_dual<=8'b1111_1111;
else if(seg_flag==0)begin
case (flag)
4'd0:seg_dual <=ZERO ;
4'd1:seg_dual <=ONE ;
4'd2:seg_dual <=TWO ;
4'd3:seg_dual <=THREE;
4'd4:seg_dual <=FOUR ;
4'd5:seg_dual <=FIVE ;
4'd6:seg_dual <=SIX ;
4'd7:seg_dual <=SEVEN;
4'd8:seg_dual <=EIGHT;
4'd9:seg_dual <=NINE ;
4'd10:seg_dual<=A ;
4'd11:seg_dual<=b ;
4'd12:seg_dual<=c ;
4'd13:seg_dual<=d ;
4'd14:seg_dual<=E ;
4'd15:seg_dual<=f ;
default: ;
endcase
end
end
endmodule
新建一个key.v文件
cpp
//状态机实现
module key (
input clk ,
input rst_n ,
input [1:0] key_in , //输入原始的按键信号
output reg [1:0] key_out //输出处理之后的按键信号
);
//参数定义
localparam IDLE = 4'b0001,//空闲
JITTLE0 = 4'b0010,//滤除第一次抖动
DOWN = 4'b0100,//稳定
JITTLE1 = 4'b1000;//滤除第二次抖动
parameter TIME_20MS = 1_000_000;//需要计数的值,20ms
//内部信号
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态
reg [19:0] cnt_20ms;//计数20ms
wire add_cnt_20ms;
wire end_cnt_20ms;
wire [1:0] nedge ;//下降沿信号
wire [1:0] pedge ;//上升沿信号
reg [1:0] key_in_r1 ;//打两拍 同步打拍
reg [1:0] key_in_r2 ;
//状态转移 同步时序逻辑描述状态转移
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
state_c <= IDLE;
else
state_c <= state_n;
end
//状态转移条件 组合逻辑
always @(*) begin
case (state_c)//一定是case 现态
IDLE:begin
if(nedge)
state_n = JITTLE0;
else
state_n = state_c;
end
JITTLE0:begin
if(end_cnt_20ms)
state_n = DOWN;
else
state_n = state_c;
end
DOWN:begin
if(pedge)
state_n = JITTLE1;
else
state_n = state_c;
end
JITTLE1 :begin
if(end_cnt_20ms)
state_n = IDLE;
else
state_n = state_c;
end
default: state_n = IDLE;
endcase
end
//20ms计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_20ms <= 0;
else if(add_cnt_20ms)begin
if(end_cnt_20ms)
cnt_20ms <= 0;
else
cnt_20ms <= cnt_20ms + 1;
end
end
assign add_cnt_20ms = (state_c == JITTLE0) || (state_c == JITTLE1);
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
//下降沿 上升沿
//同步 打拍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_in_r1 <= 2'b11;
key_in_r2 <= 2'b11;
end
else begin
key_in_r1 <= key_in; //同步按键输入信号
key_in_r2 <= key_in_r1; //打拍
end
end
//r1当前状态,r2上一个状态
assign nedge = ~key_in_r1 && key_in_r2;
assign pedge = key_in_r1 && ~key_in_r2;
//key_out
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
key_out <= 0;
else if(end_cnt_20ms &&(state_c== JITTLE1))
key_out <= ~key_in_r2;//有效脉冲 20ns
else
key_out <= 0;
end
endmodule
3、新建一个顶层文件top.v,用于将数码管和按键连接起来。
cpp
module top(
input clk,
input rst_n,
input [1:0] key_in,
output seg_sel,
output [7:0] seg_dual
);
wire [1:0] key_flag;
key key_inst(
/*input */ .clk (clk ),
/*input */ .rst_n (rst_n ),
/*input */ .key_in (key_in ), //输入原始的按键信号
/*output */ .key_out (key_flag ) //输出处理之后的按键信号
);
seg0 seg_inst(
/*input */ .clk (clk ),
/*input */ .rst_n (rst_n ),
.key_in (key_flag), //
/*input */ .seg_sel (seg_sel ),//位选
/*output*/ .seg_dual (seg_dual )//段选
);
endmodule
4、测试文件的编写
其实这里我不推荐进行波形图仿真的,因为仿真需要编写的条件非常多,相比之下使用在线调试工具进行实时调试更好。这里我也给一个测试文件,需要的小伙伴可以加以完善。
cpp
//定义时间尺度
`timescale 1ns/1ns
module top_tb ;
//输入信号定义
reg clk;
reg rst_n;
reg [1:0] key_in;
wire [7:0] seg_dual;
wire seg_sel;
defparam top_inst.cnt_inst.TIME_1s=500;
//模块例化
top top_inst(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input */.key_in (key_in ),
/*output*/.seg_sel (seg_sel ),
/*output*/.seg_dual (seg_dual)
);
//激励信号产生
parameter CLK_CLY = 20;
//时钟
initial clk=1;
always #(CLK_CLY/2)clk=~clk;
//复位
initial begin
rst_n= 1'b0;
#(CLK_CLY*3);
#5;//复位结束避开时钟上升沿
rst_n= 1'b1;
end
//激励
//激励
integer i;
initial repeat(5)begin
key_in[1] = 1;//模拟按键未按下
i ={$random}%6;//给i赋值0-5
#(CLK_CLY*500);//等待复位时间结束
#3;
key_in[1] = 0;//前按键抖动开始
#(CLK_CLY*1);
//一个5-10ms的抖动时间
repeat ((i+5)*50)begin
key_in[1] = $random;
#(CLK_CLY*1);
end
key_in[1] = 0;//按键稳定
#(CLK_CLY*100*500);
//后抖动开始
key_in[1] = 1;
#(CLK_CLY*1);
repeat ((i+5)*50)begin
key_in[1] = $random;
#(CLK_CLY*1);
end
key_in[1] = 1;//按键稳定
#(CLK_CLY*10*5000);
key_in[2] = 1;//模拟按键未按下
i ={$random}%6;//给i赋值0-5
#(CLK_CLY*500);//等待复位时间结束
#3;
key_in[2] = 0;//前按键抖动开始
#(CLK_CLY*1);
//一个5-10ms的抖动时间
repeat ((i+5)*50)begin
key_in[2] = $random;
#(CLK_CLY*1);
end
key_in[2] = 0;//按键稳定
#(CLK_CLY*100*500);
//后抖动开始
key_in[2] = 1;
#(CLK_CLY*1);
repeat ((i+5)*50)begin
key_in[2] = $random;
#(CLK_CLY*1);
end
key_in[2] = 1;//按键稳定
#(CLK_CLY*10*5000);
key_in[0] = 1;//模拟按键未按下
i ={$random}%6;//给i赋值0-5
#(CLK_CLY*500);//等待复位时间结束
#3;
key_in[0] = 0;//前按键抖动开始
#(CLK_CLY*1);
//一个5-10ms的抖动时间
repeat ((i+5)*50)begin
key_in[0] = $random;
#(CLK_CLY*1);
end
key_in[0] = 0;//按键稳定
#(CLK_CLY*100*500);
//后抖动开始
key_in[0] = 1;
#(CLK_CLY*1);
repeat ((i+5)*50)begin
key_in[0] = $random;
#(CLK_CLY*1);
end
key_in[0] = 1;//按键稳定
#(CLK_CLY*10*5000);
key_in[0] = 1;//模拟按键未按下
i ={$random}%6;//给i赋值0-5
#(CLK_CLY*500);//等待复位时间结束
#3;
key_in[0] = 0;//前按键抖动开始
#(CLK_CLY*1);
//一个5-10ms的抖动时间
repeat ((i+5)*50)begin
key_in[0] = $random;
#(CLK_CLY*1);
end
key_in[0] = 0;//按键稳定
#(CLK_CLY*100*500);
//后抖动开始
key_in[0] = 1;
#(CLK_CLY*1);
repeat ((i+5)*50)begin
key_in[0] = $random;
#(CLK_CLY*1);
end
key_in[0] = 1;//按键稳定
#(CLK_CLY*10*5000);
key_in[1] = 1;//模拟按键未按下
i ={$random}%6;//给i赋值0-5
#(CLK_CLY*500);//等待复位时间结束
#3;
key_in[1] = 0;//前按键抖动开始
#(CLK_CLY*1);
//一个5-10ms的抖动时间
repeat ((i+5)*50)begin
key_in[1] = $random;
#(CLK_CLY*1);
end
key_in[1] = 0;//按键稳定
#(CLK_CLY*100*500);
//后抖动开始
key_in[1] = 1;
#(CLK_CLY*1);
repeat ((i+5)*50)begin
key_in[1] = $random;
#(CLK_CLY*1);
end
key_in[1] = 1;//按键稳定
#(CLK_CLY*10*5000);
//模拟意外抖动
repeat (3)begin
repeat ((i+5)*50)begin
key_in = $random;
#(CLK_CLY*1);
end
key_in = 1;//按键稳定
#(CLK_CLY*5000);
end
$stop;
end
endmodule
5、下板验证
在进行下板验证之后最终实现的效果和我们所改造的预期结果一致,这里格式限制上传不了视频,就不展示了。在下板验证时设计的引脚配置等由用户自己根据需求去设置。
三、不足之处
经过最后的检查之后,发现在开发板上进行验证时,在使用按键进行数码管显示数值的改变的这个过程当中我们看不到,因为是在标志位位1时计数里面进行改变,而我们的输出是在标志位为0时进行显示的,所以看不到,这里我们对seg0设计进行一个最简单粗暴的方法进行修改。这里修改的方法很low了,在后面的数字时钟的文章中我会加以完善,不足之处多多包涵。
修改之后的seg0文件:
cpp
//分频器
module seg0(
input clk,
input rst_n,
input [1:0] key_in,
input seg_sel,//位选
output reg [7:0] seg_dual//段选
);
localparam ZERO = 8'b1100_0000, //共阳极段码
ONE = 8'b1111_1001,
TWO = 8'b1010_0100,
THREE = 8'b1011_0000,
FOUR = 8'b1001_1001,
FIVE = 8'b1001_0010,
SIX = 8'b1000_0010,
SEVEN = 8'b1111_1000,
EIGHT = 8'b1000_0000,
NINE = 8'b1001_0000,
A = 8'b1000_1000,
b = 8'b1000_0011,
c = 8'b1100_0110,
d = 8'b1010_0001,
E = 8'b1000_0110,
f = 8'b1000_1110;
reg [26:0] cnt;
wire add_cnt;
wire end_cnt;
reg [4:0] flag;
wire add_flag;
wire end_flag;
reg seg_flag;//数码管状态切换标志位
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt<=0;
else if(add_cnt)begin
if(end_cnt)
cnt<=0;
else
cnt<=cnt+1'b1;
end
end
assign add_cnt=1'b1;
assign end_cnt=add_cnt && (cnt==50_000_000-1);
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
flag<=0;
else if(add_flag)begin
if(end_flag)
flag<=0;
else
flag<=flag+1'b1;
end
end
assign add_flag=end_cnt;
assign end_flag=add_flag && (flag==16-1);
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
seg_flag<=0;
else if(key_in[0])
seg_flag<=~seg_flag;
else
seg_flag<=seg_flag;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
seg_dual<=8'b1111_1111;
else if(seg_flag==0)begin
case (flag)
4'd0:seg_dual <=ZERO ;
4'd1:seg_dual <=ONE ;
4'd2:seg_dual <=TWO ;
4'd3:seg_dual <=THREE;
4'd4:seg_dual <=FOUR ;
4'd5:seg_dual <=FIVE ;
4'd6:seg_dual <=SIX ;
4'd7:seg_dual <=SEVEN;
4'd8:seg_dual <=EIGHT;
4'd9:seg_dual <=NINE ;
4'd10:seg_dual<=A ;
4'd11:seg_dual<=b ;
4'd12:seg_dual<=c ;
4'd13:seg_dual<=d ;
4'd14:seg_dual<=E ;
4'd15:seg_dual<=f ;
default: ;
endcase
end
else if(seg_flag==1)begin
if(key_in[1])begin
case (flag)
4'd0:seg_dual <=ZERO ;
4'd1:seg_dual <=ONE ;
4'd2:seg_dual <=TWO ;
4'd3:seg_dual <=THREE;
4'd4:seg_dual <=FOUR ;
4'd5:seg_dual <=FIVE ;
4'd6:seg_dual <=SIX ;
4'd7:seg_dual <=SEVEN;
4'd8:seg_dual <=EIGHT;
4'd9:seg_dual <=NINE ;
4'd10:seg_dual<=A ;
4'd11:seg_dual<=b ;
4'd12:seg_dual<=c ;
4'd13:seg_dual<=d ;
4'd14:seg_dual<=E ;
4'd15:seg_dual<=f ;
default: ;
endcase
end
end
end
endmodule
经过本次修改之后就可以观察得到数码管显示数值的一个修改过程,到这里本次设计也就结束了。