16_数码管的静态显示

1. 理论学习

1.1 数码管简介

数码管结构图,如下所示:
在这里插入图片描述
分为八段:a、b、c、d、e、f、g、dp,其中 dp 为小数点,每一段即为一个发光二极管,这样的八段我们称之为段选信号。数码管常用的有 10 根管脚,每一段有一根管脚,另外两根管脚为一个数码管的公共端,两根互相连接。

数码管编码译码表:
在这里插入图片描述
在这里插入图片描述

1.2 74HC595 简介

74HC595 内部结构图:
在这里插入图片描述
74HC595 原理图:
在这里插入图片描述

1.3 74HC595 的使用步骤

1)首先把要传输的数据通过引脚 DS 输入到 74HC595 中。
2)产生 SHCP 时钟,将 DS 上的数据串行移入移位寄存器。SHCP 为移位寄存器时钟输入,上升沿时将输入的串行数据(DS 端输入)移入移位寄存器中。需要注要的是它是一个移位寄存器,也就是说当下一个脉冲(时钟上升沿)到来时,上一个脉冲移入的数据就会往下移动一位。如果我们串行输入8bit 数据,8bit 数据输入完之后,那么第一位输入的数据将会移动到最后面。若我们一次输入的数据超过 8bit,那么后面的数据就会通过 Q7S 端口输出,此时我们可以将该接口接到另一片74HC595 芯片的串行输入端(级联),这样数据就会随着脉冲依次移位到另一片 74HC595芯片上。
3)产生 STCP 时钟,将移位寄存器里的数据送入存储寄存器。控制其输出,STCP上升沿时移位寄存器的数据会进入数据存储寄存器中。
4)将OE引脚置为低电平,存储寄存器的数据会在 Q0—Q7 并行输出,同时并行输出的数据会被锁存起来。

2. 实验目标

根据上一小节的理论学习,我们可以设计一个这样的 6 位数码管静态显示:控制六位数码管让其以 000000、111111、222222 一直到 FFFFFF 循环显示。每个字符显示 0.5s 后变化。

3. 系统框图

在这里插入图片描述

3.1 静态数码管驱动模块

用于产生 sel, 和seg 数据

在这里插入图片描述
在这里插入图片描述

3.2 74HC595 控制模块

用于产生驱动 74HC595数据
在这里插入图片描述
在这里插入图片描述

3.3 顶层模块

顶层模块主要是对各个子功能模块的实例化,以及对应信号的连接
在这里插入图片描述
在这里插入图片描述

4. 波形图

4.1 静态数码管驱动模块

在这里插入图片描述
在这里插入图片描述

4.2 74HC595 控制模块

1)本实验我们使用系统时钟(50MHz)四分频得到的 shcp 时钟(12.5MHz)去进行驱动,而 stcp 时钟是在我们串行输入 14 位数码管之后拉高的。
在这里插入图片描述
在这里插入图片描述

5. RTL

5.1 静态数码管驱动模块

module seg_static
(
	input wire sys_clk , //系统时钟,频率 50MHz
	input wire sys_rst_n , //复位信号,低电平有效
	
	output reg [5:0] sel , //数码管位选信号
	output reg [7:0] seg //数码管段选信号
);

 parameter CNT_WAIT_MAX = 25'd25_000_000; //计数器最大值(0.5s)
 //十六进制数显示编码 数字在显示器的编码的显示 数码管段选信号
 parameter SEG_0 = 8'b1100_0000, SEG_1 = 8'b1111_1001,
           SEG_2 = 8'b1010_0100, SEG_3 = 8'b1011_0000,
		   SEG_4 = 8'b1001_1001, SEG_5 = 8'b1001_0010,
		   SEG_6 = 8'b1000_0010, SEG_7 = 8'b1111_1000,
		   SEG_8 = 8'b1000_0000, SEG_9 = 8'b1001_0000,
		   SEG_A = 8'b1000_1000, SEG_B = 8'b1000_0011,
		   SEG_C = 8'b1100_0110, SEG_D = 8'b1010_0001,
		   SEG_E = 8'b1000_0110, SEG_F = 8'b1000_1110;
		   
parameter IDLE = 8'b1111_1111; //不显示状

//cnt_wait:根据实验要求需要等待 0.5s 后显示的字符才发生变化。
 reg [24:0] cnt_wait ; //时钟分频计数器
//add_flag:当计数器计到 0.5s 时,我们拉高一个标志信号,让这个标志信号去控制数码管字符的跳转。
 reg add_flag ; //数码管数值+1 标志信号
