资讯动态

STM32F407实现1Hz分辨率FFT:频率、谐波、THD与相位差测量

发布时间:2026/10/14 19:03:40 来源:尧图企业网站定制
简介基于STM32F407的FFT频谱分析工程实现1Hz频率分辨率适用于信号频率测量、谐波分析、失真度评估与双波形相位差测量面向嵌入式开发、音频处理与电力监测等方向的工程师和进阶学习者。工程利用Cortex-M4硬件浮点单元完成数字信号处理可覆盖0~2kHz频段内的频谱分析有效识别主导频率、谐波成分与总谐波失真。压缩包共316个文件以C源文件、头文件、工程配置及编译输出为主包含STM32标准外设库、LCD显示、UCOS系统相关代码并附带hex、axf等烧录文件及PDF说明文档便于对照工程结构进行二次开发。包体大小约3.71MB结构紧凑完整。该资源已有10448人学习下载适合需要直接参考或移植FFT应用的开发者使用。通过该工程可快速获得完整算法实现与硬件适配方案节省从零搭建的时间。1. 一块 STM32F407 能把 FFT 分辨率做到 1Hz顺便把频率、谐波、失真度、相位差一次算完接到一个活儿手头只有一块 STM32F407 开发板要测量一台音频功放的输出信号——基频多少、谐波到哪几次、失真度多大、左右声道相差多少相位。没有台式音频分析仪那就用 F407 的 ADC 采样跑一段 FFT分辨率为 1Hz把频率、谐波、失真度、相位差全部算出来。这个方向对做音频、电力监控、振动分析和电机驱动的从业者相当实用一块十几块的芯片就能顶一台信号分析仪的核心功能。整套方案从原理到落地并不复杂真正的功夫在采样链路的配置和几个容易被忽略的边界条件。2. 分辨率 1Hz 的硬约束采样率、点数与 FFT 输出频率的关系2.1 1Hz 分辨率是怎么来的Δf fs / NFFT 输出的相邻频点间隔由采样率 fs 和点数 N 唯一决定公式是 Δf fs / N。想要 1Hz 分辨率必须保证 fs / N 1Hz也就是采样率数值上等于点数。最常用的组合就是 fs 4096HzN 4096或者 fs 2048HzN 2048同样得到 1Hz。注意这不是软件能“算出来”的而是采样参数事先定死的。这里有个概念要区分分辨率和测量精度不是一回事。分辨率 1Hz 意味着你能区分 100.0Hz 和 101.0Hz 这两个相邻频点而测量精度指的是读数接近真实值的程度。通过插值算法频率测量精度可以做到 0.01Hz 量级但分辨两个靠近的信号仍然受 1Hz 限制。很多人在这一步就搞混导致后面做谐波分析时对频谱峰值的解读出错。另外要意识到一个时间约束Δf 1 / TT 是时域采集窗口长度。1Hz 分辨率意味着每次测量至少要采集 1 秒时域数据。无论点数怎么加采集时间都不会变短。这是实时性设计的第一道红线。2.2 为什么 STM32F407 做这件事是顺手的F407 是 Cortex-M4 内核带硬件 FPU主频 168MHz。CMSIS-DSP 库提供了现成的 arm_rfft_fast_f32 函数4096 点实数 FFT 一次耗时才几毫秒相对 1 秒的采集窗口来说实时性非常充裕。相比 M3 或 M0省掉了一整轮 FFT 手写优化工作。它的 RAM 足够跑浮点 FFT。4096 点实数 FFT 的输入数组 16KB输出复数数组 32KB加上库内部临时缓冲几十 KB 的占用对 F407 的 192KB SRAM 来说很宽裕。再往上到 8192 点也能跑但内存明显紧张所以 4096 点是这个场景下最舒服的配置。第三点最关键F407 支持双 ADC 规则同步采样也就是 ADC1 和 ADC2 可以同时启动转换。两个波形要测相位差就必须保证两路信号在同一个采样时刻被采到。用单 ADC 轮流扫描两个通道通道间天然存在一个转换时间差相位差结果会引入系统性偏差。双 ADC 硬件同步把这个隐患从根上去掉了。2.3 参数组合怎么选拿 4096 点做模板下面这张表列出常见组合全部保证 Δf 1Hz。采样率 fs点数 N最大可测频率fs/2时域采集窗口浮点内存占用输入输出2048Hz20481024Hz1s16KB 左右4096Hz40962048Hz1s48KB 左右8192Hz81924096Hz1s96KB 左右最大可测频率受奈奎斯特限制有效频谱范围是 0 到 fs/2。谐波分析的次数上限也随之确定基频附近能看的谐波次数最多到 floor((fs/2) / f_base)。以 4096Hz 采样、被测基频 50Hz 为例理想情况下能数到 40 次谐波。实际工程里我一般看 2 到 10 次谐波就足够评估失真度后面代码里也会按这个范围写。选型建议被测信号最高频率在 2kHz 以内、RAM 预算充足用 fs 4096Hz、N 4096。这个组合既留出了 2kHz 的分析带宽又不会让 F407 的内存吃紧后面的代码都按这个配置展开。3. 采样链路搭建定时器触发 双 ADC 同步采样 DMA 搬运3.1 定时器触发频率配到 4096Hz参数怎么算FFT 对采样点的时间均匀性极其敏感。用 ADC 连续转换加软件延时的方式中断响应和代码分支会让采样间隔产生抖动反映到频谱上就是噪声底抬高。正确的做法是用定时器更新事件触发 ADC 启动转换让硬件决定采样时刻。以 TIM8 为例它挂在 APB2 总线上时钟 84MHz。目标是输出 4096Hz 触发频率配置 PSC 0ARR 20507实际频率 84MHz / (20507 1) ≈ 4095.97Hz偏差大约 0.0007%这个误差对 1Hz 分辨率来说已经足够用了。不是说一定要配出精确的 4096.000Hz后面坑里会讲怎么校准这个微小偏差。/* TIM8 定时触发 ADC目标是 4096Hz 采样率 */ TIM_TimeBaseInitTypeDef timerInit; timerInit.TIM_Prescaler 0; timerInit.TIM_CounterMode TIM_CounterMode_Up; timerInit.TIM_Period 20507; /* 84MHz / (205071) ≈ 4095.97 Hz */ timerInit.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM8, timerInit); /* 用更新事件做 ADC 触发源 */ TIM_SelectOutputTrigger(TIM8, TIM_TRGOSource_Update); TIM_Cmd(TIM8, ENABLE);这里的 PSC 和 ARR 是分频核心参数定时器时钟先除以 (PSC1)再数 (ARR1) 个脉冲输出一次事件。TIM8 的时钟源来自 APB2若 APB2 分频系数不为 1定时器时钟会翻倍配参数前先确认时钟树。PSC 0 表示不分频ARR 才是决定频率的主参量。3.2 双 ADC 规则同步采样ADC1 做主ADC2 从动相位差测量的基础是两路信号同时采样。F407 的 ADC1 和 ADC2 可以组成规则同步模式由同一个外部触发信号TIM8 的 TRGO同时启动两路转换转换结果先后落寄存器。这种模式下只需要给 ADC1 配置 DMA 请求数据会按先 ADC1 后 ADC2 的顺序排进缓冲区省掉一路 DMA 通道。ADC 时钟从 APB2 分频得到84MHz 分 4 得到 21MHz稳定且满足 F407 最高 36MHz 的约束。采样时间我一般设成 15 周期既能保证信号源阻抗较高时采样电容充分充电又不会拖慢整体节奏。每通道单次转换约 (1512) / 21MHz ≈ 1.29us对 4096Hz 采样率绰绰有余。/* 双 ADC 规则同步模式初始化 */ ADC_CommonInitTypeDef adcCommonInit; adcCommonInit.ADC_Mode ADC_Mode_RegSimult; /* 规则同步 */ adcCommonInit.ADC_Prescaler ADC_Prescaler_Div4; /* 84MHz / 4 21MHz */ adcCommonInit.ADC_DMAAccessMode ADC_DMAAccessMode_1; /* 双 ADC 共用 ADC1 的 DMA先读 ADC1 后读 ADC2 */ adcCommonInit.ADC_TwoSamplingDelay ADC_TwoSamplingDelay_5Cycles; ADC_CommonInit(adcCommonInit); /* ADC1 主通道PA0 */ ADC_InitTypeDef adcInit; adcInit.ADC_Resolution ADC_Resolution_12b; adcInit.ADC_ScanConvMode DISABLE; /* 每路只采一个通道 */ adcInit.ADC_ContinuousConvMode DISABLE; /* 由外部触发启动 */ adcInit.ADC_ExternalTrigConvEdge ADC_ExternalTrigConvEdge_Rising; adcInit.ADC_ExternalTrigConv ADC_ExternalTrigConv_T8_TRGO; /* TIM8 触发 */ ADC_Init(ADC1, adcInit); /* ADC2 从通道PA1触发源不用再配由同步模式接管 */ adcInit.ADC_ExternalTrigConv 0; ADC_Init(ADC2, adcInit); ADC_ExternalTrigConvCmd(ADC1, ENABLE); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_Cmd(ADC2, ENABLE);两个 ADC 的通道配置要放在外面单独完成这里略去 ADC_RegularChannelConfig 的调用。关键点是 ADC2 不要再独立配外部触发否则同步时序会被打破。ADC_DMAAccessMode_1 决定 DMA 读出顺序这个字段在不同固件库版本里名字可能略有差异但含义不变。3.3 DMA 循环模式与双缓冲切换采样数据的搬运交给 DMACPU 只处理中断标志。用循环模式让 DMA 不停地把 ADC 结果写进内存每写满半块缓冲区触发一次半传输中断每写满整块触发传输完成中断。CPU 在中断里只做标志位登记主循环检测到标志后处理已填充的数据块这样不会丢采样点。以 4096 点为例缓冲区要容纳两个通道各 4096 个 16 位 ADC 原始数据所以是 8192 个 uint16_t。为什么不用 float 数组直接给 FFT因为 DMA 搬运的是 ADC 外设寄存器里的 12 位原始值先以 uint16_t 存放主循环里再统一转换成 float职责清晰。#define N_POINTS 4096 volatile uint16_t adc_buffer[2 * N_POINTS]; /* 8192 个 u16按 [ADC1][ADC2] 交替排列 */ volatile uint8_t dma_half_done 0; volatile uint8_t dma_done 0; void DMA2_Stream0_IRQHandler(void) { if (DMA_GetITStatus(DMA2_Stream0, DMA_IT_HTIF)) { dma_half_done 1; /* 前半块填满 */ DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_HTIF); } if (DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF)) { dma_done 1; /* 整块填满 */ DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF); } }DMA 配置时要指定外设地址、存储器地址、传输长度和循环模式其中传输长度就是 8192 个半字。外设地址是 ADC1 的 DR 寄存器存储器地址指向 adc_buffer。注意 DMA_IT_HTIF 和 DMA_IT_TCIF 的清除顺序先清标志再处理业务逻辑避免中断重入。两个标志位在主循环里处理完当前数据后必须手动清零。主循环处理逻辑是这样检测到 dma_half_done 时处理 adc_buffer 的前半段前 4096 个 u16检测到 dma_done 时处理后 4096 个 u16处理完清零对应标志。这样数据仍在 DMA 连续写入不会互相踩踏。F407 的 DMA2 流 0 连到 ADC1中断优先级建议配到较高档位保证采样不丢。4. 从频谱到测量结果频率、谐波、失真度和相位差的计算4.1 峰值搜索与频率读数1Hz 粗读加抛物线插值拿到时域数据先转 float然后调用 CMSIS-DSP 的实数 FFT 函数把时域信号变成频谱。4096 点输入经过 arm_rfft_fast_f32 后得到复数频谱实部和虚部交替存放长度 2N8192 个 float其中前 2048 个复数对应 0 到 2048Hz 的有效频点。注意跳过第 0 个频点那是直流分量幅度通常远大于交流信号不跳过会让峰值搜索误判。/* 实数 FFT4096 点时域 - 8192 个浮点复数输出 */ arm_rfft_fast_instance_f32 fftInst; arm_rfft_fast_init_f32(fftInst, 4096); arm_rfft_fast_f32(fftInst, timeData, fftOutput, 0); /* 0 表示正向变换 */ /* 计算幅度谱2048 个有效频点 */ float32_t mag[2048]; arm_cmplx_mag_f32(fftOutput, mag, 2048); /* 跳过直流找最大幅度频点 */ uint32_t kMax 1; float32_t vMax 0; for (uint32_t k 1; k 2048; k) { if (mag[k] vMax) { vMax mag[k]; kMax k; } } /* Δf 1Hz 时bin 编号就是频率粗略值 */ float freqCoarse (float)kMax;arm_rfft_fast_f32 的第三个参数 0 是正向变换标志。输出排布是实部虚部交替比如频点 k 的实部在 fftOutput[2k]虚部在 fftOutput[2k1]。这个布局后面算相位时会再次用到。峰值搜索得到的 kMax 直接就是 1Hz 分辨率下的频率读数但想要更高精度可以在 kMax 附近做抛物线插值。float y0 mag[kMax - 1]; float y1 mag[kMax]; float y2 mag[kMax 1]; float delta 0.5f * (y0 - y2) / (y0 - 2.0f * y1 y2); float freqFine (float)kMax delta;插值公式的数学含义是把峰值附近三个幅度点拟合成一条抛物线delta 是抛物线顶点的偏移量。信噪比良好、信号较纯时freqFine 的精度能到 0.01Hz 量级。但要注意两个边界kMax 若等于 1y0 取了直流点kMax 若等于 2047y2 越界。实际代码里要先做边界保护否则数组越界会返回一个垃圾值。4.2 谐波分析与 THD 失真度幅度平方和开根号失真度也叫 THD定义是所有谐波功率之和除以基波功率开根号后换算成百分比。谐波在频谱上的位置是基波频点的整数倍基波在 kMax二次谐波在 2×kMax三次在 3×kMax。用这个关系直接查频谱幅度不需要额外搜索峰值。/* 从 2 次到 10 次谐波累加幅度平方算 THD 百分比 */ float harmSum 0.0f; for (uint32_t n 2; n 10; n) { uint32_t k n * kMax; if (k 2048) break; /* 超出奈奎斯特范围就停 */ harmSum mag[k] * mag[k]; } float thdPercent sqrtf(harmSum) / mag[kMax] * 100.0f;为什么用幅度而不是有效值因为 THD 公式里分子分母的有效值都带同一个 1/√2 系数约掉之后直接用幅度比。如果你的目的是对比不同设备统一用幅度比就好。谐波次数上限取 10 次是一种折中太高次的谐波幅度通常已经淹没在噪声底里算进去反而拉高 THD。若被测基波频率较高循环里要判断 k 是否超出 2048超出就 break。这一个计算块有个前提kMax 必须是基波的真实位置。如果信号本身失真严重或者加窗不合适频谱峰值可能偏离实际基频谐波索引链全部错位。这也是为什么谱峰搜索要带插值同时被测信号进来之前最好有一个带通滤波器把带外噪声和直流偏置压一压。4.3 相位差测量两个通道同帧 FFT取基波 bin 相位做差相位差的实现思路是双通道数据分别做 FFT在两个频谱的基波频点处各自取相位角然后相减。取相位的函数用 atan2f它能根据实部和虚部的正负号正确判断象限得到的角度范围是 -180° 到 180°。atanf 只有一个参数遇到实部为负的分母会出错所以这里必须用 atan2f。/* 通道 A 和通道 B 的基波相位角单位弧度 */ float reA fftOutA[2 * kMax]; float imA fftOutA[2 * kMax 1]; float reB fftOutB[2 * kMax]; float imB fftOutB[2 * kMax 1]; float phA atan2f(imA, reA); float phB atan2f(imB, reB); /* 相位差并折算到 -180° ~ 180° */ float diff phA - phB; while (diff 3.14159265f) diff - 2 * 3.14159265f; while (diff -3.14159265f) diff 2 * 3.14159265f; float diffDeg diff * 180.0f / 3.14159265f;需要注意这里的正负含义diffDeg 为正表示通道 A 相位超前通道 B为负表示滞后。如果你习惯以通道 B 为参考就取相反数。另一个坑是相位差公式里必须用同一帧数据里的两个通道程序里要确保 fftOutA 和 fftOutB 是从同一次双 ADC 同步触发采集得到的样本算出来的不是两帧拼接。相位测量精度依赖两个条件一是基波频点 kMax 找得准二是两通道采样时刻严格同步。前者用前面的抛物线插值解决不了的——相位插值比幅度插值复杂得多所以工程上更常用的是把采样率做好、把触发做好让整周期采样成立后者就是双 ADC 同步模式。如果这两点没保证相位差读数会无规律抖动下文会在踩坑章节里展开说。5. 相位差测量的三个典型坑与排查顺序5.1 先定位现象再动手改代码遇到读数不对的时候我习惯按这个顺序排查先看时域波形确认 ADC 有没有采到干净的数据再看频谱形态判断是不是泄漏或混叠最后才怀疑算法本身。很多相位差问题其实出在频率测量不准上基波频点偏了一个 bin相位结果自然全乱。把这个思路理顺很多“玄学”问题都能在十分钟内定位。5.2 坑一频率读数整体偏大或偏小偏差固定现象信号发生器输出精确的 1000Hz 正弦FFT 读数是 1000.4Hz 或 999.6Hz而且偏差比例固定。原因定时器分频配不出精确的 4096Hz晶振本身也存在 ppm 级误差。ARR 20507 只是把频率凑到接近 4096Hz实际采样率与标称值之间存在系统性偏差这个偏差会线性地影响频率读数。解决用已知频率的参考信号做一次校准。测出一个频率读数误差比例记到 Flash 里后续所有频率读数乘以修正系数。校准流程我一般写成一个独立函数输入参考频率 fRef测量读数 fMeas计算修正系数 fRef / fMeas下次测量时freqTrue freqCoarse * coeff。注意校准要在信号稳定、插值算法生效的条件下进行。5.3 坑二频谱在信号附近“变胖”基波旁边全是裙边谐波幅度失真现象输入一个干净的 1000Hz 正弦幅度谱上 1000Hz 附近出现一大片衰减拖尾而不是一个窄峰。谐波分析的结果里多了很多不存在的频率分量。原因非整周期采样造成的频谱泄漏。4096 点窗口刚好采集 1 秒如果被测信号的周期不能整除 1 秒窗口首尾就不是完整的整数个周期FFT 默认认为时域信号首尾相连于是产生剧烈跳变能量泄漏到所有频点。解决两个方向。一是加窗函数汉宁窗或者布莱克曼窗能把泄漏压低几个数量级代价是主瓣变宽1Hz 分辨率下两个相距很近的频率分量更难分开。二是尽量让采样点和信号周期匹配比如被测基波固定为 50Hz 时把采样率调成 3200Hz 且点数仍为 4096这样 50Hz 落在整周期上。两者权衡下来通用性最强的还是加窗结合插值但要明确加窗之后 THD 公式里的幅度需要乘以窗函数的相干增益修正系数否则基波和谐波幅度都比真实值小。5.4 坑三相位差读数忽大忽小但频率读数很稳现象同一对信号重复测量相位差今天 35°明天 42°测量期间仪器和接线都没动过频率读数却始终稳定。原因两路信号不是同一时刻采的或者触发时刻在抖动。单 ADC 轮询两个通道时两路之间天然隔了一个通道的转换时间这个时间差换算成相位偏转会随频率变化触发信号带噪声时每次采样启动时刻漂移相位差自然跟着跳。解决改用双 ADC 同步采样也就是第 3 章描述的那种硬件同步结构。再做一次硬件整形被测信号进 ADC 之前先用比较器把正弦整成方波再用方波沿触发定时器同步触发抖动可以从几十微秒压到几百纳秒。如果只是低频测量、要求不高多采几帧求平均值也能缓解但治标不治本。5.5 坑四FFT 结果偶尔整帧丢失或数据被 DMA 覆盖现象程序跑一段时间串口输出的频谱突然变了一个样幅度整体不对劲过一会儿又恢复或者某次测量结果里混进了上一帧的数据。原因DMA 在循环写的同时CPU 处理数据不及时半传输中断和传输完成中断里的标志位被主循环遗漏下一次 DMA 半帧到来时CPU 还在跑上一次的 FFT缓冲区被覆盖。解决把标志位处理改成“缓冲区分块锁”的思路中断里切换当前可读写缓冲区的索引主循环检测到新索引变化才处理对应的数据块处理完标记释放。同时给 DMA 中断配较高的抢占优先级防止被其他外设中断长时间打断。实际上 4096 点 FFT 在 F407 上只要几毫秒远快于 4096 点采集所需的 1 秒正常情况下 CPU 有大量空闲出现覆盖基本可以断定是中断优先级或标志位管理出了逻辑问题。6. 闭环验收三组测试数据和一个进阶技巧整套系统写完先别急着接真实信号用信号发生器和示波器做三组闭环验证。第一组验证频率读数分别输出 100Hz、101Hz、1000Hz 正弦确认读数对应 100.0、101.0、1000.0 左右误差在插值后小于 0.05Hz。再把两路信号输出一个 100Hz 和一个 101Hz 混合确认频谱上能清晰看到两个峰。第二组验证谐波和 THD输出一个 1kHz 正弦加 3kHz 正弦幅度为基波的 1/3理论 THD 约 33.3%程序算出来应该非常接近如果信号本身是标准方波THD 理论值约 48.3%这也是一个不错的参考点。第三组验证相位差用信号发生器双通道输出同频、相位差 30°程序读取应该在 29.5° 到 30.5° 之间再把相位差调成 -90° 和 179°确认正负和边界情况没被角度折算搞乱。一个比较实用的进阶技巧如果你后续只关心基波附近一小段频谱可以不用跑完整 FFT改用 Goertzel 算法只算几个目标频点内存占用和计算开销都大幅下降。但像这个标题里的完整需求——频率、谐波、失真度、相位差全都算——整帧 FFT 的通用性远胜过后缀算法因为谐波分析需要把整个有效频段的幅度谱都拿出来用。最后说一个我的习惯每次改完采样率或点数参数我都会先用信号发生器扫一遍全频段确认频率读数和 THD 都稳定了才接真实前端。这套方案做下来最深的感受是FFT 本身不难难的是把采样触发和两通道同步这种硬件细节压稳。参数链路理顺后面的算法都是顺水推舟。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