首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >如何设计一个基于软件的频谱分析仪(以ADALM2000为例建模)

如何设计一个基于软件的频谱分析仪(以ADALM2000为例建模)

作者头像
云深无际
发布2026-01-07 14:44:24
发布2026-01-07 14:44:24
2340
举报
文章被收录于专栏:云深之无迹云深之无迹

本质上,频谱分析仪就是一台“把信号从时域拆成各个频率成分”的功率测量仪:横轴是频率,纵轴是每个频率上的电平(电压/功率,通常用 dBm、dBV 表示)。

为Zynalog ADC设计一个Python接口实现频谱分析仪

ADALM2000实现双电压追踪+频谱分析仪

频谱泄漏:频谱分析中的“拦路虎”

是德
是德

是德

可以先把它想成:

示波器:看波形随时间怎么变 频谱仪:看“这堆波形里面都有哪些频率、各占多大能量”

下面分两大类讲:

1)传统模拟扫频型频谱分析仪(尤其是 RF 那种几 GHz 的)

2)现代 FFT 频谱分析仪(ADALM2000/示波器里内置的 Spectrum)

大多数人的FFT功能都是来自于示波器的数学功能
大多数人的FFT功能都是来自于示波器的数学功能

大多数人的FFT功能都是来自于示波器的数学功能

传统扫频型频谱分析仪:超级外差 + 扫频滤波

这是老派 RF 频谱仪的工作原理,也是很多教科书里讲的那种。

要测输入信号在每个频率点上的功率,做法:用一个可调的本振(LO),把“想看的频段里某个频率点 f_c”混频搬到一个固定的中频 IF;后面放一个带宽很窄的中频滤波器(RBW 滤波器),只让这一小段频带通过;检波、整流、取对数 → 得到“这个小频带里的总功率”;把 LO 的频率慢慢扫过整个频段,边扫边在屏幕上画点,于是横轴就是频率、纵轴就是电平。

可以理解为:

用一个“窄带可移动小窗”在频率轴上扫来扫去,每次只看这个小窗里多少能量,慢慢把整条频谱画出来。

分辨率带宽(RBW)、扫描时间、噪声底

在扫频型频谱仪里,这几个是核心概念:

这些概念可以从这个文档里面看,应该是大厂里面独一份的存在
这些概念可以从这个文档里面看,应该是大厂里面独一份的存在

这些概念可以从这个文档里面看,应该是大厂里面独一份的存在

目前是德的这个频谱分析仪可以做到110GHz,太恐怖了
目前是德的这个频谱分析仪可以做到110GHz,太恐怖了

目前是德的这个频谱分析仪可以做到110GHz,太恐怖了

RBW(Resolution Bandwidth)

相当于“频率小窗”的宽度;RBW 越小,越能看清两条很近的谱线;同时,噪声功率 ∝ RBW,所以 RBW 越小,噪声底越低(曲线更干净)。

Sweep Time(扫频时间)

RBW 越小,中频滤波器越窄、越“慢”,每个频点要等更久让输出稳定;所以扫描整个频段要更长时间;仪器通常会根据 Span、RBW、VBW 自动计算一个最小 Sweep Time。

VBW(Video Bandwidth)

是对“检波后曲线”的低通滤波;VBW 越小,曲线越平滑(多做平均),但刷屏更慢;有时你会看到“毛毛糙糙的噪声→调小 VBW→平滑噪声曲线”的效果。

这套逻辑和目前在做噪声积分、ENBW 的时候其实是一脉相承的:

RBW ≈ 滤波器相当的噪声带宽; RBW 越小,单位 Hz 的噪声功率密度看上去就越低。

FFT 频谱分析仪:先采样,再做 FFT

现代的低频 / 中频频谱分析、音频分析、ADALM2000/示波器的软件频谱,基本都是 FFT 型,思路完全不一样。(就是使用数值的方式进行纯技术,属于大力出奇迹的做法)

对于 FFT 型频谱仪(包括 Scopy 的 Spectrum Tool),流程大致是:

前端模拟链路(有时是直接输入):输入信号 → 衰减/放大 → 抗混叠滤波器 → ADC;对 RF 型的,还会先下变频到一个中频再数字化。

采样 & 缓冲

用 ADC 以采样率 fs 连续采样,得到时域序列 x[n];一次分析用 N 点:x[0...N-1]

加窗(Windowing)

为了减少频谱泄漏,对采样序列乘一个窗函数 w[n](Hann, Hamming, Blackman 等);得到 xw[n] = x[n] * w[n]

FFT

计算:

得到一组复数 X[k],每个 k 对应频率:

计算幅度/功率

幅度谱:

功率谱或 PSD(功率谱密度)可以按不同约定换算;

再变成 dB:

或功率:

多次重复采样 + FFT,做平均(线性 / 对数 / 峰值保持),降低闪动;对应扫频型里的 VBW/平均功能;最后显示横轴频率(0~fs/2),纵轴电平,和扫频型频谱仪一样的图。

