本文介绍一个远程仪器控制的例子,包含一些 Python 脚本实现自动在示波器上进行简单的测量。

Python 介绍

Python 是免费和开源的,它为核心开发人员提供了责任、庞大的支持基础以及 Python 用户检查和改进其代码库的能力。Python 有很多包用来扩展了 Python 的基本功能。Python 的包可以使用其包管理工具(称为“pip”)添加到Python 安装中。Python 无需许可,可以非常轻松地安装和配置。 而且Python简单易学,语法和结构非常人性化和直观。基于以上优点非常适合用来实现仪器自动化,可以用来替代Labview。

仪器自动化

仪器自动化涉及在计算机上编写脚本或应用程序,通过向其发送 ASCII 消息来控制测试设备。 每个仪器都有自己的一组 ASCII 消息,使用可编程仪器标准命令(或 Standard Commands for Programmable Instruments)协议定义。

SCPI

每个可远程控制的仪器都有一组记录的 SCPI 命令,允许用户使用编程接口而不是使用前面板控件或图形用户界面来控制仪器。在某些情况下,仪器的 SCPI 命令集包含在其用户手册中,但制造商通常会提供独立的程序员手册,其中记录了所有可用的命令。每个支持 SCPI 的仪器都提供一些标准命令,包括 *RST(重置/默认设置)、*ESR? (检查错误状态寄存器),*OPC? (操作完成查询)、*CLS(清除状态寄存器)和*IDN? (身份查询),但大多数命令是仪器特定。

pyVISA

仪器通信通常通过 VISA 标准来实现。 VISA 是虚拟仪器软件架构,它是测试设备和控制计算机之间通信的标准化机制。 PyVISA 是支持“虚拟仪器软件架构”(VISA) 的 Python 包,以便通过 GPIB、RS232、以太网或 USB 控制测量设备和测试设备。PyVISA 还包括有用的自定义功能、广泛的文档和有用的示例。

可以使用多种硬件接口与仪器进行通信,包括 USB、串行 (RS-232)、GPIB 和以太网,但 VISA 将其抽象化,并允许用户以相同的方式与设备交互,而不管使用的物理硬件如何 与测试设备连接。

代码示例准备

在使用 PyVISA 控制仪器之前,必须配置 VISA 应用程序,并且必须将仪器添加到 VISA 资源列表中。这通常使用制造商特定的 VISA 应用程序(例如 Keysight IO Libraries)来完成。如果使用TCP/IP通信方法,局域网上的大多数仪器都会在 Keysight IO Libraries 中自动发现,将新仪器添加到资源列表中,就从发现的仪器列表中选择它。VISA也可以通过COM(ASRL?),USB(USB0), PXI(PXI10) (PCI eXtensions for Instrumentation)来连接仪器。

代码示例

该脚本将设置示波器、提取波形数据并绘制它。

仪器控制脚本包括以下步骤:

-导入所需的Python包

-连接仪器

-定义重要的功能函数

-发送命令并获取数据

-处理和可视化数据

– 主函数中定义测量变量

通常,Python 脚本中的前几行代码会导入脚本所需的任何 Python 包。 import 语句允许 Python 脚本使用正在导入的包或模块中的任何代码。

​import pyvisaimport matplotlib.pyplot as pltimport timeimport numpy as npimport pandas as pd​

示例文件中的第一行导入 PyVISA 包。 第二行从 matplotlib 包导入 pyplot 模块,并使用“as”关键字为该模块分配一个别名。 这纯粹是为了方便,它允许用户每次想要使用该 Python 模块中的某些内容时编写 plt 而不是 matplotlib.pyplot。numpy库主要用于对多维数组执行计算,会在读取数据时用来临时存放数据。Panda用来对数据进行分析和处理。

与示波器的连接必须使用 PyVISA 软件包。该脚本使用了 PyVISA 的两段代码:ResourceManager 类及其 open_resource 方法。ResourceManager 可以列出所有可用的 VISA 资源,并创建与这些资源的连接。脚本创建了 ResourceManager 类的实例,并将其赋值给变量 resourceManager.

def __init__(self):self.resourceManager = pyvisa.ResourceManager()print(self.resourceManager.list_resources())

通过list_resources可以得到示波器的地址,然后可以赋值给address变量中:

