本文包含使用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() # 调用主函数执行测量