PyVISA 实战:用 Python 控制物理实验室常用仪器
@[toc] 1. 行文动机 2. What Do U Need? 3. NI MAX & NI VISA 4. Work In Python 行文动机

如果你是一名物理系(或是其它的理工科,想来也大差不差)的学生,那么几乎每个学期你都会享受(这个学分少但耗时长)的课程。一开始,看到各式各样的仪器,诸如函数发生器、示波器以及台式万用表等等,会感到相当新奇。
上面的按键看似琳琅满目,然而事实上,你上了几节课之后,就会发现,也不过如此。难道这些几千上万的设备仅仅只能像我们课上那样按一下测一下?如果我希望实现一些更高级的控制应该怎么办?或者哪怕不谈复杂的控制,如果我希望用发生器产生一整天的信号,难道我当真要在实验室里面按一天的按钮?
真这样的话,那大伙的科研都没得做了。此文的行文正是源于此:为大家介绍如何能够通过 python \texttt{python} python编程控制实验室里的设备,以更好的服务自己的实验需求(比如你的科研或是大创项目)。
这或许是从“只是学生”到“开始科研”的第一步。
What Do U Need ?
- 一台电脑(笔记本也OK)
- 你需要控制的仪器。不用担心我们将要介绍的方式无法控制,因为它几乎可以控制市面上所有支持标准通信接口的测试测量仪器,比如Ethernet、GPIB、serial、USB、VXI和PXI。但是本篇中,我们介绍的是最简单的最简单的USB。
- USB2.0-USB Type B的连接线,如下图所示:


NI MAX & NI VISA
NI VISA(Virtual Instrument Software Architecture)是National Instruments开发的一套标准化仪器通信架构,它为GPIB、USB、以太网、串口等各种硬件接口提供了统一的编程接口,让开发者无需关心底层通信细节。NI MAX(Measurement & Automation EXplorer)则是NI VISA配套的图形化管理工具,主要用于自动发现连接的仪器设备、查看和测试VISA资源地址、配置通信参数以及进行基础的设备诊断,是初次配置仪器时的得力助手。
在Python仪器控制中,NI VISA作为底层通信引擎,通过PyVISA库为Python程序提供实际的仪器通信能力,而NI MAX则主要在前期配置阶段发挥作用——帮助用户快速找到仪器的VISA地址,这个地址随后会被写入Python代码中用于建立连接。
NI MAX与NI VISA的官方下载页面: https://www.ni.com/en-us/support/downloads/drivers/download.ni-visa.html

下载完成后,我们需要打开NI VISA,获取设备的通信地址:

当我们使用USB线完成设备的连接后,点击Refresh。稍等一段时间,我们就会发现,VISA成功找到了新的设备:

