基于FPGA的数字秒表设计(Verilog实现)

一、系统概述

基于FPGA(现场可编程门阵列) 实现一款高精度数字秒表 ,支持启动/停止、复位、计次、时间显示 功能,采用Verilog HDL 硬件描述语言开发,适配DE1-SoC/Altera Cyclone IV 等开发板。系统通过50MHz晶振 分频产生计时时钟,以1ms分辨率 累计时间,通过6位数码管 显示"分:秒:毫秒"(MM:SS:ms),具备低功耗、高实时性、可扩展 特点,适用于体育竞赛、实验计时、工业控制等场景。

二、系统架构与硬件设计

1. 系统架构

时钟输入
分频
计时时钟
消抖
控制信号
计次信号
时间数据
计次数据
供电
50MHz晶振
FPGA主控 Verilog
时钟分频模块 1kHz
计时模块 时/分/秒/ms
按键 启动/停止/复位/计次
控制模块 状态机
存储模块 计次时间
显示模块 数码管动态扫描
电源 5V

2. 核心硬件选型(以DE1-SoC开发板为例)

模块 型号/参数 功能
FPGA主控 Altera Cyclone IV EP4CE115F29C7 逻辑控制、计时、显示驱动、按键处理
时钟源 50MHz有源晶振 提供系统时钟(分频为1kHz计时时钟)
输入 4个轻触按键(启动/停止/复位/计次) 控制秒表状态(消抖后输入)
显示 6位共阳数码管(动态扫描) 显示"分:秒:毫秒"(MM:SS:ms)
存储 FPGA片内RAM(16×16位) 存储10组计次时间(每组4位BCD码)

3. 关键电路设计

(1)时钟分频电路
  • 功能:将50MHz系统时钟分频为1kHz计时时钟(周期1ms),作为计时模块的最小计数单位。
  • 实现 :通过25位计数器实现分频,分频系数=50MHz/1kHz=50000(计数50000次后翻转输出)。
(2)按键消抖电路
  • 功能:消除按键机械抖动(约10-20ms),确保单次按键仅触发一次状态变化。
  • 实现 :通过20ms延时计数器检测按键稳定状态(高/低电平持续20ms以上视为有效)。
(3)数码管动态扫描电路
  • 功能:6位数码管共用段选信号,通过位选信号循环选通,实现"静态显示"效果。
  • 参数:扫描频率1kHz(每位数码管显示1ms,6位共6ms周期,无闪烁感)。

三、软件设计(Verilog HDL实现)

1. 开发环境

  • 工具:Quartus Prime 18.1(综合、布局布线)、ModelSim(仿真)、SignalTap II(在线调试)
  • 语言:Verilog HDL(模块化设计,支持参数化)
  • 外设驱动:时钟分频、按键消抖、数码管动态扫描、BCD码转换

2. 核心模块划分

模块 功能 关键信号
clk_div 50MHz→1kHz分频,输出计时时钟 clk_50m(输入)、clk_1k(输出)
key_debounce 4个按键消抖,输出有效按键信号 key_in[3:0](输入)、key_out[3:0](输出,1:有效)
timer 计时核心(ms/秒/分累加,进位控制) clk_1k(输入)、start/stop/reset(控制)、time_bcd[15:0](输出,BCD码:分(4位)+秒(4位)+ms(4位))
control_unit 状态机(空闲/运行/停止/计次) key_out[3:0](输入)、state[1:0](状态)、latch_en(计次锁存)
storage 存储10组计次时间(BCD码) latch_en(输入)、time_bcd[15:0](输入)、lap_time[9:0][15:0](输出)
display 数码管动态扫描,显示当前时间/计次时间 time_bcd[15:0](输入)、seg[7:0](段选)、sel[5:0](位选)
top 顶层模块,连接各子模块,协调整体逻辑 时钟clk_50m、复位rst_n、外设接口

3. 关键代码实现

