基于等精度测频法的单点频率测量系统设计与实现
2026/9/9 9:38:31 网站建设 项目流程

简介:这份资源是一套基于FPGA与Verilog的简易频率计及可调方波发生器设计工程,适合数字电路和FPGA初学者、课程设计者参考。工程实现1kHz~9.999kHz、步进1kHz的方波输出,并利用计数器与定时逻辑完成输入信号频率测量,覆盖时序逻辑、分频、边沿检测等典型知识点。压缩包共1183个文件,约41.79MB,以Quartus工程文件(cdb/hdb/tdf)为主,另有Verilog源文件(.v)、综合报告(.rpt)和PDF文档,便于对照源码与工程配置学习。已有531人浏览学习。解压后可依据源码、工程文件与说明,理解方波发生和频率测量模块的层次化设计,并在Quartus中重新编译、仿真与上板验证,帮助快速上手FPGA项目流程。 设备巡检大半年,最烦的就是测频率这种看似简单、实际上坑不少的事。手里这台老式数显频率计,测个稳定信号还行,一遇到现场电机启停、变频器干扰就乱跳。后来我自己搭了一套单点频率测量方案,把固件、上位机脚本、数据记录工具都整理成了一个发布包,也就是标题里那个“Single Place And Fre Measure.7z”。这名字直译过来就是“单点频率测量”,实际做的是基于等精度测频法的一套采集与记录工具,适合在现场只关注一个测点、但对精度和抗干扰有要求的场景。

这套方案解决的核心问题有三个:频率读数稳定、采集过程可追溯、数据格式方便后续分析。用的是常见开发板和少量外围电路,成本不高,代码量也不大,但把测频路上那些容易踩的坑基本都填平了。无论是做设备状态监测、实验室信号源校准,还是学校课程设计里的频率测量模块,这套思路都能直接用,不需要你有多深的嵌入式基础,只要会烧录固件、看懂连线图就行。

1. 整体设计思路与选型理由

1.1 为什么没直接买成品频率计

成品频率计不是不能用,但有几个痛点让我最终决定自己搭。首先是价格,一台带数据记录功能的台式频率计动辄几千块,而现场巡检经常要同时看多个测点,成本根本兜不住。其次是灵活性,成品仪器的数据接口封闭,想接自己的传感器、想改采样周期、想把数据直接推进数据库都很麻烦。最后是现场适应性,很多工业现场的干扰很强,普通频率计没有针对性滤波手段,读数飘来飘去,反而误事。

自己搭方案的好处在于,每个环节都能控制。测频算法自己写,闸门时间自己定,输入信号的整形滤波自己调,数据协议自己定义。说白了,就是图一个心里有底。这个方案里我用了一块主频不高的开发板,配合一个四通道比较器做波形整形,再加上一个温补晶振做时间基准,整个成本压在了百元以内。精度上,用等精度测频法在1Hz到1MHz范围内能做到ppm量级,这个指标在多数工业监测场景下已经完全够用了。

1.2 等精度测频法为什么是首选

测频的方法不少,最粗暴的是直接数单位时间内的脉冲数,也就是频率计数法。这种方法在测高频信号时精度尚可,但测低频信号时误差大得离谱。比如闸门时间1秒,待测信号只有20Hz,那么一个周期内只数到20个脉冲,量化误差就是1/20,也就是5%,直接没法看。另一种是测周期法,适合低频,但高频时周期太小,计时误差又盖不住了。这两种方法各有短板,只适合在频率范围固定的场景里用。

等精度测频法解决了频率范围受限的问题。它的核心思想是让闸门时间和待测信号同步,也就是用待测信号的上升沿来开启和关闭计数器,这样无论测高频还是低频,量化误差都只取决于基准时钟的计数误差,不再取决于待测信号频率。简单说,低频信号虽然计到的脉冲数少,但闸门时间也相应拉长了,相当于用更长的时间来换取分辨率。这个特性让等精度测频法成了通用频率测量的首选,我这也是实测对比后确定的方案。

2. 频率测量核心原理与参数计算

2.1 等精度测频的数学模型

