【FPGA】——用 Verilog编程实现一个 分秒计数器

发布于:2025-04-02 ⋅ 阅读:(63) ⋅ 点赞:(0)

目录

一、分频器

二、按键控制

1.按键消抖

2.按键暂停计时

三、分秒计时器

1.计时器逻辑

2.显示模块

 3.顶层模块

总结


一、分频器

在实现计数器的算法之前我们先要实现分频器,对板子的50MHz频率进行分频,从而输出一个1Hz的时钟信号。也就是让最终呈现的数字实现一秒变一下,从而达到计时器的效果。

(clock_divider.v)代码如下:

module clock_divider #(
    parameter DIVISOR = 50000000  // 默认50MHz->1Hz
)(
    input clk_in,
    input reset_n,
    output reg clk_out
);

reg [31:0] counter;

always @(posedge clk_in or negedge reset_n) begin
    if (!reset_n) begin
        counter <= 0;
        clk_out <= 0;
    end
    else begin
        if (counter >= (DIVISOR/2 - 1)) begin
            counter <= 0;
            clk_out <= ~clk_out;
        end
        else begin
            counter <= counter + 1;
        end
    end
end

endmodule

二、按键控制

1.按键消抖

由于每次按下按键时存在抖动,容易引起按键的多次触发,因此按键消抖模块就可以解决这个问题。
按键消抖主要通过延时来实现,即当按键的一个状态保持20ms以上,即锁存按键的状态,这样既保证按键消抖的稳定又保证了一定的灵敏性。

按键消抖模块 (debounce.v)

module debounce(
    input clk,
    input reset_n,
    input key_in,
    output reg key_pulse
);

reg [19:0] count;  // 20ms 消抖计数器 (50MHz时钟下,1ms=50000个周期)
reg key_in_sync1, key_in_sync2;
reg key_stable;

