记一次fft重构(二): 矫正算法重构篇

1.前言 在上一节中,我们为fft模块简单的添加了抛物线插值算法,并且衡量了插值算法对不同频率差的幅度与频率的估计情况。然而,在与AI的多轮对线之下,我了解到: 不同窗函数适用的插值算法不同。这就意味着,原来设计的架构(基于算法进行的分类)并不适用。我们需要基于窗函数,对幅值矫正算法重新分类。同时,为矫正算法模块加入相位估计算法,以应对2025-G题(电路模型探究装置)这种需要相位谱(复数谱)的情形。 2.整体架构 2.1输出规范 得益于写抛物线插值时就考虑到了将来需要加入其他频率估计算法的情形。所以之前已经有了统一接口: @dataclass class EstimateData: freq_estimated: float amplitude_estimated: float 加入相位数据只需要在下方加入 phase_estimated: float 即可。 2.2 整体架构 如同《Head First 设计模式》中提到的, 程序的设计模式,应该面向修改关闭,面向拓展开放。面向不变关闭,面向变化打开。这有点类似与《矛盾论》 中提到的"抓主要矛盾"的哲学思想。在本程序中,我们注意到,其变化量有两个: 窗函数(Window function), 插值算法。因此,我们应该面向这两个变化量留出拓展的余地,将窗函数和插值算法解耦,而面向其他无关紧要的细节,我们可以写成紧耦合的算法。 但现在有以下几个问题: 原有的抛物线算法适用于所有窗函数,只不过估计精度不同。 所有窗函数适用的相位矫正算法都相同,但是频差与幅值估计算法均不同 一个窗函数可能有好几种不同的频差估计算法(如矩形窗就有rife和cadan两种频差估计算法),但不是所有算法都适用与某个窗函数。 原有的no_interp 和 parabolic_interp函数是紧耦合的,输入复数谱,频率bin,采样率,采样点数,相干增益后直接输出EstimateData,和新算法(将频率,幅值,相位估计算法拆分成不同函数)的写法有很大不同。 首先,对于问题4,我们采取了简单直接的做法: 构造一个抽象的(通用的)函数estimate_freq_amplitude_phase(),并将其他频率估计函数设置为私有函数,以免产生错误调用。 对于问题4,我在抽象函数estimate_freq_amplitude_phase()中,选择了使用选择分支来解决这个问题,因为旧的模式不会再增加,之后的代码架构一定是新的算法模式,因此我们直接采用了这种紧耦合的方法,不过这对我们后续的拓展毫无影响。 其次,对于问题2-3,我们采用了较为复杂的策略模式来实现。选择策略模式是因为其能较为简单的在嵌入式C代码中实现(使用结构体或swich-case方式达到类似的效果) 我的办法是使用了四个字典来满足我的调用要求:每种窗函数只能调用对应的算法,不能调用适用于其他窗函数的算法。以下为字典列表 由于幅值估计算法每一个窗只有一个,所以只用维护一个算法列表即可。 _AMP_METHODS = { "rect": _amp_rect, "hann": _amp_hann, "hamming": _amp_hamming, "blackman": _amp_blackman, "blackmanharris": _amp_blackmanharris, "flattop": _amp_flattop } 频差估计算法,这个用了三个算法字典,每个字典的作用分别为auto模式下调用的插值算法,不同窗函数能调用的插值算法表,所有算法对应的函数指针(python字典的值能设置为函数,这里姑且不严谨的叫它指针) _DELTA_METHODS = { "rife": _delta_rect_rife, "cadan": _delta_rect_candan, "granke": _delta_hann_grandke, "offelli": _delta_hamming_offelli, "andria_blackman": _delta_blackman_andria, "agrez": _delta_blackmanharris_agrez, "andria_flattop": _delta_flattop_andria } 所有的插值算法列表 _AUTO_DELTA_METHODS = { #自动窗函数选择表 "rect": "cadan", "hann": "granke", "hamming": "offelli", "blackman": "andria_blackman", "blackmanharris": "agrez", "flattop": "andria_flattop" } auto模式下,不同窗函数选择的频率差估计算法 _WINDOW_CHECK_LIST = { #查看用户是否选择错了窗函数算法 "rect": ["rife", "cadan"], "hann": ["granke"], "hamming": ["offelki"], "blackman": ["andria_blackman"], "blackmanharris": ["agrez"], "flattop": ["andria_flattop"] } 用于检测用户是否选择错了窗函数算法,如果选择错了,就报错 接着是这些算法的实现,我说实话大部分都没有什么意思,基本上都是对着公式写代码,没有半点数据结构。其中δ为频率差,A为幅值。 为了简便,我们做以下符号约定。 X为FFT后得到的复数谱,$\omega$ 为窗函数 ...

2026-06-24 · 4 min · 665 words · Saki酱的通信学习之路

记一次fft模块重构(一):基础仿真篇

1.前言 fft模块可以称得上是信号题中最基础,也是最重要的模块了,大部分电赛题目中,都需要进行fft操作。基本上是信号题中的家是本。然而我们队伍中现役的fft系列模块,虽然经历了多次重构,但还是积攒了很深的历史遗留问题。因此在有了一定的DSP基础知识后,终于下定决心,在增加一些新算法的同时,对FFT模块进行彻底的重构,重构的预期目标为: 将原本与stm32紧耦合的fft模块算法移植到Xlinx fpga的PS端 添加Rife / Parabolic / Candan 插值算法,增强频率,相位,幅值测量准确度 重构窗函数算法,减小频谱泄漏 正确处理扫频时产生的fft数据,构建扫频管线 定点数重构,抛弃原来32上的基于f32的算法,更适配fpga逻辑管线 将算法层和硬件层彻底解耦 2. 算法流程仿真 2.0 测试框架设计 根据ai提示,给出了我们整个项目的框架 fft_lab/ ├── signals.py # 信号生成器(对标:你将来往 FFT 喂的数据) ├── windows.py # 窗函数库(对标:中间件层的窗函数模块) ├── estimators.py # 频率/幅度/相位估计算法(对标:顶层算法层) ├── metrics.py # 误差计算与统计(RMSE、MAE、bias) └── tests/ ├── test_windows.py # 窗函数正交性/相干增益测试 ├── test_parabolic.py # 抛物线插值精度测试 ├── test_rife.py # Rife 算法全面测试 ├── test_candan.py # Candan 算法全面测试 ├── test_harmonics.py # 谐波分析(三角波场景) └── test_two_tone.py # 双音分辨测试(2023-H 场景) 我个人认为其逻辑基本正确。 2.0.1 信号生成程序 signals.py 首先来处理最基础的部分: signals.py 首先是生成信号接口, ...

2026-06-14 · 7 min · 1435 words · Saki酱的通信学习之路