说到原理,得把公式摆出来。等精度测频有两个计数器:一个数待测信号的脉冲,另一个数基准时钟的脉冲。闸门由待测信号的边沿触发,实际测量窗口不是一个固定的时间长度,而是待测信号整数个周期。设实际闸门时间内待测信号计数值为Nx,基准时钟计数值为Ns,基准时钟频率为Fs,那么待测频率fx的计算公式是:

fx = Fs × Nx / Ns

从这个公式能看出,测量的分辨率不直接依赖Nx的大小,而是依赖Ns。Ns越大,基准时钟的量化误差越小。由于基准时钟频率通常很高,即使测量窗口只有几十毫秒,Ns也能达到几万甚至几十万,所以量化误差很小。

举个例子,我用的是温补晶振,标称频率10MHz,实际闸门时间设为200毫秒,那么Ns大约为200万。量化误差就是1/2000000,即0.5ppm。这个精度在绝大多数现场测量需求中已经相当理想了。如果希望进一步提升精度,可以把闸门时间拉长,比如2秒,量化误差就能降到0.05ppm,代价是实时性下降。

2.2 闸门时间怎么选才合理

闸门时间的选取,本质上是在精度和实时性之间做权衡。我在实现里把它做成了可配置项,支持10ms、100ms、200ms、1s四档,这样不同场景可以切换。

  • 10ms档:用于快速扫频、捕捉跳变信号,精度较低,适合信号源粗调。
  • 100ms档:常规测量,精度足够,响应速度也快,是我用得最多的一档。
  • 200ms档:比100ms档精度高一倍,适合对稳定度要求较高的测点。
  • 1s档:最高精度模式,用于校准参考源或者做精密分析。

实际使用中有个容易被忽略的点:闸门时间不是越短越好。如果待测信号本身就带有微小的频率波动,闸门时间太短会把这种波动当成误差读出来,看起来读数不稳。而闸门时间太长,又会把真实的频率变化平均掉。我通常的做法是先摸清信号的稳定度,再决定闸门档位。比如测市电频率,50Hz附近波动很小,用100ms档就够;测发动机的瞬时转速波动,就得用短闸门才能看到细节。

2.3 参考时钟的重要性

测频系统里,参考时钟就是那把“尺子”。尺子本身不准,后面的算法再精彩也没用。所以我在这个方案里没有用开发板自带的晶振,而是外接了一个温补晶振模块。这里的考虑很直接:普通晶振的频率温度漂移通常在10ppm到50ppm之间,这意味着环境温度变化二三十度,测量结果就可能偏离十几赫兹到几十赫兹。温补晶振可以把温漂压缩到1ppm以内,甚至更好。

还有一个常被忽视的细节是参考时钟的校准。即使标称10MHz的晶振,实际频率也可能有几百赫兹的偏差,这属于初始精度误差,只能通过校准消除。我在固件里留了一个校准参数,用高精度频率计实测参考时钟的实际频率,然后把偏差值写进去,软件会自动修正计算结果。这样做一轮之后,整套设备的系统精度就能逼近参考源的溯源精度,这也是把百元级硬件做到接近仪器级精度的重要手段。

3. 实操流程与关键环节实现

3.1 硬件连接与信号预处理

硬件部分我会列一个清单,照着买就行,都是常见器件:

  • 开发板一块,要求有至少两个硬件定时器输入捕获通道,我用的是常见的STM32F103。
  • 温补晶振模块一个,10MHz输出,供电3.3V。
  • 四通道比较器芯片一片,我这里用LM339,用来把任意波形的输入信号整形为方波。
  • 限幅保护电路,两个二极管加一个电阻,防止输入过压损坏芯片。
  • 电源模块,5V转3.3V的稳压板一块。

信号预处理是这套方案里比较容易出问题的一环。待测信号可能是正弦波、三角波、甚至是带偏置的方波,而单片机的定时器输入只能识别边沿跳变。所以必须先把信号整形为标准方波。LM339比较器配合一个基准电压,把输入信号与设定阈值比较,输出就是干净的方波。这里的关键是阈值要设在信号幅值的中间位置,否则输出方波的占空比会偏离50%,虽然不影响测频,但会让测量结果对噪声更敏感。

