基于Basys2的数码管动态扫描module(verilog)的模块化设计

发布于:2022-07-17 ⋅ 阅读:(224) ⋅ 点赞:(0)

    在大二下学期学习《Verilog与FPGA实现》的时候鲲鲲给我们布置了课程实验,有许多实验需要用到数码管作为输出。本文旨在用模块化的思想向大家介绍笔者的通用的数码管显示模块。

一、数码管工作原理

数码管事常见的显示装置,通常由7段或8段LED组成,根据公共端连接方式分为:共阳极数码管、共阴极数码管。

顾名思义,共阴极数码管的所有LED的阴极是接在一块的,当通入高电平数码管才点亮;共阳极数码管的所有LED是阳极接在一起,输入低电平点亮,段选码如下图所示

我们用的Basys2的数码管为共阳极数码管,因此本次设计采用共阳极段码表。

 二、Verilog模块设计

1、原理

为了节省管脚,Basys2开发板采用的是动态扫描的方式来进行数码管显示,具体如下:

(1)动态扫描的优点:

管脚对芯片来说是非常宝贵的资源,对于我们的板子来说,用到了4个8段数码管,如果用管脚直接控制LED的亮灭的话,总共需要用到4*(8+2)=40个管脚(8是abcdefg段选,2是两个共阳极),一共用到32个管脚作为控制端;而用动态扫描的方式来进行显示的话需要4个位选(选择四个数码管中的哪一个)和8个段选(abcdefg中的哪一段,当然还有两个共阳极),一共用到12个管脚作为控制端,高下立判。

(2)动态扫描原理简介:

要实现动态扫描,最常用的是数据锁存器和移位寄存器,在某一时刻输入位选选中要显示的数码管,同时送上相应的段选码,下一时刻选中下一个数码管同时送上对应的段选码,只要扫描的频率足够快,人眼就会因为视觉暂留效应而看到的4个数码管显示不同的数字。常用的刷新频率在1KHz到60Hz,太快就会亮度不够,太慢就会闪烁。

2、Verilog模块的设计

设计的模块会将输入的4个二进制码显示在4个位的数码管上,因而需要模块:分频器(将板子50MHz时钟分频为1KHz时钟)、译码模块(二进制码转为共阳极数码管段选码)、动态扫描模块(在特定时刻选中位并送上对应的段选码)、top(顶层封装),具体代码如下:

(1)分频器:

计数分频,通过计算,输入的50MHz时钟分频为1KHz时钟,需要计数50000次,为了节省资源,计数器位宽为16位刚好满足2^16<50000<2^17。

