1
设计规划
设计一个8位数码管静态显示:采用共阳极(低电平点亮)8段数码管,控制八位数码管让其以00000000、11111111、22222222一直到FFFFFFFF循环显示。每个字符显示0.5s。
硬件资源
数码管
常见数码管分为七段或八段,八段是多一个小数点。也可分为共阴极或共阳极,观察下面的原理图,共阴极数码管对应端口为高电平时对应二极管点亮,共阳极则相反。
段式数码管工作方式有两种:静态显示和动态显示。静态显示是指将8个数码管的段选信号连接在一起,就可以显示相同的数字。每个数码管的段选必须接一个8位数据线来显示字形,显示字形可一直保持,直到送入新字形码为止。如果每个数码管都接8位段选数据线,那么8个数码管就需要64根数据线。(例如8个数码管都显示0,那么每个数码管都要接一个8位的段选数据线,且控制段选信号为1100_0000)这样占用的I/O接口太多。如下图所示,我们将8个数码管的段选信号连接在一起,由位选信号去控制,每一个数码管上都有一个位选信号。那么在同一时刻8个数码管显示的字符都一样了。(例如8个数码管都显示0,那么8位位选信号为1111_1111,这样才能控制8个共阳数码管都能点亮。段选信号是连接在一起的,当它为1100_0000时,8个数码管都显示0)
即使这样控制数码管仍然需要使用16个I/O口资源。如果想要节省I/O口,可以通过74HC595芯片(位移缓存器)来实现。
74HC595芯片
使用一个串行输入口就可以并行输出八个输入的串行数据。但是一片芯片只能并行输出8位数据,但是8个数码管需要16位数据线,因此需要级联两片74HC595芯片进行输出:将Q7S引脚接入下一片的DS引脚,这样我们最少使用3个I/O口就可以控制多片芯片了。
10号引脚是主复位,低电平有效将移位寄存器的数据清零,通常接到Vcc防止数据清零。SHCP为移位寄存器时钟输入,上升沿时将输入的串行数据(DS端输入)移入移位寄存器中。如果一次输入的数据超过8bit,后面的数据会通过Q7S端口传到下一级芯片的DS端口。74HC595内部有一个8位 存储寄存器 ,由STCP(存储寄存器时钟)控制,STCP上升沿时移位寄存器的数据会进入数据存储寄存器中,令第13引脚为低即可让存储寄存器中的数据进行输出。
总结一下使用步骤:
AC620 开发板上的数码管采用串行移位寄存器芯片将串行数据转化为 16 位并行数据后进行驱动。Cyclone IV E 通过 3 根数据线,连接到两片级联的串行移位器芯片 74HC595 上,再由 74HC595 将每次 16 位串行的数据转化为 16 位并行的数据,分别用以驱动8 段 8 位数码管的段选和位选。(用户手册是7段,但是有小数点,这里统一称为8段)
我们采用的是低电平点亮的共阳极数码管。首先看位选信号,SEL[0]对应开发板最右侧的数码管,以此类推。位选信号为1111_1111才能点亮数码管。再看段选信号,以显示0为例,需要将abcdef点亮,按照左边高位右边低位的顺序,dp、g、f、e、d、c、b、a就对应1100_0000。0-F对应(0)1100_0000、(1)1111_1001、(2)1010_0100、(3)1011_0000、(4)1001_1001、(5)1001_0010、(6)1000_0010、(7)1111_1000、(8)1000_0000、(9)1001_0000、(A)1000_1000、(B)1000_0011、(C)1100_0110、(D)1010_0001、(E)1000_0110、(F)1000_1110。
编写代码
通过系统框图可以看出,分为3个模块:数码管驱动模块,芯片控制模块和数码管显示模块。数码管显示模块是 顶层模块 ,实质上完成两个子模块的实例化。上图里整个系统连接方式是Cyclone IV E连接74HC595芯片连接数码管。我们看数码管驱动和芯片控制这两个子模块,下图中数码管的引脚图需要段选信号和位选信号去驱动,74HC595芯片,除了几个常接高低电平的引脚外,它的输入引脚是接Cyclone IV E的,输出引脚是控制数码管的。顶层模块中只需要考虑芯片和Cyclone IV E之间的连线,因此顶层模块的输入是时钟和复位信号,输出是芯片的输入引脚:寄存器时钟,存储器时钟,数据输入。尤其要注意的是,因为我用的开发板没有使能引脚,他不能作为输出存在。我们再将顶层模块拆分为数码管驱动模块和芯片控制模块。数码管驱动模块考虑数码管和芯片之间的连线,因此它的输入是时钟和复位信号,输出是段选和位选信号。芯片控制模块考虑的是芯片和数码管以及Cyclone IV E的连线,因此它的输入是时钟、复位、段选和位选信号,使能信号,输出是芯片的输入引脚(寄存器时钟,存储器时钟,数据输入)。理清楚了这些,就很容易编写代码了。
1、数码管驱动模块seg_static
包括输入:时钟信号、复位信号,输出:段选信号和位选信号。这个模块最重要的是弄清楚怎么产生段选和位选信号。每隔0.5s,我们要实现00000000-FFFFFFFF的循环显示,那么位选信号我们之前探讨了必须为1111_1111才能使数码管正常工作,而段选信号我们也探讨了从0-F的段选信号,就可以画出波形图。根据波形图可以编写代码。
我们需要三个中间信号:计数器cnt_wait,标志信号add_flag,显示信号num。其中,cnt_wait从0计数到24999999即0.5s,每到计满时标志信号add_flag拉高,且显示信号跳转到下一个状态,显示信号需要从0-F循环。
module seg_static
(
input wire sys_clk ,
input wire sys_rst_n ,
output reg [7:0] sel ,
output reg [7:0] seg
);
//parameter define
parameter CNT_WAIT_MAX = 25'd24_999_999; //计数器最大值(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; //不显示状态
//reg define
reg add_flag ;
reg [24:0] cnt_wait ;
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)
cnt_wait <= 25'd0;
else
cnt_wait <= cnt_wait + 1'b1;
//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 - 1)
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'd0;
else if(add_flag == 1'b1)
num <= num + 1'b1;
else
num <= num;
//sel:选中8个数码管
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel <= 8'b00000000;
else
sel <= 8'b11111111;
//给要显示的值编码
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg <= IDLE;
else case(num)
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
endmodule
计数器cnt_wait:0.5s计数器,计数值从0-24_999_999。复位有效时归0,计数到24_999_999计满时归0,其他情况+1。
标志信号add_flag:计满0.5s拉高一个脉冲,复位有效时归0,计数到24_999_999-1时拉高,其他情况归0。
显示信号num:每当标志信号拉高时从0-F循环,复位有效时归0,判断标志信号为高电平时+1(F是1111,+1为10000溢出,但只保留后四位0000,因此F的下一个状态还是0),其他情况保持原值。
位选信号sel:位选信号长期接高点平,数码管才能显示,因此复位有效时归0,其他情况都为高电平。
段选信号seg:段选信号与显示数值的对应关系之前已经讨论过了。除了0-F的16种显示情况外增加了一种不显示的闲置状态IDLE。复位有效时段选信号为IDLE,与num数值有关。使用CASE语句判断num值并将对应的参数赋值给段选信号seg,注意要有default语句。
2、芯片控制模块
包括输入:时钟信号、复位信号,段选信号,位选信号,输出使能,输出:芯片的四个输入引脚(寄存器时钟,存储器时钟,数据输入)。这个模块最重要的是弄清楚怎么通过控制Cyclone IV E和芯片连接的引脚,让芯片产生正确的输出,去驱动数码管正确的显示图形。
注意:对于我使用的开发板,没有oe对应引脚,它是作为输入信号存在的(可阅读https://blog.csdn.net/weixin_43614528/article/details/87878938?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%2287878938%22%2C%22source%22%3A%22unlogin%22%7D)74HC595驱动模块观察其代码。而对于征途开发板,使能引脚为L11。
shcp是寄存器时钟,上升沿时数据写入移位寄存器,它有频率限制,这里采用系统时钟的4分频,即12.5MHz。stcp是存储器时钟,串行输入16位之后拉高。 en是使能信号,需要一直维持高电平 (图中的使能信号oe刚好相反,这与开发板有关,因此要仔细阅读用户手册)。
module hc595_ctrl
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [7:0] sel ,
input wire [7:0] seg ,
input wire en ,
output reg stcp ,
output reg shcp ,
output reg ds
);
//reg define
reg [1:0] cnt_4 ; //分频计数器
reg [3:0] cnt_bit ; //传输位数计数器
//wire define
wire [15:0] data ; //数码管信号寄存
//将数码管信号寄存
assign data={sel,seg[0],seg[1],seg[2],seg[3],seg[4],seg[5],seg[6],seg[7]};
//分频计数器: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 + 1'b1;
//cnt_bit:每输入一位数据加一
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'd15)
cnt_bit <= 4'd0;
else if(cnt_4 == 2'd3)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
//stcp:14个信号传输完成之后产生一个上升沿
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
stcp <= 1'b0;
else if(cnt_bit == 4'd15 && cnt_4 == 2'd3)
stcp <= 1'b1;
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:将寄存器里存储的数码管信号输入即
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
使能信号en:在这里没有给他赋值,而在顶层模块的实例化时接了高电平。
分频计数器cnt_4:4分频即0-3循环计数,复位有效时归0,计数到3计满时归0,其他情况+1。
传输位数计数器cnt_bit:每传输一个数据+1,0-15循环计数。由于分频之后四个时钟周期为一个新的时钟周期,一个新时钟周期传输一个数据。复位有效时归0,当cnt计数到3且cnt_bit计数到15计满时归0,cnt计数到3且cnt_bit没计满时+1,其他情况保持。
存储器时钟stcp:15个信号传输完成后拉高,复位有效时归0,当cnt计数到3且cnt_bit计数到15计满时拉高,其他情况保持低电平。
寄存器时钟shcp:第九节分频器中仅分频的分频器实现方法,4分频后的时钟脉冲周期是原来的4倍。复位有效时归0,计数大于等于2(2,3)时拉高,其他情况(0,1)拉低。
串行数据输出ds:对FPGA芯片来说是输出,对74HC595芯片来说是输入。复位有效时归0,当分频计数器计数值为0时开始传输数据,其他情况保持原值。传输的数据是FPGA芯片输出的16位数据,74HC595芯片会串行输出(一次输出1bit)。数据data是seg[0]...seg[7],sel拼接起来的,sel是从高位到低位的顺序,data[cnt_bit]是从低位到高位的输出,因此是从位选的低位到高位,再从段选的高位到低位依次输出。
3、顶层模块
module seg_595_static
(
input wire sys_clk ,
input wire sys_rst_n ,
output wire stcp ,
output wire shcp ,
output wire ds
);
//wire define
wire [7:0] sel;
wire [7:0] seg;
//---------- seg_static_inst ----------
seg_static seg_static_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.sel (sel ),
.seg (seg )
);
//---------- hc595_ctrl_inst ----------
hc595_ctrl hc595_ctrl_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.sel (sel ),
.seg (seg ),
.en(1'b1),
.stcp (stcp ),
.shcp (shcp ),
.ds (ds )
);
endmodule
顶层模块实质是两个实例化。
编写testbench
`timescale 1ns/1ns
module tb_seg_595_static();
//wire define
wire stcp ; //输出数据存储寄时钟
wire shcp ; //移位寄存器的时钟输入
wire ds ; //串行数据输入
wire oe ; //输出使能信号
//reg define
reg sys_clk ;
reg sys_rst_n ;
//对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;
//重新定义参数值,缩短仿真时间
defparam seg_595_static_inst.seg_static_inst.CNT_WAIT_MAX = 100;
//-------------seg_595_static_inst-------------
seg_595_static seg_595_static_inst(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.stcp (stcp ), //输出数据存储寄时钟
.shcp (shcp ), //移位寄存器的时钟输入
.ds (ds ), //串行数据输入
.oe (oe ) //输出使能信号
);
endmodule
初始化:对时钟信号和复位信号赋初值,延迟100ns后复位拉高。
产生时钟:延迟10ns后取反,周期为20ns,50MHz的时钟。
为了节省仿真时间,重新定义参数。当一个模块引用另外一个模块时,高层模块可以改变低层模块用parameter定义的参数值。低层模块的参数可以通过层次路径名重新定义。
实例化
对比波形
数码管静态驱动模块仿真波形图
这里看模块的波形,可以把原来的波形delete,再把想要的如图添加进去。后面要修改可以restart后break再重新run all。
可以看到数码显示的值(num)从0开始跳转到了1,再跳转到了2;同时段选信号(seg)的编码与显示的字符也相吻合
可以看到数码管显示的数值从4’hf跳转回0
74HC595控制模块仿真波形图
分配管脚
全部0条评论
快来发表一下你的评论吧 !