FPGA数码管动态显示
FPGA驱动6位数码管,主控芯片EP4CE6F17C8N。
所使用实验板的数码管原理图如图所示,所使用的数码管3661BS是6位共阳极的数码管。使用PNP三极管驱动数码管,当三极管基极SMG_W0引脚输入低电平时,PNP三极管导通。通过控制SMG_W0~W5的电平来控制三极管的导通,从而控制位选信号。共阳极数码管,段选信号输入低电平有效。
![](https://img2023.cnblogs.com/blog/2126010/202307/2126010-20230717102952442-86344729.png)
整个数码管动态项目共有4个模块。顶层模块seg_top、数据生成模块data_generate、数码管动态显示模块seg_dynamic、数据转换模块bcd8421。
![](https://img2023.cnblogs.com/blog/2126010/202307/2126010-20230717102952858-13979290.png)
seg_top.v
module seg_top
(
input wire clk , //系统时钟,频率50MHz
input wire reset_n , //复位信号,低有效
output wire [5:0] bit , //数码管位选信号
output wire [7:0] seg //数码管段选信号
);
wire [19:0] data ; //数码管要显示的值
wire [5:0] point ; //小数点显示,高电平有效
wire seg_en ; //数码管使能信号,高电平有效
wire sign ; //符号位,高电平显示负号
smg_dynamic smg_dynamic_inst1
(
.clk (clk), //系统时钟,频率50MHz
.reset_n (reset_n), //复位信号,低有效
.data (data), //数码管要显示的值
.point (point), //小数点显示,高电平有效
.seg_en (seg_en), //数码管使能信号,高电平有效
.sign (sign), //符号位,高电平显示负号
.bit (bit), //数码管位选信号
.seg (seg)//数码管段选信号
);
data_generate data_generate_inst1
(
.clk (clk ),
.reset_n (reset_n),
.data (data ),
.point (point ),
.seg_en (seg_en ),
.sign (sign )
);
endmodule
data_generate.v
//数据生成模块,生成数码管要显示的数据
module data_generate
#(
parameter CNT_MAX = 32'd4999_9999, //20ns*50000000=1s
parameter DATA_MAX = 20'd999_999 //数码管显示的最大数999_999
)
(
input clk,
input reset_n,
output reg [19:0] data , //数码管要显示的数据,0~999_999需要20bit
output wire [5:0] point , //小数点显示,高电平有效
output reg seg_en , //数码管使能信号,高电平有效
output wire sign //符号位,高电平显示负号
);
//不显示小数点以及负数
assign point = 6'b000_000;
assign sign = 1'b0;
reg [31:0] cnt_1s; //1s计数器
reg flag_1s; //1s标志位
//计时1s
always@(posedge clk or negedge reset_n)
if(!reset_n)
cnt_1s <= 32'b0;
else if(cnt_1s == CNT_MAX)
cnt_1s <= 32'b0;
else
cnt_1s <= cnt_1s + 1;
//记满1s产生一次标志位
always@(posedge clk or negedge reset_n)
if(!reset_n)
flag_1s <= 1'b0;
else if(cnt_1s == CNT_MAX - 1) //这里减一是为了和下次脉冲信号之间相差1s
flag_1s <= 1'b1;
else
flag_1s <= 1'b0;
//根据脉冲生成要显示的数码管数据,每1s使数码管的值+1
always@(posedge clk or negedge reset_n)
if(!reset_n)
data <= 20'b0;
else if((data == DATA_MAX) && (flag_1s == 1'b1)) //这里减一是为了和下次脉冲信号之间相差1s
data <= 20'b0;
else if(flag_1s == 1'b1)
data <= data + 1;
else
data <= data;
//数码管使能信号给高即可
always@(posedge clk or negedge reset_n)
if(!reset_n)
seg_en <= 1'b0;
else
seg_en <= 1'b1;
endmodule
seg_dynamic.v
//数码管动态显示模块
module smg_dynamic
#(
parameter CNT_MAX = 49999
)
(
input wire clk , //系统时钟,频率50MHz
input wire reset_n , //复位信号,低有效
input wire [19:0] data , //数码管要显示的值
input wire [5:0] point , //小数点显示,高电平有效
input wire seg_en , //数码管使能信号,高电平有效
input wire sign , //符号位,高电平显示负号
output reg [5:0] bit , //数码管位选信号
output reg [7:0] seg //数码管段选信号
);
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_tho; //十万位
//BCD模块例化
bcd8421 bcd8421_inst1(
.clk (clk ),
.reset_n (reset_n),
.data (data ),
.unit (unit ),
.ten (ten ),
.hun (hun ),
.tho (tho ),
.t_tho (t_tho ),
.h_tho (h_tho )
);
//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 ; //当前数码管显示的小数点
//data_reg:控制数码管显示数据
always@(posedge clk or negedge reset_n)
if(reset_n == 1'b0)
data_reg <= 24'b0;
//若显示的十进制数的十万位为非零数据或需显示小数点,则六个数码管全显示
else if((h_tho) || (point[5]))
data_reg <= {h_tho,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 clk or negedge reset_n)
if(reset_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 clk or negedge reset_n)
if(reset_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 clk or negedge reset_n)
if(reset_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 clk or negedge reset_n)
if(reset_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) //每1ms修改一次位选信号
sel_reg <= sel_reg << 1;
else
sel_reg <= sel_reg;
//控制数码管的位选信号,使六个数码管轮流显示
always@(posedge clk or negedge reset_n)
if(reset_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 clk or negedge reset_n)
if(reset_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 clk or negedge reset_n)
if(reset_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
//bit:数码管位选信号赋值,位选低电平有效
always@(posedge clk or negedge reset_n)
if(reset_n == 1'b0)
bit <= 6'b000_000;
else
bit <= ~sel_reg;
endmodule
本实验使用的是6位数码管,data_generate模块每1s数字加一,从0~999999,共需要20位二进制数。当把这20位数据传输给数码管动态显示模块时,需要将数据的每一位显示到数码管上,该20位数据不能直接反映出数码管的每一位。使用4位8421码来表示一位十进制数。
bcd8421.v
//数据转换模块,二进制转8421码,方便数码管数据的显示
module bcd8421(
input wire clk,
input wire reset_n,
input wire [19:0] data, //要转换的二进制数据,0~999_999需要20bit
//分别输出6位
output reg [3:0] unit, //个位
output reg [3:0] ten, //十位
output reg [3:0] hun, //百位
output reg [3:0] tho, //千位
output reg [3:0] t_tho, //万位
output reg [3:0] h_tho //十万位
);
reg [43:0] data_all; //43~20是BCD码,19~0是要转换的二进制数据
//低20位数据每次左移一位至高24位
reg shift_flag; //左移标志位
reg [4:0] shift_cnt; //移位计数,原来有20位二进制数要左移20次
//完成一次判断和移位操作后,计数加一
always@(posedge clk or negedge reset_n)
if(!reset_n)
shift_cnt <= 5'b0;
else if((shift_cnt == 5'd21) && (shift_flag == 1'b1))
shift_cnt <= 5'b0;
else if(shift_flag == 1'b1)
shift_cnt <= shift_cnt + 1;
else
shift_cnt <= shift_cnt;
always@(posedge clk or negedge reset_n)
if(!reset_n)
data_all <= 44'b0;
else if(shift_cnt <= 5'd0)
data_all <= {24'b0,data};
else if((shift_cnt <= 5'd20) && (shift_flag == 1'b0)) //判断是否大于4,大于4则加3,即4之后为8
begin
data_all[23:20] <= (data_all[23:20] > 4) ? (data_all[23:20] + 2'd3) : (data_all[23:20]);
data_all[27:24] <= (data_all[27:24] > 4) ? (data_all[27:24] + 2'd3) : (data_all[27:24]);
data_all[31:28] <= (data_all[31:28] > 4) ? (data_all[31:28] + 2'd3) : (data_all[31:28]);
data_all[35:32] <= (data_all[35:32] > 4) ? (data_all[35:32] + 2'd3) : (data_all[35:32]);
data_all[39:36] <= (data_all[39:36] > 4) ? (data_all[39:36] + 2'd3) : (data_all[39:36]);
data_all[43:40] <= (data_all[43:40] > 4) ? (data_all[43:40] + 2'd3) : (data_all[43:40]);
end
else if((shift_cnt <= 5'd20) && (shift_flag == 1'b1))
data_all <= data_all << 1;
else
data_all <= data_all;
always@(posedge clk or negedge reset_n)
if(!reset_n)
shift_flag <= 1'b0;
else
shift_flag <= ~shift_flag; //每次上升沿状态取反,0:大于4加三,1:执行左移操作
//当计数器等于20时,移位判断操作完成,对各个位数的BCD码进行赋值
always@(posedge clk or negedge reset_n)
if(!reset_n) begin
unit <= 4'b0;
ten <= 4'b0;
hun <= 4'b0;
tho <= 4'b0;
t_tho <= 4'b0;
h_tho <= 4'b0;
end
else if(shift_cnt == 5'd21) begin //shift_cnt等于21时表示所有操作已经完成
unit <= data_all[23:20];
ten <= data_all[27:24];
hun <= data_all[31:28];
tho <= data_all[35:32];
t_tho <= data_all[39:36];
h_tho <= data_all[43:40];
end
endmodule
实验结果:从0一直加到999999,1秒加一个数
![](https://img2023.cnblogs.com/blog/2126010/202307/2126010-20230717102953846-153928288.gif)
参考资料:野火FPGA