0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于FPGA的数码管静态显示

CHANBAEK 来源:小小研究生 作者:xxyjs2020 2023-07-28 10:03 次阅读

设计规划

设计一个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引脚为低即可让存储寄存器中的数据进行输出。

总结一下使用步骤:

  1. 首先把要传输的数据通过引脚DS输入到74HC595中。
  2. 产生SHCP时钟,将DS上的数据串行移入移位寄存器。
  3. 产生STCP时钟,将移位寄存器里的数据送入存储寄存器。
  4. 将13引脚置为低电平,存储寄存器的数据会在Q0-Q7并行输出,同时并行输出的数据会被锁存起来。

图片

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控制模块仿真波形图

图片

分配管脚

图片

图片

图片

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • FPGA
    +关注

    关注

    1625

    文章

    21665

    浏览量

    601765
  • 寄存器
    +关注

    关注

    31

    文章

    5310

    浏览量

    119991
  • 数码管
    +关注

    关注

    32

    文章

    1874

    浏览量

    90934
  • 静态显示
    +关注

    关注

    0

    文章

    19

    浏览量

    5986
收藏 人收藏

    评论

    相关推荐

    数码管静态显示原理

    数码管显示数码管静态显示原理显示器及其接口数码管动态
    发表于 11-22 08:38

    数码管静态显示电路

    数码管静态显示电路
    发表于 05-23 16:15 3746次阅读
    <b class='flag-5'>数码管</b><b class='flag-5'>静态</b><b class='flag-5'>显示</b>电路

    数码管静态显示)【汇编版】

    数码管静态显示)【汇编版】数码管静态显示)【汇编版】数码
    发表于 12-29 15:27 0次下载

    数码管静态显示)【C语言版】

    数码管静态显示)【C语言版】数码管静态显示)【C语言版】
    发表于 12-29 15:27 0次下载

    静态数码管

    静态数码管静态数码管静态数码管静态
    发表于 05-13 15:39 6次下载

    共阳数码管静态显示

    16-共阳数码管静态显示---51单片机源代码 用keil直接打开
    发表于 06-15 18:17 22次下载

    数码管静态显示_数码管静态显示程序

    数码管静态显示,就是每一个数码管的段码都要独占具有锁存功能的输出口,CPU把要显示的字码送到输出口上,就可以使
    发表于 01-15 16:09 4.3w次阅读

    数码管静态显示介绍_8位数码管静态显示程序解析

    数码管显示屏其中一类, 通过对其不同的管脚输入相对的电流,会使其发亮,从而显示出数字能够显示时间、日期、温度等所有可用数字表示的参数。本文为大家介绍一个8位
    发表于 01-15 16:53 1.5w次阅读

    利用FPGA DIY开发板实现拨码开关控制静态数码管显示

    FPGA diy作业实现拨码开关控制显示数码管0到8的静态显示
    的头像 发表于 06-20 14:07 3927次阅读

    采用FPGA DIY实现key1控制静态数码管显示

    FPGA静态数码管显示视频,通过key1控制数值的变化
    的头像 发表于 06-20 08:56 3398次阅读
    采用<b class='flag-5'>FPGA</b> DIY实现key1控制<b class='flag-5'>静态</b><b class='flag-5'>数码管</b><b class='flag-5'>显示</b>

    正点原子开拓者FPGA数码管静态显示实验

    静态驱动也称直流驱动。静态驱动是指每个数码管的每一个段码都由一个单片机的I/O端口进行驱动,或者使用如BCD码二-十进制译码器译码进行驱动。静态驱动的优点是编程简单,
    的头像 发表于 09-09 06:05 2755次阅读
    正点原子开拓者<b class='flag-5'>FPGA</b>:<b class='flag-5'>数码管</b><b class='flag-5'>静态</b><b class='flag-5'>显示</b>实验

    MCS-51实现静态数码管显示

    MCS-51实现静态数码管显示
    发表于 11-25 13:51 8次下载
    MCS-51实现<b class='flag-5'>静态</b><b class='flag-5'>数码管</b>的<b class='flag-5'>显示</b>

    静态数码管和动态数码管

    目录一,什么是数码管二,静态数码管的驱动三,动态数码管四,动态数码管显示编程实战五,使用38译码
    发表于 01-12 18:31 40次下载
    <b class='flag-5'>静态</b><b class='flag-5'>数码管</b>和动态<b class='flag-5'>数码管</b>

    数码管静态显示电路设计

    数码管静态显示电路设计
    的头像 发表于 10-31 10:59 1913次阅读

    数码管显示屏的静态显示编程是什么

    数码管显示屏的静态显示编程是一种简单的显示方式,它指的是在任意时刻,数码管上的所有段(a, b,
    的头像 发表于 08-28 17:14 472次阅读