3. 数码管动态扫描显示
任务目标
设计一个基于FPGA的4位数码管动态扫描显示电路,实现数字0~9在4个数码管上的循环显示,并通过仿真验证扫描时序的正确性。动态扫描利用人眼视觉暂留效应,以较高频率轮流点亮各数码管,从而在4位数码管上同时呈现不同数字。
设计原理与实现方案
动态扫描的核心思想是:在任意时刻仅点亮一个数码管(位选有效),同时在该位上发送对应的段码(段选),并以足够高的频率(通常>1kHz)循环扫描所有位。本设计采用状态机+计数器结构:一个时钟分频计数器产生扫描时钟(约1kHz),驱动位选信号循环移位;同时每个位对应一个显示数字(0~9循环),通过组合逻辑将数字转换为7段码输出。
Verilog代码实现
module seg_dynamic_scan (
input clk, // 系统时钟 50MHz
input rst_n, // 异步复位,低有效
output reg [3:0] seg_sel, // 位选信号,低电平有效
output reg [7:0] seg_data // 段选信号,{dp,g,f,e,d,c,b,a},低电平点亮
);
// 分频计数器:50MHz -> 1kHz 扫描频率
reg [15:0] cnt;
reg scan_clk;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 16'd0;
scan_clk <= 1'b0;
end else if (cnt == 16'd24999) begin
cnt <= 16'd0;
scan_clk <= ~scan_clk;
end else begin
cnt <= cnt + 1'b1;
end
end
// 扫描计数器:0~3 循环,对应4个数码管
reg [1:0] scan_cnt;
always @(posedge scan_clk or negedge rst_n) begin
if (!rst_n)
scan_cnt <= 2'd0;
else
scan_cnt <= scan_cnt + 1'b1;
end
// 位选译码:低电平选中对应位
always @(*) begin
case (scan_cnt)
2'd0: seg_sel = 4'b1110; // 第1位数码管
2'd1: seg_sel = 4'b1101; // 第2位数码管
2'd2: seg_sel = 4'b1011; // 第3位数码管
2'd3: seg_sel = 4'b0111; // 第4位数码管
default: seg_sel = 4'b1111;
endcase
end
// 数字生成:每个位显示不同的循环数字(0~9)
reg [3:0] num_display;
always @(*) begin
case (scan_cnt)
2'd0: num_display = (scan_cnt + 1) % 10; // 位0显示1
2'd1: num_display = (scan_cnt + 2) % 10; // 位1显示2
2'd2: num_display = (scan_cnt + 3) % 10; // 位2显示3
2'd3: num_display = (scan_cnt + 4) % 10; // 位3显示4
default: num_display = 4'd0;
endcase
end
// 7段码译码(共阴数码管,段码低电平点亮)
always @(*) begin
case (num_display)
4'd0: seg_data = 8'b11000000; // 0
4'd1: seg_data = 8'b11111001; // 1
4'd2: seg_data = 8'b10100100; // 2
4'd3: seg_data = 8'b10110000; // 3
4'd4: seg_data = 8'b10011001; // 4
4'd5: seg_data = 8'b10010010; // 5
4'd6: seg_data = 8'b10000010; // 6
4'd7: seg_data = 8'b11111000; // 7
4'd8: seg_data = 8'b10000000; // 8
4'd9: seg_data = 8'b10010000; // 9
default: seg_data = 8'b11111111;
endcase
end
endmodule
仿真验证
编写testbench验证扫描时序:检查位选信号seg_sel是否按1110→1101→1011→0111循环变化,且每个位选有效期间seg_data输出对应的段码。仿真时钟设为50MHz,观察至少4个扫描周期。
module tb_seg_dynamic_scan;
reg clk = 0;
reg rst_n = 0;
wire [3:0] seg_sel;
wire [7:0] seg_data;
seg_dynamic_scan uut (
.clk(clk),
.rst_n(rst_n),
.seg_sel(seg_sel),
.seg_data(seg_data)
);
always #10 clk = ~clk; // 50MHz
initial begin
#20 rst_n = 1;
#500000; // 仿真足够长时间观察多个扫描周期
$finish;
end
// 监控位选变化
always @(posedge clk) begin
if (rst_n && seg_sel !== 4'b1111)
$display("Time=%0t seg_sel=%b seg_data=%h", $time, seg_sel, seg_data);
end
endmodule
运行与验证方法
- 仿真工具:Vivado/ModelSim/Quartus均可,将上述两个文件加入工程。
- 编译仿真:运行testbench,观察波形。
- 通过条件:
seg_sel按1110→1101→1011→0111循环变化,每个状态持续约1ms(扫描时钟周期);- 当
seg_sel=1110时,seg_data对应数字1的段码0xF9;当seg_sel=1101时,对应数字2的段码0xA4;依此类推,位0~3分别显示1、2、3、4; - 扫描周期稳定,无毛刺或位选重叠。
常见问题
- 显示亮度不均:扫描频率过低(<500Hz)会明显闪烁,建议保持1kHz以上;频率过高(>10kHz)则可能因驱动能力不足导致亮度下降。
- 段码错误:确认数码管是共阴还是共阳,本设计按共阴低电平点亮编写,若为共阳需取反段码。
- 位选信号极性:本设计位选低电平有效,若硬件为高电平有效,需将
seg_sel赋值取反。