简介面向电子技术、数字电路课程设计场景的浮点频率计设计文档适合电子信息类专业学生、实验课教师及刚接触数字系统设计的爱好者参考。内容围绕量程达1MHz的浮点式数字频率计展开依次覆盖技术指标与任务分析、系统框图、秒脉冲电路、节拍控制电路以及分频与计数显示电路并给出74LS290、74LS160、74LS1153等器件的具体接法与置数逻辑同时附上Multisim 12仿真波形和面包板硬件调试结果可作为课程设计报告撰写与电路搭建的完整参照。压缩包内仅1个docx文档约1.49MB图文并茂地呈现原理图、控制时序与仿真截图便于直接阅读与引用。目前已有211人学习下载适合需要快速理清时基、分频、计数与启停控制设计思路并希望对照实例完成自身方案验证的读者。1. 浮点频率计最先要解决的是小数位敢不敢信很多人做频率计卡住的地方不是数不对而是小数位不敢信。一个 10 MHz 的信号整数计数在 1 秒闸门里数出 10000000看着挺准换成 1 kHz 的信号1 秒闸门才数到 1000 个脉冲分辨率只有 1 Hz第 4 位往后全靠猜。浮点频率计要解决的就是这件事让读数带上可信的小数位而且换量程不用换算法。它适合做电子设计类课程与竞赛题的人——南邮电子设计这类题目里频率计几乎是常客——也适合需要在嵌入式平台上现场量频率的工程师以及被高频准、低频虚折磨过的调试者。核心手段是等精度多周期同步测量把 ±1 误差锁死在标准时钟那一侧。2. 等精度测频原理与方案选型闸门、误差和主控怎么定动手写代码之前先要把误差从哪来算清楚。电子设计里做频率计最常见的翻车不是代码写错而是选错了测量方法用一个固定闸门去数脉冲高频段看着很好一测几十赫兹的音频信号就开始跳。选型的依据不是哪种更高级而是我需要的分辨率对应的误差项落在哪一侧。2.1 直接测频、测周法与等精度法误差差在哪方法实现方式计算公式主要误差适用频段直接测频固定闸门 T 内数被测脉冲fx Nx / T±1 个被测脉冲相对误差 1/Nx中高频测周法数被测信号一个周期内的标准时钟fx fs / M±1 个标准时钟相对误差 1/M低频等精度闸门两端对齐被测信号边沿同时数 Nx 与 Nsfx Nx · fs / Ns±1 个标准时钟相对误差 1/Ns全频段关键在于等精度法把 ±1 量化误差从被测信号侧挪到了标准时钟侧。Ns fs · T只跟闸门时间和标准频率有关跟被测信号频率无关。所以 1 Hz 的信号和 100 MHz 的信号只要闸门时间一样相对分辨率就是同一个数量级。完整误差写成δ ±( 1/Ns Δfs/fs )其中 1/Ns 是量化误差Δfs/fs 是晶振本身的频率准确度与温漂。前者靠加长闸门解决后者只能靠校准扣掉。2.2 从目标分辨率倒推闸门时间与标准时钟频率不要先定闸门再去算精度顺序反过来先说清楚我要在 1 MHz 上分辨到 0.1 Hz再倒推需要多长的闸门。下面这段脚本可以直接改参数试算用来选闸门和标准时钟省得在板子上反复改 Verilog 参数。# 等精度测频参数试算给定标准时钟与闸门算相对分辨率和某频点的绝对分辨率 def design(fs_hz: float, gate_s: float, fx_hz: float None): ns round(fs_hz * gate_s) # 闸门内标准时钟计数 Ns rel 1.0 / ns # 仅考虑量化误差的相对分辨率 out [ffs{fs_hz/1e6:.1f}MHz gate{gate_s*1e3:.0f}ms Ns{ns} rel{rel:.2e}] if fx_hz is not None: out.append(f 在 {fx_hz/1e6:.3f}MHz 处绝对分辨率 {fx_hz*rel:.4f} Hz) return \n.join(out) for g in (0.01, 0.1, 1.0): print(design(50e6, g, 1e6)) # 50MHz 标准时钟分别用 10ms / 100ms / 1s 闸门跑出来的结果很直观50 MHz 标准时钟配 10 ms 闸门Ns 500000相对分辨率 2×10⁻⁶在 1 MHz 上的绝对分辨率是 2 Hz小数点后一位都是虚的换成 1 s 闸门Ns 5×10⁷相对分辨率 1e-8同频点绝对分辨率 0.01 Hz小数点后两位才有意义。所以闸门时间本质上是在刷新率和小数位之间做交易1 s 闸门意味着每秒才更新一次读数做实时扫频显示就会很卡。标准时钟的来源也有讲究。板载有源晶振50 MHz、100 MHz 都常见比 MCU 内部 RC 好一个量级内部 RC 的温漂动辄 ±1%小数位再准也没意义。晶振误差是系统误差可以靠一次校准折算掉所以选型时更该关注的是它的短期抖动而不是标称精度那一行字。2.3 主控与前端选型FPGA 计数还是 STM32 一把梭常见的两条路FPGA MCUFPGA 负责被测信号同步、闸门生成和两组计数器MCU 只做浮点运算和外设显示。好处是高频段能真正跑到几十上百 MHz闸门两端都能对齐到被测信号边沿。缺点是板子复杂、要写状态机。单 STM32 方案一个定时器配成外部计数模式TIMx_ETR数被测信号另一个定时器做闸门定时中断里读计数值。成本低、代码短但 ETR 引脚的输入带宽有限几十 MHz 量级而且软件闸门没法精确对齐被测信号边沿等于在 Nx 上又加回一个 ±1 误差。做几十 kHz 到几 MHz 的场合够用做高频就不合适。专用计数器芯片外围简单但量程和接口灵活性差现在课程设计里基本没人用了。前端整形同样别省。比较器加正反馈迟滞LM311、TLV3501 一类把正弦、三角、方波统一整形为干净的 CMOS 电平。迟滞量太小小幅值信号在零点附近反复翻转读数会莫名其妙翻倍迟滞量太大小信号直接测不到。高频段还可以先做前置分频但要注意分频会引入 ±1 个输入周期的误差分频比越大这个误差折算到频率上越明显。3. 用 Verilog 把等精度测频的闸门与计数逻辑跑通原理清楚之后落到 RTL 上的核心只有三件事把被测信号同步到标准时钟域、让闸门的开与关都对齐到被测信号边沿、把两组计数值在关闸门那一刻原子地锁存下来。3.1 模块端口与时序约定端口方向位宽含义clk输入1标准时钟 fs直接来自板载晶振rst_n输入1异步复位低有效sig_in输入1整形后的被测信号start输入1单次启动脉冲高电平一个 clk 周期即可nx_cnt输出48闸门内被测信号整周期数 Nxns_cnt输出48闸门内标准时钟周期数 Nsdone输出1数据锁存完成给 MCU 的中断源时序约定里最重要的一条clk 和 sig_in 是异步的sig_in 绝不能直接进组合逻辑参与判断必须先打两到三拍同步。3.2 开闸门与关闸门为什么都要等被测信号边沿这是等精度法唯一的魔法所在。闸门如果按标准时钟的节拍开、按标准时钟的节拍关那么闸门边界上必然切到被测信号的不完整周期Nx 就会带上 ±1 个被测周期的误差低频时这个误差占比很大。把开、关两端都推迟到被测信号的下一个上升沿执行闸门内就是整数个被测周期Nx 本身没有量化误差误差全部集中到 Ns 的 ±1 上。代价是闸门时间会比预设值多出最多一个被测周期做低频测量时这个多出来的时间不能忽略——1 Hz 信号下就是 1 秒。3.3 可上板的 RTL同步、计数、锁存一次写完module epm_counter #( parameter integer FS_HZ 50_000_000, // 标准时钟频率必须和实际晶振一致 parameter integer GATE_MS 100, // 预置闸门时间单位 ms parameter integer CNT_WIDTH 48 // 计数器位宽按 fs_max * T_max * 2 估算 )( input wire clk, input wire rst_n, input wire sig_in, input wire start, output reg [CNT_WIDTH-1:0] nx_cnt, output reg [CNT_WIDTH-1:0] ns_cnt, output reg done ); localparam integer GATE_CYCLES FS_HZ / 1000 * GATE_MS; // 预置闸门对应的 clk 周期数 // ---- 被测信号三级同步抑制亚稳态 ---- reg [2:0] sig_sync; always (posedge clk or negedge rst_n) if (!rst_n) sig_sync 3b000; else sig_sync {sig_sync[1:0], sig_in}; wire sig_rise sig_sync[1] ~sig_sync[2]; // 上升沿已同步到 clk 域 // ---- 闸门状态机 ---- localparam [1:0] S_IDLE 2d0, S_WAIT 2d1, S_MEAS 2d2, S_DONE 2d3; reg [1:0] state; reg gate_open; // 闸门实际打开标志 reg close_req; // 计时到点请求关闸门 reg [31:0] gate_timer; reg [CNT_WIDTH-1:0] nx_acc, ns_acc; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state S_IDLE; gate_open 1b0; close_req 1b0; gate_timer 32d0; nx_acc {CNT_WIDTH{1b0}}; ns_acc {CNT_WIDTH{1b0}}; nx_cnt {CNT_WIDTH{1b0}}; ns_cnt {CNT_WIDTH{1b0}}; done 1b0; end else begin done 1b0; case (state) S_IDLE: if (start) begin // 收到启动清零后进入等待 nx_acc {CNT_WIDTH{1b0}}; ns_acc {CNT_WIDTH{1b0}}; gate_timer 32d0; close_req 1b0; state S_WAIT; end S_WAIT: if (sig_rise) begin // 对齐到被测信号上升沿再开闸门 gate_open 1b1; state S_MEAS; end S_MEAS: if (gate_open) begin ns_acc ns_acc 1b1; // 标准时钟计数误差来源 gate_timer gate_timer 1b1; if (gate_timer GATE_CYCLES - 1) close_req 1b1; // 预置时间到等下一个边沿 if (sig_rise) begin nx_acc nx_acc 1b1; // 只数完整周期 if (close_req) begin // 关闸门同样对齐到边沿 gate_open 1b0; nx_cnt nx_acc 1b1; ns_cnt ns_acc; state S_DONE; end end end S_DONE: begin done 1b1; // 单周期完成脉冲给 MCU state S_IDLE; end endcase end end endmodule逻辑上要注意三处。第一nx_acc在开闸门那个边沿不计数、在关闸门那个边沿计数所以nx_cnt赋的是nx_acc 1表示闸门内包含的完整周期数为 N。第二ns_cnt锁存的是关闭前一刻的累加值与真实时间存在最多一个 clk 周期的偏差这个 ±1 正是误差公式里的 1/Ns属于正常损耗不用去修。第三整个模块是单次触发式测完回 IDLEMCU 读到 done 之后重新给 start测量节拍由软件控制方便做多次平均。3.4 参数取值与上板验证清单参数建议取值说明FS_HZ50_000_000 或 100_000_000必须与板上晶振实际频率一致写错等于整体缩放GATE_MS10 / 100 / 1000 三档低频用长闸门高频用短闸门可在 MCU 侧动态下发CNT_WIDTH4848 位可覆盖 100 MHz × 数小时不溢出综合资源也还能接受GATE_CYCLESFS_HZ/1000*GATE_MS注意先用整数除再乘避免中间结果溢出 32 位上板前先在仿真里给一个 1 MHz 的 sig_in 和 50 MHz 的 clk跑 100 ms 闸门期望 Nx 约等于 100000、Ns 约等于 5000000两者比值乘以 fs 约等于 1 MHz。仿真对得上再上板上板后用信号源给 1 kHz读数应该在 1000.xx 附近而不是 1000 或 999——如果是整数跳变八成是 MCU 侧用了整数除法。4. 从 Nx/Ns 到带小数的频率读数浮点运算与格式化FPGA 给过来的永远是两个整数浮点频率计真正浮起来是在 MCU 这一侧完成的。这一步做错前面 RTL 写得再漂亮小数位也是假的。4.1 fx Nx·fs/Ns 的代码写法与精度边界#include stdint.h #define FS_HZ 50000000.0 /* 标准时钟必须与 Verilog 参数 FS_HZ 完全一致 */ /* 由 FPGA 锁存的两个计数值计算频率返回单位 Hz */ double freq_from_counts(uint64_t nx, uint64_t ns) { if (ns 0) return 0.0; /* 未开闸门或硬件异常防除零 */ if (nx 0) return 0.0; /* 被测信号幅度不够前端没整形出边沿 */ /* 先除后乘nx 与 ns 都是 48 位以内转 double 后相对精度约 1e-16 远小于 1/Ns 的量化误差顺序不影响结果但能避免中间乘积溢出 */ return (double)nx / (double)ns * FS_HZ; }为什么先乘后除在这个场景里要谨慎nx 大、fs 大时nx × 50e6 很容易冲到 10¹⁷ 量级超出 double 的 53 位有效位约 9×10¹⁵就开始丢低位。写成先除后乘两次运算各自都在 1e-16 相对误差内和 Ns 的 ±1 量化误差1e-7 量级比起来完全不是瓶颈。真正的精度瓶颈永远在闸门和晶振上不在浮点运算上别把精力放错地方。4.2 软件浮点、硬件 FPU 与定点输出的取舍方案典型平台优点代价软件浮点 doubleCortex-M0/M3 无 FPU精度足够代码零改动一次运算几十到上百周期显示刷新时略慢硬件单精度 floatCortex-M4F快只有 24 位有效位48 位计数值直接转 float 会丢低位硬件双精度 doubleCortex-M7 / 带双精度 FPU快且精度够芯片成本高部分库仍需软件模拟定点整数输出任意 MCU不链接浮点库代码小量程跨度大时定标麻烦单位换算要自己拼选型上有个容易踩的坑STM32F4 的 FPU 默认配置-mfpufpv4-sp-d16只支持单精度代码里写 double 照样走软件模拟。如果对刷新率不敏感就让 double 走软件模拟精度更稳如果确实要快得把这些计数值先做定点缩放再转 float别直接把 48 位整数塞进 float。4.3 显示格式化小数位由分辨率决定不是想写几位写几位#include stdio.h #include math.h /* 按分辨率决定显示位数ns 决定相对分辨率位数超过它就是假精度 */ void format_freq(char *buf, size_t len, double fx, uint64_t ns) { int digits (int)floor(log10((double)ns)) - 1; /* 相对分辨率 1/Ns留一位余量 */ if (digits 0) digits 0; if (digits 6) digits 6; /* 屏幕和库都不适合无限位数 */ if (fx 1e6) snprintf(buf, len, %.*f MHz, digits, fx / 1e6); else if (fx 1e3) snprintf(buf, len, %.*f kHz, digits, fx / 1e3); else snprintf(buf, len, %.*f Hz, digits, fx); }以 Ns 5×10⁶ 为例log10 约为 6.7digits 取 5显示到小数点后 5 位换成 1 s 闸门的 Ns 5×10⁷digits 取 6再多就是噪声了。这样做的好处是闸门时间切换时显示位数自动跟着变不会出现闸门缩短了但小数位还挂着 6 位的假精度。注意在 STM32 上用 newlib-nano 时%f默认被裁掉snprintf会原样输出 f 或空字符串。需要在链接选项里加-u _printf_float或者改用整数部分 . 小数部分手工拼字符串后者能省下十几 KB 的 Flash做小容量型号时更划算。4.4 三个常翻车的类型与单位细节第一整数除法。uint64_t f nx / ns * 50000000;里nx/ns先算成 0结果永远是 0。所有和频率相关的除法两端必须至少有一个是浮点类型。第二计数位宽与打印格式不匹配。48 位计数在 MCU 里用uint64_t存打印时用%llu用%u会高位截断读数会周期性跳变这种 bug 在低频长闸门时才暴露很难查。第三单位换算的常量写成整数。fx / 1000是整数除法fx / 1e3才是浮点除法。一个字符的差别一个量级的错误。5. 校准与排错让浮点频率计的小数位经得起复测读数能出来只是及格线能复测、能换板一致才算过关。等精度法本身消除了 Nx 的量化误差剩下的误差主要来自晶振准确度和前端整形质量这两项都是可以量化处理掉的。5.1 单点校准把晶振的系统误差扣掉用一个已知准确度的信号源或者一块标定过的有源晶振板输入 10.000000 MHz读回实测值算修正系数存进 Flash每次运算后乘一遍。#define CAL_ADDR 0x0801F000UL /* 末页存校准系数注意别和程序区重叠 */ typedef struct { double k; } cal_t; /* k 真实频率 / 实测频率 */ double apply_cal(double fx_raw) { cal_t *c (cal_t *)CAL_ADDR; /* 上电时已校验过标志位这里直接用 */ if (c-k 0.9 || c-k 1.1) return fx_raw; /* 系数明显不合理按未校准处理 */ return fx_raw * c-k; } /* 校准流程测 10MHz 标准源得到 f_meas则 */ /* k 10000000.0 / f_meas; 再擦写 Flash 写入 */逻辑说明k 是乘性系统误差标定一次就能把晶振的固定偏差压到 1e-8 以内比换更贵的晶振划算得多。参数上要留合理性检查0.91.1防止 Flash 空白值被当成有效系数用导致读数整体放大几十倍。注意 Flash 末页写入前要先擦除并且把系数和它的反码一起存上电做一次校验掉电写坏也能识别出来。5.2 现象对照表读数不动、乱跳、高频偏小分别查哪里现象优先怀疑检查点读数恒为 0前端没出边沿示波器直接量比较器输出看有没有方波读数约为真实值两倍比较器无迟滞过零点多次翻转加正反馈电阻迟滞取信号幅值的 5%10%低频跳变大于 1 个字闸门太短1/Ns 太大换长闸门或确认 MCU 侧没有整数除法高频段读数偏小前端带宽不足或分频器失效查比较器压摆率、走线核对分频比同一信号复测每次差固定值闸门边界没对齐信号边沿回看 RTL 里 S_WAIT、close_req 两处判断上电初期正常跑久了漂移晶振温漂或电源纹波换有源晶振检查比较器供电去耦5.3 平均、自适应闸门与刷新率的边界连续测 N 次取算术平均能压低随机抖动等效把分辨率提升约 √N 倍代价是刷新率除以 N。8 次平均配 100 ms 闸门刷新率还有 1.25 次/秒屏幕看着不卡分辨率相当于 800 ms 闸门这是性价比比较高的组合。但平均消不掉系统误差晶振偏了平均多少次都还是偏必须先校准再平均。自适应闸门是另一个常用技巧先测一次得到大致频率再按闸门内至少有 1000 个被测周期反推该用多长的闸门1 Hz 信号自动切到 1 s 甚至 10 s 档1 MHz 信号切回 10 ms 档。判断依据直接用上一次的读数和当前 Nx如果 Nx 小于 1000下次换更长的闸门大于 1e7换更短的。上下限都要卡住比如闸门限制在 10 ms 到 10 s 之间否则遇到无信号的情况会一路加长到无限等待。最后一句留给动手顺序先拿一个已知频率源把修正系数标定出来写进 Flash再去调小数位和显示位数反过来做小数位越多错得越好看。本文还有配套的精品资源点击获取