红框圈起来的部分中,USB0::0X1AB...的部分即为通信地址。我们需要做的,只是简单地复制到Python中即可。
Work In Python
为了能够在python中实现对于设备的控制,我们肯定需要安装依赖库:
pip install pyvisa 安装完依赖库之后,就可以进行对设备的控制了。下面我们结合一些典型操作为大家介绍如何控制:
示例代码基于常见的 USB/TCP/GPIB 设备(示波器、函数发生器、数字万用表)。更详尽的、可直接复制即用的控制代码放在Github仓库:https://github.com/HorseRunningWild/PhyExp-Device-Control1. 库导入 + Resource Manager \texttt{Resource Manager} Resource Manager
import pyvisa import time import numpy as np import matplotlib.pyplot as plt import openpyxl # 如果要写 Excel# --- 请根据实际情况修改这些地址 --- dmm_address ='USB0::0x1AB1::0x09C4::DM3R240300120::INSTR'# DMM 的地址(示例) func_gen_address ='USB0::0x1AB1::0x0641::DG4E232700999::INSTR'# 函数发生器地址(示例)# 建立 ResourceManager(与 VISA 后端连接) rm = pyvisa.ResourceManager()说明:
ResourceManager()是和系统的 VISA 库(NI-VISA / Keysight / pyvisa-py 等)对接的入口。- 地址字符串是资源字符串(resource string),不同连接方式格式不同:USB、GPIB、TCP/IP(
TCPIP::)等。可以用rm.list_resources()列出当前能发现的设备。
2. 发现设备(探索可用地址)
print("发现的 VISA 资源:")for r in rm.list_resources():print(" ", r)说明:
- 经常第一步就是
list_resources()——把它当“发现附近仪器”的函数。 - 如果找不到设备,先检查 USB 驱动、线缆、设备电源或 NI-VISA 是否安装正确。
- 当然,实现这一点本质上是通过NI MAX的,只不过我们没有通过打开NI VISA的方式,而是直接在Python中实现。
3. 打开一个设备并询问标识
inst = rm.open_resource(func_gen_address) inst.timeout =10000# 毫秒级超时,按需要调整 idn = inst.query('*IDN?')# 大多数仪器支持 *IDN? 来返回厂商/型号/序列号print("Instrument ID:", idn.strip())说明:
.open_resource()返回一个 instrument 对象,用它来write()(发送命令)和query()(发送并读取回复)。query()等于write()+read(),常用于 SCPI 命令能立即返回的场景。- 设置
timeout很重要:避免无响应时无限挂住。
4. 基本读写模式:
常用的基本读写模式有:.write()、.query()、.read()、.read_raw()
# 写命令(无返回) inst.write(':SOURce1:VOLTage 1.0')# 设置电压(取决于厂商命令)# 读命令(返回字符串) resp = inst.query(':MEASure:VOLTage:DC?') val =float(resp)# 解析为浮点数(若返回数字)说明与建议:
.write():发送不需要返回的命令(例如设置电压、开/关输出)。.query():发送后期望得到立即回复的命令(例如测量)。- 有些仪器返回二进制数据(波形),这时候需要用
.read_raw()或.read_bytes()并结合二进制解析。
5. 安全关闭(try / finally 模式)
inst =Nonetry: inst = rm.open_resource(dmm_address)# 做一些读取或设置print(inst.query('*IDN?'))finally:# 无论是否发生异常都要确保关闭连接if inst isnotNone:try: inst.close()except Exception:passtry: rm.close()except Exception:pass说明:
- 仪器连接资源要在结束时释放(
close()),否则下次打开可能失败或者 USB 资源被占用。 - 推荐把关键段放在
try/finally中,或使用 context manager(某些 pyvisa 版本支持with)。
6. 读取示波器二进制波形(通用思路)
示波器通常把波形以二进制块返回,且伴随一段 :WAV:PRE?(preamble)描述缩放信息。把原始样本转换为电压的常见公式:
V = (sample - yref) * yinc + yorg 下面是一个简化版的读取流程(Rigol / 一些示波器类似):
import numpy as np # 假设 inst 是已连接的示波器对象# 尝试读取 preamble(自带缩放参数) pre = inst.query(':WAV:PRE?')# 例如返回 CSV 字段:format,type,points,count,xinc,xorg,xref,yinc,yorg,yref parts =[p.strip().strip('"')for p in pre.split(',')if p.strip()!='']# 尝试解析常用字段 points =int(parts[2]) yinc =float(parts[7]); yorg =float(parts[8]); yref =float(parts[9])# 读取二进制数据(厂商差异大,可能需要 'WAV:DATA?' 或 ':WAV:FORMat WORD;:WAV:DATA?')# 下面仅示意 pyvisa 的二进制读取 raw = inst.read_raw()# 直接读取原始二进制块,具体命令请先通过 query(':WAV:DATA?') 触发命令# 解析 IEEE 488.2 格式的二进制块头('#' + len + digits ...),这里建议参考厂商文档或直接使用 inst.query_binary_values samples = inst.query_binary_values(':WAV:DATA?', datatype='h', container=np.array)# 'h' = 16-bit signed volts =(samples.astype(np.float64)- yref)* yinc + yorg 说明:
- 推荐使用
inst.query_binary_values()(pyvisa 提供)来直接把二进制数据转成数组,datatype、is_big_endian等参数需根据仪器选择。 - 一定要先拿到 preamble(缩放参数 yinc/yorg/yref),否则得到的只是“原始计数”。
- 仪器厂商命令差异大(Keysight/ Rigol/ Tektronix),看设备手册最保险。
7. 将数据保存为 CSV / Excel
import csv # 保存 CSVwithopen('data.csv','w', newline='')as f: writer = csv.writer(f) writer.writerow(['amp_vpp','measured_v'])for a, v inzip(amp_points, measured_v): writer.writerow([a, v])# 写入 Excel(openpyxl 示例)from openpyxl import Workbook wb = Workbook() ws = wb.active ws.append(['amp_vpp','measured_v'])for a, v inzip(amp_points, measured_v): ws.append([a, v]) wb.save('data.xlsx')说明:
- CSV 适合快速导出,Excel(openpyxl)适合给非程序员查看或交付报告。
- 写文件前建议检查目录写权限。
8. 常见问题与排查清单
- 找不到设备:检查线缆 / 设备电源 / 是否安装 NI-VISA 或 pyvisa-py。运行
rm.list_resources()看系统是否识别。 - 命令无响应或阻塞:增加
timeout;用*IDN?检查基本通信;确认是否需要先:SYST:LOCAL之类的命令切换模式。 - 二进制读取出错:先打印
:WAV:PRE?的返回,确认yinc/yref/yorg;用inst.query_binary_values()来简化解析。 - 频繁查询导致仪器卡住:减慢查询频率、增加
sleep、合并查询、或只在需要时查询(例如每 N 次读一次幅值)。 - 单位/量程不对:注意仪器的单位命令(VPP / Vrms / Vpp),并在读数前把仪器设置为期望单位。
在放置上述控制Demo代码的Github仓库:https://github.com/HorseRunningWild/PhyExp-Device-Control,还有Hill Climbing和PID算法结合实验仪器一同实现测量与控制的脚本,大家可以自行探索。