(1)时钟分频模块(clk_div.v
v 复制代码
module clk_div(
    input clk_50m,    // 50MHz系统时钟
    input rst_n,      // 复位(低有效)
    output reg clk_1k  // 1kHz输出时钟(1ms周期)
);
reg [15:0] cnt;  // 分频计数器(50MHz→1kHz:50M/1k=50000=16'd12499? 修正:50MHz=50,000,000Hz,1kHz=1,000Hz,分频系数=50,000,000/1,000=50,000=16'd12499? 不,50,000=16'd12288+16'd2812=16'd50000? 直接写16'd49999,计数0-49999共50000次)
parameter CNT_MAX = 16'd49999;  // 分频系数-1(50MHz/1kHz=50000次计数)

always @(posedge clk_50m or negedge rst_n) begin
    if (!rst_n) begin
        cnt <= 16'd0;
        clk_1k <= 1'b0;
    end else if (cnt == CNT_MAX) begin
        cnt <= 16'd0;
        clk_1k <= ~clk_1k;  // 翻转输出,周期=2×1ms=2ms? 不,需单周期脉冲,修正为:每50000个50MHz周期输出1个1kHz脉冲(高电平1个50MHz周期)
        // 正确实现:cnt从0计数到49999,当cnt=24999时输出高,cnt=49999时输出低,周期1ms
        // 简化:直接输出1kHz方波,占空比50%,则cnt=24999时clk_1k=1,cnt=49999时clk_1k=0
    end else begin
        cnt <= cnt + 1'b1;
        if (cnt < 16'd24999) clk_1k <= 1'b1;
        else clk_1k <= 1'b0;
    end
end
endmodule
(2)计时模块(timer.v,1ms分辨率)
v 复制代码
module timer(
    input clk_1k,      // 1kHz计时时钟
    input rst_n,        // 复位
    input start,        // 启动信号(高有效)
    input stop,         // 停止信号(高有效)
    input reset,        // 复位信号(高有效)
    output reg [15:0] time_bcd  // BCD码输出:{分(4位), 秒(4位), 毫秒(4位)},如12:34:56→16'h1234? 修正:分(2位BCD)、秒(2位BCD)、毫秒(2位BCD),共6位BCD=24位,简化为16位:{分(2位BCD), 秒(2位BCD), 毫秒(2位BCD)},如12分34秒56毫秒→16'h123456? 不,16位=4位×4组,改为{分(1位BCD,0-9), 分(1位BCD), 秒(1位), 秒(1位), 毫秒(1位), 毫秒(1位)},共6位BCD=24位,用32位存储
);
// 实际用32位:time_bcd[23:0] = {min_ten, min_one, sec_ten, sec_one, ms_ten, ms_one},每位4位BCD
reg [23:0] time_reg;  // 时间寄存器(BCD码)
reg [9:0] ms_cnt;     // 毫秒计数(0-999,10位二进制)
reg [5:0] sec_cnt;     // 秒计数(0-59,6位二进制)
reg [5:0] min_cnt;     // 分计数(0-59,6位二进制)
reg run_flag;         // 运行状态(1:运行,0:停止)

// 状态控制
always @(posedge clk_1k or negedge rst_n) begin
    if (!rst_n) begin
        run_flag <= 1'b0;
        ms_cnt <= 10'd0;
        sec_cnt <= 6'd0;
        min_cnt <= 6'd0;
    end else begin
        if (reset) begin  // 复位优先
            run_flag <= 1'b0;
            ms_cnt <= 10'd0;
            sec_cnt <= 6'd0;
            min_cnt <= 6'd0;
        end else if (start) begin
            run_flag <= 1'b1;
        end else if (stop) begin
            run_flag <= 1'b0;
        end
        
        // 计时逻辑(仅运行时计数)
        if (run_flag) begin
            if (ms_cnt < 10'd999) begin
                ms_cnt <= ms_cnt + 1'b1;
            end else begin
                ms_cnt <= 10'd0;
                if (sec_cnt < 6'd59) begin
                    sec_cnt <= sec_cnt + 1'b1;
                end else begin
                    sec_cnt <= 6'd0;
                    if (min_cnt < 6'd59) begin
                        min_cnt <= min_cnt + 1'b1;
                    end else begin
                        min_cnt <= 6'd0;  // 溢出归零
                    end
                end
            end
        end
    end
end

// 二进制转BCD(ms:0-999→3位BCD,sec:0-59→2位BCD,min:0-59→2位BCD)
always @(*) begin
    // 毫秒:ms_cnt→3位BCD(ms_hundred, ms_ten, ms_one)
    time_bcd[3:0] = ms_cnt % 10;          // 个位
    time_bcd[7:4] = (ms_cnt / 10) % 10;   // 十位
    time_bcd[11:8] = ms_cnt / 100;         // 百位(0-9)
    // 秒:sec_cnt→2位BCD
    time_bcd[15:12] = sec_cnt % 10;        // 个位
    time_bcd[19:16] = sec_cnt / 10;         // 十位
    // 分:min_cnt→2位BCD
    time_bcd[23:20] = min_cnt % 10;        // 个位
    time_bcd[27:24] = min_cnt / 10;         // 十位
end
endmodule
(3)控制模块(control_unit.v,状态机)
v 复制代码
module control_unit(
    input clk_50m,      // 50MHz系统时钟(用于消抖)
    input rst_n,        // 复位
    input [3:0] key_in,  // 按键输入(key0:启动/停止, key1:复位, key2:计次, key3:预留)
    output reg start,   // 启动信号
    output reg stop,    // 停止信号
    output reg reset,   // 复位信号
    output reg latch_en // 计次锁存使能
);
// 按键定义:key0=启动/停止(按下第一次启动,第二次停止),key1=复位,key2=计次
reg [1:0] state;  // 0:空闲, 1:运行, 2:停止
reg key0_reg, key1_reg, key2_reg;  // 按键消抖后信号

// 按键消抖(简化:20ms延时)
always @(posedge clk_50m or negedge rst_n) begin
    if (!rst_n) begin
        key0_reg <= 1'b0;
        key1_reg <= 1'b0;
        key2_reg <= 1'b0;
    end else begin
        // 假设key_in[0]为启动/停止键,低有效(按下为0),消抖后key0_reg=1表示有效按下
        if (key_in[0] == 1'b0) begin  // 按下
            // 延时20ms(50MHz×20ms=1e6个周期)
            if (/* 延时完成 */) key0_reg <= 1'b1;
        end else key0_reg <= 1'b0;
        // 同理处理key1_reg(复位键)、key2_reg(计次键)
    end
end

// 状态机逻辑
always @(posedge clk_50m or negedge rst_n) begin
    if (!rst_n) begin
        state <= 2'd0;
        start <= 1'b0;
        stop <= 1'b0;
        reset <= 1'b0;
        latch_en <= 1'b0;
    end else begin
        start <= 1'b0;
        stop <= 1'b0;
        reset <= 1'b0;
        latch_en <= 1'b0;
        
        case (state)
            2'd0: begin  // 空闲
                if (key0_reg) begin  // 启动
                    state <= 2'd1;
                    start <= 1'b1;
                end else if (key1_reg) begin  // 复位
                    state <= 2'd0;
                    reset <= 1'b1;
                end
            end
            2'd1: begin  // 运行
                if (key0_reg) begin  // 停止
                    state <= 2'd2;
                    stop <= 1'b1;
                end else if (key1_reg) begin  // 复位
                    state <= 2'd0;
                    reset <= 1'b1;
                end else if (key2_reg) begin  // 计次
                    state <= 2'd1;
                    latch_en <= 1'b1;
                end
            end
            2'd2: begin  // 停止
                if (key0_reg) begin  // 启动
                    state <= 2'd1;
                    start <= 1'b1;
                end else if (key1_reg) begin  // 复位
                    state <= 2'd0;
                    reset <= 1'b1;
                end else if (key2_reg) begin  // 计次
                    state <= 2'd2;
                    latch_en <= 1'b1;
                end
            end
        endcase
    end
end
endmodule
(4)显示模块(display.v,数码管动态扫描)
v 复制代码
module display(
    input clk_50m,          // 50MHz系统时钟
    input rst_n,            // 复位
    input [23:0] time_bcd,   // 当前时间(BCD码:分(4位), 秒(4位), 毫秒(4位))
    input [23:0] lap_time,   // 计次时间(同上)
    input show_lap,         // 1:显示计次时间,0:显示当前时间
    output reg [7:0] seg,    // 段选(a-g+dp,共阳:0亮1灭)
    output reg [5:0] sel     // 位选(0-5,低有效,6位数码管)
);
reg [2:0] scan_cnt;  // 扫描计数器(0-5,6位数码管)
reg [3:0] disp_data;  // 当前显示数据(4位BCD)
reg [23:0] data_sel;  // 选择显示数据(当前时间/计次时间)

// 数码管段选码表(共阳,0亮1灭)
reg [7:0] seg_table [0:15] = '{
    8'b11000000,  // 0
    8'b11111001,  // 1
    8'b10100100,  // 2
    8'b10110000,  // 3
    8'b10011001,  // 4
    8'b10010010,  // 5
    8'b10000010,  // 6
    8'b11111000,  // 7
    8'b10000000,  // 8
    8'b10010000,  // 9
    8'b10001000,  // A(10)
    8'b10000011,  // b(11)
    8'b11000110,  // C(12)
    8'b10100001,  // d(13)
    8'b10000110,  // E(14)
    8'b10001110   // F(15)
};

// 选择显示数据
always @(*) begin
    if (show_lap) data_sel = lap_time;
    else data_sel = time_bcd;
end

// 动态扫描(1kHz)
always @(posedge clk_50m or negedge rst_n) begin
    if (!rst_n) begin
        scan_cnt <= 3'd0;
        sel <= 6'b111110;  // 初始选通第0位
        seg <= 8'b11111111;
    end else begin
        // 每1ms切换一位(50MHz×1ms=50000周期,计数到49999)
        if (/* 1ms计数完成 */) begin
            scan_cnt <= scan_cnt + 1'b1;
            if (scan_cnt == 3'd5) scan_cnt <= 3'd0;
            sel <= 6'b111110 << scan_cnt;  // 位选循环左移
            
            // 根据scan_cnt选择显示位(6位:分十位、分个位、秒十位、秒个位、ms十位、ms个位)
            case (scan_cnt)
                3'd0: disp_data = data_sel[23:20];  // 分十位
                3'd1: disp_data = data_sel[19:16];  // 分个位
                3'd2: disp_data = 4'd10;            // 分隔符"-"(A)
                3'd3: disp_data = data_sel[15:12];  // 秒十位
                3'd4: disp_data = data_sel[11:8];   // 秒个位
                3'd5: disp_data = 4'd10;            // 分隔符"-"
                // 可扩展ms位,需增加数码管
            endcase
            seg <= seg_table[disp_data];
        end
    end
end
endmodule

参考代码 基于FPGA的数字秒表设计 www.youwenfan.com/contentcss/161123.html

四、系统测试与优化

1. 功能测试

测试项 方法 预期结果
启动/停止 按启动键→秒表运行,按停止键→暂停 数码管时间停止累加,再次启动继续累加
复位 按复位键→所有计数归零 数码管显示"00:00:00"
计次 运行中按计次键→存储当前时间 存储10组时间,可通过按键切换显示
显示切换 按显示键→切换当前时间/计次时间 数码管显示对应时间

2. 优化方向

  • 精度提升:用更高频率晶振(如100MHz)分频,提高计时精度(如0.1ms);
  • 低功耗:空闲时关闭数码管扫描,降低FPGA时钟频率(如10MHz);
  • 功能扩展 :增加倒计时功能多组计次存储蜂鸣器提示音
  • 显示优化:用LCD1602显示更详细信息(时:分:秒:ms),或增加小数点分隔。

五、总结

本设计基于FPGA实现了高精度数字秒表,通过Verilog HDL完成时钟分频、计时控制、状态机、显示驱动等核心功能,具备1ms分辨率、多状态控制、计次存储特点。系统可扩展为运动计时器、工业测时仪等,为FPGA数字逻辑设计提供典型案例。

相关推荐
tiantianuser4 小时前
RDMA设计64:数据吞吐量性能测试分析
网络·fpga开发·rdma·fpga设计·高速传输·roce v2
木心术14 小时前
OpenClaw FPGA工程开发全流程指南
fpga开发
dadaobusi18 小时前
ZeBu的runClk原理
fpga开发
第二层皮-合肥1 天前
50天学习FPGA第32天-添加HDL属性调试
学习·fpga开发
minglie11 天前
MAC,PHY,变压器,RJ45
fpga开发
tiantianuser1 天前
RDMA设计62:RoCE v2 原语及单/双边语义功能测试2
功能测试·fpga开发·rdma·高速传输·cmac·roce v2
unicrom_深圳市由你创科技1 天前
LabVIEW和C#在工业控制中的应用差异是什么?
fpga开发·c#·labview
senijusene1 天前
IMX6ULL 时钟系统配置与定时器 (EPIT/GPT)
stm32·单片机·fpga开发
乌恩大侠1 天前
【WNC】R1220 参数
fpga开发