always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        key_in_sync1 <= 1'b1;
        key_in_sync2 <= 1'b1;
        key_stable <= 1'b1;
        count <= 20'd0;
        key_pulse <= 1'b0;
    end
    else begin
        // 同步输入信号
        key_in_sync1 <= key_in;
        key_in_sync2 <= key_in_sync1;
        
        // 检测按键状态变化
        if (key_in_sync2 != key_stable) begin
            key_stable <= key_in_sync2;
            count <= 20'd0;
        end
        else if (count < 20'd1000000) begin  // 20ms (50MHz * 0.02s)
            count <= count + 1'b1;
        end
        else begin
            key_pulse <= key_in_sync2 & ~key_stable;  // 产生上升沿脉冲
        end
    end
end

endmodule

2.按键暂停计时

将按键的脉冲信号 转换为一个稳定的 切换信号 。每当检测到一个有效的 pause_pulse 时,就翻转 pause_toggle 的状态(从 0 变 1,或从 1 变 0)。

暂停控制模块 (pause_control.v)

module pause_control(
    input clk,
    input reset_n,
    input pause_pulse,
    output reg pause_toggle
);

always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        pause_toggle <= 1'b0;
    end
    else if (pause_pulse) begin
        pause_toggle <= ~pause_toggle;
    end
end

endmodule

三、分秒计时器

1.计时器逻辑

计数规则
秒个位 :从0 计数到 9,然后归零并触发秒十位进位。

秒十位 :从 0 计数到 5(即 59 秒),然后归零并触发分个位进位。

分个位 :从 0 计数到 9,然后归零并触发分十位进位。

分十位 :从 0 计数到 5(即 59 分),然后归零(最大显示 59:59)

分秒计数器模块 (min_sec_counter.v)

module min_sec_counter(
    input clk,          // 1Hz 时钟
    input reset_n,      // 复位信号
    input pause,        // 暂停信号
    output reg [3:0] sec_ones,  // 秒个位
    output reg [3:0] sec_tens,  // 秒十位
    output reg [3:0] min_ones,  // 分个位
    output reg [3:0] min_tens   // 分十位
);

always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        sec_ones <= 4'd0;
        sec_tens <= 4'd0;
        min_ones <= 4'd0;
        min_tens <= 4'd0;
    end
    else if (!pause) begin  // 只有不在暂停状态时才计数
        // 秒个位计数
        if (sec_ones == 4'd9) begin
            sec_ones <= 4'd0;
            // 秒十位计数
            if (sec_tens == 4'd5) begin
                sec_tens <= 4'd0;
                // 分个位计数
                if (min_ones == 4'd9) begin
                    min_ones <= 4'd0;
                    // 分十位计数
                    if (min_tens == 4'd5) begin
                        min_tens <= 4'd0;
                    end
                    else begin
                        min_tens <= min_tens + 4'd1;
                    end
                end
                else begin
                    min_ones <= min_ones + 4'd1;
                end
            end
            else begin
                sec_tens <= sec_tens + 4'd1;
            end
        end
        else begin
            sec_ones <= sec_ones + 4'd1;
        end
    end
end

endmodule

2.显示模块

七段数码管分共阳极与共阴极两种。共阳极数码管其工作特点是,当笔段电极接低电平,公共阳极接高电平时,相应笔段可以发光。共阴极数码管则与之相反,它是将发光二极管的阴极短接后作为公共阴极,当驱动信号为高电平、公共阴极接低电平时,才能发光。

七段数码管显示模块 (seven_seg_display.v)

module seven_seg_display(
    input [3:0] digit,
    output reg [6:0] seg
);

always @(*) begin
    case (digit)
        4'h0: seg = 7'b1000000;  // 0
        4'h1: seg = 7'b1111001;  // 1
        4'h2: seg = 7'b0100100;  // 2
        4'h3: seg = 7'b0110000;  // 3
        4'h4: seg = 7'b0011001;  // 4
        4'h5: seg = 7'b0010010;  // 5
        4'h6: seg = 7'b0000010;  // 6
        4'h7: seg = 7'b1111000;  // 7
        4'h8: seg = 7'b0000000;  // 8
        4'h9: seg = 7'b0010000;  // 9
        default: seg = 7'b1111111;  // 全灭
    endcase
end

endmodule

 3.顶层模块

module top(
    input clk_50m,         // 50MHz 时钟输入
    input reset_n,         // 复位按键 (低电平有效)
    input pause_key,       // 暂停/继续按键
    output [6:0] hex0,     // 秒个位数码管
    output [6:0] hex1,     // 秒十位数码管
    output [6:0] hex2,     // 分个位数码管
    output [6:0] hex3,     // 分十位数码管
    output [7:0] ledg      // LED 显示秒数 (可选)
);

// 内部信号定义
wire clk_1hz;             // 1Hz 时钟
wire pause_pulse;         // 消抖后的暂停脉冲信号
wire pause_toggle;        // 暂停状态信号
wire [3:0] sec_ones;      // 秒个位
wire [3:0] sec_tens;      // 秒十位
wire [3:0] min_ones;      // 分个位
wire [3:0] min_tens;      // 分十位

// 时钟分频模块
clock_divider #(
    .DIVISOR(50000000)    // 50MHz -> 1Hz
) clk_div (
    .clk_in(clk_50m),
    .reset_n(reset_n),
    .clk_out(clk_1hz)
);
// 按键消抖模块
debounce debounce_pause (
    .clk(clk_50m),
    .reset_n(reset_n),
    .key_in(pause_key),
    .key_pulse(pause_pulse)
);

// 暂停控制模块 (将脉冲信号转换为 toggle 信号)
pause_control pause_ctrl (
    .clk(clk_50m),
    .reset_n(reset_n),
    .pause_pulse(pause_pulse),
    .pause_toggle(pause_toggle)
);

// 分秒计数器模块
min_sec_counter counter (
    .clk(clk_1hz),
    .reset_n(reset_n),
    .pause(pause_toggle),
    .sec_ones(sec_ones),
    .sec_tens(sec_tens),
    .min_ones(min_ones),
    .min_tens(min_tens)
);
// 数码管显示模块
seven_seg_display seg0 (.digit(sec_ones), .seg(hex0));
seven_seg_display seg1 (.digit(sec_tens), .seg(hex1));
seven_seg_display seg2 (.digit(min_ones), .seg(hex2));
seven_seg_display seg3 (.digit(min_tens), .seg(hex3));


endmodule

 实现效果:

分秒计时器


总结

代码分为了很多个模块,需要仔细思考各模块之间的关系