基于DE2-115开发板的百分秒表FPGA实战开发指南
第一次接触FPGA开发时,最令人兴奋的莫过于让硬件真正"动起来"。本文将带您从零开始,在DE2-115开发板上实现一个功能完整的百分秒表,包含暂停和清零功能。不同于教科书式的理论讲解,这里将重点分享实际工程中那些容易踩坑的细节——从Quartus 13.1工程配置到数码管驱动原理,每个步骤都经过真实硬件验证。
1. 工程准备与环境搭建
1.1 Quartus 13.1工程创建要点
在开始编写Verilog代码前,正确的工程配置能避免后续许多麻烦。新建工程时需特别注意:
- 器件选择:DE2-115开发板搭载的是Cyclone IV EP4CE115F29C7
- 文件命名规范:建议采用
stopwatch_top.v作为顶层文件名,避免中文和特殊字符 - 仿真工具设置:默认使用ModelSim-Altera,需确保路径不含空格
提示:Quartus 13.1对Windows 10的兼容性需要特别注意,建议以管理员身份运行软件
1.2 开发板硬件资源确认
DE2-115开发板上的关键资源分配如下表所示:
| 资源类型 | 具体配置 | 本实验用途 |
|---|---|---|
| 时钟源 | 50MHz晶振 | 系统主时钟 |
| 数码管 | 共阳极4位 | 时间显示 |
| 按键 | KEY[0]-KEY[3] | 清零/暂停控制 |
| LED | LEDG[0]-LEDG[7] | 状态指示 |
2. 核心模块设计与实现
2.1 精准分频电路设计
DE2-115提供的50MHz时钟需要分频为100Hz驱动百分秒表。传统分频方式存在累积误差,改进方案如下:
module clock_divider ( input wire clk_50m, input wire reset_n, output reg clk_100hz ); reg [31:0] counter; localparam DIVIDER = 500000; // 50MHz/100Hz always @(posedge clk_50m or negedge reset_n) begin if (!reset_n) begin counter <= 0; clk_100hz <= 0; end else if (counter == DIVIDER/2 - 1) begin clk_100hz <= ~clk_100hz; counter <= 0; end else counter <= counter + 1; end endmodule关键改进点:
- 采用50%占空比分频,确保计时精度
- 32位计数器设计避免溢出风险
- 异步复位确保可靠初始化
2.2 可暂停的计数逻辑实现
暂停功能通过控制计数器的使能信号实现,比中断时钟更可靠:
module decimal_counter ( input wire clk, input wire reset_n, input wire pause, output reg [3:0] units, output reg [3:0] tens, output wire carry ); always @(posedge clk or negedge reset_n) begin if (!reset_n) begin units <= 0; tens <= 0; end else if (!pause) begin if (units == 9) begin units <= 0; if (tens == 9) tens <= 0; else tens <= tens + 1; end else units <= units + 1; end end assign carry = (units == 9) && (tens == 9); endmodule3. 数码管驱动与显示优化
3.1 共阳极数码管驱动原理
DE2-115采用共阳极数码管,与常见开发板有所不同。显示数字"0"的段码对应关系:
| 段位 | a | b | c | d | e | f | g |
|---|---|---|---|---|---|---|---|
| 电平 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| 引脚 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
对应的Verilog译码器实现:
module seg7_decoder ( input wire [3:0] bcd, output reg [6:0] seg ); always @(*) begin case (bcd) 4'd0: seg = 7'b1000000; 4'd1: seg = 7'b1111001; // ...其他数字编码 default: seg = 7'b1111111; endcase end endmodule3.2 显示刷新与消隐技术
为避免数码管闪烁,推荐采用扫描刷新方式:
- 设计1kHz的刷新时钟
- 依次激活每位数码管
- 同步输出对应段码
- 每位显示保持1ms
module display_scan ( input wire clk, input wire [6:0] seg0, seg1, seg2, seg3, output reg [3:0] anode, output reg [6:0] cathode ); reg [1:0] sel; always @(posedge clk) begin sel <= sel + 1; case (sel) 2'b00: begin anode <= 4'b1110; cathode <= seg0; end // 其他位选择逻辑 endcase end endmodule4. 系统集成与调试技巧
4.1 顶层模块接口设计
完整系统接口定义示例:
module stopwatch_top ( input wire CLOCK_50, input wire [1:0] KEY, output wire [6:0] HEX0, HEX1, HEX2, HEX3, output wire [7:0] LEDG ); // 内部信号声明 wire clk_100hz; wire [3:0] min_units, min_tens; wire [3:0] sec_units, sec_tens; // 模块实例化 clock_divider u_divider ( .clk_50m(CLOCK_50), .reset_n(KEY[0]), .clk_100hz(clk_100hz) ); // 其他模块连接... endmodule4.2 常见问题排查指南
实际开发中可能遇到的问题及解决方案:
数码管显示乱码
- 检查共阳极/共阴极配置
- 验证段码顺序是否与硬件匹配
- 测量各引脚电平是否正常
计时不准
- 确认分频系数计算正确
- 检查时钟约束是否添加
- 使用SignalTap观察实际时钟波形
按键抖动问题
- 添加硬件消抖电路
- 或在Verilog中实现软件消抖
- 典型消抖延时20ms为宜
5. 功能扩展与进阶优化
5.1 多模式计时功能实现
通过状态机扩展秒表功能:
typedef enum { MODE_STOP, MODE_RUN, MODE_LAP } stopwatch_mode; module mode_controller ( input wire clk, input wire reset_n, input wire mode_btn, output stopwatch_mode mode ); // 状态转换逻辑... endmodule5.2 数据存储与回放
利用DE2-115的SDRAM存储计时记录:
- 设计存储控制状态机
- 分配缓冲区地址空间
- 实现读写时序控制
- 添加数据校验机制
module record_ctrl ( input wire clk, input wire save_trig, input wire [15:0] time_data, output reg [21:0] sdram_addr, inout wire [15:0] sdram_data ); // SDRAM控制器实现... endmodule在完成基础功能后,尝试添加这些扩展功能能让项目更具挑战性。实际开发中,建议先通过仿真验证各模块功能,再逐步进行硬件测试。遇到问题时,分段调试往往比整体排查更有效率——可以先单独测试分频器输出,再验证计数器逻辑,最后整合显示部分。