使用PyVISA + DM3058E万用表 + DSG3065B-IQ射频信号源自动化测量整流电路功率转换效率(PCE)
本文包含使用AI生成的代码
"""
PCE Measurement Automation — Rectifier Efficiency Characterization
Controls RIGOL DM3058E (DMM) and DSG3000B (RF Signal Generator) via pyvisa.
"""
import logging # 导入日志模块,用于输出运行信息和错误
import csv # 导入CSV模块,用于保存测试结果为CSV文件
import time # 导入时间模块,用于设置测量等待间隔
from typing import List, Tuple # 导入类型注解:列表和元组
from dataclasses import (
dataclass,
field,
asdict,
) # 导入数据类,用于简化配置和结果的数据结构
import pyvisa # 导入pyvisa库,用于通过USB/GPIB控制仪器
import numpy as np # 导入numpy(保留备用,当前未使用)
import matplotlib
matplotlib.use("TkAgg") # 设置交互式后端(避免GUI线程问题)
import matplotlib.pyplot as plt # 导入matplotlib绘图模块
logging.basicConfig(
level=logging.INFO, format="%(asctime)s %(message)s"
) # 配置日志:输出INFO级别以上,带时间戳
log = logging.getLogger(__name__) # 创建当前模块的日志记录器
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
@dataclass # 声明为数据类,自动生成 __init__、__repr__ 等方法
class Config: # 配置类,集中管理所有可调参数
visa_dmm: str = "USB0::0x1AB1::0x09C4::DM3R244603534::INSTR" # 万用表的VISA地址
visa_sg: str = "TCPIP0::192.168.31.29::inst0::INSTR" # 信号源的VISA地址
frequencies: List[float] = field(
default_factory=lambda: [4.765e9]
) # 射频频率列表,单位Hz(支持多个频点)
resistor: float = 1.1e3 # 电阻大小,根据阻值反推功率
powers_dbm: List[float] = field(
default_factory=lambda: list(range(-20, 25, 1))
) # 功率扫描范围
dwell: float = 1.2 # 设置仪器后等待的时间,单位秒
dmm_timeout: int = 5000 # 万用表通信超时时间,单位毫秒
sg_timeout: int = 5000 # 信号源通信超时时间,单位毫秒
output_csv: str = "pce_results.csv" # 输出CSV文件的文件名
output_png: str = "pce_plot.svg" # 输出PCE曲线图的文件名
CONFIG = Config() # 实例化配置对象,程序启动时加载默认配置
# ---------------------------------------------------------------------------
# Instrument helpers
# ---------------------------------------------------------------------------
def open_resources(
rm: pyvisa.ResourceManager,
) -> Tuple: # 打开并返回万用表和信号源的连接对象
dmm = rm.open_resource(CONFIG.visa_dmm) # 通过VISA地址打开万用表
dmm.timeout = CONFIG.dmm_timeout # 设置万用表的超时时间
sg = rm.open_resource(CONFIG.visa_sg) # 通过VISA地址打开信号源
sg.timeout = CONFIG.sg_timeout # 设置信号源的超时时间
return dmm, sg # 返回万用表和信号源的对象
def reset_instruments(dmm, sg): # 重置两台仪器到已知状态
dmm.write("*RST") # 向万用表发送复位命令
sg.write("*RST") # 向信号源发送复位命令
time.sleep(0.5) # 等待500ms让仪器完成复位
def configure_dmm(dmm): # 配置万用表的测量模式
dmm.write(":FUNCtion:VOLTage:DC") # 选择直流电压测量功能
dmm.write(":RATE:VOLTage:DC S") # 设置测量速度为最慢 2.5 reading/s
err = dmm.query("SYSTem:ERRor?") # 查询是否有SCPI错误
if err and "No error" not in err: # 如果有错误(且不是"No error")
log.warning("DMM config error: %s", err) # 记录警告日志
def set_rf_frequency(sg, freq_hz: float): # 设置信号源的射频频率
sg.write(f":FREQuency {freq_hz:.1f}") # 发送频率设置命令
# 不支持SYSTem:ERRor?命令
# err = sg.query("SYSTem:ERRor?") # 查询是否有SCPI错误
# if err and "No error" not in err: # 如果有错误
# log.warning("SG freq error: %s", err) # 记录警告日志
def set_rf_power(sg, power_dbm: float): # 设置信号源的输出功率
sg.write(f":LEVel {power_dbm:.1f}dBm") # 发送功率设置命令,单位dBm
# err = sg.query("SYSTem:ERRor?") # 查询是否有SCPI错误
# if err and "No error" not in err: # 如果有错误
# log.warning("SG power error: %s", err) # 记录警告日志
def rf_output_on(sg): # 打开信号源的射频输出
sg.write(":OUTPut ON") # 发送射频开启命令
def rf_output_off(sg): # 关闭信号源的射频输出
sg.write(":OUTPut OFF") # 发送射频关闭命令
def measure_dc_voltage(dmm) -> float: # 测量整流电路的直流输出电压
return float(
dmm.query(":MEASure:VOLTage:DC?")
) # 发送电压查询命令,返回浮点数(单位V)
def measure_dc_current(dmm) -> float: # 测量整流电路的直流输出电流
return float(
dmm.query(":MEASure:CURRent:DC?")
) # 发送电流查询命令,返回浮点数(单位A)
# ---------------------------------------------------------------------------
# PCE computation
# ---------------------------------------------------------------------------
def dbm_to_watt(dbm: float) -> float: # 将dBm转换为瓦特(线性功率)
return 10 ** (dbm / 10) * 0.001 # 公式:P(W) = 10^(P(dBm)/10) × 0.001
def compute_pce(
v_dc: float, i_dc: float, p_rf_dbm: float
) -> float: # 计算功率转换效率PCE
p_dc = v_dc * i_dc # 直流输出功率 = 电压 × 电流
p_rf = dbm_to_watt(p_rf_dbm) # 射频输入功率从dBm转为瓦特
if p_rf <= 0: # 如果射频功率为0或负数(保护)
return 0.0 # 返回0%效率
return (p_dc / p_rf) * 100.0 # PCE(%) = (P_dc / P_rf) × 100
# ---------------------------------------------------------------------------
# Main sweep
# ---------------------------------------------------------------------------
@dataclass # 声明为数据类,用于存储单个测量点的数据
class MeasurementPoint: # 测量点数据结构
freq_hz: float # 射频频率(Hz)
power_dbm: float # 射频功率(dBm)
v_dc: float # 直流电压(V)
i_dc: float # 直流电流(A)
pce_pct: float # 功率转换效率(%)
def run_sweep(dmm, sg) -> List[MeasurementPoint]: # 执行完整的频率×功率扫描测量
results: List[MeasurementPoint] = [] # 初始化结果列表
for freq in CONFIG.frequencies: # 外层循环:遍历每个频率
set_rf_frequency(sg, freq) # 设置信号源频率
log.info("--- Sweeping frequency: %.2f GHz ---", freq / 1e9)
for pwr in CONFIG.powers_dbm: # 内层循环:遍历每个功率点
set_rf_power(sg, pwr) # 设置当前功率值
rf_output_on(sg) # 打开射频输出
time.sleep(CONFIG.dwell) # 等待指定时间,让电路响应稳定
v_dc = measure_dc_voltage(dmm) # 测量直流电压
i_dc = v_dc / CONFIG.resistor
# i_dc = measure_dc_current(dmm) # 测量直流电流
pce = compute_pce(v_dc, i_dc, pwr) # 计算当前点的PCE
log.info( # 打印当前测量结果到日志
"Freq=%.2fGHz P_rf=%+.0fdBm V_dc=%.6fV I_dc=%.6fA PCE=%.2f%%",
freq / 1e9,
pwr,
v_dc,
i_dc,
pce,
)
results.append(
MeasurementPoint( # 将当前测量点存入结果列表
freq_hz=freq,
power_dbm=pwr,
v_dc=v_dc,
i_dc=i_dc,
pce_pct=pce,
)
)
rf_output_off(sg) # 关闭射频输出(保护DUT)
time.sleep(0.2) # 等待200ms再进入下一个功率点
return results # 返回所有测量点的结果列表
def save_csv(results: List[MeasurementPoint], path: str): # 将测量结果保存为CSV文件
if not results: # 如果结果列表为空
log.warning("No data to save.") # 记录警告并返回
return
fieldnames = list(
asdict(results[0]).keys()
) # 从第一个数据点获取列名(freq_hz, power_dbm, ...)
with open(path, "w", newline="") as f: # 以写入模式打开CSV文件
writer = csv.DictWriter(f, fieldnames=fieldnames) # 创建CSV写入器
writer.writeheader() # 写入表头
for pt in results: # 遍历每个测量点
writer.writerow(asdict(pt)) # 将数据点转为字典并写入一行
log.info("Saved %d points to %s", len(results), path) # 记录保存完成信息
def plot_pce(
results: List[MeasurementPoint], path: str
): # 绘制PCE曲线图,不同频率用不同颜色的曲线表示
if not results:
log.warning("No data to plot.")
return
freq_groups: dict = {}
for pt in results:
freq_groups.setdefault(pt.freq_hz, []).append(pt)
plt.figure(figsize=(10, 6))
for freq, pts in sorted(freq_groups.items()):
pts_sorted = sorted(pts, key=lambda p: p.power_dbm)
powers = [p.power_dbm for p in pts_sorted]
pces = [p.pce_pct for p in pts_sorted]
plt.plot(powers, pces, marker="o", label=f"{freq / 1e9:.2f} GHz")
plt.xlabel("RF Power (dBm)")
plt.ylabel("PCE (%)")
plt.title("Power Conversion Efficiency vs RF Power")
plt.grid(True, linestyle="--", alpha=0.6)
plt.legend(title="Frequency")
plt.tight_layout()
plt.savefig(path, dpi=150)
log.info("Saved plot to %s", path)
plt.show()
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
def main(): # 主函数
rm = pyvisa.ResourceManager() # 创建VISA资源管理器,自动检测后端
dmm = sg = None # 初始化仪器变量为None
try: # try块确保异常时也能清理资源
dmm, sg = open_resources(rm) # 打开万用表和信号源连接
log.info(
"Connected — DMM: %s", dmm.query("*IDN?").strip()
) # 打印万用表型号信息
log.info("Connected — SG: %s", sg.query("*IDN?").strip()) # 打印信号源型号信息
reset_instruments(dmm, sg) # 重置两台仪器到已知状态
configure_dmm(dmm) # 配置万用表为直流电压测量模式
sg.write(":UNIT:POWer dBm") # 设置信号源功率单位为dBm
results = run_sweep(dmm, sg) # 执行功率扫描测量
save_csv(results, CONFIG.output_csv) # 保存结果到CSV文件
plot_pce(results, CONFIG.output_png) # 绘制PCE曲线图
log.info("Measurement complete.") # 打印测量完成
except Exception: # 捕获任何异常
log.exception("Error during measurement") # 打印完整的异常堆栈
raise # 重新抛出异常,让上层处理
finally: # finally块确保资源被释放
if sg: # 如果信号源对象已创建
try:
rf_output_off(sg) # 确保关闭射频输出(安全保护)
except Exception: # 如果关闭失败
pass # 忽略错误(已有异常时不再干扰)
sg.close() # 关闭信号源连接
if dmm: # 如果万用表对象已创建
dmm.close() # 关闭万用表连接
rm.close() # 关闭VISA资源管理器
if __name__ == "__main__": # 判断是否为主程序入口(非被导入)
main() # 调用主函数执行测量

浙公网安备 33010602011771号