
学习信号与系统最绕不开的就是频域分析。时域波形看得见但系统到底能通过哪些频率、信号里包含哪些频率分量全部要依靠频谱分析和频域分析来回答。这篇博客把频谱分析、采样定理、调制、滤波器这四块内容串成一条可操作的实验主线用 Python 仿真把教材里的公式变成可以肉眼观察的曲线。代码全部可以直接运行环境只需要 Python 3、NumPy、SciPy 和 Matplotlib不挑显卡不挑平台普通电脑足够。文章会围绕这几个重点展开先用 FFT 观察合成信号的幅度谱再验证采样频率不满足采样定理时出现的混叠现象然后分别做 AM 调幅和 FM 调频的频谱搬移实验最后设计 FIR/IIR 滤波器并观察滤波前后的波形和频谱变化。每一节都给出完整代码、运行思路和判断标准。如果你正在复习信号与系统或者刚接触数字信号处理这篇内容可以当一条“从时域到频域”的入门实践路线来看。本文适合这几类读者正在学信号与系统的本专科学生准备通信、电子、自动化等方向笔试面试的工程师以及想把 FFT、采样、调制、滤波器这些基础概念补扎实的算法开发。文章不追求推导所有数学细节重点是把概念和仿真对应起来。读完以后你应该能自己生成一段信号用 Python 画出它的频谱理解为什么采样率要高于信号最高频率的两倍也能快速设计一个低通滤波器并验证滤波效果。1. 核心学习主线速览先给一张整体表把本文涉及的模块、核心概念、仿真工具和典型输出列清楚。后面的章节就按这个顺序展开。学习模块核心概念仿真工具典型实验输出频谱分析傅里叶变换、DFT、FFT、幅度谱、相位谱、频率分辨率numpy.fft时域波形图、单边幅度谱采样与混叠奈奎斯特采样定理、采样率、混叠频率信号生成 FFT不同采样率下的频谱峰值位置调制载波、调幅 AM、调频 FM、频谱搬移、边带信号生成 频谱分析AM/FM 已调信号频谱滤波器低通、高通、带通、FIR/IIR、频率响应、零相位滤波scipy.signal幅频响应曲线、滤波前后波形与频谱系统分析系统函数 H(s)、幅频特性、相频特性、Bode 图scipy.signal.lti系统 Bode 图一条主线是任何信号都可以看成不同频率正弦分量的叠加频域分析就是把这些分量拆开采样决定了离散信号能否唯一代表连续信号调制把基带信号的频谱搬到高频载波附近便于发射和频段管理滤波则是系统对频率分量进行选择性通过或抑制。把这几个环节连起来看信号与系统最核心的工程逻辑就清楚了。2. 适用场景、工具准备与前置条件2.1 这个知识链适合什么场景频谱分析和滤波器设计不是纯理论工程里到处可见。音频处理要分析频谱并做均衡振动信号要通过频谱找故障特征频率通信系统要设计基带波形、调制解调器和匹配滤波器雷达和声呐要通过频谱和频域相关性做目标检测。这些场景的底层都是傅里叶变换、采样定理、调制和滤波器这几个要素。不建议只停留在套公式层面。公式能帮你应付考试但实际问题往往是FFT 画出来频率轴不对、信号混叠了不知道、滤波器设计出来不收敛、调幅解调结果有失真。这些问题看教材很难定位跑一遍仿真就清楚了。所以这套知识更适合用“代码复现 参数调整 结果观察”的方式学习。2.2 数据合规与使用边界如果实验素材来自真实采集信号比如语音、生理信号、振动信号或无线频谱要注意数据来源的授权问题。分析真实语音前应获得说话人同意分析医疗或工业数据要遵守所在机构的隐私和保密要求。本文全部使用合成信号不涉及真实采集数据可以安全本地运行。2.3 环境准备操作系统不限Windows、macOS、Linux 均可。需要 Python 3.8 以上版本推荐用虚拟环境隔离依赖。安装命令如下python -m venv ssp_env # Windows 激活 ssp_env\Scripts\activate # macOS / Linux 激活 source ssp_env/bin/activate pip install numpy scipy matplotlib安装完成后在 Python 中确认版本import numpy as np import scipy import matplotlib print(numpy:, np.__version__) print(scipy:, scipy.__version__) print(matplotlib:, matplotlib.__version__)运行正常就能继续。代码里的绘图窗口可能会在无图形界面的服务器上弹出失败建议在本地电脑运行如果必须在远程环境跑可以把plt.show()改成plt.savefig(output.png)保存图片。3. 频谱分析实战FFT 与频域观察频谱分析是后续所有实验的基础。这一节先构造一个包含 50 Hz 和 120 Hz 两个正弦分量的信号再加一点噪声用 FFT 观察它的幅度谱。3.1 生成合成信号假设采样率是 1000 Hz采样时长 1 秒信号由 1.0 幅值的 50 Hz 正弦和 0.6 幅值的 120 Hz 正弦叠加而成。生成代码如下import numpy as np import matplotlib.pyplot as plt fs 1000 # 采样率单位 Hz T 1.0 # 采样时长单位 s N int(fs * T) # 总样本数 t np.arange(N) / fs f1, f2 50, 120 x 1.0 * np.sin(2 * np.pi * f1 * t) 0.6 * np.sin(2 * np.pi * f2 * t) x 0.1 * np.random.default_rng(42).standard_normal(N)这里使用固定随机种子保证每次运行结果一致。加噪声是为了让频谱更接近实际信号噪声会让谱线下面出现一条比较平缓的背景。3.2 计算 FFT 并绘制单边幅度谱对实信号做频谱分析一般用numpy.fft.rfft它只计算正频率部分数据量减半且足够使用。频率轴用rfftfreq生成。幅度谱要做单边校正FFT 结果是对称的单边谱的谱线幅值要乘以 2 再除以 N直流分量不乘 2。X np.fft.rfft(x) freqs np.fft.rfftfreq(N, 1 / fs) # 单边幅度谱校正实信号的一半能量在负频率 amp 2.0 * np.abs(X) / N amp[0] / 2 # 直流分量单独处理不乘 2 fig, ax plt.subplots(2, 1, figsize(8, 6)) ax[0].plot(t, x) ax[0].set_xlim(0, 0.1) ax[0].set_xlabel(时间 / s) ax[0].set_ylabel(幅度) ax[0].set_title(时域波形) ax[1].stem(freqs[:200], amp[:200], basefmt ) ax[1].set_xlabel(频率 / Hz) ax[1].set_ylabel(幅度) ax[1].set_title(单边幅度谱) plt.tight_layout() plt.show()这段代码的关键点是频率轴和幅度校正。如果不做2 / N的校正谱峰幅值会很小和时域波形的幅值对不上。预期结果是50 Hz 处出现约 1.0 的谱峰120 Hz 处出现约 0.6 的谱峰其余位置散布着噪声形成的低幅值谱线。3.3 频率分辨率与频谱泄漏频谱分析常见的两个问题是频率分辨率不足和频谱泄漏。频率分辨率等于fs / N。在采样率固定时观察时间越长N 越大分辨率越高。如果两个频率分量相差不到一个分辨率频谱上就会重叠无法区分。要提高分辨率最直接的办法是增加采样时长而不是靠零填充。零填充只能让频谱曲线更平滑不会真正提高物理分辨率。频谱泄漏来自非整周期截断。信号没有包含整数个周期FFT 会把能量撒到相邻频点上谱峰变矮、变宽。使用窗函数可以缓解。Hanning 窗是最常用的选择w np.hanning(N) Xw np.fft.rfft(x * w) amp_w 2.0 * np.abs(Xw) / N freqs_w np.fft.rfftfreq(N, 1 / fs) plt.figure(figsize(8, 4)) plt.plot(freqs_w[:200], amp_w[:200]) plt.xlabel(频率 / Hz) plt.ylabel(幅度) plt.title(加 Hanning 窗后的幅度谱) plt.show()加窗之后旁瓣明显变小但主瓣会变宽单个频率峰的宽度增加。这是工程里常见的折中减少泄漏牺牲一点分辨率。实际做频谱分析时先不加窗看大概再根据谱峰是否拖尾决定是否加窗。4. 采样与混叠采样定理的直观验证4.1 采样定理怎么理解采样定理说的是为了能从离散样本无失真恢复连续信号采样率必须大于信号最高频率的两倍即fs 2 * fmax。这个两倍频率通常叫奈奎斯特率而fs / 2叫奈奎斯特频率。如果采样率不够高频分量会被折叠到低频区域产生一个不存在的频率分量这就是混叠。混叠频率的计算可以用abs(f_signal - k * fs)取最小值得到其中 k 是整数。用代码验证这个现象比背公式更直观。4.2 400 Hz 信号在不同采样率下的频谱峰值构造一个 400 Hz 正弦信号分别用 2000 Hz、800 Hz、500 Hz 采样。前两个采样率大于 2 倍信号频率频谱峰值应该在 400 Hz最后一个采样率不满足采样定理频谱峰值会出现在 100 Hz也就是 400 Hz 折叠到 500 Hz 以下的位置。import numpy as np f_sig 400 fs_list [2000, 800, 500] for fs in fs_list: N int(fs * 1.0) t np.arange(N) / fs x np.sin(2 * np.pi * f_sig * t) X np.fft.rfft(x) freqs np.fft.rfftfreq(N, 1 / fs) amp 2.0 * np.abs(X) / N # 跳过直流分量找最大谱峰 peak_idx np.argmax(amp[1:]) 1 print(ffs {fs:4d} Hz, 最大谱峰 {freqs[peak_idx]:6.1f} Hz)运行结果会非常直观fs 2000 Hz, 最大谱峰 400.0 Hz fs 800 Hz, 最大谱峰 400.0 Hz fs 500 Hz, 最大谱峰 100.0 Hz第三个结果就是混叠。400 Hz 信号被 500 Hz 采样后因为高于fs / 2 250 Hz被折叠到400 - 500 -100的镜像位置取绝对值后显示为 100 Hz。从这个实验可以记住ADC 采样前端必须保证输入信号里不含有高于fs / 2的成分否则混叠一旦发生后续滤波也无法恢复。4.3 抗混叠滤波思路工程上解决混叠的办法是在 ADC 之前加抗混叠低通滤波器把高于奈奎斯特频率的信号能量滤掉。采样率越高抗混叠滤波器的过渡带越容易设计但 ADC 成本、存储和计算量也会变大。实际系统要在两者之间权衡。如果做离线仿真也可以先用scipy.signal.butter设计一个低通滤波器把超高频分量滤掉再降低采样率。这对应实际设备里的抽取操作。先低通、再抽取是采样率转换的标准流程后面滤波器章节会用代码实现低通滤波正好可以接在这里。5. 调制实验AM/FM 频域搬移观察调制是通信系统里最典型的频域操作。它的本质是把基带信号的频谱从低频搬移到载波频率附近这样做一是方便天线辐射二是支持不同业务占用不同频段。调制方式很多这里用 AM 和 FM 两个实验观察频域变化。5.1 AM 调幅信号频谱AM 信号的表达式可以写成s_am(t) (1 m * cos(2 * pi * fm * t)) * cos(2 * pi * fc * t)其中m是调幅指数fc是载波频率fm是调制信号频率。当m 1时可以保证包络不越过零点。下面用载波 2000 Hz、调制信号 100 Hz、调幅指数 0.5 做仿真。import numpy as np import matplotlib.pyplot as plt fs 10000 T 0.02 t np.arange(int(fs * T)) / fs fc, fm, m 2000, 100, 0.5 message np.cos(2 * np.pi * fm * t) carrier np.cos(2 * np.pi * fc * t) s_am (1 m * message) * carrier S np.fft.rfft(s_am) freqs np.fft.rfftfreq(len(s_am), 1 / fs) amp 2.0 * np.abs(S) / len(s_am) amp[0] / 2 plt.figure(figsize(8, 4)) plt.plot(freqs, amp) plt.xlim(1500, 2500) plt.xlabel(频率 / Hz) plt.ylabel(幅度) plt.title(AM 信号幅度谱载波 2000 Hz调制信号 100 Hz) plt.grid(True) plt.show()频谱图上会出现三个明显的峰载波 2000 Hz 处一个主峰1900 Hz 和 2100 Hz 处两个对称边带。两个边带分别对应调制信号频率的上下搬移。调幅指数 m 越大边带幅度相对载波越强当 m 接近 1 时每个边带幅度约是载波幅度的一半。观察这个频谱就能理解 AM 信号的带宽为什么是调制信号最高频率的两倍。5.2 FM 调频信号的频谱FM 信号的表达式是s_fm(t) cos(2 * pi * fc * t beta * sin(2 * pi * fm * t))beta是调频指数。AM 的频谱只有三条谱线而 FM 的频谱边带更丰富由贝塞尔函数决定。下面用同一组载波和调制频率取beta 2.0观察频谱beta 2.0 s_fm np.cos(2 * np.pi * fc * t beta * np.sin(2 * np.pi * fm * t)) S_fm np.fft.rfft(s_fm) amp_fm 2.0 * np.abs(S_fm) / len(s_fm) amp_fm[0] / 2 plt.figure(figsize(8, 4)) plt.plot(freqs, amp_fm) plt.xlim(1500, 2500) plt.xlabel(频率 / Hz) plt.ylabel(幅度) plt.title(FM 信号幅度谱beta2.0) plt.grid(True) plt.show()和 AM 相比FM 的谱线数量明显更多占据的带宽也更大。工程上常用卡森公式估算 FM 带宽约等于2 * (beta 1) * fm即 2 * (2 1) * 100 600 Hz。虽然这里有简化但能看出调频指数越大带宽越宽。通过这个实验可以直观理解为什么 FM 音质好但占用频带宽。5.3 AM 解调观察调制实验的下半部分是解调。AM 最简单的方法是相干解调接收端乘上同频同相的本地载波再经过低通滤波器就能把基带信号恢复出来。demod 2.0 * s_am * np.cos(2 * np.pi * fc * t)这行代码会生成原始基带信号、两倍载波频率的高频分量以及载波泄漏的混合物。下一步设计低通滤波器把高频部分滤掉就能看到恢复的 100 Hz 正弦。滤波器设计在下一节实现如果你现在手边已经有低通滤波函数可以直接接上去验证。6. 滤波器设计系统响应与滤波效果滤波器是“系统”在频域最直接的表现。一个连续时间系统可以用传递函数 H(s) 描述它的频率响应 H(jω) 决定了系统对各个频率分量的增益和相位变化。滤波器的设计目标就是构造一个幅度响应满足要求的系统。6.1 一阶低通系统的频率响应先看一个最简单的一阶系统H(s) 1 / (0.001 * s 1)。这个系统对低频信号增益接近 1对高频信号逐渐衰减。用 SciPy 画 Bode 图from scipy.signal import TransferFunction, bode sys TransferFunction([1], [0.001, 1.0]) w, mag, phase bode(sys) f w / (2 * np.pi) fig, ax plt.subplots(2, 1, figsize(8, 6)) ax[0].semilogx(f, mag) ax[0].set_xlabel(频率 / Hz) ax[0].set_ylabel(幅度 / dB) ax[0].set_title(一阶低通系统幅频特性) ax[0].grid(True, whichboth) ax[1].semilogx(f, phase) ax[1].set_xlabel(频率 / Hz) ax[1].set_ylabel(相位 / deg) ax[1].set_title(一阶低通系统相频特性) ax[1].grid(True, whichboth) plt.tight_layout() plt.show()Bode 图里的转折频率约在1 / 0.001 / (2 * pi) ≈ 159 Hz。频率低于转折频率时幅度接近 0 dB高于转折频率后按每倍频程衰减约 20 dB。这个实验把“系统频率响应”这个概念落到曲线上系统的行为不是只看传递函数公式而是要看它对不同频率正弦信号的实际增益。6.2 设计一个 Butterworth 低通滤波器数字滤波器设计工程上常用scipy.signal.butter配合sos格式数值稳定性比直接展开的b,a更好。下面设计一个 4 阶、截止频率 40 Hz 的低通滤波器采样率 1000 Hz并把滤波前后的波形和频谱对比from scipy.signal import butter, sosfiltfilt fs 1000 t np.arange(fs) / fs # 构造 10 Hz 100 Hz 混合信号 x np.sin(2 * np.pi * 10 * t) np.sin(2 * np.pi * 100 * t) # 4 阶 Butterworth 低通截止频率 40 Hz sos butter(4, 40, low, fsfs, outputsos) y sosfiltfilt(sos, x) fig, ax plt.subplots(2, 1, figsize(8, 6)) ax[0].plot(t, x) ax[0].set_title(滤波前10 Hz 100 Hz) ax[0].set_xlabel(时间 / s) ax[0].set_ylabel(幅度) ax[1].plot(t, y) ax[1].set_title(滤波后保留 10 Hz抑制 100 Hz) ax[1].set_xlabel(时间 / s) ax[1].set_ylabel(幅度) plt.tight_layout() plt.show()滤波后的波形应该基本表现为 10 Hz 正弦100 Hz 分量被明显衰减。这里使用sosfiltfilt做零相位滤波输出没有相移适合离线分析。如果做实时滤波要改用scipy.signal.sosfilt但会引入群延迟。6.3 高频和带通滤波器的切换设计高通和带通滤波器只需要修改butter的参数。例如# 高通截止频率 10 Hz sos_hp butter(4, 10, high, fsfs, outputsos) # 带通通带 20 Hz 到 80 Hz sos_bp butter(4, [20, 80], bandpass, fsfs, outputsos)带通滤波器在调制解调里非常有用。AM 解调后基带信号在低频段而载波泄漏在 2000 Hz 附近用低通即可如果希望滤除某个频段才需要带通。通过切换滤波器类型并观察频谱可以建立“滤波就是频率选择”的直觉。6.4 观察滤波器幅频响应设计完滤波器要验证它是否符合预期直接看滤波器的幅频响应曲线from scipy.signal import sosfreqz w, h sosfreqz(sos, worN8000, fsfs) plt.figure(figsize(8, 4)) plt.plot(w, 20 * np.log10(np.maximum(abs(h), 1e-12))) plt.axvline(40, colorred, linestyle--, label截止频率 40 Hz) plt.xlabel(频率 / Hz) plt.ylabel(幅度 / dB) plt.title(低通滤波器幅频响应) plt.legend() plt.grid(True) plt.show()从这条曲线可以看到截止频率附近的过渡带以及大于 40 Hz 之后衰减速度。滤波器阶数越高过渡带越窄但计算量和相位延迟也会增加同时数值稳定性更敏感。设计滤波器时先画幅频响应图再决定滤波参数这是最稳妥的流程。7. 批量频谱分析与工程化脚本前面几节的代码都是单段信号分析。实际工程里经常需要对一批音频文件或采集文件做批量频谱分析。这一节给一个最小可用的批量处理脚本把输入目录里的 WAV 文件逐个读进来计算频谱并保存频谱图同时输出主峰频率和幅值。7.1 单个文件的频谱分析函数import os from glob import glob import numpy as np from scipy.io import wavfile import matplotlib.pyplot as plt def analyze_wav(path, out_dir): fs, data wavfile.read(path) # 多声道转单声道整型转浮点 if data.ndim 1: data data.mean(axis1) data data.astype(np.float64) data data / (np.max(np.abs(data)) 1e-12) N len(data) X np.fft.rfft(data) freqs np.fft.rfftfreq(N, 1 / fs) amp 2.0 * np.abs(X) / N amp[0] / 2 # 跳过直流分量找最大谱峰 peak_idx np.argmax(amp[1:]) 1 peak_freq freqs[peak_idx] peak_amp amp[peak_idx] base os.path.splitext(os.path.basename(path))[0] plt.figure(figsize(8, 4)) plt.plot(freqs, amp) plt.xlabel(频率 / Hz) plt.ylabel(幅度) plt.title(f{base} spectrum, fs{fs} Hz) plt.grid(True) plt.savefig(os.path.join(out_dir, base _spec.png), dpi120) plt.close() return base, fs, peak_freq, peak_amp函数返回文件名、采样率、主峰频率和主峰幅度。主峰频率在很多场景里就是最关心的特征值例如电机振动信号的基频音频信号的基频。7.2 批量处理目录out_dir ./spectrums os.makedirs(out_dir, exist_okTrue) for wav_path in glob(./signals/*.wav): result analyze_wav(wav_path, out_dir) print(f{result[0]}, fs{result[1]} Hz, peak{result[2]:.2f} Hz, amp{result[3]:.4f})这个脚本的输出可以直接重定向到 CSV 文件方便后续统计python batch_spectrum.py result.csv如果文件列表很大可以增加try/except捕获单个文件失败避免一个损坏文件中断整个任务。如果文件本身很长一次性做完整 FFT 可能占用大量内存这时需要考虑分段平均谱或 STFT而不是直接取全部样本。7.3 批处理任务的工程注意点批量处理最忌讳“只见结果不见中间状态”。建议每处理一个文件都记录采样率、样本数、主峰频率、主峰幅度并保存频谱图。把输入文件、频谱图、结果表格分目录存放方便排查。一旦发现某个文件的频谱异常能快速定位是采集问题、文件格式问题还是算法问题。8. 常见问题与排查方法仿真过程中最容易踩的坑集中在频率轴、幅值校正、采样率和滤波器边界条件。下面把常见现象整理成一张排查表。问题现象可能原因排查方式解决方案FFT 频率轴显示不对50 Hz 信号不在 50 Hz 附近rfftfreq的 d 参数写错或忘记除以采样率检查rfftfreq(N, 1/fs)的第二个参数确保频率轴由rfftfreq生成参数为采样间隔频谱峰值和时域幅值对不上没有做单边幅度谱校正对比 50 Hz 正弦的理论幅值单边谱乘以 2 再除以 N直流项单独处理两个频率很近的分量看不清频率分辨率不足fs/N太大增加采样时长或观察点数加长采样时间不要单纯加零填充谱峰宽大、旁边有拖尾频谱泄漏信号截断不完整看是否整周期采样加 Hanning 窗接受主瓣变宽采样后出现不存在的低频峰混叠检查输入频率和采样率关系提高采样率或在采样前加抗混叠低通AM 包络解调有失真调幅指数 m 大于 1或低通带宽不合适检查 m 值和波形包络设置 m 1调整低通截止频率滤波器滤波后波形有相位偏移使用了因果滤波lfilter画滤波前后波形对比离线处理用filtfilt或sosfiltfilt滤波器输出出现明显延迟和边缘振荡零相位滤波的前向-反向处理在首尾产生预冲观察边缘 1 到 2 个周期增加padlen或多采样点或改用因果滤波滤波器阶数太高计算不稳定高阶b,a形式数值精度差改为sos格式用outputsos必要时降阶批量处理时某个文件中断整个任务没有做异常捕获查看报错文件路径在循环内加try/except记录失败文件下面挑几个容易反复出现的问题多说几句。频率轴的问题是新手最容易遇到的。np.fft.rfftfreq(N, d)的第二个参数是采样间隔也就是1/fs而不是采样率 fs。写成1/fs之后横轴单位才是 Hz。如果写成fs横轴单位会变成周期数所有频率都会对不上。幅度校正也容易混淆。完整 FFT 结果是对称的正负频率各占一半能量所以单边谱把正频率部分乘以 2再把直流项单独处理。这里默认信号是实信号。如果是复信号直接用np.fft.fft且不需要幅值翻倍。滤波器边界问题主要体现在filtfilt上。零相位滤波的好处是相位不失真但因为是前向、反向各跑一次信号首尾会出现预冲现象信号越短预冲越明显。解决方法是适当加长采样长度或者在调用sosfiltfilt时指定稍大的padlen参数。实时处理系统不能使用零相位滤波必须接受因果滤波带来的群延迟。9. 学习建议与最佳实践把前面 8 节跑完以后建议按下面的顺序再巩固一遍。第一先复现 FFT 频谱分析把单边谱的幅值校正、频率轴计算和窗函数影响彻底搞明白。这是所有后续操作的基础。可以先从纯正弦信号开始确认谱峰位置和幅值都正确再叠加第二个频率、第三个频率观察频率分辨率变化。第二做采样定理实验时不要只记录“满足和不满足”两种结果要画一张表列出采样率、奈奎斯特频率、信号频率、混叠后频率。这能帮助你形成计算混叠频率的直觉。记住关键判断标准信号频率小于采样率一半时是真实频率大于采样率一半时是混叠频率。第三调制实验要同时看时域和频域。AM 信号看包络和边带FM 信号看谱线数量增加和带宽变宽。把载波频率、调制频率、调幅指数、调频指数分别改几次观察频谱变化。参数改变后如果结果不符合预期优先怀疑频率轴计算和数据点数是否足够。第四滤波器设计一定要先画幅频响应再处理信号。不要拿到滤波器直接滤波结果不对也不知道是参数选错还是代码边界问题。把低通、高通、带通三种类型各设计一遍再用一个包含两个频率成分的合成信号验证效果。工程化实践方面建议维护一套固定的分析脚本把参数集中放在顶部采样率、数据长度、滤波器类型、截止频率、窗体类型。每次实验可以复制一份改参数后保留输出方便回溯对比。批量处理时给输出文件加统一命名规则比如signal1_spec.png、signal1_peak.csv避免后面整理结果时混乱。学习顺序上最推荐这条路径先合成正弦信号学会画频谱然后验证采样定理再做 AM/FM 调制解调最后设计滤波器处理调制信号。整套内容跑通后下一步可以扩展学习短时傅里叶变换观察频率随时间变化的信号可以学习 Z 变换和数字滤波器优化也可以接触软件无线电里的正交调制和匹配滤波。这里面的核心其实还是本文反复强调的那一句话频域分析是理解信号和系统的通用语言采样、调制、滤波都是在这个语言下的具体操作。建议先跑第 3 节的 FFT 代码确认 50 Hz 和 120 Hz 两个谱峰正确出现再依次推进。最容易踩的三个坑是采样率写错、幅值校正没做、滤波器设计完没有先看幅频响应。把这三个点提前规避掉后面的实验基本都能顺利复现。