记一次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酱的通信学习之路

Arch下iphone的备份与恢复

最近我的iPhone更新完ios26.4.1后遇到了重启之后就会bootloop的问题,不过通过进入恢复模式/诊断模式后再重启就可以正常启动。查看iphone诊断日志,发现可能是nvram出现了问题。于是希望通过不保留数据刷机来解决这个问题。在选择备份数据的工具上,我选择了pymobiledevice3作为我的备份工具。 注:我可能感觉选择基于libimobiledevice的idevicebackup2工具比较好,但是未经过测试,不予评论。 安装流程 因为pymobiledevice3需要涉及到物理usb访问的问题,且PEP668不让直接运行pip install,所以我这里基于一个已经停止维护的AUR仓库python-pymobiledevice3进行改包。 找个合适的位置,先git clone下来 git clone https://aur.archlinux.org/python-pymobiledevice3.git cd python-pymobiledevice3 编辑PKGBUILD,找到 pkgver=6.0.1 字段,将其改成github中的最新版本号(我这里是9.13.0),最新版本号可在pymobiledevice3_releases中查看。 然后,仿照requirements.txt,修改下面的depends=...一行,这里提供ai生成的依赖行。 depends=( 'openssl' 'libusb' 'python' 'python-construct' 'python-asn1' 'python-click' 'python-coloredlogs' 'ipython' 'python-bpylist2' 'python-pygments' 'python-hexdump' 'python-daemonize' 'python-gpxpy' 'python-pykdebugparser' 'python-pyusb' 'python-tqdm' 'python-requests' 'xonsh' 'python-parameter-decorators' 'python-packaging' 'python-pygnuutils' 'python-cryptography' 'python-pycrashreport' 'python-fastapi' 'uvicorn' 'python-nest-asyncio' 'python-pillow' 'python-inquirer3' 'python-ifaddr' 'python-hyperframe' 'python-srptools' 'python-qh3' 'python-developer_disk_image' 'python-opack2' 'python-psutil' 'python-pytun-pmd3' 'python-prompt_toolkit' 'python-sslpsk-pmd3' 'python-python-pcapng' 'python-plumbum' 'python-wsproto' 'python-typer' 'python-defusedxml' 'python-pyimg4' 'python-construct-typing' 'python-typer-injector' 'pyton-ipsw_parser' ) 修改后保存,运行 updpkgsums 来更改新包的checksum 现在我们针对几个aur源中不存在的包手搓几个PKGBUILD。创建一个目录,然后创建一个PKGBUILD文件。以下PKGBUILD文件均为ai生成,不保证其正确性,但本人测试没有问题。 python-construct-typing pkgname=python-construct-typing _pkgname=construct-typing pkgver=0.7.0 pkgrel=1 pkgdesc='Extension for construct that adds typing features' arch=('any') url='https://github.com/timrid/construct-typing' license=('MIT') depends=('python' 'python-construct') makedepends=('python-build' 'python-wheel' 'python-installer' 'python-setuptools') source=("https://files.pythonhosted.org/packages/8c/0c/2db6f7e1ae9795e436c6a0dc0bc38b12b8c8a228cb63203e24190b755b3b/construct_typing-0.7.0-py3-none-any.whl") sha256sums=('c92383c6e8e5d07ba25811c8d5163820458d821e73bb1006541f43f89788646c') build() { cd "$srcdir" # wheel 不需要编译,直接解包作为构建产物 python -m wheel unpack "construct_typing-${pkgver}-py3-none-any.whl" -d build } package() { cd "$srcdir" python -m installer --destdir="$pkgdir" "construct_typing-${pkgver}-py3-none-any.whl" } python-typer-injector更改source链接和python -m wheel unpack行即可。 接下来是两个包的特殊处理: ...

2026-05-24 · 2 min · 241 words · Saki酱的通信学习之路

Arch中的一些体验小优化

