简介:这份资源是2015年全国大学生电子设计竞赛F题的等精度测频完整工程,面向学习FPGA数字系统设计与Verilog编程的学生及参赛者,解决如何用50MHz采样时钟精确测量输入信号频率并通过UART回传结果的问题。压缩包共428个文件、约31.63MB,以Quartus工程文件为主,包含34个Verilog源码、大量cdb与hdb编译数据库、tdf与qip等工程配置、rpt与logdb报告日志,以及jic、sof等下载烧录文件,完整保留了从源码到综合适配的全过程。已有1319人学习下载。读者可从中获得测频计核心逻辑、UART串口通信模块、时钟分频与均值处理等可复用代码,并借助工程文件理解等精度测量、同步设计与资源优化的实现思路,是数字系统设计入门到进阶的典型参考实例。
1. 等精度测频到底解决了什么痛点
做过频率测量的工程师大多踩过同一个坑:用固定闸门去数脉冲,低频信号误差大得离谱,高频信号又浪费了计数器的位宽。等精度测频这套方法,核心思路是让闸门时间跟着被测信号的周期走,而不是死守一个固定值。2015年电赛F题把这道题摆到了台面上,要求用FPGA实现一套能在宽频范围内保持恒定相对误差的测频系统。
它适合谁?正在做数字频率计、传感器信号采集、或者需要在FPGA上做高精度时间间隔测量的从业者。你不需要昂贵的频率基准,只要有一路稳定的参考时钟,就能把相对误差压到跟参考时钟同量级。这篇文章会把Verilog实现拆开讲清楚:闸门怎么同步、计数器怎么设计、误差怎么算、代码怎么调。读完你能直接在自己的板子上复现一套可用的等精度测频模块。
2. 等精度测频的Verilog实现:从闸门同步到计数输出
2.1 为什么固定闸门测频会翻车
先算一笔账。假设闸门时间固定为1秒,被测信号频率是10Hz,那么闸门内只能数到10个脉冲,±1个字的计数误差就是10%的相对误差。如果被测信号是10MHz,同样1秒闸门能数到一千万个脉冲,±1个字的误差只有0.00001%。问题在于,你不可能为每个频段都换一个闸门时间。
等精度测频的做法是:用被测信号的边沿去同步闸门的开启和关闭。实际闸门时间不再等于预设值,而是被测信号周期的整数倍。这样被测信号的计数误差始终是±1个字,而实际闸门时间的误差来自参考时钟的±1个字。最终相对误差由参考时钟的精度决定,与被测信号频率无关。
这里有一个关键前提:参考时钟频率必须远高于被测信号频率,否则实际闸门时间的量化误差会反过来主导总误差。常见做法是参考时钟取100MHz以上,被测信号上限控制在参考时钟的1/10以内。
2.2 闸门同步与计数器架构
整个模块分三块:闸门同步逻辑、参考时钟计数器、被测信号计数器。闸门同步逻辑负责把预设的闸门时间(比如1ms、10ms、100ms)同步到被测信号的时钟域,产生一个实际闸门信号。参考时钟计数器在实际闸门有效期间对参考时钟计数,被测信号计数器对被测信号计数。
// 闸门同步:将预设闸门时间同步到被测信号时钟域 module gate_sync ( input wire clk_ref, // 参考时钟,100MHz input wire clk_test, // 被测信号,作为时钟使用 input wire rst_n, input wire gate_preset, // 预设闸门使能,来自参考时钟域 output reg gate_actual // 实际闸门,被测信号域 ); reg gate_preset_d1, gate_preset_d2; // 两级同步器,消除亚稳态 always @(posedge clk_test or negedge rst_n) begin if (!rst_n) begin gate_preset_d1 <= 1'b0; gate_preset_d2 <= 1'b0; end else begin gate_preset_d1 <= gate_preset; gate_preset_d2 <= gate_preset_d1; end end // 在实际闸门开启期间,等待被测信号的下一个上升沿关闭 always @(posedge clk_test or negedge rst_n) begin if (!rst_n) gate_actual <= 1'b0; else if (gate_preset_d2 && !gate_actual) gate_actual <= 1'b1; // 开启实际闸门 else if (!gate_preset_d2 && gate_actual) gate_actual <= 1'b0; // 关闭实际闸门 end endmodule这段代码的逻辑说明:gate_preset来自参考时钟域,是一个宽度等于预设闸门时间的脉冲。经过两级同步器后进入被测信号时钟域,然后在实际闸门的上升沿和下降沿都对齐到被测信号的边沿。参数说明:clk_ref建议不低于100MHz,clk_test是被测信号直接接入全局时钟引脚。两级同步器是必须的,少一级就可能出现亚稳态导致闸门宽度抖动。
2.3 两个计数器的Verilog写法与位宽选择
参考时钟计数器和被测信号计数器的结构相同,区别在于时钟源和位宽。参考时钟频率高,同样闸门时间内计数值大,位宽要够。被测信号频率低,位宽可以小一些,但为了统一处理,通常取相同位宽。
// 参考时钟计数器:在实际闸门期间对clk_ref计数 module counter_ref ( input wire clk_ref, input wire rst_n, input wire gate_actual_sync, // 同步到clk_ref域的实际闸门 output reg [31:0] cnt_ref ); always @(posedge clk_ref or negedge rst_n) begin if (!rst_n) cnt_ref <= 32'd0; else if (gate_actual_sync) cnt_ref <= cnt_ref + 1'b1; else cnt_ref <= cnt_ref; // 闸门关闭后保持,供读取 end endmodule // 被测信号计数器:在实际闸门期间对clk_test计数 module counter_test ( input wire clk_test, input wire rst_n, input wire gate_actual, output reg [31:0] cnt_test ); always @(posedge clk_test or negedge rst_n) begin if (!rst_n) cnt_test <= 32'd0; else if (gate_actual) cnt_test <= cnt_test + 1'b1; else cnt_test <= cnt_test; end endmodule逻辑说明:两个计数器都在各自时钟域内工作,闸门有效时累加,无效时保持。gate_actual_sync需要把被测信号域的实际闸门再同步回参考时钟域,同样用两级同步器。参数说明:位宽取32位是保守做法,如果参考时钟100MHz、最大闸门1秒,计数值最大1亿,27位就够,但32位留足余量且对齐字节边界。被测信号计数器同理。
2.4 频率计算公式与误差分析
测频结果的计算公式:
f_test = (cnt_test × f_ref) / cnt_ref
其中f_ref是参考时钟频率,cnt_ref是参考时钟计数值,cnt_test是被测信号计数值。这个公式成立的前提是实际闸门同时作用于两个计数器,且闸门边沿对齐到被测信号周期。
相对误差分析:被测信号计数误差为±1个字,参考时钟计数误差为±1个字。总相对误差约为:
Δf/f ≈ ±(1/cnt_test + 1/cnt_ref)
当cnt_test足够大时(比如大于1000),第一项可以忽略,误差主要由参考时钟的±1个字决定。这就是等精度测频的核心优势:相对误差恒定,不随被测频率降低而恶化。
实际工程中,cnt_test在低频时可能只有几十,这时候第一项就不能忽略了。解决办法是增大预设闸门时间,让低频信号也能积累足够的计数值。常见做法是预设闸门时间取10ms、100ms、1s三档,根据被测信号频率自动切换。
3. 等精度测频的避坑与排查:5个血泪教训
3.1 闸门信号跨时钟域没做同步,计数结果随机跳变
现象:测频结果偶尔跳变,跳变幅度没有规律,复位后有时正常有时不正常。
原因:gate_preset从参考时钟域直接拉到被测信号域使用,没有做跨时钟域同步。亚稳态导致闸门宽度随机变化,两个计数器的计数窗口不一致。
解决:所有跨时钟域信号必须经过至少两级同步器。如果信号宽度可能小于目标时钟周期,还需要做脉冲展宽或握手处理。上面代码里的gate_sync模块就是干这个的,两级触发器缺一不可。
3.2 计数器位宽不够,高频段直接溢出归零
现象:被测信号频率较高时,测频结果突然变成很小的值或者零。
原因:被测信号计数器位宽选小了。比如选了16位,被测信号10MHz、闸门1秒,计数值需要达到一千万,16位最大才65535,早就溢出了。
解决:先算最大计数值。cnt_max = f_max × gate_max。参考时钟100MHz、闸门1秒,cnt_ref最大1亿,需要27位。被测信号如果上限10MHz,同样需要27位。统一取32位最省心,综合工具会自动优化掉未使用的位。
3.3 实际闸门关闭后计数器没有保持,读数时已经归零
现象:读取测频结果时发现计数器值总是零或者一个很小的数。
原因:计数器在闸门关闭后继续计数或者被复位了。如果代码写成else cnt <= 0,闸门一关计数就没了,后续读取逻辑根本读不到有效值。
解决:闸门关闭后计数器必须保持当前值,直到下一次测量开始前再复位。上面代码里else cnt <= cnt就是保持。读取逻辑要在闸门关闭后、下一次闸门开启前完成数据锁存。
3.4 参考时钟和被测信号共用地线导致串扰
现象:测频结果在特定频率点附近误差突然增大,换一块板子又正常。
原因:参考时钟和被测信号走线太近,共用地线阻抗导致串扰。被测信号的谐波耦合到参考时钟线上,引起参考时钟抖动。
解决:PCB布局时参考时钟走线远离被测信号输入,地平面完整分割。被测信号输入加施密特触发器整形,消除边沿抖动。参考时钟源选用低抖动的晶振,不要用FPGA内部PLL分频出来的时钟做参考。
3.5 闸门时间切换时没有等待当前测量完成
现象:自动切换闸门档位时,测频结果出现一次错误值,然后恢复正常。
原因:闸门时间切换逻辑在测量过程中直接改变了预设值,导致当前测量周期的实际闸门宽度异常。
解决:闸门切换必须等待当前测量周期完全结束。用一个状态机控制:测量中→等待闸门关闭→锁存数据→切换档位→启动下一次测量。不要在闸门有效期间修改任何预设参数。
4. 把等精度测频做到极致:一个自动量程的工程技巧
前面讲的都是固定闸门时间的做法。实际产品里,被测信号频率可能从几Hz到几十MHz,固定闸门时间没法同时兼顾低频精度和高频速度。我一般会加一个自动量程逻辑:先用一个短闸门快速估测频率范围,再根据估测结果选择最合适的闸门时间。
具体做法是设三档闸门:1ms、10ms、100ms。上电后先用1ms闸门测一次,如果cnt_test小于1000,说明信号频率低于1MHz,切换到10ms档;如果还小于1000,切换到100ms档。如果cnt_test大于100000,说明信号频率很高,保持1ms档甚至缩短到0.1ms档。这样在保证精度的前提下,测量速度也能接受。
// 自动量程状态机(简化版) module auto_range ( input wire clk_ref, input wire rst_n, input wire [31:0] cnt_test, input wire measure_done, // 一次测量完成脉冲 output reg [1:0] gate_sel, // 00:1ms 01:10ms 10:100ms output reg range_locked // 量程锁定标志 ); localparam IDLE = 2'd0, EVAL = 2'd1, LOCK = 2'd2; reg [1:0] state; always @(posedge clk_ref or negedge rst_n) begin if (!rst_n) begin state <= IDLE; gate_sel <= 2'b00; range_locked <= 1'b0; end else begin case (state) IDLE: begin if (measure_done) state <= EVAL; end EVAL: begin if (cnt_test < 32'd1000) begin if (gate_sel < 2'b10) gate_sel <= gate_sel + 1'b1; // 增大闸门 else state <= LOCK; end else if (cnt_test > 32'd100000) begin if (gate_sel > 2'b00) gate_sel <= gate_sel - 1'b1; // 减小闸门 else state <= LOCK; end else begin state <= LOCK; end end LOCK: begin range_locked <= 1'b1; if (!measure_done) state <= IDLE; // 等待下一次测量 end endcase end end endmodule逻辑说明:状态机在每次测量完成后评估cnt_test,如果太小就增大闸门时间,太大就减小闸门时间,直到落在合理区间后锁定量程。参数说明:cnt_test的阈值1000和100000是根据经验取的,对应相对误差约0.1%和0.001%。gate_sel输出到闸门生成模块,控制预设闸门宽度。range_locked置位后不再自动切换,除非收到复位或手动切换命令。
这个自动量程逻辑有一个细节要注意:评估必须在测量完成后进行,不能在测量过程中改闸门。另外,量程切换后第一次测量的结果要丢弃,因为闸门宽度变了,计数器还没稳定。我一般会在切换后等一个完整测量周期再采信数据。
验证方法:用信号发生器输出一个从1Hz到20MHz的扫频信号,观察测频结果的相对误差是否始终保持在参考时钟精度附近。如果某个频段误差突然变大,检查该频段对应的闸门档位是否合理,以及计数器是否接近溢出。
最后说一个我自己的习惯:每次改完闸门逻辑,先用一个固定频率信号跑100次测量,看结果的标准差。如果标准差大于理论值,说明同步逻辑或者计数器保持逻辑有问题。这个习惯帮我省了很多调试时间。希望帮到你。
本文还有配套的精品资源,点击获取