☰
FPGA数字频率计设计:三档闸门、六位数码管与时序避坑
2026/11/3 21:27:16 网站建设 项目流程

简介:面向电子信息、电子科学与技术等专业课程设计与FPGA入门学习者的数字频率计设计文档,围绕10Hz~10MHz频率测量、六位数码管以kHz显示、1S/0.1S/0.01S三档闸门选择、复位及闸门/溢出指示等要求展开。资源包仅含1个doc文档,约412KB,正文按分频器、门控电路、计数器、锁存与译码显示、顶层电路等模块组织,并配有Verilog代码、仿真过程与实验板实现说明,对每一模块的设计目标、端口关系及仿真波形均有交代。文档还说明位数、测试频率范围、计数器溢出标志、闸门标志与记忆显示方式等技术指标,给出ISE软件下的仿真实现流程、ELB实验板与SPARTAN-3A平台的使用背景,以及误差分析和实验总结,便于读者对照理解测频原理、模块划分与调试思路。已有707人学习,适合需要完成同类课设、复盘Verilog模块设计或查漏补缺的读者参考,也可作为课程设计撰写与实验复现的参考。

1. 六位数码管一直跳:数字频率计这个题目到底难在哪

数字频率计这个题目,每年都有人卡在同一个地方:Verilog 代码仿真波形一条不差,下载到板子上六只数码管却一直在跳,或者干脆全灭。基于 FPGA 的数字频率计,难点从来不是写几个 always 块,而是把 10 Hz~10 MHz 这么宽的量程、1 s/0.1 s/0.01 s 三档闸门、六位数码管以 kHz 为单位显示、外加闸门和溢出两盏指示灯,串成一条时序自洽的链路。分频器给时间基准,门控电路决定“量多久”,计数器决定“量到多少”,锁存译码决定“怎么给人看”,四段任何一处节拍对不上,前面仿真再漂亮也没用。这套基于 Verilog 的数字频率计方案,从需求拆指标开始,逐个模块给代码和仿真要点,最后落到一组可以照着验收的测试数据,正在做频率计毕设或课程设计、需要一份能跑通也能写进报告的人,可以直接照着这套结构走。

2. 需求拆解与技术选型:10 Hz~10 MHz 怎么变成三档闸门和六位显示

任务书里“频率测量范围 10 Hz~10 MHz,用 6 只数码管以 kHz 为单位显示”这句话看着轻描淡写,实际同时约束了三件事:计数器要几位、闸门要几档、小数点放在哪一位。这三个参数互相牵制,先算清楚再动手,比写完代码再回头改要省事得多。我一般会先拿测频公式 f = N/T 把三档闸门各自的能力列成一张表,后面写代码时每个比较边界都能在表里找到出处。

2.1 量程、分辨率与闸门时间:三个指标其实是一道三角题

测频的本质是数脉冲:在已知长度 T 的闸门时间里,数到 N 个被测脉冲,频率就是 N/T。T 取 1 s 时 N 直接就是赫兹数;T 取 0.1 s 时 N 是被测频率的十分之一;T 取 0.01 s 时 N 是百分之一。六位十进制最大只能表示 999999,所以每档闸门能覆盖的上限就被卡死了:1 s 档最多到 999.999 kHz,再高就溢出;0.1 s 档理论能到 9.99999 MHz,测 10 MHz 时刚好差一个字溢出;只有 0.01 s 档,10 MHz 对应 N = 100000,六位数码管才装得下。

闸门时间计数值 N 与频率 f 的关系分辨率六位不溢出的上限小数点位置(从右数)
1 sN = f1 Hz999.999 kHz第 4 位后,显示 XXX.XXX
0.1 sN = f / 1010 Hz9 999.99 kHz第 3 位后,显示 XXXX.XX
0.01 sN = f / 100100 Hz99 999.9 kHz第 2 位后,显示 XXXXX.X

这张表最直接的用处,是它解释了显示模块里那段看起来莫名其妙的小数点逻辑:档位字 ff 为 01 时,位选扫到第 3 位点亮小数点;ff 为 10 时扫到第 2 位;ff 为 11 时扫到第 1 位。位选计数从 0 开始、0 对应最低位,所以“扫到第 3 位点亮”翻译成人话就是“最低三位是小数”,显示结果正好是 XXX.XXX kHz。单位换算不是靠除法算出来的,是靠小数点移位实现的,这省掉了一个除法器,代价是三档闸门的小数点位置必须和闸门时间严格对应,写错一位,读数就差十倍。

还有一个容易被跳过的取舍:10 Hz 这个下限,用 1 s 闸门只有 10 个计数,±1 个字的量化误差就是 ±10%,这已经是这套方案精度最差的工作点。想改善只有两条路——把闸门拉长到 10 s,或者对低频信号改成测周期再取倒数。题目给的三档闸门是固定的,所以这里只能接受,但报告里要主动写出来,别等老师问。

2.2 为什么是 FPGA 加 Verilog,而不是一堆 74 系列计数器