FFT 型的“RBW”来自哪里?

对于 FFT 频谱,频率分辨率是:

想要更好的分辨率:增大 N(采更长时间的数据),或者减小采样率 fs;FFT 里的“等价 RBW”就是 Δf 再乘上窗函数的 ENBW;

所以 FFT 频谱仪里,看到的 “RBW = XXX Hz” 本质就是

对我们来说这很好理解:在之前的文章中做 ENBW、噪声积分时算过很多类似的东西。

FFT 与扫频型的差异

可以一次性获取整个带宽的频谱(0~fs/2),速度快;对低频/中频、小信号噪声分析特别方便;和数字系统结合紧密,可以做各种高级处理(相干平均、跨谱、FRF 等)。

但是缺点明确,比如最重要的就是带宽受 ADC 采样限制(fs/2);对非常高频的 RF 场景,需要用下变频+宽带 ADC 才能实现;而且对动态范围、杂散、抖动等指标要求很高,硬件成本不低。

走进 10G 采样的直采 ADC:奇历士CAE2200(带时域测试版)可以使用这样的ADC直接计算。

和网络分析仪、示波器的关系

前面写过网络分析仪,现在可以把三者放在一起看:

示波器:看时间波形 x(t),偶尔可以附带一个 FFT 功能。

频谱分析仪(Spectrum Analyzer):只看 x(t) 的频谱 |X(f)|(功率/幅度),不关心输入输出关系。

网络分析仪(Network Analyzer): 自己产生激励 x(t),同时测输入和输出,算:

看的是“系统”的频率响应,而不是单个信号的频谱。

从信号处理角度看:

频谱仪:给定一个信号,算它的 FFT。

网络分析仪:给定输入、输出两路信号,算它们的比值/相关性(相当于测系统传递函数)。

像 ADALM2000(M2K)、以及很多数字示波器里都有“频谱 / Spectrum / FFT”模式,其实就是:前端模拟链路 + ADC;固定采样率 fs 采集一段数据;在 FPGA/ARM 或 PC 上做窗函数 + FFT;把结果以 dB/√Hz 或 dBV 画成频谱。

用代码重现一遍 “示波器 → 导出数据 → FFT → 频谱图” 的流程,那就是自制频谱分析仪了。

现在完成对ADALM2000的频谱分析仪过程建模

ADALM2000 的频谱分析模式,本质就是:前端模拟链路 → ADC 采样 → 一些数字预处理(抽取/滤波/校准)→ FFT → 标定和显示;我按“从输入电压到屏幕上的 dB 曲线”这个顺序,把整个链路建模一遍,以后可以直接照这个框架用 Python 重现任意一个ADC的频谱分析逻辑。

第一步:ADC 采样参数 → 离散时间序列

窗函数、CG 和 ENBW:从 FFT 系数到物理幅值

这是把“纯 FFT 数字结果”变成“真实电压/噪声”的关键。

窗函数 w[n]

单频正弦幅度重建

M2K 的频谱分析器如果显示的是“dBV/√Hz”,则会把每个 bin 的 RMS 电压除以 √ENBW,从而得到近似“噪声密度”单位。

参考 ADALM2000 的具体链路

ADALM2000软件设计:HDL数据流向

之前文章写过HDL 结构,可以在频谱模式下这样简化:

模拟前端 → AD9963,12-bit,100 MSPS;在FPGA 内部接口:100 MHz 时钟、使用12 bit×2 通道数据。

AXI_ADC_DECIMATE(数字抽取 + 滤波)

CIC + FIR 抽取,得到一个较低采样率 (比如 1 MSPS 或 100 kS/s)这个滤波器本身就决定了一部分前端带宽/抗混叠特性;将一段长度 N 的数据被搬到内存,供 ARM / PC 读取。

完整的 Python 建模示例(贴近 M2K)

给一个“12 bit ADC + FFT 频谱分析”的建模示例:模拟一个带 1 kHz 正弦 + 白噪声的输入;采样率 100 kS/s,N = 16384 点;使用 Hann 窗;计算并显示:线性频谱(dBV);噪声底(dBV/√Hz)。

这里只是仿真,不直接用 M2K;以后可把“产生 x[n]”改成“从 M2K 读回数据”。

代码语言:javascript
复制
信号频率 bin: 164 对应频率: 1000.9765624999999 Hz
估计 Vrms: 0.13912767159019965 V

脚本做了从 ADC 参数 → FFT → 最终 dB 频谱的完整建模流程:模拟了前端模拟信号 + 噪声 → 量化 → 窗函数 → FFT;用 CG 把窗口带来的幅度缩减补回来;最后用 ENBW 把噪声转换成“密度”单位。

后记

