4. 数码管动态扫描

任务目标

在FPGA开发板上驱动4位共阴极数码管,通过动态扫描方式稳定显示一组数字(例如"1234")。核心是掌握扫描时序设计:任一时刻仅点亮一位数码管,以较高频率循环轮询各位,利用视觉暂留效应形成多位同时显示的观感。

硬件连接与依赖

  • 4位数码管为共阴极,段选信号(a~g, dp)接FPGA引脚,位选信号(sel0~sel3)分别控制4个位的公共阴极。
  • 本案例以DE0/DE1开发板为参考,若实际板卡不同,需根据原理图调整引脚分配。
  • 时钟输入:50MHz系统时钟。

核心原理

动态扫描的关键参数是刷新频率。每位数码管在一个扫描周期内被点亮1/4时间,若扫描频率低于50Hz,人眼会感到明显闪烁。本设计将扫描周期设为1ms(每位点亮250μs),整体刷新率为1kHz,远高于闪烁阈值。段选数据由位选信号控制的多路选择器输出,确保当前被点亮的位显示其对应的数字。

完整代码

module seg_dynamic_scan (
    input       clk,        // 50MHz
    input       rst_n,      // 异步复位,低有效
    output reg [6:0] seg,   // {a,b,c,d,e,f,g} 共阴极段码
    output reg [3:0] sel    // 位选,高电平选中
);

    // 待显示数字(BCD码):千位=1,百位=2,十位=3,个位=4
    reg [3:0] num [0:3];
    initial begin
        num[0] = 4'd1;  // 千位
        num[1] = 4'd2;  // 百位
        num[2] = 4'd3;  // 十位
        num[3] = 4'd4;  // 个位
    end

    // 共阴极段码表(0~9)
    function [6:0] seg_code;
        input [3:0] bcd;
        begin
            case (bcd)
                4'd0: seg_code = 7'b1000000; // 0
                4'd1: seg_code = 7'b1111001; // 1
                4'd2: seg_code = 7'b0100100; // 2
                4'd3: seg_code = 7'b0110000; // 3
                4'd4: seg_code = 7'b0011001; // 4
                4'd5: seg_code = 7'b0010010; // 5
                4'd6: seg_code = 7'b0000010; // 6
                4'd7: seg_code = 7'b1111000; // 7
                4'd8: seg_code = 7'b0000000; // 8
                4'd9: seg_code = 7'b0010000; // 9
                default: seg_code = 7'b1111111; // 全灭
            endcase
        end
    endfunction

    // 扫描计数器:1ms周期(50MHz / 1000 = 50000)
    reg [15:0] cnt;
    reg [1:0]  scan_pos;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt <= 16'd0;
            scan_pos <= 2'd0;
        end else if (cnt == 16'd49999) begin
            cnt <= 16'd0;
            scan_pos <= scan_pos + 2'd1;  // 轮换位选
        end else begin
            cnt <= cnt + 16'd1;
        end
    end

    // 位选与段码输出
    always @(*) begin
        case (scan_pos)
            2'd0: begin sel = 4'b0001; seg = seg_code(num[0]); end // 千位
            2'd1: begin sel = 4'b0010; seg = seg_code(num[1]); end // 百位
            2'd2: begin sel = 4'b0100; seg = seg_code(num[2]); end // 十位
            2'd3: begin sel = 4'b1000; seg = seg_code(num[3]); end // 个位
            default: begin sel = 4'b0000; seg = 7'b1111111; end
        endcase
    end

endmodule

引脚约束(Quartus .qsf示例)

set_location_assignment PIN_N2  -to clk
set_location_assignment PIN_B8  -to rst_n
set_location_assignment PIN_C15 -to seg[0]
set_location_assignment PIN_E15 -to seg[1]
set_location_assignment PIN_F15 -to seg[2]
set_location_assignment PIN_G15 -to seg[3]
set_location_assignment PIN_H15 -to seg[4]
set_location_assignment PIN_J15 -to seg[5]
set_location_assignment PIN_K15 -to seg[6]
set_location_assignment PIN_L14 -to sel[0]
set_location_assignment PIN_M14 -to sel[1]
set_location_assignment PIN_N14 -to sel[2]
set_location_assignment PIN_P14 -to sel[3]

以上引脚编号仅为示例,实际需根据开发板原理图修改。

运行与验证

  1. 在Quartus中新建工程,将上述Verilog代码设为顶层实体,添加引脚约束后编译综合。
  2. 下载bitstream至FPGA开发板。
  3. 通过标准:4位数码管稳定显示"1234",无闪烁、无重影。若出现闪烁,说明扫描频率过低,可减小cnt计数上限(例如改为24999,即0.5ms周期);若出现重影,则需检查位选信号与段码输出的时序对齐,确保切换位选时段码已稳定。

常见问题

  • 显示亮度不均:不同位数的LED点亮时间相同,但人眼对某些数字的亮度感知略有差异,属正常现象。若差异明显,可检查各段限流电阻是否一致。
  • 高位消隐:若需显示"0001"而非"1",只需将num数组对应位设为0即可,本代码已支持前导零显示。