🔢 第14课:七段数码管

七段编码 + 多路复用扫描

🏆 成就:数码达人 ✅ Verilator验证

🔢
数码达人
Verilator仿真显示HELLO

💡 七段数码管

7个LED段(a~g)+1小数点(dp)。多路复用扫描:快速轮流点亮每一位,利用视觉暂留。

aaa f b ggg e c ddd .dp 0=0x3F 1=0x06 2=0x5B 3=0x4F 4=0x66 5=0x6D 6=0x7D 7=0x07 8=0x7F 9=0x6F H=0x76 E=0x79 L=0x38 O=0x3F
Verilogsevseg_hello.v核心
reg [7:0] seg_buf [0:3]; // 直接存段码
initial begin
    seg_buf[0]=8'h76; // H
    seg_buf[1]=8'h79; // E
    seg_buf[2]=8'h38; // L
    seg_buf[3]=8'h38; // L
end
localparam SCAN_TICKS=50000; // 1ms@50MHz
reg [15:0] scan_counter; reg [1:0] scan_idx;
always @(posedge clk) begin
    if(scan_counter>=SCAN_TICKS-1) begin
        scan_counter<=0; scan_idx<=scan_idx+1;
        digit_sel<=4'b1111; // 消隐
    end else scan_counter<=scan_counter+1;
    if(scan_counter==5) case(scan_idx) // 点亮
        0: begin digit_sel<=4'b1110; seg<=seg_buf[0]; end
        1: begin digit_sel<=4'b1101; seg<=seg_buf[1]; end
        2: begin digit_sel<=4'b1011; seg<=seg_buf[2]; end
        3: begin digit_sel<=4'b0111; seg<=seg_buf[3]; end
    endcase
end

💡 共阴极 vs 共阳极

共阴极:段码1=亮(本课使用)。共阳极:段码0=亮,取反即可。

📐 七段编码完整表

七段数码管段位定义: aaa 编码: {dp,g,f,e,d,c,b,a} f b ggg 数字/字母 → 段码(共阴极) e c ddd .dp 0→0x3F 1→0x06 2→0x5B 3→0x4F 4→0x66 5→0x6D 6→0x7D 7→0x07 8→0x7F 9→0x6F A→0x77 b→0x7C C→0x39 d→0x5E E→0x79 F→0x71 H→0x76 L→0x38 P→0x73 U→0x3E 空→0x00 -=0x40 _=0x08 多路复用扫描频率计算: 4位 × 1ms/位 = 4ms/帧 = 250Hz 远高于人眼50Hz临界 → 看起来4位同时亮! 注意:每位切换时需要短暂消隐(Blank)防止鬼影

🧪 Verilator测试台

