实验目标:驱动数码管实现000000-FFFFFF的循环静态显示,每个字符的显示时间为0.5s。
前言
数码管是一种半导体发光器件,基本单元是发光二极管。
共阳极数码管指的是所有的阳极都连接在一起,低电平点亮二极管。
分析一下内容为0的十六进制段码C0:
十六进制的C为二进制的1100,则C0表示为二进制为:1100 0000
,可以综合二进制的段码分析出,这8位二进制数从右向左依次为abcdefg和dp
。/从左往右:dp gfedcba
六位八段数码管需要8个段选+6个位选=14个IO口。
通过位选信号(sel[0]等)控制哪一个数码管点亮,段选是连接在一起的,故送入所有数码管的段选信号都是相同的,称为静态显示。
段选信号应该先传入最高位
,再传入最低位。
使用74HC595芯片的原因是他只需要4个端口,可以节省10个端口。
连接到fpga芯片的端口为DS(将串行数据移位到fpga中)、SHCP(移位寄存器时钟输入,溢出的数据通过Q7S端口输出)、STCP(存储寄存器时钟)和OE(输出使能信号)端口。MR端口连接VCC,目的是防止数据的清零。
输出:74HC595芯片内部有一个8位寄存器,由STCP信号控制。在其上升沿时,会将移位寄存器中的数据写入存储寄存器中,当OE低电平时,就将存储寄存器中的数据通过15、1、2、3、4、5、6、7这8个端口传输出去,这8个端口与数码管相连接。这样就实现了串行输入
到并行输出
的转换。
一、系统框图及波形图
将输入时钟信号和复位信号,输出为ds、shcp、stcp、oe信号的过程分为两个模块,第一个模块用于产生位选信号和段选信号,第二个模块将位选和段选信号与时钟信号和复位信号一同作为输入。
图中时钟和复位信号处的黑点表示导通。
接下来介绍第二个模块的波形图:
图片为一部分节选,后续补充见下方文字叙述:
***data[13:0]***是十四位宽的变量,目的是拼接位选和段选数据,方便后面的传输。前面提到,按照seg[0]…seg[7] sel[7]…sel[0]的顺序传输数据。左边是最高位
,对应段选信号的最低位
。
***cnt[1:0]***是声明的计数器,目的是对时钟进行分频,分频的原因是shcp和stcp是时钟信号,其频率不能太高也不能太低,这里进行四分频。50MHz的四分频
(记数四次)是12.5MHz,计数器从0记数到最大值3就归零。
ds输出的是串行数据,14个数据为1个循环(需要再声明一个计数器,这个计数器(cnt_bit[3:0])对输出的比特
进行记数),这14个数据有6个位选,8个段选。
当复位有效时,串行数据ds
输出为0,其他时刻让其等于data[cnt_bit],当cnt_bit为0时,ds的值data[0]就是变量data的最低位,即sel[0]。
为了让移位时钟shcp信号准确地采集数据,shcp信号的上升沿应该对准串行数据ds信号的稳定状态,即其中间位置。即在分频时钟cnt[1:0]为2时将电平拉高,由于是时序逻辑,故延迟一拍。
存储寄存器时钟stcp初值为低电平,当所有的14位数据都传输完成时(延迟一拍)将其拉高(将移位寄存器中的数据输入到存储寄存器),保持两拍(bit器为0,分频计数器保持1和2,初始状态也保持这个高电平,但此时没有信号写入)的高电平。
使能信号oe是低电平有效,使其保持低电平即可。
二、rtl代码
此处为产生sel和seg信号部分的rtl代码:
module seg595
#(
parameter CNT_MAX=25'd24_999_999
)
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
output reg [5:0] sel , //数码管位选信号
output reg [7:0] seg //数码管段选信号
);
//变量的定义
reg [24:0] cnt;
reg [3:0] data;//F是1111,4位宽
reg cnt_flag;
//变量的赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt<= 25'd0;
else if(cnt == CNT_MAX)
cnt<= 25'd0;
else
cnt<= cnt + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data<=4'd0;
//else if((cnt == CNT_MAX)&&(data==4'd15))
else if((cnt_flag==1'b1)&&(data==4'd15))
data<=4'd0;
else if(cnt == CNT_MAX)
data<=data+1'b1;
else
data<=data;
//flag信号的目的是切换字符的显示,故将cnt == CNT_MAX换成cnt_flag==1'b1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_flag<=1'b0;
else if(cnt == CNT_MAX-1)
cnt_flag<=1'b1;
else
cnt_flag<=1'b0;
//输出的赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel<=6'b000_000;
else
sel<=6'b111_111;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg<=8'hff;
else case(data)
4'd0: seg <=8'hc0;
4'd1: seg <=8'hf9;
4'd2: seg <=8'ha4;
4'd3: seg <=8'hb0;
4'd4: seg <=8'h99;
4'd5: seg <=8'h92;
4'd6: seg <=8'h82;
4'd7: seg <=8'hf8;
4'd8: seg <=8'h80;
4'd9: seg <=8'h90;
4'd10: seg <=8'h88;
4'd11: seg <=8'h83;
4'd12: seg <=8'hc6;
4'd13: seg <=8'ha1;
4'd14: seg <=8'h86;
4'd15: seg <=8'h8e;
default:seg <=8'hff; //闲置状态,不显示
endcase
//输出是sel和seg,说明这只是第一个模块,还需要编写顶层模块
endmodule 你
这一部分是第一个模块
部分的代码,还应编写一个顶层模块的代码,最后编写第二模块部分的代码。
顶层模块的代码
:
module dingcengmokuai
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
output wire stcp , //输出数据存储寄时钟
output wire shcp , //移位寄存器的时钟输入
output wire ds , //串行数据输入
output wire oe //输出使能信号
);
//引出位选和段选
wire [5:0] sel;
wire [7:0] seg;
//实例化
seg595
#(
.CNT_MAX(25'd24)
)
seg595_inst
(
.sys_clk (sys_clk ) , //系统时钟,频率50MHz
.sys_rst_n(sys_rst_n) , //复位信号,低电平有效
.sel (sel ) , //数码管位选信号
.seg (seg ) //数码管段选信号
);
//第一个子功能模块的实例化完成
endmodule
第二个模块代码
:
module hc595_ctrl
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [5:0] sel,
input wire [7:0] seg,
output reg ds ,
output reg stcp ,
output reg shcp ,
output wire oe
);
//data使用assign语句,wire型
wire [13:0] data;
//计数器使用always语句,reg型
reg [1:0] cnt;
reg [3:0] cnt_bit;
assign data={seg[0],seg[1],seg[2],seg[3],seg[4],seg[5],seg[6],seg[7],sel[5:0]};
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt<=2'd0;
else if(cnt==2'd3)
cnt<=2'd0;
else
cnt<=cnt+1'b1;
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==2'd3))
cnt_bit<=4'd0;
else if(cnt==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)
ds<=1'b0;
else if(cnt==2'd3)
ds<=data[cnt_bit];
else
ds<=ds;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
shcp<=1'b0;
else if(cnt==2'd2)
shcp<=1'b1;
else if(cnt==2'd0)
shcp<=1'b0;
else
shcp<=shcp;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
stcp<=1'b0;
else if((cnt_bit==4'd0)&&(cnt==2'd0))
stcp<=1'b1;
else if((cnt_bit==4'd0)&&(cnt==2'd2))
stcp<=1'b0;
else
stcp<=stcp;
//使能信号始终保持低电平,用assign语句进行赋值
assign oe=1'b0;
endmodule
三、测试代码
此处为第一个模块的测试代码
:
`timescale 1ns/1ns
module tb_seg595();
reg sys_clk ;
reg sys_rst_n ;
wire [5:0] sel;
wire [7:0] seg;
//对sys_clk,sys_rst_n赋初始值
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#100
sys_rst_n <= 1'b1;
end
//clk:产生时钟
always #10 sys_clk <= ~sys_clk;
seg595
#(
.CNT_MAX(25'd24)
)
seg595_inst
(
.sys_clk (sys_clk ) , //系统时钟,频率50MHz
.sys_rst_n(sys_rst_n) , //复位信号,低电平有效
.sel (sel ) , //数码管位选信号
.seg (seg ) //数码管段选信号
);
endmodule
顶层模块的测试代码
:
`timescale 1ns/1ns
module tb_dingcengmokuai();
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;
#100
sys_rst_n <= 1'b1;
end
always #10 sys_clk <= ~sys_clk;
dingcengmokuai dingcengmokuai_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低电平有效
.stcp (stcp ), //输出数据存储寄时钟
.shcp (shcp ), //移位寄存器的时钟输入
.ds (ds ), //串行数据输入
.oe (oe ) //输出使能信号
);
endmodule
四、仿真结果
此处为第一个模块的仿真结果
,与波形图绘制一致。