使用PyVISA + DM3058E万用表 + DSG3065B-IQ射频信号源自动化测量整流电路功率转换效率(PCE)

📅 2026/7/12 20:48:10 👁️ 阅读次数 📝 编程学习
使用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 matplotlibmatplotlib.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/serr = 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.001def compute_pce(v_dc: float, i_dc: float, p_rf_dbm: float
) -> float:  # 计算功率转换效率PCEp_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)  # 计算当前点的PCElog.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.")  # 记录警告并返回returnfieldnames = 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.")returnfreq_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  # 初始化仪器变量为Nonetry:  # 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")  # 设置信号源功率单位为dBmresults = 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()  # 调用主函数执行测量