FPGA零基础学习:数码管驱动设计

2021-03-23 09:52:14 浏览数 (1)

FPGA零基础学习:数码管驱动设计

本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。

系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。后续会陆续更新 Xilinx 的 Vivado、ISE 及相关操作软件的开发的相关内容,学习FPGA设计方法及设计思想的同时,实操结合各类操作软件,会让你在技术学习道路上无比的顺畅,告别技术学习小BUG卡破脑壳,告别目前忽悠性的培训诱导,真正的去学习去实战应用,这种快乐试试你就会懂的。话不多说,上货。

数码管驱动设计

作者:郝旭帅 校对:陆辉

开发板上拥有一个六位一体的数码管,利用数码管可以显示一些数据。

硬件介绍

数码管共有八个段选信号,通过电阻直接与FPGA相连接;有六个供电端,分别三极管相连接,三极管的控制端由三八译码器的输出控制,三八译码器的输入是由FPGA控制输出。

数码管也称LED数码管,不同行业人士对数码管的称呼不一样,其实都是同样的产品。

数码管按段数可分为七段数码管和八段数码管,八段数码管比七段数码管多一个发光二极管单元,也就是多一个小数点(DP)这个小数点可以更精确的表示数码管想要显示的内容;按能显示多少个(8)可分为1位、2位、3位、4位、5位、6位、7位等数码管。

按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管。共阳数码管是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管,共阳数码管在应用时应将公共极COM接到 5V,当某一字段发光二极管的阴极为低电平时,相应字段就点亮,当某一字段的阴极为高电平时,相应字段就不亮。共阴数码管是指将所有发光二极管的阴极接到一起形成公共阴极(COM)的数码管,共阴数码管在应用时应将公共极COM接到地线GND上,当某一字段发光二极管的阳极为高电平时,相应字段就点亮,当某一字段的阳极为低电平时,相应字段就不亮。

例如:如果想要显示数字“1”,则B、C段亮,其他段不亮就可以了。

数码管动态显示接口是单片机中应用最为广泛的一种显示方式之一,动态驱动是将所有数码管的8个显示笔划"a,b,c,d,e,f,g,dp"的同名端连在一起,另外为每个数码管的公共极COM增加位选通控制电路,位选通由各自独立的I/O线控制,当FPGA输出字形码时,所有数码管都接收到相同的字形码,但究竟是哪个数码管会显示出字形,取决于FPGA对位选通COM端电路的控制,所以我们只要将需要显示的数码管的选通控制打开,该位就显示出字形,没有选通的数码管就不会亮。

开发板上的数码管为共阳极数码管,并且为动态显示接口,在COM端的三极管是PNP三极管,故而输出低电平时,三极管导通。

三八译码器的原理为C、B、A组成输入信号,根据C、B、A的输入值,对应选择Y(低电平选中)。

三八译码器的使能端已经通过电路固定好,一直处于使能状态。FPGA只需要控制C、B、A即可,然后就会选中对应的数码管。

设计原理

通过分时轮流控制各个数码管的的COM端,就使各个数码管轮流受控显示,这就是动态驱动。在轮流显示过程中,每位数码管的点亮时间为1~2ms,由于人的视觉暂留现象及发光二极管的余辉效应,尽管实际上各位数码管并非同时点亮,但只要扫描的速度足够快,给人的印象就是一组稳定的显示数据,不会有闪烁感,动态显示的效果和静态显示是一样的,能够节省大量的I/O端口,而且功耗更低。

每一位数码管都可以显示十进制的0~9,或者显示十六进制的0~9以及A~F。一个数码管可以显示任意的四位二进制,六个数码管可是显示任意的24位二进制。

24位二进制实际上会分为6组四位的二进制,分别显示到对应的数码管上。

在设计时,首先设计1ms的计时器。1ms中切换一次选中的管子;根据选中的管子,选择出对应的四位二进制,然后将二进制译码为对应的段选信号输出。

共阳极数码管段选信号码表为:

架构设计和信号说明

本模块命名为seven_tube_drive。

在编写代码时,将data信号作为端口信号,下板时由于没有外界提供此信号,所以下板时,会将此信号从端口处省略掉,内部直接产生一个固定的数值。

seven_tube_drive设计实现

设计一个1ms的计数器,每1ms切换一次要点亮的数码管。根据要点亮的位从data的24位中选择出对应的四位,然后将四位数据译码为段选信号即可。

设计代码为:

代码语言:javascript复制
module seven_tube_drive (
  
  input   wire            clk,
  input   wire            rst_n,
  
  input   wire    [23:0]  data,
  
  output  reg     [7:0]   seven_tube_seg_n,
  output  reg     [2:0]   seven_tube_sel
);

  localparam  T_1ms   =   50_000;
  
  reg             [15:0]  cnt;
  reg             [2:0]   sel;
  reg             [3:0]   show_data;
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      cnt <= 16'd0;
    else
      if (cnt < T_1ms - 1'b1)
        cnt <= cnt   1'b1;
      else
        cnt <= 16'd0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      sel <= 3'd0;
    else
      if (cnt == T_1ms - 1'b1)
        if (sel < 3'd5)
          sel <= sel   1'b1;
        else
          sel <= 3'd0;
      else
        sel <= sel;
  end
  
  always @ (posedge clk) seven_tube_sel <= sel;
  
  always @ * begin
    case (sel)
      3'd0    :   show_data = data[23:20];
      3'd1    :   show_data = data[19:16];
      3'd2    :   show_data = data[15:12];
      3'd3    :   show_data = data[11:8];
      3'd4    :   show_data = data[7:4];
      3'd5    :   show_data = data[3:0];
      default :   show_data = 4'd0;
    endcase
  end

  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      seven_tube_seg_n <= 8'hff;
    else
      case (show_data)
        4'd0    :  seven_tube_seg_n <= 8'b1100_0000;
        4'd1    :  seven_tube_seg_n <= 8'b1111_1001;
        4'd2    :  seven_tube_seg_n <= 8'ha4;
        4'd3    :  seven_tube_seg_n <= 8'hb0;
        4'd4    :  seven_tube_seg_n <= 8'h99;
        4'd5    :  seven_tube_seg_n <= 8'h92;
        4'd6    :  seven_tube_seg_n <= 8'h82;
        4'd7    :  seven_tube_seg_n <= 8'hf8;
        4'd8            :     seven_tube_seg_n <= 8'h80;
        4'd9    :  seven_tube_seg_n <= 8'h90;
        4'd10    :  seven_tube_seg_n <= 8'h88;
        4'd11    :  seven_tube_seg_n <= 8'h83;
        4'd12    :  seven_tube_seg_n <= 8'hc6;
        4'd13    :  seven_tube_seg_n <= 8'ha1;
        4'd14    :  seven_tube_seg_n <= 8'h86;
        4'd15    :  seven_tube_seg_n <= 8'h8e;
        default        :  seven_tube_seg_n <= 8'hff;
      endcase
  end

endmodule

在设计中,定义了一个sel寄存器,端口的seven_tube_sel为sel寄存器的寄存信号,时序上会比sel晚一拍。show_data信号的产生是利用sel寄存器和外部data的组合逻辑信号,时序上和sel同步;端口的seven_tube_seg_n为show_data信号的寄存信号,时序上会比show_data信号晚一拍。因此seven_tube_seg_n和seven_tube_sel信号是同步的,都比sel信号晚一拍。此时数码管就不会出现“鬼影”。

当数码管的seven_tube_sel和seven_tube_seg_n不同步时,就会导致选中的管子和想要显示的数字不是完全同步的,由于不同步的时间相对比较少,所以显示出错误的数字的时间较短,点亮的程度就会比较小,称为“鬼影”。

RTL仿真

在仿真时,将T_1ms的参数修改为10。

data的数据按照16进制的方式赋值即可,赋值后不要进行更改,否则不利于仿真图的查看。

仿真代码为:

代码语言:javascript复制
`timescale 1ns/1ps

module seven_tube_drive_tb;

  reg                 clk;
  reg                 rst_n;
  
  reg     [23:0]      data;
  
  wire    [7:0]       seven_tube_seg_n;
  wire    [2:0]       seven_tube_sel;
  
  seven_tube_drive seven_tube_drive_inst(
  
      .clk                (clk),
      .rst_n              (rst_n),
      
      .data               (data),
      
      .seven_tube_seg_n   (seven_tube_seg_n),
      .seven_tube_sel     (seven_tube_sel)
    );
    
  initial clk = 1'b0;
  always # 10 clk = ~clk;
  
  initial begin
    rst_n = 1'b0;
    data = 24'h123456;
    # 201
    rst_n = 1'b1;
    # 5000
    $stop;
  end

endmodule

在仿真图中,将cnt、sel、show_data信号调出;将data信号设置为十六进制显示,cnt设置为无符号位显示,sel设置为无符号位显示,show_data设置为十六进制显示。

可以从图中看出,设计符合我们的设计要求。

板级测试

下板时,将T_1ms修改为50_000。并且将部分设计代码修改为下列格式:

代码语言:javascript复制
module seven_tube_drive (
  
  input   wire            clk,
  input   wire            rst_n,
  
//  input   wire    [23:0]  data,
  
  output  reg     [7:0]   seven_tube_seg_n,
  output  reg     [2:0]   seven_tube_sel
);

  wire            [23:0]  data;
  assign data = 24'h123456;
  
  localparam  T_1ms   =   50_000;
  
  reg             [15:0]  cnt;
  reg             [2:0]   sel;
  reg             [3:0]   show_data;
  ······

进行分析综合后,进行分配管脚。下板后,就可以看到数码管上显示123456。

设计者可以修改data的赋值,再次综合后,观测数码管显示数据是否正确。

测试完成后,在后续使用过程中,还是需要将data设置为端口,以供给其他模块进行驱动。

0 人点赞