还有一个必须注意的问题是输入端要加RC低通滤波。工业现场的电磁干扰很常见,如果直接把信号线接到比较器,高频噪声可能让输出方波出现额外的毛刺边沿,导致定时器多计数,频率读数虚高。我加了一个截止频率约1MHz的RC滤波器,配合比较器的迟滞功能,把毛刺抑制掉了。迟滞量也不是越大越好,太大会影响低幅值信号的有效触发,我实测下来设在200mV左右比较平衡。

3.2 固件实现的关键代码逻辑

固件最核心的是两个定时器的配置。我用定时器2的输入捕获通道来捕捉待测信号的边沿和计数值,用定时器3对基准时钟计数。等精度测频的关键在于两个计数器必须在同一个待测信号边沿上同时启停,所以不能简单地用中断里读计数值的方式来实现,因为中断响应有延迟,两个计数器停止的瞬间可能不在一个边沿上。

我的实现方式是让待测信号同时连接到定时器2的输入捕获引脚和一个普通IO引脚。定时器2配置为上升沿捕获,捕获事件触发中断,在中断里读取定时器2和定时器3的当前计数值。由于两个定时器共用一个时钟源,而且读取操作在同一个中断里顺序执行,中间只隔了几条指令,这个时间差造成的误差在可接受范围内,比直接开闸关闸的方式精确得多。

void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); // 读取待测信号计数值 capture_nx = TIM_GetCapture1(TIM2); // 读取基准时钟计数值 capture_ns = TIM3->CNT; measure_done = 1; } }

测量完成标志置位后,主循环里做频率计算和结果输出。计算之前要先判断有没有溢出或者异常值,比如两次捕获间待测信号计数值为0,说明信号断了,这时候要报错而不是输出一个荒谬的频率。

3.3 上位机数据记录与可视化

现场测量就一个痛点,现象出现了但数据没记录,事后复盘全靠回忆。所以我给这套方案配了一个简单的上位机脚本,通过串口接收设备输出的时间和频率值,实时写入CSV文件,同时画一条滚动曲线,方便直接观察趋势。

脚本是用Python写的,用了pyserial和matplotlib两个库,逻辑不复杂。串口数据帧的格式是文本行,用逗号分隔,一行包含时间戳、原始计数值、计算频率三个字段。设备端每完成一次测量就输出一行,上位机按行解析,写入CSV,并保留最近200个点用于绘图。这样即使现场无人值守,数据也不会丢。

import serial import csv import datetime ser = serial.Serial("COM10", 115200, timeout=1) with open("freq_log.csv", "a", newline="") as f: writer = csv.writer(f) while True: line = ser.readline().decode().strip() if line: timestamp = datetime.datetime.now().isoformat() parts = line.split(",") if len(parts) == 3: writer.writerow([timestamp, parts[0], parts[1], parts[2]])

上位机脚本这边有个细节值得说一下:CSV文件要注意“边写边落盘”,否则突然断电或者程序异常退出,缓冲区的数据会丢失。我用的是with open()配合newline=""的方式,每次写完一行后调用一次flush(),确保数据及时写入磁盘。因为测频数据密度不高,一般每秒几行,频繁flush的性能损耗可以忽略。

4. 常见问题与排查技巧实录

4.1 读数乱跳,问题往往在信号整形

排查频率计异常,我总结了一句话:先看波形,再看时序,最后才怀疑算法。第一个要查的就是输入信号的整形环节。大部分读数乱跳的问题,源头在于输入信号质量太差,比较器输出的方波带毛刺,或者边沿不够陡峭,导致定时器捕捉到错误的跳变。

用示波器看比较器输出端的波形是最直接的排查方式。如果看到上升沿附近有抖动、回勾,就说明整形环节需要加强。我的处理办法是在比较器输出端加一个施密特触发器,或者利用单片机IO口内部的上拉/下拉电阻配合软件滤波。如果你没有示波器,也可以用万用表测一下信号幅值,确认输入信号确实在比较器阈值附近来回穿越,那基本就是阈值设置不合理。

4.2 低温环境下频率漂移明显

冬天做户外设备巡检的时候,我发现读数会比室内偏低一点点,虽然差异很小,但仔细对比还是能看出来。这个问题的根源在参考晶振上,普通晶振对温度敏感,低温下频率会偏移。解决方法是换温补晶振,前面硬件清单里也是这么选的。

