时间:2024-07-25 来源:网络搜集 关于我们 0
设计一个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引脚为低即可让存储寄存器中的数据进行输出。
总结一下使用步骤:首先把要传输的数据通过引脚DS输入到74HC595中。
产生SHCP时钟,将DS上的数据串行移入移位寄存器。
产生STCP时钟,将移位寄存器里的数据送入存储寄存器。
将13引脚置为低电平,存储寄存器的数据会在Q0-Q7并行输出,同时并行输出的数据会被锁存起来。
我们采用的是低电平点亮的共阳极数码管。首先看位选信号,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 = 25d24_999_999; //计数器最大值(0.5s) //十六进制数显示编码 parameter SEG_0 = 8b1100_0000, SEG_1 = 8b1111_1001, SEG_2 = 8b1010_0100, SEG_3 = 8b1011_0000, SEG_4 = 8b1001_1001, SEG_5 = 8b1001_0010, SEG_6 = 8b1000_0010, SEG_7 = 8b1111_1000, SEG_8 = 8b1000_0000, SEG_9 = 8b1001_0000, SEG_A = 8b1000_1000, SEG_B = 8b1000_0011, SEG_C = 8b1100_0110, SEG_D = 8b1010_0001, SEG_E = 8b1000_0110, SEG_F = 8b1000_1110; parameter IDLE = 8b1111_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 == 1b0) cnt_wait <= 25d0; else if(cnt_wait == CNT_WAIT_MAX) cnt_wait <= 25d0; else cnt_wait <= cnt_wait + 1b1; //add_flag:0.5s拉高一个标志信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1b0) add_flag <= 1b0; else if(cnt_wait == CNT_WAIT_MAX - 1) add_flag <= 1b1; else add_flag <= 1b0; //num:从 4h0 加到 4hf 循环 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1b0) num <= 4d0; else if(add_flag == 1b1) num <= num + 1b1; else num <= num; //sel:选中8个数码管 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1b0) sel <= 8b00000000; else sel <= 8b11111111; //给要显示的值编码 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1b0) seg <= IDLE; else case(num) 4d0: seg <= SEG_0; 4d1: seg <= SEG_1; 4d2: seg <= SEG_2; 4d3: seg <= SEG_3; 4d4: seg <= SEG_4; 4d5: seg <= SEG_5; 4d6: seg <= SEG_6; 4d7: seg <= SEG_7; 4d8: seg <= SEG_8; 4d9: seg <= SEG_9; 4d10: seg <= SEG_A; 4d11: seg <= SEG_B; 4d12: seg <= SEG_C; 4d13: seg <= SEG_D; 4d14: seg <= SEG_E; 4d15: 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 == 1b0) cnt_4 <= 2d0; else if(cnt_4 == 2d3) cnt_4 <= 2d0; else cnt_4 <= cnt_4 + 1b1; //cnt_bit:每输入一位数据加一 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1b0) cnt_bit <= 4d0; else if(cnt_4 == 2d3 && cnt_bit == 4d15) cnt_bit <= 4d0; else if(cnt_4 == 2d3) cnt_bit <= cnt_bit + 1b1; else cnt_bit <= cnt_bit; //stcp:14个信号传输完成之后产生一个上升沿 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1b0) stcp <= 1b0; else if(cnt_bit == 4d15 && cnt_4 == 2d3) stcp <= 1b1; else stcp <= 1b0; //shcp:产生四分频移位时钟 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1b0) shcp <= 1b0; else if(cnt_4 >= 4d2) shcp <= 1b1; else shcp <= 1b0; //ds:将寄存器里存储的数码管信号输入即 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1b0) ds <= 1b0; else if(cnt_4 == 2d0) 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(1b1), .stcp (stcp ), .shcp (shcp ), .ds (ds ) ); endmodule顶层模块实质是两个实例化。
编写testbench
`timescale 1ns/1nsmodule tb_seg_595_static();//wire definewire 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 = 1b1; sys_rst_n <= 1b0; #100 sys_rst_n <= 1b1; 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)的编码与显示的字符也相吻合
74HC595控制模块仿真波形图
分配管脚
全编译后上板验证
小小有话说:最近很忙,很久没更新,非常抱歉,后面我有空就会努力更新,一起进步!