用 Python(PyVISA) 實現儀器自動化

micheal_steven發表於2024-04-22

本文介紹一個遠端儀器控制的例子,包含一些 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 pyvisa
import matplotlib.pyplot as plt
import time
import numpy as np
import 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 plotting
xIncrement = 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 file
length = len(rawData)
t0 = -xReference*xIncrement+xOrigin
with 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 data
data = 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')
最後得到的示波器資料後繪出訊號如下:

參考資料:

python人工智慧 PyTorch醫療診斷: 用 PyTorch 實現一個 醫療診斷 任務,程式碼方案分享1(圖文詳解)

python人工智慧 PyTorch金融預測: 用 PyTorch 實現一個 金融預測 任務,程式碼方案分享1(圖文詳解)

python人工智慧 RL PyTorch 強化學習: 用 PyTorch 實現一個 RL 強化學習 任務,程式碼方案分享1(圖文詳解)

相關文章