1.雾凇拼音部分符号输出为英文标点 在写md,latex等文件时要不停切换中英文标点,非常的麻烦,所以干脆在配置文件中自定义half_shape标点映射自行更换 在~/.local/share/fcitx5/rime/rime_ice.custom.yaml添加以下段落 punctuator/half_shape: "¥": "$" # 人民币符号 -> 美元符 "「": "{" "」": "}" "【": "[" "】": "]" "·": "`" # 间隔号 -> 反引号 "、": "\\" # 顿号 -> 反斜杠 "~": "~" 重启fcitx即可. 这种方案本质上是替换了rime输入法输出的符号,所以在日常输入中,被替换的这些符号也会保持英文标点的状态.这点需要注意. 2. 微信无法粘贴图片 微信的剪贴板bug也是老生常谈的问题,这里ai推荐我使用wl-copy写一个脚本,安装脚本后,部分解决问题 先安装wl-paste和wl-copy yay -S wl-clipboard 再看看~/.local/bin是否存在 mkdir -p ~/.local/bin 然后 vim ~/.local/bin/wx-clip-fix.sh 写入以下脚本 #!/bin/bash # 依赖: wl-clipboard (确保已安装 wl-paste 和 wl-copy) wl-paste --watch bash -c ' # 避免无限循环:仅当内容是纯文本才处理 if wl-paste -n --list-types | grep -q "text/plain"; then clip=$(wl-paste -n) # 匹配微信/QQ的临时图片路径 if [[ "$clip" == file://*/WeChat_Data/*\.jpg ]] || [[ "$clip" == file://*/WeChat_Data/*\.png ]]; then filepath=$(echo "$clip" | sed "s/^file:\/\///") if [ -f "$filepath" ]; then # 重新作为图片写入剪贴板,打断原有 MIME mime=$(file --mime-type -b "$filepath") wl-copy -t "$mime" < "$filepath" # 可选:通知反馈 (需要安装 libnotify) # notify-send "Clipboard" "已转换微信图片为原生格式" fi fi fi ' 在niri配置文件中添加spawn-at-startup ...

2026-05-19 · 2 min · 262 words · Saki酱的通信学习之路

Saki酱的DSP大冒险(零):绪论

