关于数码管的基本知识大家可以参考我上一篇文章数码管的静态显示,
动态数码管的驱动方式
使用 1ms 的刷新时间让六个数码管轮 流显示:第 1ms 点亮第一个数码管,第 2ms 点亮第二个数码管,以此类推依次点亮六个数 码管,6ms 一个轮回,也就是说每个数码管每 6ms 点亮 1ms,这样就能让人眼感觉到数码 管一直在亮了。点亮相应数码管的时候给其显示相应的值,这样就可以使六个数码管显示 不同的值了,这就是驱动数码管动态显示的方法。
实验目的
让六位数码管显示从十进制数 0 开始计数,每 0.1s 加 1,一直到加到十进制数 999999。到达 999999 之后回到 0 开始重新计数。
程序设计
整体框架设计
我们可以先设计出系统的整体框架结构,以便我们明白我们的程序可以分为那几部分进行,明确系统的构成。
根据系统框架结构我们可以分析出该系统可以分为六个子模块,现在我们依次来介绍这六个子模块:
模块名称 | 功能描述 |
data_gen | 数据生成模块 |
seg_danamic | 动态数码管显示驱动模块 |
bcd_8421 | 二进制转bcd码模块 |
hc595_crtl | 74HC595控制模块 |
seg_595_dynamic | 数码管动态显示模块 |
top_seg_595 | 顶层模块 |
接下来我们根据各子模块来完成整个实验:
数据生成模块
模块框图:
信号 | 位宽 | 类型 | 功能描述 |
sys_clk | 1bit | Input | 系统时钟信号,频率50Mhz |
sys_rst_n | 1bit | Input | 复位信号低电平有效 |
point | 6bit | Output | 输出小数点 |
data | 20bit | Output | 输出数据 |
sign | 1bit | Output | 输出符号 |
seg_en | 1bit | Output | 数码管使能信号 |
绘制数据波形图
cnt_100ms:我们实验的要求是每 0.1s 让显示的数据加 1,所以我们需要用到一个间隔 为 0.1s 的循环计数器。计数器其从 0 开始计数,计到 0.1s(4999999)时归 0 开始下一个 0.1s 计数。因为计数器的时钟为 50MHz,一个时钟为 1/50MHz(s),也就是 20ns,所 以 0.1s=4999_999*20ns。
cnt_flag:当计数器计到 0.1s 时拉高一个标志信号,让这个标志信号去控制数据的加 1。 data:输出数据。当检测到 0.1s 到来的标志信号为高时让其加 1,当加到 999999 并检 测到 0.1s 到来的标志信号时让其归 0,开始下一轮的数据显示。
point:输出小数点。在这里我们让其高电平有效(以本次使用的数码管为例,即当小 数点对应段选信号为低,位选信号为高时点亮有效),本次实验不需要显示小数点,让每 一位都为 0 即可。
seg_en:数码管使能信号。因为我们一直在显示,所以一直给其拉高就行
sign:负号显示,高电平有效。因本次实验不显示负号,给其一直为低电平即可。
module data_gen
#(
parameter CNT_MAX = 23'd4999_999,
parameter DATA_MAX = 20'd999_999
)
(
input wire sys_clk,
input wire sys_rst_n,
output reg [19:0] data,
output wire[5:0] point,
output reg seg_en,
output wire sign
);
reg [22:0] cnt_100ms;
reg cnt_flag;
//不显示小数点和负数
assign point = 6'b000_000;
assign sign = 1'b0;
//cnt_100ms循环计数计数计到100ms
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_100ms <= 23'd0;
else if(cnt_100ms == CNT_MAX)
cnt_100ms <= 23'd0;
else
cnt_100ms <= cnt_100ms + 1'b1;
//cnt_flag:每100ms产生一个标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_flag <= 1'b0;
else if(cnt_100ms == CNT_MAX - 1'b1)
cnt_flag <= 1'b1;
else
cnt_flag <= 1'b0;
//数码管显示数据0~999——999
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data <= 20'd0;
else if((data == DATA_MAX) && (cnt_flag == 1'b1))
data <= 20'd0;
else if(cnt_flag == 1'b1)
data <= data + 1'b1;
else
data <= data;
//数码管使能信号拉高
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg_en <= 1'b0;
else
seg_en <= 1'b1;
endmodule
二进制转BCD码模块
根据实验目标可知我们要让数码管显示的数是十进制数,而我们数码管是通过段选信 号和位选信号去控制每个数码管进行显示的,一位数码管显示的是一个十进制数,而我们 的十进制数是以二进制进行编码的,如果我们直接使用二进制编码进行显示,那么每个数 码管显示的数就不是十进制数而是十六进制数了。所以我们需要将二进制编码的十进制数 转换为 BCD 编码的十进制数进行显示。
BCD 码(Binary-Coded Decimal),又称二 - 十进制码,使用 4 位二进制数来表示 1 位 十进制数中的 0~9 这 10 个数码,是一种二进制的数字编码形式,用二进制编码的十进制代 码。
BCD 码根据权值的有无可分为“有权码”和“无权码”。其中的权字表示的是权值, 有权码的四位二进制数中的每一位都有一个固定的权值,而无权码是没有权值的。常见的 有权码有 8421 码、5421 码和 2421 码,8421 码它的权值从左到右是 8421,5421 码的权值 从左到右是 5421,2421 码的权值是 2421,其中 8421 码是最为常用的 BCD 编码,本实验 中使用的也是这种编码。常用的无权码有余 3 码、余 3 循环码。
以本次实验我们使用的 8421BCD 编码 为例,比如说十进制数 5,它的 8421BCD 码为 0101,那么我们怎么通过 0101 得到我们的 数字 5 呢,这里需要用到一个算法,就是将其每位二进制数乘以它的权值然后相加。十进 制 5 的 8421BCD 码为 0101,即:1×1 + 0×2 + 1×4+ 0×8 = 5。其它有权码也是这种计算方 式,
为什么转换为 BCD 码就能进行十 进制的显示了呢?这里我们举个例子,例如我们需要显示十进制数据 234,根据我们前面讲的动态显示原理,在第一个显示周期我们点亮数码管 1,并让其显示值 4;在第二个显示 周期我们点亮数码管 2,并让其显示值 3;在第三个显示周期我们点亮数码管 3,并让其显 示值 2;第四、五、六个周期我们不点亮其余数码管即可,以此循环就完成了十进制数 234 的显示。那个我们如何才能在点亮相应的值时给其显示相应的值呢?我们先看看 234 的十 进制数的二进制表示为:1110_1010;234 的 8421BCD 码为:0010_0011_0100;可以发现 如果我们使用二进制数赋值我们并不能准确的给到 2、3、4 值,而如果我们用 8421BCD 码 给其赋值的话,每 4 位 8421BCD 码代表一个十进制数,那么就能完美的进行显示了。
绘制本模块的实验框图
信号 | 位宽 | 类型 | 功能描述 |
sys_clk | 1bit | Input | 系统时钟频率为50MHZ |
sys_rst_n | 1bit | Input | 复位信号,低电平有效 |
data | 20bit | Input | 输入以二进制表示的十进制数 |
unit | 1bit | Output | 个位BCD码 |
ten | 1bit | Output | 十位BCD码 |
hun | 1bit | Output | 百位BCD码 |
tho | 1bit | Output | 千位BCD码 |
t_tho | 1bit | Output | 万位BCD码 |
h_hun | 1bit | Output | 十万位BCD码 |
绘制波形图:
cnt_shift:移位判断计数器,前面我们说到我们输入转换的二进制码有多少位我们就需 要进行多少次判断移位操作,这里我们 data 数据的位宽为 20 位,所以这里我们声明移位 判断计数器对移位 20 次进行判断控制。
data_shift:移位判断数据寄存器,该寄存器用于存储移位判断操作过程中的数据,这 里我们输入的二进制位宽为 20 位,待转换成的 BCD 码位宽为 24 位,所以这里我们声明该 寄存器的位宽为输入的二进制位宽和待转换完成的 BCD 码位宽之和,即 44 位。根据波形 图可知,这里我们设计当移位计数器等于 0 时寄存器的低 20 位即为待转换数据,而由于还 没开始进行转换,高 24 位的 BCD 码我们补 0 即可。
shift_flag:移位判断操作标志信号。前面说到我们需要对数据进行移位和判断,判断 在前移位在后,所以这里我们声明一个标志信号,用于控制判断和移位的先后顺序,当 shift_flag 为低时对数据进行判断,当 shift_flag 为高时对数据进行移位。需要注意的是无论 是移位操作和判断操作都是在单个系统时钟下完成的,故我们判断 20 次移位 20 次在 40 个 系统时钟内就能完成。 unit、ten、hum、tho、t_tho、h_hun:六路 BCD 码,前面我们说到我们开发板上有六 位数码管,故可以显示的最大值是六位十进制数,随意这里我们声明了六路 BCD 码,一个 BCD 码代表十进制的一个位数,其中 unit 代表个位、ten 代表十位、hun 代表百位、tho 代 表千位、t_tho 代表万位、h_hun 代表十万位。当我们的移位判断计数器等于 21 是说明 20 位二进制转 BCD 码的移位判断操作已经完成,此时 data_shift 里寄存的数据就是转换的寄 存数据,该数据的高 24 位即转换完成后的 BCD 码。所以当 cnt_shift 等于 20 是就将寄存的 BCD 码值赋值为相对应的各个位数。
编写二进制转BCD码
module bcd_8421
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [19:0] data ,
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_hun
);
reg [4:0] cnt_shift ;
reg [43:0] data_shift ;
reg shift_flag ;
//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;
//当计数器等于二十时,移位判断操作完成,对各个位数的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
数码管动态显示驱动模块
模块框图:
信号 | 位宽 | 类型 | 功能描述 |
sys_clk | 1bit | Input | 系统时钟,频率50MHZ |
sys_rst_n | 1bit | Input | 复位信号,低电平有效 |
point | 6bit | Input | 输入小数点 |
data | 20bit | Input | 输入数据 |
seg_en | 1bit | Input | 数码管使能信号 |
sign | 1bit | Input | 输入符号 |
sel | 6bit | Output | 数码管位选信号 |
seg | 8bit | Output | 数码管段选信号 |
波形图绘制:
根据波形图对信号进行逐一说明:
point:输入小数点控制信号,高电平有效,这里我们假设要让第二个数码管显示小数 点,其余数码管不显示小数点,那么此时 point 的输入的值就应该是 6’b000010。
seg_en:数码管使能信号,这里一直让其拉高即可。
data:输入的十进制数据,假设这里我们输入的十进制数为 9876。
sign:符号位控制信号,高电平有效。假设我们需要显示的是负数,那么这里就让符 号位控制信号为高即可。 unit、ten、hun、tho、t_tho、h_hun:这六个信号就是我们例化的 bcd_8421 模块转化的 的 8421BCD 码,也就是说这六个 BCD 码就是输入十进制数 9876 各个位的 BCD 码。所以 这里个位(unit)是 6,十位(ten)是 7,百位(hun)是 8,千位(tho)是 9,万位和十 万位都为 0。
data_reg:数码管待显示内容寄存器,因为这里我们假设输入要显示的十进制数为 9876,并且显示负号,所以前五个数码管就会显示-9876 的数值,此时最高位数码管什么都 不显示,我们用 X 表示,所以这里六个数码管显示的内容就是:X-9876。
cnt_1ms:前面讲到要让显示的数码管不会有闪烁感,我们需要使用 1ms 的扫描时间去 扫描各个数码管。所以这里我们需要一个 1ms 的计数器对 1ms 进行循环计数。
flag_1ms:1ms 计数标志信号,当 1ms 计数器计到 1ms 时拉高该标志信号,我们使用 该标志信号去控制位选数码管计数器的计数。
cnt_sel:位选数码管计数器。我们在理论学习中说到动态扫描方式是用 1ms 的刷新时 间让六个数码管轮流显示:第 1ms 点亮第一个数码管,第 2ms 点亮第二个数码管,以此类 推依次点亮六个数码管,6ms 一个轮回,也就是说每个数码管每 6ms 点亮一次。那问题是 我们怎么去选中这个要显示的数码管并且给其要显示的值呢?这个时候我们就引入了一个 cnt_sel 信号,让其从 0~5 循环计数,1 个数代表一个数码管,可以看做是给数码管编号。 这样的话我们只要选择计数器的值就相当于选中了其中对应的数码管。特别要说明的是我 们的 cnt_sel 计数器必须与数码管的刷新状态一致,也就是 1ms 计 1 个数。
sel_reg:数码管位选信号寄存器,为了让数码管位选信号和段选信号同步,这里我们 先将位选信号进行寄存。刷新到哪个数码管就将 sel 中对应位(6 个位宽,每一位对应一个 数码管)给高点亮即可。选中点亮的数码管后我们需要给其要显示的值,所以我们引入一 个新的信号。
data_disp:当前点亮数码管显示的值。若我们此时点亮的是第一个数码管,那么我们 就需要给第一个数码管显示值 6,若刷新到第二个数码管,那么我们就需要给第二个数码 管显示值 7,以此类推;当刷新到第五个数码管时,此时显示的是负号,那么我们该如何 表示呢?这里我们让该信号的值为 10 来表示,也就是说当 data_disp 的值为 10 时就让数码 管显示负号,同理这里我们定义 data_disp 的值为 11 时让数码管什么也不显示,即不点亮 数码管。
dot_disp:当前数码管显示的小数点,我们输入的 point 信号是点亮第二个数码管的小 数点,而我们的数码管是低电平点亮,所以这里当扫描到第二个数码管时让 dot_disp 信号 为低即可。 seg:数码管段选信号,我们根据数码管编码译码表当扫描到哪个数码管显示需要显示 的值时,我们将对于的段点亮即可。
sel:数码管位选信号。将数码管位选信号寄存器打一拍即可,这样就能实现数码管段 选信号和位选信号的同步。
module seg_dynamic
(
input wire sys_clk,
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 CNT_MAX = 16'd49_999
)
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 [23:0] data_reg;
reg [15:0] cnt_1ms ;
reg [2:0] cnt_sel ;
reg [5:0] sel_reg ;
reg [3:0] data_disp;
reg dot_disp;
reg flag_1ms;
//主程序
//data_reg:控制数码管显示数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_reg <= 24'd0;
//若显示十进制的十万位为非零数据或需要显示小数点,则六个数码管全部显示
else if((h_hun) || (point[5]))
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
else if((t_tho) || (point[4]) && (sign == 1'b1))
data_reg <= {4'd10,t_tho,tho,hun,ten,unit};
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] ;
3'd1: data_disp <= data_reg[7:4] ;
3'd2: data_disp <= data_reg[11:8] ;
3'd3: data_disp <= data_reg[15:12];
3'd4: data_disp <= data_reg[19:16];
3'd5: data_disp <= data_reg[23:20];
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;
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
数码管动态显示模块
我们将数码管动态显示驱动模块和 74HC595 控制模块整合到一个模块之中,后面我们 工程要用到数码管动态显示时我们直接例化这一个模块即可,较为方便。其模块框图如图所示:
该模块主要是对数码管动态显示驱动模块和 74HC595 控制模块的实例化,以及对应信 号的连接,
module seg_595_dynamic
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [19:0] data ,
input wire [5:0] point ,
input wire seg_en ,
input wire sign ,
output wire stcp ,
output wire shcp ,
output wire ds ,
output wire oe
);
//wire define
wire [5:0] sel;
wire [7:0] seg;
seg_dynamic seg_dynamic_inst
(
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n) ,
.data (data ) ,
.point (point ) ,
.seg_en (seg_en ) ,
.sign (sign ) ,
.sel (sel ) ,
.seg (seg )
);
hc595_crtl hc595_crtl_inst
(
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n) ,
.sel (sel ) ,
.seg (seg ) ,
.stcp (stcp ) ,
.shcp (shcp ) ,
.ds (ds ) ,
.oe (oe )
);
endmodule
595控制模块
module hc595_crtl
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [5:0] sel ,
input wire [7:0] seg ,
output reg stcp ,
output reg shcp ,
output reg ds ,
output wire oe
);
reg [1:0] cnt_4;
reg [3:0] cnt_bit;
wire [13:0] data;
assign data = {seg[0],seg[1],seg[2],seg[3],seg[4],seg[5],seg[6],seg[7],sel};
assign oe = 1'b0;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_4 <= 2'd0;
else if(cnt_4 == 2'd3)
cnt_4 <= 2'd0;
else
cnt_4 <= cnt_4 + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_bit <= 4'd0;
else if(cnt_4 == 2'd3 && cnt_bit == 4'd13)
cnt_bit <= 4'd0;
else if(cnt_4 == 2'd3)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
stcp <= 1'b0;
else if(cnt_4 == 2'd3 && cnt_bit == 4'd13)
stcp <= 1'b1;
else
stcp <= 1'b0;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
shcp <= 1'b0;
else if (cnt_4 >= 4'd2)
shcp <= 1'b1;
else
shcp <= 1'b0;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
ds <= 1'b0;
else if (cnt_4 == 2'd0)
ds <= data[cnt_bit];
else
ds <= ds;
endmodule
顶层模块设计
模块框图
顶层模块主要是对各个子功能模块的实例化,以及对应信号的连接。
module top_seg_595
(
input wire sys_clk,
input wire sys_rst_n,
output wire stcp,
output wire shcp,
output wire ds,
output wire oe
);
wire [19:0] data;
wire [5:0] point;
wire seg_en;
wire sign;
data_gen
#(
.CNT_MAX (23'd4999_999),
.DATA_MAX (20'd999_999)
)
data_gen_inst
(
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n) ,
.data (data ) ,
.point (point ) ,
.seg_en (seg_en ) ,
.sign (sign )
);
seg_595_dynamic seg_595_dynamic_inst
(
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n ) ,
.data (data ) ,
.point (point ) ,
.seg_en (seg_en ) ,
.sign (sign ) ,
.stcp (stcp ) ,
.shcp (shcp ) ,
.ds (ds ) ,
.oe (oe )
);
endmodule
仿真代码编写:
`timescale 1ns/1ns
module tb_top_seg_595();
reg sys_clk;
reg sys_rst_n;
wire stcp;
wire shcp;
wire ds;
wire oe;
initial
begin
sys_clk = 1'b1;
sys_rst_n = 1'b0;
#30
sys_rst_n = 1'b1;
end
always #10 sys_clk <= ~sys_clk;
defparam top_seg_595_inst.seg_595_dynamic_inst.seg_dynamic_inst.CNT_MAX=16'd19;
defparam top_seg_595_inst.data_gen_inst.CNT_MAX = 23'd49;
top_seg_595 top_seg_595_inst
(
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n ) ,
.stcp (stcp ) ,
.shcp (shcp ) ,
.ds (ds ) ,
.oe (oe )
);
endmodule
仿真波形:
本人亲测结果是正确的,需要自己仿真后慢慢分析波形
绑定FPGA管脚上板验证
上板验证结果为:
由于时间关系没有跑到999_999.文章来源:https://www.toymoban.com/news/detail-715671.html
创作不易有用请点赞,后续我还会继续更新相关实验。 文章来源地址https://www.toymoban.com/news/detail-715671.html
到了这里,关于FPGA基本实验之数码管的动态显示的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!