[FGPA基础学习]分秒计数器的制作

分秒计数器设计

​ 本次实验内容为:DE2-115板子上用 Verilog编程实现一个 分秒计数器,并具备按键暂停、按键消抖功能

一、系统架构设计

  1. 顶层模块划分

    复制代码
    顶层模块(top)
    ├── 按键消抖模块(key_debounce)
    ├── 边沿检测模块(edge_detect)
    ├── 时钟分频模块(clk_divider)
    ├── 分秒计数器(min_sec_counter)
    └── 七段译码器(seg7_decoder)
  2. 模块交互逻辑

    plaintext 复制代码
    CLK(50MHz) → 分频 → 1Hz时钟 → 计时模块
    KEY → 消抖模块 → 暂停控制 → 计时模块
    计时输出 → 数码管显示模块 → 物理数码管

二、核心模块实现

顶层模块

verilog 复制代码
module top(
    input CLOCK_50,         // 50MHz时钟(PIN_Y2)
    input KEY0,             // 复位
    input KEY1,             // 暂停
    output [0:6] HEX7, HEX6,// 分十位、分个位
    output [0:6] HEX5, HEX4 // 秒十位、秒个位
);

wire clk_1hz;               // 1Hz时钟信号
wire key_stable;            // 消抖后按键信号
wire pause_trigger;         // 边沿检测信号
reg pause_state = 1'b0;     // 暂停状态寄存器

wire [3:0] min_tens, min_ones;
wire [3:0] sec_tens, sec_ones;

// 按键消抖模块(20ms消抖)
debounce #(.DEBOUNCE_MS(20)) debounce_inst(
    .clk(CLOCK_50),
    .button_in(~KEY1),
    .button_out(key_stable)
);

// 边沿检测模块
edge_detect edge_inst(
    .clk(CLOCK_50),
    .signal_in(key_stable),
    .edge_out(pause_trigger)
);

// 时钟分频模块
clk_divider clk_div_inst(
    .clk(CLOCK_50),
    .reset(~KEY0),
    .pause(pause_state),
    .clk_out(clk_1hz)
);

// 分秒计数器
min_sec_counter counter(
    .clk(clk_1hz),
    .reset(~KEY0),
    .min_tens(min_tens),
    .min_ones(min_ones),
    .sec_tens(sec_tens),
    .sec_ones(sec_ones)
);

// 显示译码模块
seg7_decoder hex7(.bcd(min_tens), .seg(HEX7));
seg7_decoder hex6(.bcd(min_ones), .seg(HEX6));
seg7_decoder hex5(.bcd(sec_tens), .seg(HEX5));
seg7_decoder hex4(.bcd(sec_ones), .seg(HEX4));

// 暂停状态机
always @(posedge CLOCK_50 or posedge ~KEY0) begin
    if (~KEY0) pause_state <= 1'b0;
    else if (pause_trigger) pause_state <= ~pause_state;
end

endmodule

时钟分频模块

上次实验提到过:DE2-115开发板配备了一个固定的时钟源。该开发板内置了一个50MHz的晶振,因此对于50MHz的时钟频率,时钟周期为20ns。

verilog 复制代码
module clk_divider(
    input clk,
    input reset,
    input pause,
    output reg clk_out
);
    parameter DIV_FACTOR = 26'd49_999_999; // 50MHz→1Hz
    reg [25:0] cnt;
    
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            cnt <= 0;
            clk_out <= 0;
        end else if (!pause) begin
            cnt <= (cnt == DIV_FACTOR) ? 0 : cnt + 1;
            if (cnt == DIV_FACTOR) clk_out <= ~clk_out;
        end
    end
endmodule

按键模块

我选择KEY0做为复位按钮,KEY1作为控制分秒计数器的暂停按钮

DE2-115 提供了四个按钮开关,每个按钮开关都通过一个施 密特触发器进行了去抖动处理。四个施密特触发器的输出信 号,分别为KEY0、KEY1、KEY2、KEY3,直接连接到了Cyclone IV E FPGA。当按钮没有 被按下的时候,它的输出是高电平,按下去则给出一个低电平

verilog 复制代码
module key_debounce #(
    parameter DEBOUNCE_MS = 20  // 可配置消抖时间
)(
    input clk,
    input button_in,
    output reg button_out
);
    localparam MAX_COUNT = 50_000_000 * DEBOUNCE_MS / 1000;
    reg [23:0] cnt;
    
    always @(posedge clk) begin
        if (button_in != button_out) begin
            cnt <= (cnt == MAX_COUNT-1) ? 0 : cnt + 1;
            if (cnt == MAX_COUNT-1) button_out <= button_in;
        end else begin
            cnt <= 0;
        end
    end
