首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >从ADALM2000看网络分析仪原理

从ADALM2000看网络分析仪原理

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

ADALM2000 示波器信号链设计.补篇

ADALM2000 示波器信号链设计

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

手头的ADALM2000,越玩越好玩,这篇分享一个有趣的,但是平时研发不常见的仪器:网络分析仪(可绘制电路的波特图、奈奎斯特图和尼科尔斯图)测量范围:1Hz 至 10MHz

看一个是德的仪器
看一个是德的仪器

看一个是德的仪器

这个是测试的结果
这个是测试的结果

这个是测试的结果

测量的时候就是这样的方案,一边激励,一边测量
测量的时候就是这样的方案,一边激励,一边测量

测量的时候就是这样的方案,一边激励,一边测量

专业的仪器测量的范围都很宽,而我们这个小玩具才最大到10M
专业的仪器测量的范围都很宽,而我们这个小玩具才最大到10M

专业的仪器测量的范围都很宽,而我们这个小玩具才最大到10M

但是对了解原理没有什么问题。

网络分析仪的核心思想:测量“传递函数”

任何线性系统(电路、滤波器、放大器、传感器前端……)都可以抽象成:

在频域就是:

所以 网络分析仪做的事情,本质就是测出

然后画成两张图:

幅频特性: → dB/Hz 曲线(Bode 图的上半部分)

相频特性: → 相位-频率(Bode 图下半部分)

这就是“网络分析”的本体。

通用工作流程(不管是大型 VNA 还是 ADALM2000)

产生已知激励信号

仪器内部有一个“源”(source):

正弦扫频:或多频合成(chirp、multisine);幅度、频率都由仪器精确控制和知道(这点很关键:自己知道“它给了什么”)。

通过被测网络(DUT)

把激励加在 DUT 上,比如:低通滤波器、运放电、LC 谐振、传感器、功放、变压器……

测量点一般有两处:“输入端”电压 作为参考 ,“输出端”电压 作为响应

同时采集参考与响应

用两个同步 ADC 通道,以相同采样率采:

CH1:参考信号 (或电流)

CH2:响应信号

同步采样保证:相位关系是可信的,不被通道延时差搞乱。

频域提取 / 锁相检测

对采来的数据做频域分析,一般有两条路:

单频正弦 + 锁相放大式算法

对每个频点 f,采一小段时间窗的数据,和 做相关:

这相当于在目标频率上做一个窄带锁相检测,SNR 很高。

一段波形做 FFT

对整个时间窗做 FFT:

找到对应频率 bin ,然后:

不管哪种方式,得到的 都是一个复数,包含幅度和相位。

计算幅度 + 相位

然后画幅度:(dB),相位(度):

扫频

为了得到频率响应,就让频率从 扫到:

线性扫:步进 Δf,比如 100 Hz、200 Hz、300 Hz…

对数扫:每 decade 固定点数,比如 10 Hz → 100 Hz → 1 kHz → 10 kHz…

每个频点重复上面的采集 + 计算,就得到整条 Bode 曲线。

ADALM2000 上的“网络分析仪”具体怎么工作

Scopy 里的 Network Analyzer 工具,本质就是用一个集成的:两个 AWG 通道(DAC)+ 两个 Scope 通道(ADC)

直接用内部
直接用内部

直接用内部

来实现一个 低频 FRA(频率响应分析仪)

信号路径(硬件)

典型连接方式(最常见的用法):

AWG CH1 输出一个正弦波 → 通过电阻/电路输入到 DUT。

Scope CH1 测量 输入端电压 Vin(参考)

Scope CH2 测量 输出端电压 Vout(响应)

在 HDL 里是这样走的:DDR 中缓存好一段正弦波样点(比如一个周期 N 点):通过 AXI_DMAC_A/B → AXI_DAC_INTERPOLATE → AXI_AD9963(TX) → 板外 DUT → 板上前端 → AXI_AD9963(RX) → AXI_ADC_DECIMATE → AXI_ADC_TRIGGER/FIFO → AXI_DMAC → DDR;ARM + Linux从 DDR 读取采样数据,再交给上位机软件(Scopy)做频域计算。

内部设计

里面有 插值/抽取触发/FIFO 在这里就派上用场了:

