【FPGA实战】基于DE2-115实现数字秒表
- 一、项目概述
 - 二、层次化设计架构
 - 三、核心模块实现原理
 - 
- 
- [1. 时钟分频模块(clock_divider.v)](#1. 时钟分频模块(clock_divider.v))
 - [2. 按键处理模块](#2. 按键处理模块)
 - 
- [2.1 消抖(debounce .v)](#2.1 消抖(debounce .v))
 - [2.2 边沿检测(edge_detector .v)](#2.2 边沿检测(edge_detector .v))
 
 - [3. 时间计数模块(time_counter .v)](#3. 时间计数模块(time_counter .v))
 - [4. 显示驱动模块(seven_seg_display.v)](#4. 显示驱动模块(seven_seg_display.v))
 - 5.顶层模块(minute_second_counter_top.v)
 
 
 - 
 - 三、引脚配置
 - 四、实验效果
 - 五、总结与扩展
 
一、项目概述
本设计实现了一个基于FPGA的数字秒表系统,主要功能包括:
- 精确到秒的计时功能(最大59:59)
 - 按键控制启动/暂停(切换模式)
 - 四位数码管显示(带分钟小数点)
 - 50MHz时钟分频处理
 - 按键消抖处理
 
二、层次化设计架构
采用自顶向下的设计方法,分为五个核心模块:
- 顶层模块:minute_second_counter_top
 - 时钟分频模块:clock_divider
 - 按键处理模块:debounce + edge_detector
 - 时间计数模块:time_counter
 - 显示驱动模块:seven_seg_display
 
三、核心模块实现原理
1. 时钟分频模块(clock_divider.v)
我们需要的是一个能够计数到60s然后进位1min的分秒计数器,所以秒计数器是0-59,分计数器也是0-59。
那么要实现这个分秒计数器的话,我们需要分频将系统时钟分频成1Hz的信号,作为计数器的基准时钟。
其次就是需要一个计数器控制模块,当秒计数器在使能信号有效时,每个时钟周期+1,达到59时归零,分计数器+1。当暂停按键被按下时使能信号被关闭,计数器停止。
module clock_divider (
    input clk,
    input reset_n,
    output reg one_sec_enable
);
reg [25:0] counter;
always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        counter <= 0;
        one_sec_enable <= 0;
    end else begin
        if (counter == 26'd49_999_999) begin
            counter <= 0;
            one_sec_enable <= 1;
        end else begin
            counter <= counter + 1;
            one_sec_enable <= 0;
        end
    end
end
endmodule
        2. 按键处理模块
由于按键按下时,信号会抖动,所欲我们需要在检测到按键变化后再延迟一段时间,再次进行检测以判断按键状态是否稳定。当然,这个延迟时间不要太长大概20ms。
如果稳定,则输出有效的按键信号,我们可以用计数器来实现案件后延时时间的计数。
2.1 消抖(debounce .v)
module debounce (
    input clk,
    input reset_n,
    input key_in,
    output reg key_out
);
reg [19:0] counter;
reg key_in_sync;
always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        key_in_sync <= 1;
        counter <= 0;
        key_out <= 1;
    end else begin
        key_in_sync <= key_in;
        if (key_in_sync != key_out) begin
            if (counter == 20'd999_999) begin
                key_out <= key_in_sync;
                counter <= 0;
            end else begin
                counter <= counter + 1;
            end
        end else begin
            counter <= 0;
        end
    end
end
endmodule
        2.2 边沿检测(edge_detector .v)
module edge_detector (
    input clk,
    input reset_n,
    input signal_in,
    output rising_edge
);
reg signal_delay;
always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        signal_delay <= 1'b0;
    end else begin
        signal_delay <= signal_in;
    end
end
assign rising_edge = signal_in & ~signal_delay;
endmodule
        检测按键释放的上升沿
确保每次按键只触发一次动作
3. 时间计数模块(time_counter .v)
采用四级BCD计数器级联:
module time_counter (
    input clk,
    input reset_n,
    input one_sec_enable,
    input start_pause_rise,
    output reg [3:0] min_tens,
    output reg [3:0] min_ones,
    output reg [3:0] sec_tens,
    output reg [3:0] sec_ones
);
reg pause_state;
always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        pause_state <= 1'b0;
        {sec_ones, sec_tens, min_ones, min_tens} <= 16'd0;
    end else begin
        // 暂停状态切换
        if (start_pause_rise) begin
            pause_state <= ~pause_state;
        end
        
        // 时间计数
        if (!pause_state && one_sec_enable) begin
            if (sec_ones == 9) begin
                sec_ones <= 0;
                if (sec_tens == 5) begin
                    sec_tens <= 0;
                    if (min_ones == 9) begin
                        min_ones <= 0;
                        min_tens <= (min_tens == 5) ? 0 : min_tens + 1;
                    end else begin
                        min_ones <= min_ones + 1;
                    end
                end else begin
                    sec_tens <= sec_tens + 1;
                end
            end else begin
                sec_ones <= sec_ones + 1;
            end
        end
    end
end
endmodule
        4. 显示驱动模块(seven_seg_display.v)
要将分秒计数器的值通过七段数码管显示,我们就要将计数值(二进制)转化16进制。
module seven_seg_display (
    input clk,
    input reset_n,
    input [3:0] min_tens,
    input [3:0] min_ones,
    input [3:0] sec_tens,
    input [3:0] sec_ones,
    output reg [7:0] hex3,
    output reg [7:0] hex2,
    output reg [7:0] hex1,
    output reg [7:0] hex0,
    output reg [3:0] dig
);
reg [16:0] scan_counter;
reg [1:0] scan_sel;
// 扫描计数器
always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        scan_counter <= 0;
        scan_sel <= 0;
    end else begin
        if (scan_counter == 17'd49_999) begin
            scan_counter <= 0;
            scan_sel <= scan_sel + 1;
        end else begin
            scan_counter <= scan_counter + 1;
        end
    end
end
// 位选信号
always @(*) begin
    case (scan_sel)
        2'b00: dig = 4'b1110;
        2'b01: dig = 4'b1101;
        2'b10: dig = 4'b1011;
        2'b11: dig = 4'b0111;
        default: dig = 4'b1111;
    endcase
end
// 七段译码
always @(posedge clk) begin
    case (scan_sel)
        2'b00: begin
            hex0 <= seg7(sec_ones, 1'b0);
            {hex3, hex2, hex1} <= {8'hFF, 8'hFF, 8'hFF};
        end
        2'b01: begin
            hex1 <= seg7(sec_tens, 1'b0);
            {hex3, hex2, hex0} <= {8'hFF, 8'hFF, 8'hFF};
        end
        2'b10: begin
            hex2 <= seg7(min_ones, 1'b1); // 分钟个位带小数点
            {hex3, hex1, hex0} <= {8'hFF, 8'hFF, 8'hFF};
        end
        2'b11: begin
            hex3 <= seg7(min_tens, 1'b0);
            {hex2, hex1, hex0} <= {8'hFF, 8'hFF, 8'hFF};
        end
    endcase
end
// 七段译码函数
function [7:0] seg7;
    input [3:0] bcd;
    input dp;
    begin
        case (bcd)
            4'd0: seg7 = {dp, 7'b1000000};
            4'd1: seg7 = {dp, 7'b1111001};
            4'd2: seg7 = {dp, 7'b0100100};
            4'd3: seg7 = {dp, 7'b0110000};
            4'd4: seg7 = {dp, 7'b0011001};
            4'd5: seg7 = {dp, 7'b0010010};
            4'd6: seg7 = {dp, 7'b0000010};
            4'd7: seg7 = {dp, 7'b1111000};
            4'd8: seg7 = {dp, 7'b0000000};
            4'd9: seg7 = {dp, 7'b0010000};
            default: seg7 = 8'b11111111;
        endcase
    end
endfunction
endmodule
        5.顶层模块(minute_second_counter_top.v)
module minute_second_counter_top (
    input clk,           // 50MHz时钟
    input reset_n,       // 复位信号
    input start_pause,   // 启动/暂停按键
    output [7:0] hex3,   // 分钟十位
    output [7:0] hex2,   // 分钟个位(带小数点)
    output [7:0] hex1,   // 秒十位
    output [7:0] hex0,   // 秒个位
    output [3:0] dig     // 数码管位选
);
// 内部信号定义
wire start_pause_debounced;
wire start_pause_rise;
wire one_sec_enable;
wire [3:0] min_tens, min_ones, sec_tens, sec_ones;
// 模块实例化
clock_divider u_clock_divider (
    .clk(clk),
    .reset_n(reset_n),
    .one_sec_enable(one_sec_enable)
);
debounce u_debounce (
    .clk(clk),
    .reset_n(reset_n),
    .key_in(start_pause),
    .key_out(start_pause_debounced)
);
edge_detector u_edge_detector (
    .clk(clk),
    .reset_n(reset_n),
    .signal_in(start_pause_debounced),
    .rising_edge(start_pause_rise)
);
time_counter u_time_counter (
    .clk(clk),
    .reset_n(reset_n),
    .one_sec_enable(one_sec_enable),
    .start_pause_rise(start_pause_rise),
    .min_tens(min_tens),
    .min_ones(min_ones),
    .sec_tens(sec_tens),
    .sec_ones(sec_ones)
);
seven_seg_display u_seven_seg_display (
    .clk(clk),
    .reset_n(reset_n),
    .min_tens(min_tens),
    .min_ones(min_ones),
    .sec_tens(sec_tens),
    .sec_ones(sec_ones),
    .hex3(hex3),
    .hex2(hex2),
    .hex1(hex1),
    .hex0(hex0),
    .dig(dig)
);
endmodule
        三、引脚配置
引脚配置如下图所示:


四、实验效果
[Verilog]DE2-115分秒计时器演示视频
五、总结与扩展
本设计实现了完整的秒表计时功能和按键交互。
在实现过程中出现了许多问题,建议先构思实现代码再进行层次化设计,这样会省很多麻烦。