endmodule

边沿检测模块

verilog 复制代码
module edge_detect(
    input clk,
    input signal_in,
    output reg edge_out
);
    reg signal_delay;
    always @(posedge clk) begin
        signal_delay <= signal_in;
        edge_out <= signal_in & ~signal_delay; // 上升沿检测
    end
endmodule

计数器模块

verilog 复制代码
module min_sec_counter(
    input clk,
    input reset,
    output reg [3:0] min_tens,
    output reg [3:0] min_ones,
    output reg [3:0] sec_tens,
    output reg [3:0] sec_ones
);
    // 秒计数器
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            sec_ones <= 4'd0;
            sec_tens <= 4'd0;
        end else begin
            if (sec_ones == 4'd9) begin
                sec_ones <= 4'd0;
                sec_tens <= (sec_tens == 4'd5) ? 4'd0 : sec_tens + 1;
            end else begin
                sec_ones <= sec_ones + 1;
            end
        end
    end
    
    // 分钟计数器
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            min_ones <= 4'd0;
            min_tens <= 4'd0;
        end else if (sec_tens == 4'd5 && sec_ones == 4'd9) begin
            if (min_ones == 4'd9) begin
                min_ones <= 4'd0;
                min_tens <= (min_tens == 4'd5) ? 4'd0 : min_tens + 1;
            end else begin
                min_ones <= min_ones + 1;
            end
        end
    end
endmodule

数码管模块

之前提及到过,可以利用38译码器点亮7段数码管(其实只需要7段译码器即可),但是我们的输入情况有9种,所以七段译码器需要将4位BCD码转换为对应的段码;也说过数码管的点亮逻辑:FPGA输出低电压的时候,对应的字码段点亮,反之则熄灭

进行分秒展示,一共要用到4个数码管,为了更直观展示我选择:HEX7,HEX6作为分钟位,HEX5,HEX4做为秒位

数码管显示数字 需要亮起来的小段 对应的7位二进制数
0 HEX[0]~[5] 0000001
1 HEX[1]~[2] 1001111
2 HEX[0],[1],[3],[4],[6] 0010010
3 HEX[0],[1],[2],[3],[6] 0000110
4 HEX[1].[2].[5],[6] 1001100
5 HEX[0],[2],[3],[5],[6] 0100100
6 HEX[0],[2],[3],[4],[5],[6] 0100000
7 HEX[1]~[3] 0001111
8 HEX[0]~[6] 0000000
9 HEX[0],[1],[2],[3],[5],[6] 0001000
verilog 复制代码
module seg7_decoder(
    input [3:0] bcd,
    output reg [6:0] seg
);
    always @(*) begin
        case(bcd)
            4'd0 : seg = 7'b0000_001; // 0
            4'd1 : seg = 7'b1001_111; // 1
            4'd2 : seg = 7'b0010_010; // 2
            4'd3 : seg = 7'b0000_110; // 3
            4'd4 : seg = 7'b1001_100; // 4
            4'd5 : seg = 7'b0100_100; // 5
            4'd6 : seg = 7'b0100_000; // 6
            4'd7 : seg = 7'b0001_111; // 7
            4'd8 : seg = 7'b0000_000; // 8
            4'd9 : seg = 7'b0001_000; // 9
        default: seg = 7'b1111_111; // 灭
        endcase
    end
endmodule

HEX4~7对应的引脚图如下:

引脚配置:

实现效果

• 上电后数码管显示00:00并开始计时

• 按下KEY[1]后立即暂停,再次按下恢复

• 复位键KEY[0]可清零计数器

总结:按键边沿检测模块使用双寄存器级联技术捕获有效按键信号,配合FSM状态机实现暂停/恢复功能切换

相关推荐
西岸行者3 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
ZPC82103 天前
docker 镜像备份
人工智能·算法·fpga开发·机器人
ZPC82103 天前
docker 使用GUI ROS2
人工智能·算法·fpga开发·机器人
悠哉悠哉愿意3 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码3 天前
嵌入式学习路线
学习
毛小茛3 天前
计算机系统概论——校验码
学习
babe小鑫3 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms3 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下3 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。3 天前
2026.2.25监控学习
学习