插值让 AWG 内部数据率低一些,减少 DDR 读带宽;最终仍以高 DAC 速率输出;抽取让采样数据率低一些,减少 USB 传输和处理负担;然后触发 + FIFO可以保证在每个频点上采到的波形都是“稳定状态”,并且有足够的 pre-trigger/post-trigger 波形用来做 FFT。

模拟 Scopy 的网络分析器算法

用 M2K 输出扫频正弦 + 采样数据;做 FFT / 计算 H(f);画 Bode 图。

先生成扫频正弦(swept sine);对每个频点,生成理想输入 x(t),通过一个“虚拟 DUT”(比如一阶 RC 低通)得到输出 y(t);用 FFT 在该频点对应的 bin 上算:

画幅频 + 相频 Bode 图。

未来只要把 simulate_dut() 换成真实的 M2K 采集函数即可。

如何脱离上位机用Python计算

主要改这两块:

替换 simulate_dut()

现在版本是用理论幅相算出来的 y(t);换成“调用 libm2k / Scopy后端”:用 AWG 输出频率为 f_sig 的正弦;用 Scope 同步采集两路数据 vin[n], vout[n];然后把这两个数组丢给 measure_transfer_fft() 即可。

示意式伪代码大概是:

代码语言:javascript
复制
def acquire_from_m2k(f_sig, fs, n_cycles, vin_amp):
    # 1. 配置 AWG:频率 f_sig、幅度 vin_amp
    # 2. 配置 Scope:采样率 fs,通道1测 Vin,通道2测 Vout
    # 3. 等待系统稳定(可以丢掉前几十个周期)
    # 4. 抓取 N 点数据 → vin, vout
    return t, vin, vout

然后在主循环里改成:

代码语言:javascript
复制
# 原来:
t, vin = generate_sine(...)
vout = simulate_dut(t, vin, f_sig, fc=fc_dut)

# 替换为:
t, vin, vout = acquire_from_m2k(f_sig, fs, n_cycles, vin_amp)

频点、周期数、平均次数

可以加上“每个频点重复测量 M 次再平均”,提高 SNR:

或者先在 ADC 侧做叠加平均,再 FFT。

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

def generate_sine(f_sig, fs, n_cycles, amplitude=1.0):
    """
    生成某个频率下的正弦波测试信号。
    f_sig: 被测信号频率 (Hz)
    fs: 采样率 (Hz)
    n_cycles: 采样的周期数
    amplitude: 正弦幅度
    """
    T = n_cycles / f_sig           # 总时长
    N = int(np.round(T * fs))      # 采样点数
    t = np.arange(N) / fs
    x = amplitude * np.sin(2 * np.pi * f_sig * t)
    return t, x


def rc_lowpass_H(f, fc):
    """
    一阶 RC 低通的理论传递函数 H(j2πf) = 1 / (1 + j f/fc)
    f: 频率 (Hz) 或 numpy 数组
    fc: 截止频率 (Hz)
    """
    s = 1j * f / fc
    return 1.0 / (1.0 + s)


def simulate_dut(t, x, f_sig, fc=1e3):
    """
    用“理想一阶 RC 低通”来模拟 DUT。
    这边不做真正的滤波器卷积,而是直接用理论幅度/相位来构造 y(t):
        y(t) = |H(f)| * sin(2π f t + φ)
    这样能保持算法结构,又不依赖 SciPy 等库。
    将来你用 M2K 时,把这一函数替换成“采集真实 y(t)”即可。
    """
    H = rc_lowpass_H(f_sig, fc)
    mag = np.abs(H)
    phase = np.angle(H)
    y = mag * np.sin(2 * np.pi * f_sig * t + phase)
    return y


def measure_transfer_fft(x, y, fs, f_sig):
    """
    用 FFT 在 f_sig 对应的频率 bin 上估计 H(f) = Y/X。
    x, y: 两路同步采样信号 (numpy 数组)
    fs: 采样率 (Hz)
    f_sig: 当前扫频点 (Hz)
    返回: H_est (复数),包含幅度和相位
    """
    # 统一长度
    N = min(len(x), len(y))
    x = x[:N]
    y = y[:N]

    # 加窗,减小泄漏(这里用 Hann 窗)
    window = np.hanning(N)
    xw = x * window
    yw = y * window

    # 只做 rFFT(实信号),只关注正频
    X = np.fft.rfft(xw)
    Y = np.fft.rfft(yw)
    freqs = np.fft.rfftfreq(N, d=1.0/fs)

    # 找到最接近 f_sig 的 bin
    k = np.argmin(np.abs(freqs - f_sig))

    # 估计传递函数
    # 注意:真实系统里要处理 X[k] ≈ 0 的情况,这里简单略过
    H_est = Y[k] / X[k]
    return H_est


