从50MHz到LED闪烁:我的第一个FPGA项目之Quartus II数控分频器实战记录
从50MHz到LED闪烁:我的第一个FPGA项目之Quartus II数控分频器实战记录
第一次接触FPGA开发板时,那块印着Altera标志的小板子安静地躺在防静电袋里,就像个等待被唤醒的电子精灵。作为刚入门的新手,我决定从最基础的LED闪烁实验开始,没想到这个看似简单的项目背后,竟藏着从50MHz高频时钟到肉眼可见的LED闪烁这样奇妙的信号转换之旅。本文将完整记录如何通过Quartus II软件实现数控分频器的全过程,包括PLL配置、Verilog编码、管脚分配等关键步骤,以及那些只有实操过才会知道的"坑点"。
1. 硬件准备与环境搭建
我的开发板搭载的是Cyclone IV系列FPGA芯片,板载一颗50MHz的有源晶振。在开始编码前,需要先准备好以下硬件和软件环境:
硬件清单:
- FPGA开发板(带50MHz晶振和用户LED)
- USB-Blaster下载器
- 杜邦线若干(用于拨码开关连接)
软件安装:
# Quartus II Prime Lite Edition下载命令示例(Linux) wget https://download.altera.com/akdlm/software/acdsinst/20.1std/720/ib_tar/Quartus-lite-20.1.0.720-linux.tar tar -xvf Quartus-lite-20.1.0.720-linux.tar ./setup.sh
注意:安装时务必选择与开发板匹配的器件系列支持包。我第一次安装时漏选了Cyclone IV支持,导致后续无法识别器件。
安装完成后,新建工程时需要特别注意三个关键设置:
- 工程目录不要包含中文或空格
- 器件型号必须精确匹配(我的板子是EP4CE6E22C8)
- 仿真工具选择None(初学者可暂不涉及仿真)
2. 时钟树构建:从50MHz到2Hz
FPGA设计中最关键的就是时钟信号的处理。板载的50MHz时钟对于LED闪烁来说太高频了,需要经过多级分频:
2.1 PLL锁相环配置
Quartus内置的PLL IP核可以将输入时钟倍频或分频。通过Tools → IP Catalog打开IP核目录,搜索"ALTPLL":
// PLL参数配置示例 module pll_clk( input inclk0, // 50MHz输入 output c0 // 10MHz输出 ); altpll pll_inst( .inclk0(inclk0), .c0(c0), .areset(1'b0), .locked() ); defparam pll_inst.clk0_divide_by = 5; endmodule配置参数时容易踩的坑:
- 输入频率必须准确填写50MHz
- 输出时钟使能选项要勾选
- 锁定信号(locked)最好引出观察
2.2 Verilog分频器设计
将PLL输出的10MHz时钟分频到2Hz,需要2500000分频系数。这里采用计数器实现:
module div_2hz( input clk_10m, // 10MHz输入 output reg clk_2hz // 2Hz输出 ); reg [23:0] counter; // 24位计数器 always @(posedge clk_10m) begin if(counter == 24'd2_499_999) begin clk_2hz <= ~clk_2hz; counter <= 0; end else begin counter <= counter + 1; end end endmodule调试心得:初次编写时计数器位宽不足,导致分频不准确。建议分频系数超过16位时使用32位寄存器。
3. 数控分频核心实现
有了2Hz的基础时钟,接下来实现通过拨码开关控制的分频器。设计思路是用4位开关输入决定分频比:
| 开关状态 | 分频比 | 输出频率 |
|---|---|---|
| 0001 | 2 | 1Hz |
| 0010 | 4 | 0.5Hz |
| 0100 | 8 | 0.25Hz |
| 1000 | 16 | 0.125Hz |
核心代码如下:
module programmable_divider( input clk_2hz, input [3:0] sw, // 拨码开关输入 output reg led_out // LED输出 ); reg [3:0] counter; always @(posedge clk_2hz) begin if(counter >= sw-1) begin led_out <= ~led_out; counter <= 0; end else begin counter <= counter + 1; end end endmodule实际测试时发现两个问题:
- 开关全零时会导致死锁 → 添加默认值处理
- 切换开关时输出抖动 → 增加时钟同步寄存器
修正后的关键部分:
always @(posedge clk_2hz) begin reg [3:0] sw_sync; sw_sync <= sw; // 时钟同步 if(sw_sync == 0) sw_sync <= 4'd1; // 默认分频比2 if(counter >= sw_sync-1) begin led_out <= ~led_out; counter <= 0; end else begin counter <= counter + 1; end end4. 工程集成与调试
4.1 顶层模块连接
将各模块在Block Diagram中连接时,需要注意信号命名一致性:
+--------+ +---------+ +------------------+ | 50MHz |---->| PLL |---->| div_2hz | | 晶振 | | (10MHz) | | (2Hz基础时钟) | +--------+ +---------+ +------------------+ | v +---------------------+ | programmable_divider| | (数控分频核心) | +---------------------+ | +------------------+------------------+ | | | v v v +------------+ +------------+ +------------+ | 拨码开关 | | LED指示灯 | | 未用管脚 | | (SW[3:0]) | | (USER_LED) | | (三态设置) | +------------+ +------------+ +------------+4.2 管脚分配技巧
通过Assignment Editor分配管脚时,新手常犯的错误:
- 混淆了原理图符号和实际管脚编号
- 未设置未用管脚为三态输入
- 漏掉电压等级配置
正确的管脚分配表示例:
| 信号名称 | 管脚编号 | 备注 |
|---|---|---|
| CLK_50M | PIN_E1 | 50MHz时钟输入 |
| SW[0] | PIN_M1 | 拨码开关位0 |
| SW[1] | PIN_T8 | 拨码开关位1 |
| USER_LED | PIN_A15 | 用户LED |
在Assignments → Device → Device and Pin Options中:
- 选择Unused Pins为"As input tri-stated"
- 核对I/O Bank电压与实际电路一致
4.3 下载与调试
生成sof文件后,通过USB-Blaster下载时如果遇到"No Hardware"错误,可按以下步骤排查:
- 检查JTAG连接器方向是否正确
- 运行
jtagconfig命令查看设备识别情况 - 在Quartus中手动选择USB-Blaster驱动
第一次成功看到LED按预期频率闪烁时,那种成就感至今难忘。当拨动开关改变分频比,LED的闪烁节奏随之变化的瞬间,所有调试时的挫败感都化为了继续探索的动力。