module clk_div(
    input clk,
    output reg clk_div
    );
    //输入板子50MHz时钟,输出1KHz时钟
    //计数分频:50000次
    reg [15:0]cnt = 16'd0;
    always@(posedge clk)
    begin
        if(cnt == 16'd50000)
        begin
            cnt <= 16'd0;
        end
        else
        begin
            cnt <= cnt + 16'd1;
        end
    end
    //输出时钟控制
    always@(posedge clk)
    begin
        if(cnt <= 16'd24999)
        begin
            clk_div <= 1'b1;
        end
        else
        begin
            clk_div <= 1'b0;
        end
    end
    
endmodule

 2、译码器模块:

一个很简单的case分支实现译码,输入4位二进制编码,输出对应的8位段选码。

module wei_encoder(
    input [3:0]hex_number,
    output reg [7:0]display_code
    );
    //将输入的十六进制代码转换为共阳极数码管段选编码并输出
    always@(*)
    begin
        case(hex_number)
            4'b0000:display_code = 8'hc0;//0
            4'b0001:display_code = 8'hf9;//1
            4'b0010:display_code = 8'ha4;//2
            4'b0011:display_code = 8'hb0;//3
            4'b0100:display_code = 8'h99;//4
            4'b0101:display_code = 8'h92;//5
            4'b0110:display_code = 8'h82;//6
            4'b0111:display_code = 8'hf8;//7
            4'b1000:display_code = 8'h80;//8
            4'b1001:display_code = 8'h90;//9
            4'b1010:display_code = 8'h88;//A
            4'b1011:display_code = 8'h83;//B
            4'b1100:display_code = 8'hc6;//C
            4'b1101:display_code = 8'ha1;//D
            4'b1110:display_code = 8'h86;//E
            4'b1111:display_code = 8'h8e;//F
            default:display_code = 8'hff;//无
        endcase
    end
endmodule

3、动态扫描模块:

输入分频后的1KHz时钟作为位选信号的扫描频率,通过计数器的数值case输出位选和相应的段选信号。

module Display(
    input clk_div,
    input [7:0]Out_number_1,//第一位数字对应的段选码
    input [7:0]Out_number_2,//第二位数字对应的段选码
    input [7:0]Out_number_3,//第三位数字对应的段选码
    input [7:0]Out_number_4,//第四位数字对应的段选码
    output reg [7:0]duan_code,
    output reg [3:0]wei_code
    );
    //将输入的十六进制代码转换为共阳极数码管段选码动态扫描输出
    reg [2:0]sel_cnt;//扫描计数器
    //动态扫描
    always@(posedge clk_div)
    begin
        if(sel_cnt == 3'd4)
        begin
            sel_cnt <= 3'd0;
        end
        else
        begin
            sel_cnt <= sel_cnt + 3'd1;
        end
    end
    always@(posedge clk_div)
    begin
            case(sel_cnt)
                3'd0:
                begin
                    wei_code <= 4'b0001;
                    duan_code <= Out_number_1;
                end
                3'd1:
                begin
                    wei_code <= 4'b0010;
                    duan_code <= Out_number_2;
                end
                3'd2:
                begin
                    wei_code <= 4'b0100;
                    duan_code <= Out_number_3;
                end
                3'd3:
                begin
                    wei_code <= 4'b1000;
                    duan_code <= Out_number_4;
                end
                default:
                begin
                    wei_code <= 4'b0000;
                    duan_code <= 8'hff;
                end
            endcase
    end

endmodule

4、顶层模块:

在顶层中例化上述三个模块,封装为一个模块,方便实例化调用,具体流程:分频器将输入的clk(50MHz)分频为clk_div(1KHz),送往Display用于扫描计数器的动态扫描,wei_encoder将输入的二进制数转化为共阳极数码管对应的段选码,送往Display模块扫描输出。

module Display(
    input clk_div,
    input [7:0]Out_number_1,//第一位数字对应的段选码
    input [7:0]Out_number_2,//第一位数字对应的段选码
    input [7:0]Out_number_3,//第一位数字对应的段选码
    input [7:0]Out_number_4,//第一位数字对应的段选码
    output reg [7:0]duan_code,
    output reg [3:0]wei_code
    );
    //将输入的十六进制代码转换为共阳极数码管段选码动态扫描输出
    reg [2:0]sel_cnt;//扫描计数器
    //动态扫描module SEG_display(
    input clk,
    input [7:0]number1,
    input [7:0]number2,
    output [3:0]wei_code,
    output [7:0]duan_code
    );
    //声明内部信号线
    wire [7:0]Out_number_1;//将要输出的段选码
    wire [7:0]Out_number_2;
    wire [7:0]Out_number_3;
    wire [7:0]Out_number_4;
    wire clk_div;//分频后的时钟
    
    //模块实例化
    //时钟分频,用于数码管计数扫描
    clk_div div(
        .clk(clk),
        .clk_div(clk_div)
    );
    //将将要输出的数转化为段选码
    wei_encoder encoder1(
        .hex_number(number1[3:0]),
        .display_code(Out_number_1)
    );
    wei_encoder encoder2(
        .hex_number(number1[7:4]),
        .display_code(Out_number_2)
    );
    wei_encoder encoder3(
        .hex_number(number2[3:0]),
        .display_code(Out_number_3)
    );
    wei_encoder encoder4(
        .hex_number(number2[7:4]),
        .display_code(Out_number_4)
    );
    //数码管动态扫描显示
    Display display(
        .clk_div(clk_div),
        .Out_number_1(Out_number_1),
        .Out_number_2(Out_number_2),
        .Out_number_3(Out_number_3),
        .Out_number_4(Out_number_4),
        .duan_code(duan_code),
        .wei_code(wei_code)
    );
endmodule

    always@(posedge clk_div)
    begin
        if(sel_cnt == 3'd4)
        begin
            sel_cnt <= 3'd0;
        end
        else
        begin
            sel_cnt <= sel_cnt + 3'd1;
        end
    end
    always@(posedge clk_div)
    begin
            case(sel_cnt)
                3'd0:
                begin
                    wei_code <= 4'b0001;
                    duan_code <= Out_number_1;
                end
                3'd1:
                begin
                    wei_code <= 4'b0010;
                    duan_code <= Out_number_2;
                end
                3'd2:
                begin
                    wei_code <= 4'b0100;
                    duan_code <= Out_number_3;
                end
                3'd3:
                begin
                    wei_code <= 4'b1000;
                    duan_code <= Out_number_4;
                end
                default:
                begin
                    wei_code <= 4'b0000;
                    duan_code <= 8'hff;
                end
            endcase
    end

endmodule

三、写在最后:

这个Verilog数码管动态扫描模块可以适用于Basys2几乎所有的项目,今后我将陆续更新鲲鲲的《Verilog与FPGA实现》 给我们布置的八个实验,一方面巩固自己的知识,另一方面有助于学弟学妹们学习。笔者不才,如有疏漏,欢迎各位网友批评指正。

本文含有隐藏内容,请 开通VIP 后查看