免责声明 本人初学DSP,笔记中难免有错误,希望大家多多海涵。同时欢迎错误指正与技术交流。 本文中的数学部分更偏向于“直观理解”而非“严谨推导”,无法保证其严谨性。 本人不是很懂乐器,笔记中对乐器的类比难免有不恰当之处,请多多海涵,不要在意,会意即可。 DSP在这里指 “Digital Signal Processing” 即数字信号处理,而非DSP芯片(Digital signal processor)。 由于这份笔记会结合信号系统与DSP的内容,考虑到DSP主要研究的是离散信号与系统的处理方法,所以在这篇笔记中统称为“信号系统”。 这份笔记主要着重工程实践相关内容,目的是建立对信号系统的一个初步认识,不一定会围绕考试相关内容。 前言 春节时,与某位数学系的朋友讨论专业上的事情。谈到信号与系统 这门课程。朋友为我提供了一个新的视角:利用线性代数的思想来研究它们。我以这个思路读完Lyons的《Understanding Digital Signal Processing》后收获颇丰。故结合Oppenheim的 《信号与系统》和 Proakis的《数字信号处理》系统性的梳理一下自己的思想和感悟。 正文 1.绪论 想要研究信号系统,我们首先要搞懂三个问题:为什么学?学什么?怎么学?即研究目标、研究对象、研究方法。搞清楚这三个问题,对我们研究一个学科,乃至一个知识体系都是很有帮助的。 2.研究目标 通信没有信号系统,就像乐队没有键盘。 ——某棕发贝斯手 图1-1 正在帮爱音挑选效果器的多惠 正如组乐队一样,每个人组乐队都有不同的目的:某棕发吉他主唱为了闪闪发光,心动不已,某灰色长发主唱为了达到顶点。我们对一个学科进行深入的研究,总得有自己的研究目的。失去研究目的的学科是没有生命力的,其发展也将停滞不前。Oppenheim对这个问题给出了答案:我们研究信号系统,正是因为生活中许多问题,都能转化成为信号系统相关的问题进行研究。麦克风会不会改变主唱的音色和音调?效果器如何改变电吉他发出的声音?想要研究清楚这些问题,就需要运用到系统分析的相关知识。同时,我们也设计一些新的系统。比如混音师进行混音,让乐曲富有层次感。除了上述这些方面,我们还能运用这种思维研究图像处理、自动控制等方面的问题,这里不逐一列举。总而言之,我们研究这门学科有两个核心目标:利用特定信号分析系统,基于特定信号设计系统。而所有的研究方法和研究内容,均围绕这两个核心目标来进行。 图1-1中多惠对效果器的描述:”要锵锵的呢,还是咣咣的呢?“就是一种对系统特性的简易分析。 3.研究对象 诶,这不是吉他吗? 这是六弦贝斯。 ——孤独摇滚 图1-2 发现吉他买成六弦贝斯的喜多酱 确定研究目标后,我们只有选择合适的研究对象,才能达成研究目的,确立研究方法。如果像某位红发现充吉他手,不调查清楚,想弹吉他却搞了把六弦贝斯回来,便是不可取的。而在信号系统中,我们核心的研究对象有三个:信号、系统与它们的关系。为了弄清楚研究对象,我们首先得给出一个较为严谨的定义,以防后续出现错误。有四根弦的不只有贝斯,还有琵琶。有六根弦的不止有吉他,还有六弦贝斯。如果不给出一个严谨的定义,那便会闹出来把六弦贝斯当吉他买的笑话。 3.1 信号 根据Oppenheim的定义,信号是一个或多个变量的函数。这句话至关重要,它告诉了我们以下几个信息: 1.在所有自变量都确定的前提下,函数值有且只有一个。这就意味着在所有变量都确定的情况下,信号的值是唯一的。 2.我们可以用一切研究函数的方法,如微分,积分等分析学方法,来研究信号。我们还可以对信号套用一些函数运算的相关定义与性质(这些性质在研究线性时不变系统时非常有用)。 在实践中,我们注意到有四类函数非常重要,分别是:冲激函数、阶跃函数、周期函数、复指数函数。因此,在接下来的研究中,我们将着重研究这四类信号(关于它们的详细定义,我将会在下一节提及)。利用这些特殊信号,我们能更容易地分析系统的普遍性质。 同时,我们发现:离散信号可以看做连续信号通过采样(Sampling)得出的,这意味着我们可以运用相似的思想方法来研究离散信号,这大大降低了我们对离散信号的研究难度。这个将在讲述采样的章节中详细提及。 连续信号:在无限或有限范围内定义的信号(即在定义域内连续) 离散信号:在有限集内定义的信号 图1-3 连续信号与离散信号的示意图 图1-3是一张连续信号与离散信号的示意图,有助于我们直观地理解连续信号与离散信号之间的区别。 3.2 系统 麦克风能放大歌手的声音,效果器能产生失真音效,录音机能重现录下来的声音。这些装置的共同特点,就是对于特定的输入信号,产生一个特定输出。输入信号不同,输出信号也不同。电吉他和电贝斯就算用相同的音响和效果器进行演奏,它们的音色仍大相径庭。回想刚才对于信号的定义,我们很自然地想到:如果将输入的信号看做一个自变量,而输出的信号就是这个自变量的函数。但特殊的一点在于:系统的自变量和因变量都是信号。对于这种特殊的函数,我们称系统将信号$x(t)$变换为$y(t)$,记作 $ y(t)=\cal{T}[f(t)] $ 或 $ x(t)\xrightarrow{\cal{T}}y(t) $ 花体字$\cal{T}$表示系统为生成$y(t)$而对$x(t)$进行的变换(也称算子) 图1-4 连续系统与离散系统示意图 如图1-4所示,我们可以把系统想象成一个神秘的魔法小盒子,你扔进去一个信号,就会出来另一个信号。 值得注意的是,系统与中学阶段学过的复合函数,是两个截然不同的东西。系统的输入输出都是信号,而复合函数的输入输出都是一个数。 因为上面提到的连续和离散信号的分类,我们可以简单地将系统分为两类 连续系统:输入连续信号,输出连续信号。 离散系统:输入离散信号,输出离散信号。 在采样这一章节中,我们还将认识另一类特殊的系统:混合系统,这种系统连接了连续的模拟世界与离散的数字世界。在这里不过多提及。 ...

2026-03-23 · 1 min · 110 words · Saki酱的通信学习之路