简介:基于STM32F407的FFT频谱分析工程,面向嵌入式开发者与信号处理工程师,实现1Hz高分辨率频谱分析,支持信号频率测量、谐波成分分析、总谐波失真度评估以及双波形相位差检测,适用于音频工程、电力系统监测和通信调试等场景。资源共316个文件,以C/H源码和MDK工程文件为主,同时包含编译生成的axf/hex固件、PDF讲解文档以及LCD显示与UCOS相关代码,压缩包大小3.71MB,结构完整,可直接打开工程参考或烧录测试。已有10448人学习下载,在STM32 FFT应用领域具有一定参考价值。项目覆盖0~2KHz分析范围,从底层ADC采样到FFT运算再到LCD显示均有对应实现,包含FFT计算、谐波提取和相位差测量等关键模块,适合需要开展实时频谱分析、失真度检测或频率测量开发的读者作为基础模板,便于二次修改与移植。
1. 用STM32F407做1Hz分辨率FFT测量:这块板子完全扛得住,坑都在细节里
STM32F407上做FFT不稀奇,稀奇的是把分辨率做到1Hz,再同时输出信号频率、谐波、失真度和双通道相位差,这在168MHz的MCU上并不是默认就能跑好的方案。很多人第一次接这个需求,总以为“FFT是库函数,调用就有结果”,结果一上电发现频率读数飘、谐波幅度对不上、相位差总有固定偏移,最后只能怪硬件不稳。实际上1Hz分辨率背后藏着采样时间窗、内存布局、窗函数和通道同步四道坎,每一道都有明确的数学约束和调试手段。这篇文章把我自己做过的采集、变换、测算和标定流程完整拆开,从采样参数怎么定到相位差为什么翻车,按步骤复现一遍,新手能跟着跑通,老手能直接拿去核对边界和参数。
2. 分辨率计算的物理账:采样点数、采样率与时间窗怎么凑出1Hz
2.1 为什么1Hz分辨率不是调参问题,而是内存和时间窗问题
FFT的频率分辨率由公式Δf = Fs / N决定,Fs是采样率,N是FFT点数。想要分辨率等于1Hz,只要让Fs和N的比值等于1就行,比如Fs=4096Hz、N=4096,或者Fs=8192Hz、N=8192。表面上看数学很简单,但落到F407上要同时算清两笔账:一笔是采样时间窗T = N / Fs,不管怎么组合,1Hz分辨率都需要至少1秒的连续采样窗口;另一笔是内存账,FFT缓冲区的体积随着N翻倍。4096点单通道复数FFT需要32KB的float数组,8192点直接翻到64KB,再把窗函数、ADC DMA缓冲和双通道副本放进去,192KB的SRAM很快就紧张了。很多人把采样率提到48kHz又想要1Hz分辨率,那需要48000点,这已经不是F407直接算的点了。
FFT点数必须是2的幂,所以采样率也最好选2的幂。常见组合就三种:Fs=1024、N=1024,奈奎斯特频率512Hz,适合40次工频谐波以内的测量;Fs=4096、N=4096,奈奎斯特频率2048Hz,测到音频没问题;Fs=8192、N=8192,奈奎斯特频率4096Hz,内存占用量变高,但高频成分更全。选型时还要注意一点:1Hz分辨率的含义是“能区分间隔1Hz的两个等幅信号”,并不是说测量读数只能显示整数。配合后面讲的插值方法,单一频率的读数精度可以远超1Hz。
2.2 采样率-点数组合三套配置:内存与测量范围选个平衡
下面的表格给出一组能直接用于F407的组合,内存估算包含双通道FFT缓冲、窗函数和ADC原始缓冲区,实际工程还要留出栈、堆和显示缓冲,别卡得太死。
| 采样率Fs | FFT点数N | 频率分辨率 | 最高分析频率 | 时间窗 | 双通道预估内存 |
|---|---|---|---|---|---|
| 1024 Hz | 1024 | 1.0 Hz | 512 Hz | 1 s | 约 20 KB |
| 4096 Hz | 4096 | 1.0 Hz | 2048 Hz | 1 s | 约 80 KB |
| 8192 Hz | 8192 | 1.0 Hz | 4096 Hz | 1 s | 约 160 KB |
我一般优先选4096点这套,理由是内存余量充足,还能放心开LCD缓冲或者跑个简单的UI。如果被测信号是工频50Hz,2次谐波100Hz,40次谐波2000Hz,2048Hz的奈奎斯特频率刚好够用,但50Hz的41次谐波就会折返进频谱里造成假的“高次谐波”,所以真正做谐波分析时不要只看表上分辨率。8192点那套适合需要看3kHz以上分量但内存又有余量的场景,代价是FFT计算耗时接近翻倍,F407仍然能跑,只是刷新率会掉到每秒几帧。采样率还要配合抗混叠滤波器,ADC输入前级必须有一个截止频率在奈奎斯特频率附近的RC或运放低通,否则高频折叠进来会让谐波和失真度结果完全失真。
2.3 CMSIS-DSP库的实数与复数FFT取舍
F407带FPU和DSP指令,算FFT靠的是芯片原厂提供的DSP库,里面有两类常用接口:实数FFT(arm_rfft_fast_f32)和复数FFT(arm_cfft_f32)。实数FFT输入是N个float,内存占用更小,速度更快,但输出格式是打包的,需要按约定位置“拆”出幅度谱,拆错一次结果就全乱。复数FFT输入是N个复数,也就是2N个float,实部和虚部交错存放,填数据时把每个采样点放在偶下标、虚部填0就行,输出格式直观,直接用arm_cmplx_mag_f32求幅值。对于4096点,复数FFT在F407上耗时大约几毫秒,完全不影响1秒一次的数据刷新,所以我更推荐用复数接口,代码容易读也容易排错。
直接定义数组并调用变换的核心代码是这样:
#include "arm_math.h" #define FFT_SIZE 4096u #define SAMPLE_RATE 4096.0f static float32_t fft_buf[FFT_SIZE * 2]; /* 复数输入缓冲区 */ static float32_t mag_buf[FFT_SIZE / 2]; /* 单边幅值谱 */ static float32_t win_buf[FFT_SIZE]; /* 窗函数 */ void fft_init(void) { /* 周期Hann窗,n以FFT_SIZE为周期 */ for (uint32_t n = 0; n < FFT_SIZE; n++) { win_buf[n] = 0.5f * (1.0f - cosf(2.0f * PI * n / FFT_SIZE)); } /* 只需初始化一次,arm_cfft_f32内部会建旋转因子表 */ arm_cfft_init_f32(&arm_cfft_sR_f32_len4096, FFT_SIZE); }这段代码里的窗口函数要特别说明一下:很多人用0.5*(1-cos(2PIn/(N-1))),那是信号处理教科书里的对称窗,而FFT实际处理的是一帧“周期延拓”的信号,所以这里用周期窗更合适,让窗函数首尾连续,基波落在整数谱线上时泄漏接近于零。arm_cfft_init_f32里的旋转因子表是DC表,static数组占一点SRAM,但能避免每次变换重复建表。选复数接口的代价是内存比实数接口多一个虚部数组,但对4096点这个量级,80KB之内能塞下,仍然在F407的SRAM承受范围内。
3. 从ADC采样到频率输出:4096点FFT的完整测频流程
3.1 采样触发与DMA缓冲:先保证数据不丢
频率测量的准确性首先不取决于FFT,而取决于采样率是否稳定。F407的ADC建议用定时器触发,让每个采样点之间的时间间隔严格等于1/Fs,不要用HAL_ADC_Start加延时轮询的方式,那会被中断和任务调度打乱,产生抖动。用定时器触发配合DMA循环模式是常见做法:定时器输出PWM或TRGO信号给ADC外部触发,ADC完成转换后由DMA把结果搬进内存,整个过程不占CPU。采样率由定时器的PSC和ARR决定,计算公式是Fs = TIM_CLK / (PSC+1) / (ARR+1)。实现时要注意TIM_CLK不是168MHz,通常挂在APB1上,最高84MHz,除以分频和重装载值后才是真正的采样率。
DMA配置用循环模式,每当传输完成一轮4096点,就触发一次转换完成回调。下面代码是HAL风格的大致结构,具体引脚号和DMA通道需要按实际板子接线调整:
#define ADC_SAMPLES 4096u static uint16_t adc_buf[ADC_SAMPLES]; void sample_init(TIM_HandleTypeDef* htim) { /* ADC1启用外部触发,触发源为定时器TRGO */ ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_4; sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES; HAL_ADC_ConfigChannel(&hadc1, &sConfig); /* DMA循环模式,外设到内存,数据宽度半字 */ hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc.Init.Mode = DMA_CIRCULAR; HAL_DMA_Init(&hdma_adc); __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc); HAL_TIM_Base_Start(htim); HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, ADC_SAMPLES); } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { process_frame(); /* 一轮4096点采集完成 */ } }这里的SamplingTime不是采样周期,而是ADC单次转换的采样保持时间,84个ADC时钟周期是为了降低信号源内阻带来的采样误差。真正决定Fs的是定时器,所以线上调试时必须回读定时器的实际频率,而不是想当然地把SAMPLE_RATE写成4096.0f。如果外部晶振不准确或者定时器分频有偏差,测频结果会出现比例性偏移,比如真实采样率是4080Hz,你按4096算,所有频率读数都会偏大0.4%。我习惯在调试串口打一帧采样数据,用示波器测一下相邻采样点之间的间隔,确认偏差小于0.1%再继续往下做。
3.2 加窗与对数插值:让频率输出稳定在1Hz档位
ADC拿到的原始数据在送入FFT之前,先要减去直流偏置,再乘窗函数。减去2048这个中点值,可以防止直流分量占据了0号bin把动态范围吃掉。接着把数据归一化到-1到1之间,然后乘上Hann窗。为什么要加窗:只要被测信号的频率不是采样率除以点数的整数倍,基波能量就会泄漏到相邻bin,峰值位置不再精确。Hann窗把泄漏压低到主瓣附近,但代价是主瓣变宽,峰值位置必须靠插值恢复。
频率估计用对数幅值抛物线插值,比线性插值稳定得多。原因是Hann窗的频谱主瓣在对数坐标下更接近抛物线,插值残差更小。具体步骤:先找到幅度谱中最大值所在的bin,取这个bin左右各一个bin的对数幅值,计算偏移量d。代码如下:
uint32_t peak = 0; float max_mag = 0.0f; for (uint32_t i = 1; i < FFT_SIZE / 2 - 1; i++) { float m = mag_buf[i]; if (m > max_mag) { max_mag = m; peak = i; } } if (peak < 2) { peak = 2; /* 远离DC,避免低频噪声干扰 */ } float p = logf(mag_buf[peak - 1]); float c = logf(mag_buf[peak]); float n = logf(mag_buf[peak + 1]); float d = 0.5f * (p - n) / (p - 2.0f * c + n); float freq = ((float)peak + d) * SAMPLE_RATE / FFT_SIZE;这个d的理论范围是-0.5到0.5之间,如果算出来的d超过±1,说明信号太弱或旁边有个等幅干扰,这时freq结果不要信,宁可标记为无效帧也不要把跳变值显示出来。为了让读数稳定,我还会对连续几帧的freq结果做一次滑动中值滤波,只取中间值,对抑制偶发的噪声毛刺很有效。这里的1Hz分辨率说的是能分辨两个相邻频率分量,而插值让单一频率读数的精度达到了0.01Hz量级,两者不要混为一谈。
3.3 主循环里跑FFT与峰值搜索:一块代码说清整个链路
把前面的环节串起来,整个处理流程可以放在DMA回调里直接调用,也可以把回调里只置标志位,主循环里再处理。我一般倾向于回调置标志、主循环处理,这样FFT计算不会阻塞DMA采样,也不容易在中断里踩堆栈。完整的一帧处理函数大致如下:
static void process_frame(void) { float vin = 3.3f / 4096.0f; /* 12位ADC,满量程3.3V */ for (uint32_t i = 0; i < FFT_SIZE; i++) { float x = (float)adc_buf[i]; x = (x - 2048.0f) * vin; /* 去除直流偏置,得到实际电压 */ x *= win_buf[i]; /* 施加Hann窗 */ fft_buf[2 * i] = x; fft_buf[2 * i + 1] = 0.0f; } arm_cfft_f32(&arm_cfft_sR_f32_len4096, fft_buf, 0, 1); arm_cmplx_mag_f32(fft_buf, mag_buf, FFT_SIZE / 2); float freq = estimate_fundamental_freq(); float thd = calculate_thd(freq); send_result(freq, thd); }这段代码把整条链路的顺序固定下来了:先预处理采样数据,再做复数FFT,接着求幅值谱,最后估频和算谐波。arm_cmplx_mag_f32的输出是双单边谱,下标0对应DC,下标FFT_SIZE/2对应奈奎斯特频率,这里只取前半部分合理。注意预处理中减去的2048是按12位ADC中值算的,如果ADC参考电压不是3.3V,要换成VREF的实测值。整个处理过程中堆栈开销最大的就是arm_cfft_f32,它内部会使用临时缓冲区,所以在中断里调用时务必确认栈空间足够,否则会出现随机进HardFault的玄学问题。
4. 谐波与失真度:在基波周围做“局部寻峰”,别拿全谱段硬算
4.1 谐波搜索的排除带与窗口宽度设定
谐波分析不是把FFT幅值谱里每个整数倍频点直接取值就完事,因为基波频率本身有微小误差,实际谐波不可能正好落在整数bin上。正确做法是先拿到基波频率f0,再对每个谐波次数k,在k*f0附近开一个局部搜索窗口,窗口里找局部峰值。窗口宽度怎么设?Hann窗主瓣约4个bin,也就是4Hz,加上参数估计误差,我一般用±4Hz作为搜索半径,也就是±4个bin。如果信噪比很差或者信号有频率抖动,可以放宽到±6Hz,但太宽会把邻近的窄带噪声当成谐波。
排除带也很关键。当基波非常强时,其泄漏会蔓延到2倍频附近,造成二次谐波“虚高”。Hann窗在远离主瓣处衰减比较快,但如果基波比二次谐波大40dB以上,泄漏仍然可能压过真实谐波。常见做法是在谐波搜索时至少跳开基波位置左右各4个bin,同时把参与THD计算的谐波幅度设置一个门限,低于门限的直接置零。这里的门限我建议用相对基波的比值,比如基波幅值的0.001倍,对应-60dB,也在ADC的12位分辨能力之内。门限设太低,噪声全部算成谐波,失真度虚高;门限设太高,真实谐波被丢掉,失真度偏小。第一次调试时可以把所有搜索到的分量都打出来看一眼,再定门限。
4.2 THD计算与噪声门限:为什么算出来的失真度总偏大
THD的计算公式是各次谐波幅值平方和开根号,再除以基波幅值,通常算到10次谐波。IMD和噪声不参与THD,但这在测量中是很难严格分开的。用4096点、1Hz分辨率的FFT,噪声底在理想条件下大约-70dB到-80dB之间,但ADC本身的量化噪声、前级放大器的噪声、还有采样抖动,都会把底噪抬起来。如果门限设在-60dB,而噪声底已经被抬到-55dB,那么每个谐波位置附近都可能抓到噪声峰,THD会明显偏大。解决方向有两种:一是降低门限但配合幅度一致性检查,单独一个点超过门限不算,要求连续两个搜索点都超过才算;二是对多帧求平均,幅值谱平滑后再算THD,代价是响应变慢。
计算代码里要特别注意谐波搜索范围不要越界,同时用int32_t处理bin下标,避免unsigned减法回绕:
#define MAX_HARMONIC 10u #define GATE_RATIO 0.001f /* 相对基波的-60dB门限 */ static float calculate_thd(float fundamental_freq) { float h1 = mag_buf[peak_bin]; if (h1 < 1e-6f) { return 0.0f; } float acc = 0.0f; float gate = h1 * GATE_RATIO; for (uint32_t h = 2; h <= MAX_HARMONIC; h++) { float target_bin = fundamental_freq * (float)h * FFT_SIZE / SAMPLE_RATE; int32_t k0 = (int32_t)(target_bin + 0.5f); int32_t start = k0 - 4; int32_t end = k0 + 4; float local_max = 0.0f; for (int32_t j = start; j <= end; j++) { if (j < 1 || j >= (int32_t)(FFT_SIZE / 2)) { continue; } float m = mag_buf[j]; if (m > local_max) { local_max = m; } } if (local_max > gate) { acc += local_max * local_max; } } return sqrtf(acc) / h1; }这段代码里h1直接用峰值bin的幅值,没有做窗增益补偿,但THD是比值计算,窗增益在分子分母里抵消,所以不影响结果。如果你顺带要显示各次谐波的绝对幅度,就必须除以Hann窗的相干增益0.5,也就是把幅值乘2。另外,当基波频率低于100Hz时,10次谐波也才1kHz,还在奈奎斯特范围内,但搜索窗口下标的起点要避开DC附近排除带。我遇到过测一个50Hz信号,二次谐波搜索结果里包含了基波泄漏的尾巴,门限一放,THD直接跳到5%,实际上信号失真只有0.5%,这就是排除带没留够。
4.3 幅值恢复与校正:窗函数增益、ADC基准与分压系数
如果仪器要输出绝对的谐波电压而非百分比,那么幅值链路上有三处必须校准:窗函数增益、ADC参考电压、前端分压比。Hann窗的相干增益是0.5,意味着对一个纯正弦波,FFT峰值bin的幅值只有真实幅值乘以0.5再乘以N/2的缩放关系,直接用arm_cmplx_mag_f32的原始结果会偏低一倍。恢复时需要乘2,再乘上采样数据的归一化系数和分压系数。
校准流程我一般是这样做的:用一个已知幅值的正弦信号接进去,频率选在整bin位置,比如Fs=4096时选512Hz,这样128点一个周期,正好落在128号bin上。测量到的峰值幅值记为m,已知有效值或峰值记为A,那么总增益系数就是A/m。调试时把这组系数存进flash,开机时读取,比理论推算省心很多。分压系数如果是一个固定电阻分压网络,还要考虑电阻精度和温度漂移,工业级方案会预留软件校准接口,让产线用标准源标定一次。
还有一个常见的坑:ADC的参考电压不是3.3V整。很多板子用LDO输出3.3V,误差可能到±1%,对频率不影响,但谐波幅度会跟着偏,所以幅值校准时要用万用表实测VREF代入计算,不要直接写3.3。校准之后失真度百分比因为比值关系不受VREF影响,但绝对谐波电压会。做产品方案时,至少保留这组校准系数的软件入口,后续产线调起来会省很多事。
5. 相位差测量与同步采集避坑:偏角、跳变、反相的三类现场
5.1 双ADC同步采样:相位差测量的第一步
测两个波形的相位差,最核心的前提是两路采样必须严格同时,而不是先后采。先后采会有固定的时间偏差,折算成相位会随信号频率变化,频率越高误差越大。F407上有三个ADC,可以配置ADC1和ADC2为规则同步模式,由同一个定时器触发同时启动转换。配置时重点在TwoSamplingDelay,它决定两路ADC启动之间的间隔,设置为1个ADC周期,可以把通道间的时间偏移压到很小。下面是用HAL库配置双ADC同步模式的核心片段:
ADC_MultiModeTypeDef multimode = {0}; multimode.Mode = ADC_DUALMODE_REGSIMULT; /* 规则通道同步模式 */ multimode.DMAAccessMode = ADC_DMAACCESSMODE_DISABLED; multimode.TwoSamplingDelay = ADC_TWOSAMPLINGDELAY_1CYCLE; HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode);这段配置只负责把ADC1和ADC2“绑定”成同步触发,实际信号仍然分别走各自的通道和DMA缓冲区。同步模式建立后,ADC1和ADC2在同一个定时器边沿开始转换,DMA搬运各自的采样结果到两个独立的数组,这样后续FFT处理时,两路数据的起点就是对齐的。不要在回调里把两路数据分别拿去做FFT再比较相位,只要数据起点是对齐的就没问题。
还有一点容易被忽略:如果两个输入通道用了不同的抗混叠滤波器,比如通道A是二阶RC低通,通道B是一阶RC低通,那么在分析频率附近会引入不同的相位滞后,相位差结果会随频率变化。做相位测量时,两个输入通道的前级滤波器必须做到一样的截止频率和阶数,否则软件怎么校准都补不齐这个频变误差。
5.2 基波相位的提取与相位差计算公式
两个通道各做一次FFT之后,从各自幅值谱中找到基波峰值bin,再取出该bin对应的复数的实部和虚部,用atan2f求相位。受窗函数和栅栏效应影响,单通道的绝对相位和实际信号过零点并不严格对应,但两路信号频率相同、窗函数相同、泄漏模式相同,相位差值仍然是可信的。相位差的范围是-π到π,直接相减后要归一到这个区间,否则会看到0到2π之间跳变的读数。
float reA = fft_a[2 * peak_a]; float imA = fft_a[2 * peak_a + 1]; float reB = fft_b[2 * peak_b]; float imB = fft_b[2 * peak_b + 1]; float phase_a = atan2f(imA, reA); float phase_b = atan2f(imB, reB); float diff = phase_a - phase_b; /* 归一到 [-PI, PI] */ while (diff > PI) diff -= 2.0f * PI; while (diff < -PI) diff += 2.0f * PI;这段代码里peak_a和peak_b分别是两路基波的峰值bin。正常工作时两个通道应该用同一个bin,因为频率理论上一致,但实际会有微小差别,所以各自取峰值更稳。如果两路信号幅度相差很大,比如一路饱和、一路只有几毫伏,那么弱信号通道的相位会被噪声淹没,diff会乱跳。我一般会加一个有效状态判断:两路基波幅值都大于设定门限时才更新相位差输出,否则保持上一帧有效值,这样仪表不会在弱信号时显示无意义的跳变数字。
5.3 现象排查:相位差为什么总有一个固定偏角
第一个高频踩坑现象是:相位差读数始终比真实值偏大或偏小一个固定角度,而且频率越高差得越多。原因是两路没有真正同步采集,比如用ADC1的注入组轮询采A路,再切到B路,中间隔了几个微秒。解决办法是改用双ADC同步模式,同步之后残留偏差很小。如果同步模式下仍然有固定偏角,就可能是两个输入前级的滤波电容参数不一致,这时候只能做软件补偿:接入同相信号,记录初始偏差phase_offset,之后每帧直接减掉。补偿代码只有一行:
diff -= phase_offset; if (diff > PI) diff -= 2.0f * PI; if (diff < -PI) diff += 2.0f * PI;第二个常见现象是相位差读数在接近±180°时来回跳,一会儿显示+179°,一会儿显示-179°。这不是电路问题,而是相位差本来就接近边界,atan2f的一个小抖动就会让结果越过±π。解决方法是不要对瞬时帧直接输出,而是把连续几帧的diff都归到同一方向后再平均,或者记录上一帧的符号,发现跳变幅度大于π时按“越过边界”处理。更简单的方法是在后端加一个小的低通滤波,但要注意接近边界时不能简单平均,必须先把角度展开成连续时间序列再滤波。
第三个现象是反相,显示180°而且很稳定。常见原因是某个通道的输入极性反了,或者前级差分放大器接反,也有可能是ADC通道引脚的采样保持电压反了。处理办法是先让两个输入端口短接同一信号源,看相位差是否约等于0°,如果显示180°,先查硬件极性,再查软件里两路缓冲区的通道映射关系是否颠倒了。校准逻辑里要保留这类自检模式,产线调试时能省很多排查时间。
6. 用信号发生器给这套1Hz分辨率方案“验明正身”
验证这套系统不能只看内部计算,最直接的办法是用一台信号发生器输出标准正弦波,把频率设成整数和非整数两种,比如50Hz、50.3Hz、997Hz,再用高精度频率计做参考。判定标准我一般按0.05Hz以内算合格,因为信号发生器本身的频率精度也在0.01Hz量级,插值估频的误差主要来自ADC噪声和窗函数,0.05Hz是个合理余量。相位差验证则用双通道信号发生器同时输出同频信号,一个通道固定0°,另一个通道从0°扫到300°,每30°记录一次,误差在1°以内就可以认为相位差测量可用。下面是一张典型验证记录表:
| 参考频率 | FFT读数 | 误差 | 参考相位差 | 实测相位差 |
|---|---|---|---|---|
| 50.00 Hz | 50.002 Hz | +0.002 Hz | 0.0° | 0.08° |
| 50.30 Hz | 50.296 Hz | -0.004 Hz | 90.0° | 90.15° |
| 997.00 Hz | 997.03 Hz | +0.03 Hz | 300.0° | 299.92° |
验证无头绪时可以从一个桌面技巧开始:不接任何仪表,直接把一个已知频率的方波信号接进去。方波的谐波非常丰富,如果谐波列表能同时看到3次、5次、7次谐波且幅值比接近1/3、1/5、1/7,说明整条链路是通的;如果看到的谐波位置不对,或者THD算出来离理论值48%差太多,问题大概率出在采样率配置或频谱峰值搜索上。这个技巧对新手尤其好用,不用高精度仪表也能快速判断方案是否落地。
我现在做这类测量仪器有一个固定习惯:先信串口打印的第一帧原始数据,再信FFT输出,最后才信校准后的显示值。任何一个环节的数据“看起来合理”都不是结论,必须用已知信号做交叉验证。FFT方案最大的优势是没有模拟鉴相电路的温度漂移,最大的风险是数字链路里某个细节错了却表现得很正常。希望帮到你。
本文还有配套的精品资源,点击获取