zoukankan      html  css  js  c++  java
  • 数码管的动态扫描与驱动

    数码管的基本原理

        关于数码管,一个单个的数码管可以看做是多个led灯的集合,如下图所示

    其中的8都是LED组成的,通过引脚上电即可点亮不同的LED然后组成不同的数字,这个过程在数码管的设计中叫做段选。

        在多个数码管的情况下,需要选择哪个数码管点亮,这个在数码管设计中称作位选,多个数码管可以通过位选和段选完成电子时钟设计等功能。

    下面通过项目对于多个数码管进行点亮,让其在开发板上显示不同的数据。

    预计实验现象:

          在quartus的in system source and probes editor 工具,输入需要显示在数码管上的数据,则数码管显示对应数据。

    相关知识点:

        数码管动态扫描的实现、in system source and probes editor调试工具的使用。

               注意:这里重点讲的是有位选和段选的连接方式的,友晶采用的是并口的连接方式的,不要看这里,直接看后面

    设计过程:

        1、数码管动态扫描实现。

        2、In system sources and probes edit (ISSP)调试工具的使用

        3、4输入查找表,6位输出。

        4、分频模块,从系统时钟分频得到1KHz的扫描时钟

        5、6选一多路选择器,选择为当前数码管的位置。

    驱动模块逻辑电路图:

     

    下面就是照着逻辑电路图来编写程序了。

    创建工程,添加文件

          

     
    
     
    
    module segment(disp_data,rst_n,clk,en ,sel,seg);
    
    input clk;//50M
    input rst_n;
    input en;
    input [23:0]disp_data;
    output [5:0]sel;//位选(控制哪个数码管亮)
    output reg [6:0]seg;//段选(控制数码管显示什么数据)
    
    
    //分频器的代码,这里为了完整,不做多个文件来写模块了
    reg[14:0] diviter_cnt; //25000-1
    reg clk_1k;
    reg [5:0]sel_r;
    reg [3:0]data_temp;//待显示数据缓存
    
    //生成一个分频计数器计数
    always@(posedge clk or negedge rst_n)
    if (!rst_n)
    diviter_cnt<=15'd0;
    else if (!en)
    diviter_cnt<=15'd0;
    else if (diviter_cnt==24999)
    diviter_cnt<=15'd0;
    else 
    diviter_cnt<=diviter_cnt+1'b1;
    
    //1k扫描时钟生成
    always@(posedge clk or negedge rst_n)
    if (!rst_n)
    clk_1k<=1'b0;
    else if (diviter_cnt==24999)
    clk_1k<=~clk_1k; //大型设计中,这种产生分频器的方法是不可以的
    
    
    //位选移位寄存器
    always@(posedge clk_1k or negedge rst_n)
    if (!rst_n)
    sel_r<=6'b000_001;
    else if(sel_r==6'b100_000)
    sel_r<=6'b000_001;
    else 
    sel_r<=sel_r<<1;
    
    //设计一个6选一多路器
    always@(*)
    case(sel_r)
    6'b000_001:data_temp=disp_data[3:0];
    6'b000_010:data_temp = disp_data[7:4];
    6'b000_100:data_temp=disp_data[11:8];    
    6'b001_000:data_temp=disp_data[15:12];    
    6'b010_000:data_temp=disp_data[19:16];    
    6'b100_000:data_temp=disp_data[23:20];    
    default
    data_temp<=4'b0000;
    endcase
    
    //译码器
    always@(*)
    case (data_temp)
    4'h0:seg=7'b1000000;//这里按数码管码表来
    4'h1:seg=7'b1111001;
    4'h2:seg=7'b0100100;
    4'h3:seg=7'b0110000;
    4'h4:seg=7'b0011001;
    4'h5:seg=7'b0010010;
    4'h6:seg=7'b0000010;
    4'h7:seg=7'b1111000;
    4'h8:seg=7'b0000000;
    4'h9:seg=7'b0010000;
    4'ha:seg=7'b0001000;
    4'hb:seg=7'b0000011;
    4'hc:seg=7'b1000110;
    4'hd:seg=7'b0100001;
    4'he:seg=7'b0000110;
    4'hf:seg=7'b0001110;
    endcase
    
    //二选一多路器
    assign sel=(en)?sel_r:6'b000_000;
    
    endmodule

    编写testbench文件来进行仿真

     

    `timescale 1ns/1ns
    
    `define clk_period 20
    
    module HXE_tb;
    
    reg Clk; //50M
    reg Rst_n;
    reg En;    //数码管显示使能,1使能,0关闭
    
    reg [31:0]disp_data;
    
    wire [7:0] sel;//数码管位选(选择当前要显示的数码管)
    wire [6:0] seg;//数码管段选(当前要显示的内容)
    
    HXE8 HXE8(
    .Clk(Clk),
    .Rst_n(Rst_n),
    .En(En),
    .disp_data(disp_data),
    .sel(sel),
    .seg(seg)
    );
    
    initial Clk = 1;
    always#(`clk_period/2) Clk = ~Clk;
    
    initial begin
    Rst_n = 1'b0;
    En = 1;
    disp_data = 32'h12345678;
    #(`clk_period*20);
    Rst_n = 1;
    #(`clk_period*20);
    #20000000;
    disp_data = 32'h87654321;
    #20000000;
    disp_data = 32'h89abcdef;
    #20000000;
    $stop;
    end
    
    endmodule

    点击仿真运行,可以看到sel和seg的输出与我们期望的是一样的,即位选进行移位操作,段选显示123456和abcdef。

    一般都需要进行后仿,才能得到实际的工作时的数据波形,这里由于使用的是Cyclone V系列的芯片,而quartus 取消了对该系列的门级仿真,故而此次设计的后仿就不做了。但是我们这里发现,友晶的开发板的连接模式与下面这种常规的位选段选接法不同,其连接方式为并行接法,每个数码管连接一个IO管脚,通过IO管脚的设置来决定数据的显示,这里两者的区别是位选的有无。

    这里开始讲解了两者之间的区别,并结合友晶的开发板进行的设计

    重新编写segment_2程序

    module segment_2(disp_data,rst_n,clk,en ,data_out);
    input clk;//50M
    input rst_n;
    input en;
    input [23:0]disp_data;
    
    
    output reg [41:0]data_out;
    reg[24:0]cnt;//定义计数寄存器
    reg [3:0]data_temp;//待显示数据缓存
    reg[6:0] seg,seg1,seg2,seg3,seg4,seg5;//段选(控制数码管显示什么数据)
    reg [2:0]num;
    
    
    initial begin 
    num<=3'b000;
    end
    
    //设定延时闪烁时间
    always@(posedge clk or negedge rst_n)
    //设置500ms的延时
    if(rst_n==1'b0)
    cnt<=25'd0;
    else if (cnt==25'd24_999_999)//(500_000_000/20) -1的结果。
    cnt<=25'd0;
    else
    cnt<=cnt+1'b1;
    
    //运用状态机进行disp_data的数据抽取
    
    always@(posedge clk or negedge rst_n)
    if (rst_n==1'b0)
    begin
    num=0;
    data_temp=4'b1000;
    end
    
    else if (cnt==25'd24_999_999)//等待500ms后切换下一个数据
    case (num) //按次数转换
    3'b000: begin data_temp=disp_data[3:0];num=num+1; end
    3'b001:begin data_temp=disp_data[7:4] ; num=num+1;end
    3'b010:begin data_temp=disp_data[11:8] ;num=num+1;end
    3'b011:begin data_temp=disp_data[15:12] ; num=num+1;end
    3'b100:begin data_temp=disp_data[19:16] ; num=num+1;end
    3'b101:begin data_temp=disp_data[23:20] ; num=num+1;end
    
    default
    num=0;
    endcase 
    
    
    //对抽取的数据进行译码
    always@(*)
    begin
    case (data_temp)
    4'h0:seg=7'b1000000;//这里按数码管码表来
    4'h1:seg=7'b1111001;
    4'h2:seg=7'b0100100;
    4'h3:seg=7'b0110000;
    4'h4:seg=7'b0011001;
    4'h5:seg=7'b0010010;
    4'h6:seg=7'b0000010;
    4'h7:seg=7'b1111000;
    4'h8:seg=7'b0000000;
    4'h9:seg=7'b0010000;
    4'ha:seg=7'b0001000;
    4'hb:seg=7'b0000011;
    4'hc:seg=7'b1000110;
    4'hd:seg=7'b0100001;
    4'he:seg=7'b0000110;
    4'hf:seg=7'b0001110;
    endcase
    
    
    //完成显示
    data_out=({seg,seg,seg,seg,seg,seg,seg});    
    end
    
    
    
    endmodule

     编写testbench文件并设定路径

    `timescale 1ns/1ns
    `define clk_period 20
    module segment_2_tb;
    
    reg clk;//50M
    reg rst_n;
    reg en;
    reg [23:0]disp_data;
    wire [41:0]data_out;//段选(控制数码管显示什么数据)
    
    segment_2 segment0(.disp_data(disp_data),
    .rst_n(rst_n),
    .clk(clk),
    .en (en),
    .data_out(data_out)
    );
    
    initial clk = 1;
    always#(`clk_period/2) clk = ~clk;
    initial begin 
    rst_n=1'b0;
    en=1;
    disp_data=24'h123456; 
    #(`clk_period*20)
    rst_n=1;
    #(`clk_period*20)
    #20_000_000;
    disp_data<=24'habcdef;
    #20_000_000;
    $stop;
    end
    endmodule

     仿真波形如图

     到了这一步就需要一个工具,即之前说到的In sysytem sources and probes editor (ISSP)

    打开一个IP核

     

     

    之后一直next知道finish,ctrl+o将产生的文件添加到工程中。将端口添加到顶层文件中

    新建一个segment_top文件,将文件端口都链接进来

    module segment_top(clk,rst_n,data_out);
    
    input clk;//50M
    input rst_n;
    wire [23:0]disp_data;
    
    
    output [41:0]data_out;
    
    segment_data segment_data0(
    .probe(),
    .source(disp_data));    
    
    segment_2 segment0(.disp_data(disp_data),
    .rst_n(rst_n),
    .clk(clk),
    .en (1'b1),
    .data_out(data_out));
    
    endmodule

    至此,工程修改完了,之后就是分配引脚

     

    将.sof文件下载到开发板中

    下载之后,开发板上的数码管一直显示00_00_00,这是因为没有给它需要显示的值。

    现在在电脑上调用in system programer 工具

    将数据格式改为16进制

    在Data框中输入希望显示的数据,会发现数码管上的数据会随着输入的数据的变化而变化,按下复位按键KEY0时,数码管显示全为8,松手后重头开始计数。

     

    到这里,数码管的点亮的试验的基本目标就已经完成了,显示的基本功能与板上程序相似。

  • 相关阅读:
    .NET Core 之 MSBuild 介绍
    棒!使用.NET Core构建3D游戏引擎
    在ASP.NET Core MVC中构建简单 Web Api
    TypeScript 中的 SOLID 原则
    《C# 并发编程 · 经典实例》读书笔记
    ASP.NET Core 中的那些认证中间件及一些重要知识点
    消息队列 Kafka 的基本知识及 .NET Core 客户端
    【数列区间询问中的分块思想】
    2013 Multi-University Training Contest 8
    2013 Multi-University Training Contest 7
  • 原文地址:https://www.cnblogs.com/noticeable/p/7202428.html
Copyright © 2011-2022 走看看