FPGA数码管动态显示(详细说明)
目录
一、实验说明
数码管显示主要有静态显示和动态显示,静态显示特点是在一个时间上所有位显示是一样的数值,显示单一无法满足更多的需求。这个情况下就需要选择更加灵活的动态显示。
二、理论学习
2.1动态显示原理
由上图数码管接线可知数码管LED灯的所有阳级(正极)共接,故该数码管为共阳数码管。这六位数码管段阴级(负极)都并联,在阳极都给高电平的同时负极驱动,就会使得六位数码管显示内容一致,效果为静态显示。所以要实现动态显示程序设计会更加复杂。
动态显示目的是六位数码管上内容都不一样比如显示742347这个数。先了解一个现象:
余晖效应:人眼在观察景物时,光信号传入大脑神经,需经过一段短暂的时间,光的作用结束后,视觉影像并不立即消失的现象也称视觉暂留现象。当停止向发光二极管供电时发光二极管亮度在人眼中仍能维持一段时间。
由上面现象可知,若一个数码管在 1s 内点亮两次,人眼很明显的看到其亮了两次,若 1s 内点亮 10 次呢?只能看到其在快速的闪烁,若点亮 100 次 1000次呢?总有一个速度是人眼分辨不出来在闪烁的(1ms)。所以一个数码管一直亮不需要一直给高电平,只要在很短的时间点亮一次后熄灭,再点亮熄灭。在六位数码管中,在很短的时间点亮第一位数码管的同时,驱动段选,然后在点亮下一位同时驱动段选,六位以此循环看起来六位数码管就是同一时刻显示且显示的内容不一样,这样的方法称为动态扫描。
最后总结动态显示的驱动方式:使用 1ms 的刷新时间让六个数码管轮流显示,:第 1ms 点第一个数码管,第 2ms 点亮第二个数码管,以此类推依次点亮六个数码管, 6ms 一个轮回。点亮相应数码管的时候给驱动段选使其显示相应的值,这样就可以使六个数码管显示不同的值,并且人眼察觉不到数码管在闪烁。
三、实践
实验目标:六位数码管显示从十进制数 0 开始计数,每 0.1s 加 1,一直到加到十进制数999999,到达 999999 之后回到 0 开始重新计数,以此循环。
3.1六位数码管硬件电路说明
3.2程序设计
3.2.1整体说明
由实验要求,可以作如下设计框架。
根据框图可以看到实验一共分为 4 个模块,下面分模块介绍。
3.2.2顶层设计(top_seg)
模块功能:顶层设计划分出来系统的层次,不涉及代码的编写,只是系统的输入输出口与功能模块的例化。注意:在添加RTL代码时应先添加顶层文件,否则会进行报错!
/* Author: xsy
Create Date: 20221114
Description: 顶层模块:调用各个功能模块,使数码管每过0.1s 由0自加1直到999_999 @1较为复杂的电路需模块分层
method: */
`timescale 1ns / 1ns
module top_seg
(
input sys_clk,
input sys_rst_n,
output [5:0] sel, //数码管位选信号
output [7:0] seg //数码管段选信号
);
wire [19:0] data; //数码管显示的数值
wire en; //数码管显示使能信号,高电平有效
seg_data seg_data_inst( //数据产生模块,每过0.1s数据自加一
.sys_clk (sys_clk) ,
.sys_rst_n (sys_rst_n) ,
.data (data)
);
seg_dynamic seg_dynamic_inst( //数码管动态显示模块
.sys_clk (sys_clk) ,
.sys_rst_n (sys_rst_n) ,
.data (data) ,
.sel (sel) ,
.seg (seg)
);
endmodule
3.2.3数据产生(seg_data)
模块功能:计时器每过0.1s,数据加1直至999_999后回到1又循环计数。如果要显示其他内容,在此模块进行更改即可。
/* Author: xsy
Create Date: 20221114
Description: 数据产生模块:数码管每过0.1s,数据由0自加1直到999_999
method: */
`timescale 1ns / 1ns
module seg_data
#(
parameter CNT_MAX = 23'd4999_999 , //最大计数值0.1s:5000_000个脉冲
//5000_000 x 20ns = 100_000_000ns = 0.1s
parameter DATA_MAX = 20'd99 //计数最大值
)
(
input sys_clk ,
input sys_rst_n ,
output reg [19:0] data
);
//计数器cnt与最大计数值搭配使用,用于计时100ms(0.1s)
reg [22:0] cnt;
//0.1s计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
cnt <= 23'b0;
else if(cnt == CNT_MAX)
cnt <= 23'b0;
else
cnt <= cnt + 1'b1;
//计数器每过0.1s,数码从0累加到999999
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
data <= 20'b0;
else if((cnt == CNT_MAX) && (data == DATA_MAX))
data <= 20'b0;
else if (cnt == CNT_MAX)
data <= data +1'b1;
else
data <= data;
endmodule
3.2.4二进制转BCD码(bcd_8421)
模块功能:计算机内部只能进行二进制数0与1的运算,故无法直接显示在数码管上显示十进制数,这里采用二进制转BCD码的方式进行转换。
BCD码:又称二 —十进制码,用4位二进制数表示一位十进制数的一种形式。例如4——>0100
下面以显示十进制数234为例,介绍如何将其转化为对应的 8421BCD 码。
首先,准备工作我们需在输入的二进制数前进行补零,补零的个数由十进制数位数决定,故234有三位数需要12个零。第一个脉冲到来进行BCD码的判断,当BCD码大于4则加3,反之BCD码保持不变。再将输入的二进制向BCD码移入一位,第二个脉冲到来又进行BCD码的判断,重复上述过程至第八个脉冲判断移位结束,第九个脉冲取出转换结果给数据驱动模块。
/* Author: xsy
Create Date: 20221114
Description: 二进制转BCD码
method: */
`timescale 1ns/1ns
module bcd_8421
(
input wire sys_clk ,
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码
);
reg [4:0] cnt_shift ;//移位判断计数器 移动次数由十转为二进制的位数决定
reg [43:0] data_shift ;//移位判断数据 寄存器 存放BCD码与二进制码
reg shift_flag ;//移位判断标志信号 用于控制移位判断的先后顺序
//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;
//cnt_shift: 从0到21循环计数(当计为20时进行判断移位,21时则是取BCD码数据)
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;
//判断、移位数据寄存器,存放BCD码与二进制码
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) //计数器为0时,BCD码为零
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;
//当计数器等于21时,将结果输出
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) //输出BCD码
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
3.2.5数码管显示驱动(seg_dynamic)
该模块得到BCD码后进行数码管位控制和段控制,驱动数码管显示。
/* Author: xsy
Create Date: 20221118
Description: 数码管驱动模块
method: / */
`timescale 1ns/1ns
module seg_dynamic
#(
parameter CNT_MAX = 16'd49_999 //数码管 刷新 时间计数最大值 1ms
)
(
input wire sys_clk ,
input wire [19:0] data ,
output reg [5:0] sel , //数码管位选信号
output reg [7:0] seg //数码管段选信号
);
//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 ; //当前数码管显示的数据
//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;
//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) //h_hun非空即数据有六位
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
//若显示的十进制数的 万 位为非零数据则值显示在5个数码管上
else if(t_tho)
data_reg <= {4'd11,t_tho,tho,hun,ten,unit};//4'd11(第六位)定义为不显示
//若显示的十进制数的 千 位为非零数据则值显示4个数码管
else if(tho)
data_reg <= {4'd11,4'd11,tho,hun,ten,unit};
//若显示的十进制数的 百 位为非零数据则值显示3个数码管
else if(hun)
data_reg <= {4'd11,4'd11,4'd11,hun,ten,unit};
//若显示的十进制数的 十 位为非零数据则值显示2个数码管
else if(ten)
data_reg <= {4'd11,4'd11,4'd11,4'd11,ten,unit};
//若上面都不满足就只显示 个 位数码管
else
data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd11,unit};
//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(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;
//控制数码管段选信号,显示内容
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 <= 8'b1100_0000; //显示数字0
4'd1 : seg <= 8'b1111_1001; //显示数字1
4'd2 : seg <= 8'b1010_0100; //显示数字2
4'd3 : seg <= 8'b1011_0000; //显示数字3
4'd4 : seg <= 8'b1001_1001; //显示数字4
4'd5 : seg <= 8'b1001_0010; //显示数字5
4'd6 : seg <= 8'b1000_0010; //显示数字6
4'd7 : seg <= 8'b1111_1000; //显示数字7
4'd8 : seg <= 8'b1000_0000; //显示数字8
4'd9 : seg <= 8'b1001_0000; //显示数字9
default:seg <= 8'b1111_1111;
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;
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
四、仿真验证
逐个模块进行激励,查看波形。过于繁琐这里不展示。
五、上板验证(图片展示)
六.实验总结
1.主要掌握动态扫描的方法
2.比较单片机的动态显示,原理一样。但是使用fpga更复杂
*说明:本人是学习教程后加工后分享出来的,深知没有加入创意望理解。