六位 BCD 计数、六位锁存、六位译码、六选一扫位、三档闸门切换,用分立芯片搭下来要十几片,改一档闸门时间就得重新插线,调试时哪根线虚焊了都够查半天。放进一片 FPGA 里,同样的逻辑改一个参数就行,而且每一级都能单独拉出来仿真。板上用的是一款 Spartan-3A 系列、型号 XC3S200A 的器件,资源对这种规模的设计绰绰有余,真正的约束在时钟和引脚,不在逻辑单元。

选 Verilog 而不是原理图输入,理由也很实际:分频、计数、状态机这类结构用代码描述只有十几行,用原理图连要画一屏;而且 Verilog 是工艺无关的,同一份代码换一颗器件、换一家厂商的 EDA 工具,综合约束改一改就能过。ISE 那套流程(设计输入 → 行为仿真 → 综合 → 约束引脚 → 布局布线 → 下载)走一遍,基本就是数字系统设计的完整闭环,课设要训练的就是这个流程本身。

2.3 模块划分:五个模块和它们的信号边界

顶层只干一件事——连线。真正干活的是下面四个模块,划分依据是“时钟域”和“职责”两条:分频器只碰 48 MHz 主时钟;门控电路在时标沿上跑;计数器跟着被测信号走;显示模块自己有 1 kHz 扫描时钟。跨模块只传数据和脉冲,不传中间状态。

模块职责关键输入关键输出
clk_div把 48 MHz 分成 1 Hz、10 Hz、100 Hz、1 kHz 四路时标clk、rst_nclk_1hz、clk_10hz、clk_100hz、clk_1khz
gate_ctrl三档按键选择、产生开闸使能、锁存脉冲、清零脉冲三路按键、三路时标gate_out1、clk_latch、clk_clear、ff[2:1]
counter六位 BCD 计数、溢出判定clk_in(被测信号)、gate_out1、clk_clearcnt1~cnt6、yichu
display锁存、位选扫描、七段译码、小数点cnt1~cnt6、clk_latch、ff、clk_1khzse[3:1]、segs[7:1]、dp、led

信号命名上有两个坑值得提前说:gate_out1 在原文里又当“开闸标志”又当“计数使能”,实际只有“高电平期间允许计数”这一个含义,别把它理解成闸门本身;clk_latch 和 clk_clear 都是单拍脉冲,宽度只有一个时标周期,接到计数器这种异步端口上必须当成使能来处理,不能当时钟。这两点在后面避坑那一章还会展开。

3. 时基与门控:48 MHz 分出四路时标,再把闸门切成开闸、锁存、清零三段

整个系统的时间基准只有一处——板载晶振。所有“1 秒”“0.1 秒”都是从它分出来的,这一级出问题,后面所有读数会等比例地错,而且错得很整齐,反而不容易发现。门控电路则是把连续的时间切成一段一段的测量窗口,窗口长度决定量程档位,窗口之间的空隙用来锁存和清零,节拍必须留够,抢一拍就闪屏。

3.1 分频器:系数为什么是 24 000 000 而不是 48 000 000

分频用的是最朴素的翻转法:计数器数到 N 就把输出取反一次。取反一次只是半个周期,所以输出方波的周期等于 2N 个输入时钟周期。板载时钟按 48 MHz 算,要得到周期 1 s 的 1 Hz 方波,N 就是 48 000 000 / 2 = 24 000 000。这个“除以二”是新手最容易漏掉的一步,漏了以后所有档位的时间都差一倍,测出来的频率正好差两倍,看上去像是“算法错了”,其实是分频系数错了。