def __init__(self):self.address = 'USB0::0x0957::0x0588::CN50301291::INSTR'self.resourceManager = pyvisa.ResourceManager()# self.resourceManager.list_resources()print(self.address)def open(self):self.instance = self.resourceManager.open_resource(self.address)self.idn = self.instance.query('*IDN?')print(self.idn)

在open函数中使用了 open_resource 的方法创建了一个通信实例instance。该对象可用于与仪器通信。

最开始是对仪器的初始化,一般情况仪器控制脚本应以 *RST 命令开始。该命令由 SCPI 协议定义,适用于所有测试和测量设备。它将仪器返回到默认配置。*OPC?是另一个非常有用的同步命令。它会等到前面的命令执行完毕,然后再继续执行脚本。

 def reset(self):self.instance.write('*rst')def opc(self):self.instance.write('*opc')

接下是打开示波器的通道,如下可设置耦合方式和单位:

def open_ch(self, ch, cplg, unit):self.instance.write(':channel1:display OFF')self.instance.write(f':channel{ch}:display ON')self.instance.write(f':channel{ch}:coupling %s' %cplg)self.instance.write(f':channel{ch}:unit %s' %unit)

然后是对示波器的垂直和水平轴的控制,其函数如下,指令根据示波器的编程手册。本示例使用的是 F-字符串。F-strings 在运行时将 运行时用一个值替换放在大括号内的任何表达式。

def vertical_ch(self, ch, scale, position):self.instance.write(f':channel{ch}:scale {scale}')self.instance.write(f':channel{ch}:offset {position}')def horizontal_ch(self, scale, position):self.instance.write(f':timebase:scale {scale}')self.instance.write(f':timebase:offset {position}')

示波器的触发设置函数如下:

def trigger_set(self, ch, mode, level, slope):self.instance.write(':trigger:mode %s' % mode)self.instance.write(f':trigger:edge:source {ch}')self.instance.write(f':trigger:edge:level {level}')self.instance.write(':trigger:edge:slope %s' %slope)

接下来是从示波器中读取数据,本例中使用 query_binary_values 方法。根据Aligent DSO1024A的编程手册,Byte格式写和读指令之间需间隔大于200ms。经过调试,在 query_binary_values指令前加time.sleep(2)便可正确运行了。

def get_waveform(self, ch, mode, format):#self.instance.write(':waveform:points 600')self.instance.write(':waveform:points:mode %s' %mode)self.instance.write(':waveform:format %s' %format)self.instance.write(f':waveform:source channel{ch}')print(self.instance.query(':waveform:preamble?'))time.sleep(2)rawData = self.instance.query_binary_values('waveform:DATA?', datatype = 'B', container = np.array, delay = 0.2)# Query x and y values to scale the data appropriately for plottingxIncrement = float(self.instance.query(':waveform:xIncrement?'))xOrigin = float(self.instance.query(':waveform:xOrigin?'))xReference = float(self.instance.query(':waveform:xReference?'))yIncrement = float(self.instance.query(':waveform:yIncrement?'))yReference = float(self.instance.query(':waveform:yReference?'))yOrigin = float(self.instance.query(':waveform:yOrigin?'))#Save as csv filelength = len(rawData)t0 = -xReference*xIncrement+xOriginwith open('data.csv', mode = 'w') as file:for i in range(0,length): xvalues = t0 + xIncrement*i file.write(str(xvalues)) file.write(",") yvalues = float(yReference - rawData[i]) * yIncrement - yOrigin file.write(str(yvalues)) file.write("\n")#plotting the measurement datadata = pd.read_csv('data.csv')data_x = data.iloc[:,0]data_y = data.iloc[:,1]plt.plot(data_x, data_y)plt.title('Waveform')plt.xlabel('Time (Sec)')plt.ylabel('Voltage (V)')plt.savefig('test.png')plt.show()

在query_binary_values语句之后得到的数据需要通过转换才能得到正确的时间和电压值,然后使用with open … as file 将数据进行保存,最后使用matplotlib.pyplot进行绘画。

主函数的运行程序如下:

if __name__ == "__main__":instr = DSO1024A()instr.open()instr.reset()instr.opc()instr.open_ch(2, 'DC', 'VOLTS')instr.vertical_ch(2, 1, 0)instr.horizontal_ch(1.0e-3, 2.0e-3)instr.trigger_set(2, 'EDGE', 2, 'positive')instr.get_waveform(2, 'maximum', 'BYTE')

最后得到的示波器数据后绘出信号如下: