当前位置: 首页 > news >正文

从计数器到状态机:用Verilog设计一个简易数字秒表(基于FPGA开发板)

从计数器到状态机:用Verilog设计一个简易数字秒表(基于FPGA开发板)

在FPGA开发的世界里,计数器是最基础也最核心的模块之一。但单纯让LED灯按照二进制规律闪烁,远不能满足工程师的成就感。本文将带你从零开始,将一个简单的4位计数器扩展为一个功能完整的数字秒表,涵盖从时钟分频、按键消抖到状态机设计的全流程。

1. 项目架构设计

一个完整的数字秒表系统远比单纯的计数器复杂。我们需要考虑以下几个核心模块:

  • 时钟分频模块:将FPGA板载的50MHz高频时钟转换为1Hz的秒脉冲信号
  • 按键消抖模块:处理机械按键的抖动问题,确保每次按键只触发一次动作
  • 计数器核心:基于原始4位计数器扩展为60进制(秒)和60进制(分)的计时单元
  • 显示驱动:将二进制计数值转换为七段数码管能显示的BCD码
  • 状态控制:用有限状态机管理秒表的启动/暂停、清零和计圈功能

1.1 系统框图

整个系统的信号流如下:

50MHz时钟 → 分频模块 → 1Hz时钟 → 状态机 → 计数器组 → BCD转换 → 数码管显示 ↑ ↑ 按键信号 → 消抖模块

2. 关键模块实现

2.1 增强型计数器设计

原始的4位计数器需要扩展为适合秒表应用的60进制计数器。以下是改进后的Verilog代码片段:

module counter_60( input clk, // 1Hz时钟 input reset_n, // 异步清零 input en, // 同步使能 output [5:0] value // 0-59的计数值 ); reg [5:0] count; always @(posedge clk or negedge reset_n) begin if (!reset_n) count <= 6'd0; else if (en) begin if (count == 6'd59) count <= 6'd0; else count <= count + 1; end end assign value = count; endmodule

这个计数器模块具有三个关键特性:

  1. 异步低电平复位(reset_n)
  2. 同步使能控制(en)
  3. 自动归零的60进制计数

2.2 按键消抖处理

机械按键的抖动会导致多次误触发。典型的消抖电路需要约20ms的延时判断:

module debounce( input clk, // 50MHz时钟 input button_in, // 原始按键信号 output reg button_out // 消抖后信号 ); reg [19:0] counter; reg button_sync; always @(posedge clk) begin button_sync <= button_in; if (button_sync ^ button_out) begin counter <= counter + 1; if (&counter) button_out <= button_sync; end else counter <= 0; end endmodule

提示:实际项目中,消抖时间应根据具体按键特性调整,通常在10-50ms之间

2.3 状态机设计

秒表的操作逻辑最适合用有限状态机(FSM)实现。我们定义三个状态:

状态描述转换条件
IDLE初始状态START → RUN
RUN正在计时PAUSE → PAUSED, LAP → LAP_RECORD
PAUSED暂停状态START → RUN, RESET → IDLE

对应的Verilog实现:

module fsm_controller( input clk, input reset_n, input start_pause, // 消抖后的开始/暂停键 input lap_reset, // 消抖后的计圈/复位键 output reg running, // 计数器使能信号 output reg clear // 计数器清零信号 ); typedef enum {IDLE, RUN, PAUSED} state_t; state_t current_state; always @(posedge clk or negedge reset_n) begin if (!reset_n) begin current_state <= IDLE; running <= 0; clear <= 0; end else begin clear <= 0; // 清零信号只保持一个周期 case (current_state) IDLE: if (start_pause) begin current_state <= RUN; running <= 1; end RUN: if (start_pause) begin current_state <= PAUSED; running <= 0; end PAUSED: begin if (start_pause) begin current_state <= RUN; running <= 1; end else if (lap_reset) begin current_state <= IDLE; clear <= 1; end end endcase end end endmodule

3. 系统集成与优化

3.1 多计数器级联

完整的秒表需要秒和分两个计数器级联工作:

[秒计数器] → 每60秒触发 → [分计数器]

实现代码片段:

wire sec_en = running; // 秒计数器使能 wire min_en = (sec_value == 59) && sec_en; // 分计数器使能 counter_60 sec_counter( .clk(clk_1hz), .reset_n(!clear), .en(sec_en), .value(sec_value) ); counter_60 min_counter( .clk(clk_1hz), .reset_n(!clear), .en(min_en), .value(min_value) );

3.2 显示驱动设计

将二进制数值转换为七段数码管显示需要两个步骤:

  1. 二进制转BCD码(Binary-Coded Decimal)
  2. BCD码到七段码解码

以下是二进制转BCD的算法实现:

module bin2bcd( input [5:0] bin, // 0-59的二进制输入 output [3:0] tens, // 十位数BCD output [3:0] ones // 个位数BCD ); assign tens = bin / 10; assign ones = bin % 10; endmodule

3.3 资源优化技巧

在FPGA实现时,可以考虑以下优化:

  • 时钟使能:替代多时钟域设计,降低时序问题风险
  • 流水线设计:对显示解码等组合逻辑进行分段寄存
  • 参数化设计:使用parameter定义计数器位宽等可配置项

例如,参数化的计数器模块:

module generic_counter #( parameter WIDTH = 6, parameter MAX = 59 )( input clk, input reset_n, input en, output [WIDTH-1:0] value ); reg [WIDTH-1:0] count; always @(posedge clk or negedge reset_n) begin if (!reset_n) count <= 0; else if (en) count <= (count == MAX) ? 0 : count + 1; end assign value = count; endmodule

4. 调试与验证

4.1 仿真测试要点

完善的测试应该覆盖以下场景:

  1. 正常计时流程(启动→暂停→继续→复位)
  2. 边界条件测试(59秒→00秒时分钟进位)
  3. 异常操作测试(快速连续按键)

仿真测试代码框架:

initial begin // 初始化 reset_n = 0; start_pause = 0; lap_reset = 0; #100 reset_n = 1; // 测试场景1:正常启动和暂停 #20 start_pause = 1; #20 start_pause = 0; #500 start_pause = 1; #20 start_pause = 0; // 测试场景2:计圈功能 #300 lap_reset = 1; #20 lap_reset = 0; // 测试场景3:复位 #200 lap_reset = 1; #20 lap_reset = 0; end

4.2 板上调试技巧

实际硬件调试时,这些方法很实用:

  • LED辅助调试:用LED指示状态机当前状态
  • 虚拟IO:通过串口输出内部计数器值
  • 时钟降频:调试时使用更低频率(如0.5Hz)方便观察

例如,添加状态指示LED:

assign led_state = (current_state == IDLE) ? 3'b001 : (current_state == RUN) ? 3'b010 : 3'b100;

5. 功能扩展思路

基础秒表实现后,可以考虑以下增强功能:

  • 计圈存储:用寄存器堆存储多个计圈时间
  • 无线同步:通过蓝牙或Wi-Fi模块同步时间到手机
  • RTC集成:加入实时时钟芯片提供基准时间
  • 模拟表盘:用VGA或LCD显示传统指针式表盘

例如,计圈功能的简单实现:

reg [11:0] lap_times [0:3]; // 存储4组计圈时间 reg [1:0] lap_index; // 当前计圈索引 always @(posedge clk) begin if (lap_pressed && running) begin lap_times[lap_index] <= {min_value, sec_value}; lap_index <= lap_index + 1; end end

在实际项目中,从简单计数器到完整秒表的演进过程,最能体现数字系统设计的思维方式。每个模块的划分、接口的定义、状态的控制都需要综合考虑功能需求、资源占用和时序约束。当看到自己设计的秒表在开发板上精确运行时,那种成就感正是FPGA开发的魅力所在。

http://www.jsqmd.com/news/750822/

相关文章:

  • 如何用WorkshopDL免费下载Steam创意工坊模组:跨平台玩家的终极解决方案
  • 从零开始:如何用Harepacker-resurrected打造你的专属《冒险岛》世界
  • 2025最权威的十大AI写作网站横评
  • TwitchNoSub浏览器扩展:5分钟免费解锁Twitch订阅限制的完整指南
  • 厦门大学考研辅导班推荐:排名深度评测与选哪家分析 - michalwang
  • 使用curl命令快速测试Taotoken大模型API的接入与响应
  • 别再只用gzip了!手把手教你为Vite+Vue项目配置Brotli压缩,打包体积再瘦身
  • 3步解锁Windows 11安装:终极TPM绕过与硬件限制解决方案指南
  • 如何让你的老旧电视焕发新生?MyTV-Android电视直播应用完整指南
  • 如何用OpenDroneMap快速构建专业级3D模型和数字地图?5步完整教程
  • 如何快速上手Firmware Extractor:Android固件提取的完整入门指南
  • OmenSuperHub:惠普OMEN游戏本性能释放神器,轻松解除功耗限制
  • 英雄联盟本地自动化工具League Akari:重新定义你的游戏体验
  • 科研党必备:LaTeX-OCR模型下载慢?国内镜像加速与手动配置保姆级指南
  • 2026年AI降重哪家强?这3款工具必收藏! - 降AI实验室
  • 企业内如何通过Taotoken的审计日志功能追踪大模型API使用情况
  • WinUtil:一款免费的Windows工具箱,帮你轻松完成系统优化和软件批量安装
  • OPV:基于结果的思维链验证工具解析
  • 终极宽屏解决方案:如何让《植物大战僵尸》完美适配现代显示器
  • OpenClaw实战:AI代理自动化系统的生产级架构与技能工厂设计
  • Transformer残差连接与深度聚合技术解析
  • FPGA数字信号处理入门:用查找表实现DDS(直接数字频率合成)的核心——sin/cos波形生成
  • 从游戏到编程思维:通过ICode‘绿色飞板’训练场,轻松理解Python中的事件驱动与状态检测
  • 终极指南:如何让Windows电脑变身苹果AirPlay接收器
  • SteamAutoCrack终极指南:三步实现游戏离线自由运行,彻底告别DRM限制
  • owl4ce/dotfiles高级技巧:自定义图标与字体配置终极指南
  • 汽车ECU刷写后必做一步:用UDS 11服务(ECUReset)重启的完整流程与避坑指南
  • 新手避坑指南:用BU64843芯片玩转1553B总线,从看懂时序图到实战配置
  • TLE数据格式详解:Space-Track示例里的每个数字到底代表什么?
  • 如何在3分钟内为你的Obsidian笔记添加完整Excel功能:新手终极指南