def sweep_network_analyzer(
    f_start=10.0,
    f_stop=1e5,
    points_per_decade=20,
    fs=1e6,
    n_cycles=200,
    vin_amp=1.0,
    fc_dut=1e3
):
    """
    扫频测量 DUT 的幅频 + 相频(这里 DUT 用一阶 RC 低通模拟)。
    将来如果接 M2K,只要改“获取 y(t)”那一步即可。

    参数含义:
    - f_start, f_stop: 扫频起止频率 (Hz)
    - points_per_decade: 每十倍频点的采样数(对数扫频)
    - fs: 采样率 (Hz)
    - n_cycles: 每个频点采多少个周期
    - vin_amp: 输入正弦幅度
    - fc_dut: 虚拟 RC 低通的截止频率 (Hz)
    """
    # 生成对数扫频频点
    n_decades = np.log10(f_stop) - np.log10(f_start)
    n_points = int(n_decades * points_per_decade)
    freqs = np.logspace(np.log10(f_start), np.log10(f_stop), n_points)

    mag_db_list = []
    phase_deg_list = []

    for f_sig in freqs:
        # 1) 生成输入正弦
        t, vin = generate_sine(f_sig, fs, n_cycles, amplitude=vin_amp)

        # 2) 通过 DUT(此处为仿真;真实 M2K 时用采集)
        vout = simulate_dut(t, vin, f_sig, fc=fc_dut)

        # 3) 用 FFT 算传递函数
        H_est = measure_transfer_fft(vin, vout, fs, f_sig)

        mag_db = 20 * np.log10(np.abs(H_est))
        phase_deg = np.angle(H_est, deg=True)

        mag_db_list.append(mag_db)
        phase_deg_list.append(phase_deg)

    mag_db_array = np.array(mag_db_list)
    phase_deg_array = np.unwrap(np.deg2rad(phase_deg_list))  # 弧度展开
    phase_deg_array = np.rad2deg(phase_deg_array)

    return freqs, mag_db_array, phase_deg_array


def plot_bode(freqs, mag_db, phase_deg, title="Simulated Network Analyzer"):
    """
    画一个双子图 Bode 图:上幅度,下相位。
    """
    fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(8, 6))

    ax1.semilogx(freqs, mag_db)
    ax1.set_ylabel("Magnitude (dB)")
    ax1.grid(True, which="both", ls=":")

    ax2.semilogx(freqs, phase_deg)
    ax2.set_ylabel("Phase (deg)")
    ax2.set_xlabel("Frequency (Hz)")
    ax2.grid(True, which="both", ls=":")

    fig.suptitle(title)
    plt.tight_layout()
    plt.show()


if __name__ == "__main__":
    # 示例:被测 DUT 是 fc = 1 kHz 的一阶 RC 低通
    fc = 1e3

    freqs, mag_db, phase_deg = sweep_network_analyzer(
        f_start=10,
        f_stop=1e5,
        points_per_decade=20,
        fs=1e6,
        n_cycles=200,
        vin_amp=1.0,
        fc_dut=fc
    )
    plot_bode(freqs, mag_db, phase_deg,
              title=f"Simulated RC Lowpass (fc = {fc:.0f} Hz)")

https://www.analog.com/cn/resources/analog-dialogue/studentzone/studentzone-july-2019.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-12-02,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 网络分析仪的核心思想:测量“传递函数”
  • 通用工作流程(不管是大型 VNA 还是 ADALM2000)
  • 产生已知激励信号
  • 通过被测网络(DUT)
  • 同时采集参考与响应
  • 频域提取 / 锁相检测
  • 计算幅度 + 相位
  • 扫频
  • ADALM2000 上的“网络分析仪”具体怎么工作
    • 信号路径(硬件)
  • 内部设计
  • 模拟 Scopy 的网络分析器算法
  • 如何脱离上位机用Python计算
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档