不过要说明的是,温补晶振也不是完全不受温度影响,只是漂移量小很多。如果测量场景的温度范围特别宽,比如从零下二十度到零上五十度,那就要考虑恒温晶振或者做温度补偿校准了。我在固件里预留的温度传感器接口,就是为了后续加上温度补偿用的。实测下来,外接温补晶振后,整套设备在室温范围内的频率漂移已经低于测量误差的量级,对绝大多数监测需求没有影响。

4.3 串口偶尔丢数据,调整缓冲区策略

使用中另一个典型问题是上位机偶尔丢掉几行数据,造成曲线有断点。起因是设备端串口发送的速度超过了上位机解析写入的速度,缓冲区满了以后,旧数据被覆盖。这个问题的解决思路有两个方向:一是降低设备端的发送频率,二是提高上位机的接收处理效率。

我采用的是“设备端只发频率和计数值,上位机自己加时间戳”的策略,减少每行数据的内容,压缩串口传输时间。同时把读取串口和写CSV的操作分离,读串口的线程只负责把数据放进队列,写CSV的线程从队列取数据再落盘。这样的设计解耦了接收和存储,即使磁盘写入偶尔慢一点,队列也能起到缓冲作用。

import queue import threading data_queue = queue.Queue(maxsize=1000) def read_serial(ser): while True: line = ser.readline().decode().strip() if line: data_queue.put(line) def write_csv(file): while True: line = data_queue.get() file.write(line + "\n") file.flush()

这里有一个取舍:队列太短容易满,太长又占用内存且断线后数据时效性变差。我实测1000的容量足够应对115200波特率下的连续传输,内存占用也完全在可接受范围内。

4.4 自检校准到底怎么做

整套系统搭完,校准这一步绝对不能跳过。方法不复杂:找一个已知频率的高精度信号源,比如GPS驯服钟输出的1PPS信号,或者其他实验室级别的信号发生器,接到被测输入端,记录设备读数与标准值之间的误差,然后调整固件里的参考时钟修正系数。

让我用一个具体数字解释这个过程。假设标准信号源输出100kHz,设备实测读数为99.998kHz,误差为负2Hz,偏差比例就是20ppm。这说明参考时钟的实际频率比标称值高了约20ppm,或者说标称10MHz的实际大概是10.0002MHz。把修正系数设为0.99998,重新测量,读数就应该回到100kHz附近。

校准需要在高频和低频两个点各做一次,因为不同频率档位的电路延迟可能不同。校准完成后,把修正系数固化到内部Flash里,以后每次开机自动加载,这样才是一台可以放心交付给别人使用的测量设备,而不是只在自己桌上能跑的玩具。

5. 这套方案的局限性与后续扩展方向

任何硬件方案都有边界,这套单点频率测量方案也不例外。首先是测频上限,我的硬件设计里比较器LM339的响应速度有限,实际稳定可靠的上限大概在5MHz左右,再高就需要换用高速比较器或者前置分频器。如果你要测射频信号,这个方案就不合适了,得另起炉灶。其次是单通道的限制,名字里就写了“Single Place”,一次只能测一个点,想测多路就得做通道切换,或者直接上多路并行采集。

扩展方向上,我目前已经在尝试把多路巡检的频率都接到同一个设备上,通过模拟开关做分时切换,每路信号轮流接入测频电路。这样做有个问题:分时切换后,每一路的有效测量时间被压缩了,闸门时间变短,精度会下降。所以我更倾向于保留单路测频的高精度特性,另加一路低精度的巡检通道,两者用途不同,互不干扰。

数据层面,我也想把CSV存储改成轻量级的数据库,比如SQLite,这样查询和统计更方便,还能直接对接一些报表工具。固件里预留的通信协议扩展字段,就是为了后续加控制指令和参数下发用的。不过这些都属于锦上添花,当前这套方案的稳定性和准确性已经满足了我百分之九十的使用场景。

最后再分享一个经验:现场测量这件事,设备的精度固然重要,但数据记录的完整性和可追溯性往往更容易被忽视。没有记录,测量结论就缺少依据;有了记录,哪怕当时没发现问题,事后也能回放分析。所以如果让我给这套方案排优先级,测频算法只排第二,数据记录排第一。希望这份拆解对你自己搭测量设备有帮助,少走点我踩过的弯路。

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

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

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

立即咨询