module clk_div ( input wire clk, // 板载 48 MHz 晶振 input wire rst_n, // 低电平有效复位 output reg clk_1hz, output reg clk_10hz, output reg clk_100hz, output reg clk_1khz ); // 24_000_000 需要 25 位(2^25 = 33 554 432),留一位余量 reg [24:0] cnt1, cnt2, cnt3, cnt4; always @(posedge clk) begin if (!rst_n) begin {clk_1hz, clk_10hz, clk_100hz, clk_1khz} <= 4'b0000; cnt1 <= 0; cnt2 <= 0; cnt3 <= 0; cnt4 <= 0; end else begin // 48 MHz / (2 * 24 000 000) = 1 Hz,周期 1 s if (cnt1 == 25'd24_000_000 - 1) begin cnt1 <= 0; clk_1hz <= ~clk_1hz; end else cnt1 <= cnt1 + 1'b1; // 48 MHz / (2 * 2 400 000) = 10 Hz,周期 0.1 s if (cnt2 == 25'd2_400_000 - 1) begin cnt2 <= 0; clk_10hz <= ~clk_10hz; end else cnt2 <= cnt2 + 1'b1; // 48 MHz / (2 * 240 000) = 100 Hz,周期 0.01 s if (cnt3 == 25'd240_000 - 1) begin cnt3 <= 0; clk_100hz<= ~clk_100hz;end else cnt3 <= cnt3 + 1'b1; // 48 MHz / (2 * 24 000) = 1 kHz,周期 1 ms,给扫描显示用 if (cnt4 == 25'd24_000 - 1) begin cnt4 <= 0; clk_1khz <= ~clk_1khz; end else cnt4 <= cnt4 + 1'b1; end end endmodule

四路输出共用一个输入时钟、各自独立的计数器,综合出来是四组并行比较器加四个触发器,资源比串行级联多一点点,好处是四路时标的相位关系确定,不会因为级联延迟累积出偏移。参数怎么改:换一块 50 MHz 的板子,四个系数整体乘 50/48,变成 25M、2.5M、250k、25k;只要保证每路都是“目标周期 × 时钟频率 ÷ 2”。

真正麻烦的是仿真。1 Hz 输出跑一个完整周期要 4800 万个时钟周期,波形窗口里根本看不出来。我一般会临时把四个系数改成 24、24、24、24,跑 100 个周期看波形,确认四路都在翻转、复位能拉低、翻转点是 N-1 而不是 N;逻辑对了再把系数改回来,重新综合。这个临时改系数的动作看着糙,比盯着一条直线看半天管用。

3.2 三档闸门选择:按键互斥和档位字 ff

三个带锁按键在硬件上已经做了互斥,任何时候只会有一个被按下,但代码里不能依赖这个前提——按键抖动、上电瞬间的中间态、甚至按下两个键的误操作都要能兜住。做法是按优先级写成 if-else 链,并且在没有按键时不改变档位,而不是清零。

// 三档闸门选择,clk_1khz 作为采样时钟,避免按键毛刺 always @(posedge clk_1khz or negedge rst_n) begin if (!rst_n) begin ff <= 2'b00; gate_sel <= 3'b000; end else begin if (key1) begin ff <= 2'b01; gate_sel <= 3'b001; end // 1 s 档 else if (key10) begin ff <= 2'b10; gate_sel <= 3'b010; end // 0.1 s 档 else if (key100) begin ff <= 2'b11; gate_sel <= 3'b011; end // 0.01 s 档 // 没有按键按下时保持原档位,不要清零,否则显示的小数点会闪 end end

ff 只有两位,取 01、10、11 三个值,00 留给“未选择”这个非法态,显示模块遇到 00 就把小数点全灭。用 1 kHz 采样按键,采样间隔 1 ms,比机械按键的抖动时间(通常 5~20 ms)短,理论上还是可能采到抖动,所以更稳的做法是再加一级 20 ms 的计数消抖;课设规模下,带锁按键本身抖动小,1 kHz 采样基本够用,但报告里要提一句“若使用普通轻触按键需增加消抖”。

3.3 开闸时序:gate_out1、clk_latch、clk_clear 三拍的先后顺序

一次完整的测量被切成三段:开闸计数、关闸锁存、清零复位。这三段的时长和先后关系,是整份设计里最容易写错的地方,因为它不影响仿真能不能跑,只影响读数准不准。

// 用所选档位的时标作为节拍,把一次测量切成 14 拍 reg [3:0] seq; always @(posedge gate or negedge rst_n) begin // gate 为当前选中档位的时标 if (!rst_n) seq <= 4'd0; else seq <= (seq == 4'd13) ? 4'd0 : seq + 1'b1; end always @(posedge gate or negedge rst_n) begin if (!rst_n) begin gate_out1 <= 1'b0; clk_latch <= 1'b0; clk_clear <= 1'b0; end else begin gate_out1 <= (seq < 4'd10); // 前 10 拍开闸 clk_latch <= (seq == 4'd11); // 关闸后一拍锁存 clk_clear <= (seq == 4'd13); // 再两拍清零,然后回到 0 end end

三个信号的时间关系是:seq 从 0 走到 9 的这 10 拍里 gate_out1 保持高电平,计数器放行;seq 到 10 时闸门关闭,计数器停住,此时计数值就是这一轮的测量结果;seq 到 11 时给出 clk_latch,把六个 BCD 值打进锁存器,显示模块开始显示新数据;seq 到 13 时给出 clk_clear,把计数器清回全零,准备下一轮。中间空出来的第 12 拍是缓冲,防止锁存和清零在同一个时标沿上抢。

这里的关键参数是“开闸 10 拍”。1 s 档时标是 clk_10hz,周期 0.1 s,10 拍正好 1 s;0.1 s 档时标周期 0.01 s,10 拍正好 0.1 s;0.01 s 档时标周期 1 ms,10 拍正好 10 ms。同一套状态机,靠换时标就实现了三档闸门,不用为每档单独写逻辑——这是这个设计里最省事的一处。

4. 计数与显示:六位 BCD 进位链、溢出标志和 1 kHz 动态扫描

计数器和显示模块之间隔着一层锁存器,这个结构不是可有可无的装饰。闸门开着的时候,计数器每个被测脉冲就变一次,1 MHz 信号意味着每秒变一百万次,直接把计数器接到译码器上,人眼看到的就是一片滚动的乱码。锁存器的作用是在闸门关闭的那一刻把结果“冻住”,下一轮测量结果到来之前,屏幕一直显示这个冻住的值,也就是任务书里说的“记忆显示”。

4.1 六位 BCD 计数:为什么不数二进制再转 BCD

二进制计数到 999999 只要 20 位,看起来比六组 4 位 BCD 省触发器,但显示

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询