SystemVerilogsevseg_hello_tb.sv
module sevseg_hello_tb;
    logic clk,rst_n; logic [3:0] digit_sel;
    logic [7:0] seg;
    sevseg_hello uut(.*);
    initial clk=0; always #10 clk=~clk;

    reg [7:0] captured_seg[0:3];
    always @(posedge clk) if(digit_sel!=4'b1111)
        case(digit_sel)
            4'b1110: captured_seg[0]<=seg;
            4'b1101: captured_seg[1]<=seg;
            4'b1011: captured_seg[2]<=seg;
            4'b0111: captured_seg[3]<=seg;
        endcase

    function string seg_to_char(input [7:0] s);
        case(s)
            8'h76:return"H"; 8'h79:return"E";
            8'h38:return"L"; 8'h3F:return"O";
            8'h00:return" "; default:return"?";
        endcase
    endfunction

    initial begin
        rst_n=0;#500;rst_n=1;
        $display("========== 七段数码管验证 ==========");
        #300_000; // 等待4位都扫描完
        $display("  Digit0: 0x%02H → %s",captured_seg[0],seg_to_char(captured_seg[0]));
        $display("  Digit1: 0x%02H → %s",captured_seg[1],seg_to_char(captured_seg[1]));
        $display("  Digit2: 0x%02H → %s",captured_seg[2],seg_to_char(captured_seg[2]));
        $display("  Digit3: 0x%02H → %s",captured_seg[3],seg_to_char(captured_seg[3]));
        $display("  显示结果: %s%s%s%s",
            seg_to_char(captured_seg[0]),seg_to_char(captured_seg[1]),
            seg_to_char(captured_seg[2]),seg_to_char(captured_seg[3]));
        $display("==========================================");
        $finish;
    end
    initial #5_000_000 $finish;
endmodule

🔬 进阶:滚动显示

Verilog滚动显示片段
reg [7:0] msg_rom[0:7];
initial begin
    msg_rom[0]=8'h76; msg_rom[1]=8'h79;
    msg_rom[2]=8'h38; msg_rom[3]=8'h38;
    msg_rom[4]=8'h3F; msg_rom[5:7]=8'h00;
end
reg [2:0] scroll_offset;
reg [26:0] scroll_timer;
always @(posedge clk)
    if(scroll_timer>=25_000_000) begin
        scroll_timer<=0; scroll_offset<=scroll_offset+1;
    end else scroll_timer<=scroll_timer+1;
always @(*) begin
    seg_buf[0]=msg_rom[scroll_offset+0];
    seg_buf[1]=msg_rom[scroll_offset+1];
    seg_buf[2]=msg_rom[scroll_offset+2];
    seg_buf[3]=msg_rom[scroll_offset+3];
end

💡 共阴极 vs 共阳极

共阴极:所有LED阴极连在一起接地,段码1=亮(本课使用)

共阳极:所有LED阳极连在一起接VCC,段码0=亮,取反即可 seg=~char_to_seg(ch)

确认你的开发板使用哪种,否则显示会完全反了!

🎉 恭喜你完成全部14课!你已经掌握了FPGA开发的核心技能:从LUT原理到VGA显示、从游戏开发到通信协议。继续探索,创造更多有趣的项目吧!

📐 多路复用扫描详解

4位数码管多路复用: 时间 ──→ Digit3: ████────────────────████──── Digit2: ────████────────────████──── Digit1: ────────████────────████──── Digit0: ────────────████────████──── 每位亮1ms,4位循环 = 4ms一帧 = 250Hz 人眼视觉暂留 > 50Hz → 看起来4位同时亮! 信号时序: digit_sel[3:0] → 哪一位选中(低有效) seg[7:0] → 选中位的七段编码 消隐(Blanking): 切换位时先短暂关闭所有位 防止"鬼影"(前一位的数据 残留在新位上) 4位×1ms = 250Hz → 完美,无闪烁 4位×5ms = 50Hz → 临界,可能微闪 4位×10ms = 25Hz → 明显闪烁!

🎯 实战挑战

挑战1:实现0~9999计数器显示

挑战2:滚动显示"HELLO WORLD"

挑战3:把七段数码管加进Pong游戏做计分板

挑战4:实现数码管闪烁效果(得分时闪烁)

💡 共阴极 vs 共阳极

共阴极:所有LED阴极连在一起接地,段码1=亮(本课使用)

共阳极:所有LED阳极连在一起接VCC,段码0=亮,取反 seg=~char_to_seg(ch)

确认你的开发板使用哪种,否则显示会完全反了!

🎉 恭喜你完成全部14课!你已经掌握了FPGA开发的核心技能:从LUT原理到VGA显示、从游戏开发到通信协议。继续探索,创造更多有趣的项目吧!

🧪 Verilator测试台

SystemVerilogsevseg_hello_tb.sv片段
// 追踪每位显示的段码
reg [7:0] captured_seg[0:3];
always @(posedge clk) if(digit_sel!=4'b1111)
    case(digit_sel)
        4'b1110: captured_seg[0]<=seg;
        4'b1101: captured_seg[1]<=seg;
        4'b1011: captured_seg[2]<=seg;
        4'b0111: captured_seg[3]<=seg;
    endcase
// 段码→字符
function string seg_to_char(input [7:0] s);
    case(s) 8'h76:return"H"; 8'h79:return"E";
           8'h38:return"L"; 8'h3F:return"O";
           default:return"?";
    endcase
endfunction

🎯 实际应用:Pong游戏计分板

把七段数码管加进Pong游戏:左侧2位显示玩家1分数,右侧2位显示玩家2分数。这就是模块化设计的力量——前面的模块直接复用!

💡 共阴极 vs 共阳极

共阴极:段码1=亮(本课使用)。共阳极:段码0=亮,取反即可 seg=~char_to_seg(ch)

🎉 恭喜你完成全部14课!继续探索,创造更多有趣的项目吧!

📐 七段编码完整表

七段数码管段位定义: aaa 编码: {dp,g,f,e,d,c,b,a} f b ggg 数字/字母 → 段码 e c ddd .dp 0→0x3F 1→0x06 2→0x5B 3→0x4F 4→0x66 5→0x6D 6→0x7D 7→0x07 8→0x7F 9→0x6F A→0x77 b→0x7C C→0x39 d→0x5E E→0x79 F→0x71 H→0x76 L→0x38 P→0x73 U→0x3E 空→0x00 -=0x40 多路复用扫描频率: 4位 × 1ms/位 = 4ms/帧 = 250Hz 远高于人眼50Hz临界 → 看起来同时亮!

🔬 进阶:滚动显示

Verilog滚动显示片段
reg [7:0] msg_rom[0:7];
initial begin
    msg_rom[0]=8'h76; // H
    msg_rom[1]=8'h79; // E
    msg_rom[2]=8'h38; // L
    msg_rom[3]=8'h38; // L
    msg_rom[4]=8'h3F; // O
    msg_rom[5:7]=8'h00; // 空
end
reg [2:0] scroll_offset;
reg [26:0] scroll_timer; // 0.5s@50MHz
always @(posedge clk)
    if(scroll_timer>=25_000_000) begin
        scroll_timer<=0;
        scroll_offset<=scroll_offset+1;
    end else scroll_timer<=scroll_timer+1;
// 更新显示缓冲
always @(*) begin
    seg_buf[0]=msg_rom[scroll_offset+0];
    seg_buf[1]=msg_rom[scroll_offset+1];
    seg_buf[2]=msg_rom[scroll_offset+2];
    seg_buf[3]=msg_rom[scroll_offset+3];
end