//num每个数码管显示的字符
 reg [3:0] num ; //数码管显示的十六进制数
 
 //cnt_wait:0.5 秒计数
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_wait <= 25'd0;
	else if(cnt_wait == CNT_WAIT_MAX - 25'd1)
		cnt_wait <= 25'd0;
	else	
		cnt_wait <= cnt_wait + 25'd1;

// add_flag:0.5s 拉高一个标志信号
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		add_flag <= 1'b0;
	else if(cnt_wait == CNT_WAIT_MAX - 25'd1 - 25'd1)
		add_flag <= 1'b1;
	else	
		add_flag <= 1'b0;
		
//  num:从 4'h0 加到 4'hf 循环
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		num <= 4'h0;
	else if(add_flag == 1'b1)
		num <= num + 1'b1;
	else	
		num <= num;

 //sel:选中六个数码管
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		sel <= 6'b000000;
	else	
		sel <= 6'b111111;


//给要显示的值编码
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		seg <= IDLE;
	else
		begin
			case(num)
				// 这里的num是用10进制表示的
				// 定义的num 是用16进制表示的
				// 不在乎用什么进制表示,只要最后的数值正确。
				4'd0: seg <= SEG_0;
				4'd1: seg <= SEG_1;
				4'd2: seg <= SEG_2;
				4'd3: seg <= SEG_3;
				4'd4: seg <= SEG_4;
				4'd5: seg <= SEG_5;
				4'd6: seg <= SEG_6;
				4'd7: seg <= SEG_7;
				4'd8: seg <= SEG_8;
				4'd9: seg <= SEG_9;
				4'd10: seg <= SEG_A;
				4'd11: seg <= SEG_B;
				4'd12: seg <= SEG_C;
				4'd13: seg <= SEG_D;
				4'd14: seg <= SEG_E;
				4'd15: seg <= SEG_F;
				default:seg <= IDLE ; //闲置状态,不显示
			endcase
		end

endmodule

5.2 74HC595 控制模块

module hc595_ctrl
(
	input wire sys_clk , //系统时钟,频率 50MHz
	input wire sys_rst_n , //系统时钟,频率 50MHz
	input wire [5:0] sel , //数码管位选信号
	input wire [7:0] seg , //数码管段选信号
	
	/*
		stcp:存储寄存器时钟,
		当我们 14 位数码管控制信号传输完之后我们需要拉高一个
		stcp 时钟来将信号存入存储寄存器之中
		最后一个数据是在 cnt_bit=13 且 cnt=2 时传输的,
		所以我们就在下一个时钟(cnt_bit=13 且 cnt=3 时)将 stcp 拉高一个时钟产生上升沿即可
	*/
	output reg stcp , //数据存储器时钟
	/*
		shcp:移位寄存器时钟,上升沿时将数据写入移位寄存器中
		其频率即为系统时钟四分频(12.5MHz)
		我们在 ds 数据的中间状态拉高产生上升沿,
		这样可以使 shcp 采得的 ds 数据更加稳定。如波形图所示我们在 cnt=2
		时拉高,cnt=0 时拉低,
	*/
	output reg shcp , //移位寄存器时钟
	//ds:串行数据输出
	output reg ds , //串行数据输入
	output wire oe //使能信号,低有效

);

reg [1:0] cnt_4 ; //分频计数器,产生四分频的时钟
 //cnt_bit:传输位数计数器。我们知道我们需要传输 14bit 的数据
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 = ~sys_rst_n;

//分频计数器:0~3 循环计数
//产生四分频的时钟(当是这个四分频的时钟有区别)
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 + 2'd1;

//cnt_bit:每输入一位数据加一,传输位数计数器
// 为什么 cnt_bit 会在 cnt_4 == 2'd3 加一呢?
// 因为传送数据是由 移位寄存器时钟 决定的
// 一个shcp周期 传输一位 这是规定好的
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		cnt_bit <= 4'd0;
	// 注意顺序的问题
	// 注意两个条件都满足的问题
	else if(cnt_bit == 4'd13 && cnt_4 == 2'd3)
		cnt_bit <= 4'd0;
	else if(cnt_4 == 2'd3)
		cnt_bit <= cnt_bit + 4'd1;
	else	
		cnt_bit <= cnt_bit;
		
//stcp:14 个信号传输完成之后产生一个上升沿
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		stcp <= 1'd0;
	else if(cnt_bit == 4'd13 && cnt_4 == 2'd3)
		stcp <= 1'd1;
	else	
		stcp <= 1'b0;

shcp:产生四分频移位时钟
// 这里时钟的产生注意,为了性能的优化,产生的时钟方式有所不同
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;

//ds:将寄存器里存储的数码管信号输入即
//ds 传输的时间是一个shcp的时钟周期
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

5.3 顶层模块

module seg_595_static
(
	input wire sys_clk , //系统时钟,频率 50MHz
	input wire sys_rst_n , //系统时钟,频率 50MHz
	
	
	output wire stcp , //移位寄存器时钟
	output wire shcp , //移位寄存器的时钟输入
	//ds:串行数据输出
	output wire ds , //串行数据输入
	output wire oe //使能信号,低有效
);

wire [5:0] sel;
wire [7:0] seg;

seg_static seg_static_inst
(
	.sys_clk (sys_clk ), //系统时钟,频率 50MHz
	.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
	.sel (sel ), //数码管位选信号
	.seg (seg ) //数码管段选信号
);

hc595_ctrl hc595_ctrl_inst
(
	.sys_clk (sys_clk ), //系统时钟,频率 50MHz
    .sys_rst_n (sys_rst_n), //复位信号,低有效
    .sel (sel ), //数码管位选信号
    .seg (seg ), //数码管段选信号
  
    .stcp (stcp ), //输出数据存储寄时钟
    .shcp (shcp ), //移位寄存器的时钟输入
    .ds (ds ), //串行数据输入
    .oe (oe ) //输出使能信号

);

endmodule

6. testbench

6.1 静态数码测试

`timescale 1ns/1ns
module tb_seg_static();


//因为 testbench 不对外进行信号的输入输出,只是自己产生
//激励信号提供给内部实例化待测 RTL 模块使用,所以端口列表
//中没有内容,只是列出“()”,当然可以将“()”省略,括号
//后有个“;”不要忘记
//要在 initial 块和 always 块中被赋值的变量一定要是 reg 型
//在 testbench 中待测试 RTL 模块的输入永远是 reg 型变量



 //输出信号,我们直接观察,也不用在任何地方进行赋值
 //所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)

reg sys_clk;
reg sys_rst_n;


 //输出信号,我们直接观察,也不用在任何地方进行赋值
 //所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
wire [5:0] sel;
wire [7:0] seg;

//初始化值在没有特殊要求的情况下给 0 或 1 都可以。如果不赋初值,仿真时信号
//会显示为不定态(ModelSim 中的波形显示红色)

initial
//initial 只在通电执行一次
//在仿真中 begin...end 块中的内容都是顺序执行的,
//在没有延时的情况下几乎没有差别,看上去是同时执行的,
//如果有延时才能表达的比较明了;
//而在 rtl 代码中 begin...end 相当于括号的作用, begin...end 在 Testbench 中的用法及意义(区别   -----------------------------------------------------)
//在同一个 always 块中给多个变量赋值的时候要加上
	begin 
		sys_clk = 1'b1; //时钟信号的初始化为 1,且使用“=”赋值,
                        //其他信号的赋值都是用“<=”
		sys_rst_n <= 1'b0; //因为低电平复位,所以复位信号的初始化为 0
		

		#20 //延时20ns
		sys_rst_n <= 1'b1; //初始化 20ns 后,复位释放,因为是低电平复位	
		
		//都是顺序执行的
	end

//产生时钟信号
always #10 sys_clk = ~sys_clk;

// 使用 defparam 对rtl 常数重新赋值 
defparam seg_static_inst.CNT_WAIT_MAX = 25'd25;

seg_static seg_static_inst
(
	//前面的“in1”表示被实例化模块中的信号,后面的“in1”表示实例化该模块并要和这个
	//模块的该信号相连接的信号(可以取名不同,一般取名相同,方便连接和观察)
	//“.”可以理解为将这两个信号连接在一起
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n), 
	
	.sel(sel),
	.seg(seg) 
);

endmodule						 


6.2 74HC595 控制测试

`timescale 1ns/1ns
module tb_hc595_ctrl();


//因为 testbench 不对外进行信号的输入输出,只是自己产生
//激励信号提供给内部实例化待测 RTL 模块使用,所以端口列表
//中没有内容,只是列出“()”,当然可以将“()”省略,括号
//后有个“;”不要忘记
//要在 initial 块和 always 块中被赋值的变量一定要是 reg 型
//在 testbench 中待测试 RTL 模块的输入永远是 reg 型变量



 //输出信号,我们直接观察,也不用在任何地方进行赋值
 //所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)

reg sys_clk;
reg sys_rst_n;
reg [5:0] sel ;//数码管位选信号
reg [7:0] seg ;//数码管段选信号


 //输出信号,我们直接观察,也不用在任何地方进行赋值
 //所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
wire stcp ;//数据存储器时钟
wire shcp ; //移位寄存器时钟
wire ds ; //串行数据输入
wire oe ;//使能信号,低有效

//初始化值在没有特殊要求的情况下给 0 或 1 都可以。如果不赋初值,仿真时信号
//会显示为不定态(ModelSim 中的波形显示红色)

initial
//initial 只在通电执行一次
//在仿真中 begin...end 块中的内容都是顺序执行的,
//在没有延时的情况下几乎没有差别,看上去是同时执行的,
//如果有延时才能表达的比较明了;
//而在 rtl 代码中 begin...end 相当于括号的作用, begin...end 在 Testbench 中的用法及意义(区别   -----------------------------------------------------)
//在同一个 always 块中给多个变量赋值的时候要加上
	begin 
		sys_clk = 1'b1; //时钟信号的初始化为 1,且使用“=”赋值,
                        //其他信号的赋值都是用“<=”
		sys_rst_n <= 1'b0; //因为低电平复位,所以复位信号的初始化为 0
		
		sel <= 6'b111111;
		seg <= 8'b1100_0000;
		#20 //延时20ns
		sys_rst_n <= 1'b1; //初始化 20ns 后,复位释放,因为是低电平复位	
		
		#500000000
		seg <= 8'b1111_1001;
		//都是顺序执行的
	end

//产生时钟信号
always #10 sys_clk = ~sys_clk;


hc595_ctrl hc595_ctrl_inst
(
	//前面的“in1”表示被实例化模块中的信号,后面的“in1”表示实例化该模块并要和这个
	//模块的该信号相连接的信号(可以取名不同,一般取名相同,方便连接和观察)
	//“.”可以理解为将这两个信号连接在一起
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n), 
	.sel(sel),
	.seg(seg),
	
	.stcp(stcp),//数据存储器时钟
	.shcp(shcp), //移位寄存器时钟
	.ds(ds), //串行数据输入
	.oe(oe)//使能信号,低有效
	
);

endmodule						 


6.3 顶层模块测试

`timescale 1ns/1ns
module tb_seg_595_static();


//因为 testbench 不对外进行信号的输入输出,只是自己产生
//激励信号提供给内部实例化待测 RTL 模块使用,所以端口列表
//中没有内容,只是列出“()”,当然可以将“()”省略,括号
//后有个“;”不要忘记
//要在 initial 块和 always 块中被赋值的变量一定要是 reg 型
//在 testbench 中待测试 RTL 模块的输入永远是 reg 型变量



 //输出信号,我们直接观察,也不用在任何地方进行赋值
 //所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)

reg sys_clk;
reg sys_rst_n;
 //输出信号,我们直接观察,也不用在任何地方进行赋值
 //所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
wire stcp ;//数据存储器时钟
wire shcp ; //移位寄存器时钟
wire ds ; //串行数据输入
wire oe ;//使能信号,低有效

//初始化值在没有特殊要求的情况下给 0 或 1 都可以。如果不赋初值,仿真时信号
//会显示为不定态(ModelSim 中的波形显示红色)

initial
//initial 只在通电执行一次
//在仿真中 begin...end 块中的内容都是顺序执行的,
//在没有延时的情况下几乎没有差别,看上去是同时执行的,
//如果有延时才能表达的比较明了;
//而在 rtl 代码中 begin...end 相当于括号的作用, begin...end 在 Testbench 中的用法及意义(区别   -----------------------------------------------------)
//在同一个 always 块中给多个变量赋值的时候要加上
	begin 
		sys_clk = 1'b1; //时钟信号的初始化为 1,且使用“=”赋值,
                        //其他信号的赋值都是用“<=”
		sys_rst_n <= 1'b0; //因为低电平复位,所以复位信号的初始化为 0
		
		#20 //延时20ns
		sys_rst_n <= 1'b1; //初始化 20ns 后,复位释放,因为是低电平复位	
		
		//都是顺序执行的
	end

//产生时钟信号
always #10 sys_clk = ~sys_clk;

//重新定义参数值,缩短仿真时间
// 注意这里点了2次
defparam seg_595_static_inst.seg_static_inst.CNT_WAIT_MAX = 100;

seg_595_static seg_595_static_inst
(
	//前面的“in1”表示被实例化模块中的信号,后面的“in1”表示实例化该模块并要和这个
	//模块的该信号相连接的信号(可以取名不同,一般取名相同,方便连接和观察)
	//“.”可以理解为将这两个信号连接在一起
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n), 
	
	.stcp(stcp),//数据存储器时钟
	.shcp(shcp), //移位寄存器时钟
	.ds(ds), //串行数据输入
	.oe(oe)//使能信号,低有效
	
);

endmodule						 


猜你喜欢

转载自blog.csdn.net/HeElLose/article/details/131287332