ADALM2000 的频谱分析仪确实就是“ADC 采样 + 数字预处理 + FFT + 标定 + 显示”,但每一步(采样率、窗函数、CG/ENBW、单位换算)都决定了看到的频谱形状、噪声底和数值的含义。

代码语言:javascript
复制
import numpy as np
import matplotlib.pyplot as plt

# 1. ADC / FFT 参数
fs = 100_000.0      # 采样率 100 kS/s
N = 16384           # FFT 点数
B = 12              # 12 bit ADC
V_FS = 1.0          # 满量程 ±1 V,示例

# 2. 构造模拟输入信号:1 kHz 正弦 + 白噪声
f_sig = 1000.0
A_sig = 0.2         # 峰值 0.2 V → 约 0.141 Vrms

t = np.arange(N) / fs
x_analog = A_sig * np.sin(2 * np.pi * f_sig * t)

# 设定输入噪声密度 50 nV/√Hz(随便举例)
e_n = 50e-9
# 对应到每个采样点的时域噪声标准差:sigma = e_n * sqrt(fs/2)
# (粗略关系,准确推导更复杂,这里当示范)
sigma_noise = e_n * np.sqrt(fs/2)
noise = np.random.normal(scale=sigma_noise, size=N)

x_analog += noise

# 3. ADC 量化建模:12 bit,范围 ±V_FS
# 3.1 限幅
x_clip = np.clip(x_analog, -V_FS, V_FS)

# 3.2 量化步长
Delta = 2 * V_FS / (2**B)
codes = np.round(x_clip / Delta)   # 量化到整数码
codes = np.clip(codes, -(2**(B-1)), 2**(B-1)-1)

# 3.3 再转换回电压(这一步和 M2K 驱动做的类似)
x_digital = codes * Delta

# 4. 窗函数:Hann
window = np.hanning(N)
CG = np.sum(window) / N           # 相干增益
ENBW_factor = np.sum(window**2) * N / (np.sum(window)**2)
df = fs / N
ENBW = df * ENBW_factor

xw = x_digital * window

# 5. FFT(只看正频)
X = np.fft.rfft(xw)
freqs = np.fft.rfftfreq(N, d=1/fs)

# 6. 从 FFT 幅值恢复单频正弦的峰值、RMS、电压谱等

# 6.1 幅度谱(峰值,针对单频信号)
#     A_est[k] = 2 * |X[k]| / (N * CG)
A_est = 2 * np.abs(X) / (N * CG)
Vrms_line = A_est / np.sqrt(2)

# 6.2 把正弦的那个 bin 标出来看看
k_sig = np.argmin(np.abs(freqs - f_sig))
print("信号频率 bin:", k_sig, "对应频率:", freqs[k_sig], "Hz")
print("估计 Vrms:", Vrms_line[k_sig], "V")

# 7. 噪声谱:计算近似“电压噪声密度”(V/√Hz)
#    Vrms_line 是每个 bin 的等效 RMS(包含信号+噪声)。
#    对于噪声,可以除以 sqrt(ENBW) 得到近似噪声密度。
Vn_density = Vrms_line / np.sqrt(ENBW)

# 8. 转换为 dBV 与 dBV/√Hz
spec_dBV = 20 * np.log10(Vrms_line / 1.0 + 1e-20)
spec_dBV_Hz = 20 * np.log10(Vn_density / 1.0 + 1e-30)

# 9. 作图
plt.figure(figsize=(10, 6))
plt.semilogx(freqs, spec_dBV, label="Line spectrum (dBV)")
plt.xlabel("Frequency (Hz)")
plt.ylabel("Amplitude (dBV)")
plt.grid(which="both", ls=":")
plt.legend()
plt.title("FFT Spectrum (windowed, Hann)")
plt.tight_layout()
plt.show()

plt.figure(figsize=(10, 6))
plt.semilogx(freqs, spec_dBV_Hz, label="Noise density (dBV/√Hz)")
plt.xlabel("Frequency (Hz)")
plt.ylabel("Noise density (dBV/√Hz)")
plt.grid(which="both", ls=":")
plt.legend()
plt.title("Estimated Noise Spectral Density")
plt.tight_layout()
plt.show()
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-12-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云深之无迹 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 传统扫频型频谱分析仪:超级外差 + 扫频滤波
    • 分辨率带宽(RBW)、扫描时间、噪声底
  • FFT 频谱分析仪:先采样,再做 FFT
    • FFT 型的“RBW”来自哪里?
    • FFT 与扫频型的差异
  • 和网络分析仪、示波器的关系
  • 现在完成对ADALM2000的频谱分析仪过程建模
  • 第一步:ADC 采样参数 → 离散时间序列
  • 窗函数、CG 和 ENBW:从 FFT 系数到物理幅值
    • 窗函数 w[n]
    • 单频正弦幅度重建
  • 参考 ADALM2000 的具体链路
  • ADALM2000软件设计:HDL数据流向
  • 完整的 Python 建模示例(贴近 M2K)
  • 后记
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档