问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

FPGA数码管动态扫描技术详解:从原理到Verilog实现

创作时间:
作者:
@小白创作中心

FPGA数码管动态扫描技术详解:从原理到Verilog实现

引用
CSDN
1.
https://blog.csdn.net/m0_58544042/article/details/142529023

数码管动态扫描是FPGA应用中的一个经典案例,通过控制数码管的选通信号和段码信号,可以在有限的硬件资源下实现多位数码管的显示。本文将从数码管动态扫描的原理、逻辑建模到Verilog代码实现进行详细讲解。

数码管动态扫描原理

下图是常见的数码管样式:

我们通过控制a、b、c、d、e、f、g、dp这8个管脚来实现从0到F(15)的数码显示,其内部结构如下:

可以看到数码有共阴极和共阳极两种接法,控制各个端口的高低电平即可实现数码显示,其中SEL为选通信号;两种接法对应数码显示的对照表如下:

控制多个数码管的结构如图:

可以看到,多个数码管共用管脚,这样做节省了引脚,只需通过SEL信号进行选通控制实现不同数码管的亮灭;

逻辑建模

我们预实现8个LED灯按照预设的数据进行点亮;

逻辑建模过程如下:

  1. 控制实现不同LED灯点亮,需要设计点亮时间间隔,已知人眼的视觉暂留为20ms,所以要设计间隔小于20ms,这样在人眼中8个LED灯就是同时点亮的,选取间隔为1ms,因此需要设计1ms计数器。
  2. 共8个LED灯,即8个状态,每隔1ms为一个状态,因此需要用1ms计数器控制计数值为8的计数器计数,这样就实现了不同时刻选通不同LED灯。
  3. 在某一时刻确定了要点亮的LED灯后,需要明确要显示的数值,因此选通信号要控制一个8选1选择器,这个选择器的8个端口分别为8个时刻想要显示的数码值。
  4. 确定某一时刻数码管要显示的值后,需要将该数值用共阴极或共阳极的二进制方式进行表示,此时才能正确的显示。

原理图大致如下:

Verilog代码实现

由于代码各部分均较简单,直接附代码如下:

module Hex_8(clk,restn,led_data,sel,seg);
input clk,restn;
input [31:0]led_data;
output reg [7:0]sel;
output reg [7:0]seg;
parameter clk_freq = 50_000_000;
parameter led_freq = 1000;
parameter MCNT = clk_freq/led_freq-1;
//1ms 时钟计数器
reg [29:0]clk_1ms;
always@(posedge clk or negedge restn)
    if(!restn)
        clk_1ms<=0;
    else if(clk_1ms==MCNT)
        clk_1ms<=0;
    else
        clk_1ms<=clk_1ms+1;
        
 //位计数器
 reg[2:0]cnt;
always@(posedge clk or negedge restn)
    if(!restn)
        cnt<=0;
    else if(clk_1ms==MCNT)
        cnt<=cnt+1;
//3_8译码器
always@(posedge clk or negedge restn)
    if(!restn)
        sel<=8'b0000_0000;
    else 
        case(cnt)
        3'd0:sel<=8'b0000_0001;
        3'd1:sel<=8'b0000_0010;
        3'd2:sel<=8'b0000_0100;
        3'd3:sel<=8'b0000_1000;
        3'd4:sel<=8'b0001_0000;
        3'd5:sel<=8'b0010_0000;
        3'd6:sel<=8'b0100_0000;
        3'd7:sel<=8'b1000_0000;
        endcase
//输入数据定义
reg [3:0]data_temp;
always@(*)
    case(cnt)
        3'd0:data_temp<=led_data[3:0];
        3'd1:data_temp<=led_data[7:4];
        3'd2:data_temp<=led_data[11:8];
        3'd3:data_temp<=led_data[15:12];
        3'd4:data_temp<=led_data[19:16];
        3'd5:data_temp<=led_data[23:20];
        3'd6:data_temp<=led_data[27:24];
        3'd7:data_temp<=led_data[31:28];
        endcase
always@(posedge clk)
     case(data_temp)
        4'd0: seg<=8'b1100_0000;
        4'd1: seg<=8'b1111_1001;
        4'd2: seg<=8'b1010_0100;
        4'd3: seg<=8'b1011_0000;
        4'd4: seg<=8'b1001_1001;
        4'd5: seg<=8'b1001_0010;
        4'd6: seg<=8'b1000_0010;
        4'd7: seg<=8'b1111_1000;
        4'd8: seg<=8'b1000_0000;
        4'd9: seg<=8'b1001_0000;
        4'd10:seg<=8'b1000_1000;
        4'd11:seg<=8'b1000_0011;
        4'd12:seg<=8'b1100_0110;
        4'd13:seg<=8'b1010_0001;
        4'd14:seg<=8'b1000_0110;
        4'd15:seg<=8'b1000_1110;
        endcase
endmodule

测试代码:

`timescale 1ns / 1ns
module Hex_8_tb();
reg clk;
reg restn;
reg[31:0]led_data;
wire[7:0]sel;
wire[7:0]seg;
Hex_8 Hex_8_inst(
        .clk(clk),
        .restn(restn),
        .led_data(led_data),
        .sel(sel),
        .seg(seg)
        );
initial begin
    restn = 0;
    #201
    restn = 1;
    #1000
    led_data = 32'h2024_0925;
    #10000000
    $stop;
end
initial clk = 1;
always #10 clk = ~clk;
endmodule

仿真结果:(输入数据为20240925)经过对照表格,也是没有问题的

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号