一、系统概述
基于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数字逻辑设计提供典型案例。