Ustatnia modyfikacja 13 listopada 2023 przez Olek
W ramach mojej pracy w UMG stworzyłem program w Python który automatycznie pobiera bieżące wartości pomiarów z urządzenie i odpowiednio je rejestruje. Wynik na bieżąco przedstawia w postaci gotowych wykresów-charakterystyk. Dla wygody obsługi program posiada przyjazny interfejs graficzny napisany z wykorzystaniem popularnej biblioteki to Python-a tkinter (tk)1. Komunikację z urządzenie zapewnia biblioteka VISA2 (pyvisa3). Urządzenie musi być odpowiednio skonfigurowane i podłączone do sieci Ethernet. Na komputerze powinny być zainstalowane oprogramowanie do komunikacji z urządzeniami pomiarowymi NI4 VISA5. W poniższym przykładzie program posłużył do pomiarów prądów zerowych tranzystora bipolarnego w różnych temperaturach otoczenia Ta. Uzyskane wyniki na bieżąco zostały przeliczane i porównywane z wartościami granicznymi aby nie uszkodzić mierzonego tranzystora.

- Wskazanie urządzenia, z którym się chcemy podłączyć, według standardów VISA
- Parametry programu
- Tytuł pomiaru/wykresu/danych
- Ta – temperatura otoczenia. Na jej podstawie tworzona jest etykieta serii danych jak również obliczana temperatura Tj
- Tj_max – maksymalna temperatura złącza. Służy do ostrzeżenia przed przekroczeniem tej wartości. Po jej przekroczeniu wyniki pomiarów (pkt 3) są przedstawiane na czerwono
- Tutaj przedstawiany jest aktualny pomiar z urządzenia jak również obliczane wartości P i Tj. Przycisk ZAPISZ służy do zapisania bieżącego pomiaru.
- Wykres tworzony na podstawie zapisanych pomiarówa.
- Funkcje służące do konfiguracji, przeglądania i zapisu wykresu
- Funkcje sterujące programem i zapisanymi danymi.
- https://docs.python.org/3/library/tkinter.html ↩︎
- Virtual Instrument Software Architecture ↩︎
- https://pyvisa.readthedocs.io/en/latest/ ↩︎
- National Instruments ↩︎
- https://www.ni.com/en/support/downloads/drivers/download.ni-visa.html ↩︎
Poniżej cały omawiany skrypt.
"""
Komunakacja z multimetrem rigol
z interfejsem graficznym - it
"""
from __future__ import absolute_import, division, print_function
import serial
import time
import tkinter as tk
from builtins import * # @UnusedWildImport
from ctypes import POINTER, c_double, c_ulong, c_ushort, cast
from tkinter import filedialog as fd
from tkinter import messagebox, ttk
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
import os.path
import re
import pyvisa
import sys
import os
from matplotlib.ticker import EngFormatter
from datetime import datetime
from pathlib import Path
import yaml #pracuje już na bieżącej pyyaml czyli 6; poprezednio pip install pyyaml==5.4.1
c_port = 'port'
c_title = 'title'
c_Ta = 'Ta'
c_Tj_max = 'Tj_max'
c_Rth = 'Rth'
column_time = 'Czas'
column_U = 'U'
column_I = 'I'
CFG = yaml.load(
"""
port: 'TCPIP0::192.168.0.154::inst0::INSTR'
title: 'Prąd zerowy BJT C-CiC (GA05JT03)'
Ta: 25
Tj_max: 200
Rth: 10
"""
, Loader=yaml.Loader)
#print(yaml.dump(CFG))
#sys.exit(0)
WERSJA = "0.0.1"
def f(v):
fm = EngFormatter(sep="", places=2)
return fm.format_eng(v)
class Rigol(tk.Frame, object):
def __init__(self, master=None):
super(Rigol, self).__init__(master)
example_dir = os.path.dirname(os.path.realpath(__file__))
# icon_path = os.path.join(example_dir, 'MCC.ico')
# master.iconbitmap(icon_path)
master.wm_title(type(self).__name__)
master.minsize(width=400, height=400)
master.grid_columnconfigure(0, weight=1)
master.grid_rowconfigure(0, weight=1)
self.grid(sticky=tk.NSEW)
self.tab_info_text = None
self.tab_plt = None
self.fig_plt = None
self.canvas = None
self.rigs = []
dir = os.path.dirname(os.path.realpath(__file__))
fname = dir+'\\'+type(self).__name__+'.yaml'
if os.path.isfile(fname) :
with open(fname, 'r') as file:
#CFG = yaml.load(file)
CFG = yaml.load(file, Loader=yaml.Loader)
#print(yaml.dump(CFG))
#pass
self.create_widgets()
self.set_data()
self.run = False
#self.after(int(CFG[c_timer])*1000, self.idle_time)
self.after(100, self.idle_time)
self.after(1000, self.idle_1000ms)
self.ii = 0
#self.df = pd.DataFrame(columns=[column_U,column_I])
#self.df.index.name = column_time
#self.df = pd.DataFrame(columns=[column_I])
self.df = pd.DataFrame()
self.df.index.name = column_U
#print(self.df)
def create_widgets(self):
device_frame = tk.LabelFrame(self, text = "Urządzenie")
device_frame.pack(fill=tk.X, anchor=tk.CENTER, padx=3, pady=3)
l = tk.Label(device_frame)
l["text"] = "Rigol:"
l.grid(row=0, column=0, sticky=tk.W)
self.port = tk.Entry(device_frame, width=40)
self.port.grid(row=0, column=1, sticky=tk.W, padx=3, pady=3)
l = tk.Label(device_frame)
l["text"] = " Wersja programu: "+WERSJA
l.grid(row=0, column=3, sticky=tk.W)
fr = tk.LabelFrame(self, text = "Parametry", height = 20)
fr.pack(fill=tk.X, anchor=tk.NW, padx=3, pady=3)
c = 0
l = tk.Label(fr)
l["text"] = "Tytuł:"
l.grid(row=0, column=c, sticky=tk.W)
c=c+1
self.title = tk.Entry(fr, width=60)
self.title.grid(row=0, column=c, sticky=tk.W, padx=3, pady=3)
c=c+1
l = tk.Label(fr)
l["text"] = "Ta:"
l.grid(row=0, column=c, sticky=tk.W)
c+=1
self.Ta = tk.Entry(fr, width=10)
self.Ta.grid(row=0, column=c, sticky=tk.W, padx=3, pady=3)
c+=1
l = tk.Label(fr)
l["text"] = "Tj_max:"
l.grid(row=0, column=c, sticky=tk.W)
c+=1
self.Tj_max = tk.Entry(fr, width=10)
self.Tj_max.grid(row=0, column=c, sticky=tk.W, padx=3, pady=3)
c=c+1
l = tk.Label(fr)
l["text"] = "Rth:"
l.grid(row=0, column=c, sticky=tk.W)
c+=1
self.Rth = tk.Entry(fr, width=10)
self.Rth.grid(row=0, column=c, sticky=tk.W, padx=3, pady=3)
c=0
fr = tk.LabelFrame(self, text = "Pomiar", height = 20)
fr.pack(fill=tk.X, anchor=tk.NW, padx=3, pady=3)
b = tk.Button(fr)
b["text"] = "[---------------Z A P I S Z---------------]"
b["command"] = self.send_cmd
b.grid(row=1, column=c, padx=3, pady=3, sticky=tk.W)
c+=1
self.pomiar = tk.Label(fr)
self.pomiar["text"] = "pomiar"
u=500
i=0.00006
p = u*i
Ta = 25
Rth = 10
Tj = Ta + p * Rth
self.pomiar["text"] = f'U={f(u)}V I={f(i)}A P={f(p)}W Tj={f(Tj)}C'
#self.pomiar["text"] = f'xyz'
self.pomiar.grid(row=1, column=c, sticky=tk.E)
c+=1
self.results_group = tk.LabelFrame(self, text="Wyniki", padx=3, pady=3)
self.results_group.pack(expand=1, fill=tk.BOTH, anchor=tk.NW, padx=3, pady=3)
self.results_group.grid_columnconfigure(1, weight=1)
tabs = ttk.Notebook(self.results_group)
self.tab_info_text = ttk.Frame(tabs, height = 200)
self.tab_info_text.pack(fill=tk.BOTH)
self.tab_plt = ttk.Frame(tabs, height = 200)
tabs.add(self.tab_info_text, text='Log (teksty info)')
tabs.add(self.tab_plt, text='Wykres')
# t2 = ttk.Label(tab_plt, text= 'opis 2')
# t2.grid(column=0, row=0)
self.info_text = tk.Text(self.tab_info_text, height = 10)
self.info_text.pack(expand=1, side=tk.LEFT, fill=tk.BOTH)
# create a Scrollbar and associate it with txt
sb = tk.Scrollbar(self.tab_info_text, command=self.info_text.yview)
sb.pack(side=tk.RIGHT, fill=tk.Y)
tabs.pack(expand=1, fill=tk.BOTH)
button_frame = tk.Frame(self, height = 50)
button_frame.pack(expand = 0, fill=tk.NONE, side=tk.BOTTOM, anchor=tk.SE)
c = 0
self.but_connect= tk.Button(button_frame)
self.but_connect["text"] = "Podłącz"
self.but_connect["command"] = self.connect
self.but_connect.grid(row=0, column=c, padx=3, pady=3)
c += 1
b = tk.Button(button_frame)
b["text"] = "Zapisz parametry"
b["command"] = self.save_CFG
b.grid(row=0, column=c, padx=3, pady=3)
c += 1
b = tk.Button(button_frame)
b["text"] = "Zapis dane"
b["command"] = self.save_df
b.grid(row=0, column=c, padx=3, pady=3)
c += 1
b = tk.Button(button_frame)
b["text"] = "Odczyt dane"
b["command"] = self.load_df
b.grid(row=0, column=c, padx=3, pady=3)
c += 1
self.quit_button = tk.Button(button_frame)
self.quit_button["text"] = "Wyjście"
self.quit_button["command"] = self.master.destroy
self.quit_button.grid(row=0, column=c, padx=3, pady=3)
c += 1
def get_data(self):
CFG[c_port] = self.port.get()
CFG[c_title] = self.title.get()
try:
ss = self.Ta.get()
CFG[c_Ta] = int(ss)
except:
pass
try:
CFG[c_Tj_max] = int(self.Tj_max.get())
except:
pass
try:
CFG[c_Rth] = int(self.Rth.get())
except:
pass
def set_data(self):
try:
self.port.insert(0, str(CFG[c_port]))
self.title.insert(0, str(CFG[c_title]))
self.Ta.insert(0, str(CFG[c_Ta]))
self.Tj_max.insert(0, str(CFG[c_Tj_max]))
self.Rth.insert(0, str(CFG[c_Rth]))
except:
pass
def connect(self):
self.get_data()
if len(self.rigs)>0:
for rig in self.rigs:
self.print_info('Rozłączyłem się z portem ' + CFG[c_port] + '\n')
rig = rm.open_resource(port)
self.print_info('Rozłączyłem się z portem ' + CFG[c_port] + '\n')
self.but_connect["text"] = "Podłącz"
self.ser = None
else:
port = CFG[c_port]
try:
rm = pyvisa.ResourceManager()
self.print_info('Zasoby"\n')
for l in rm.list_resources():
self.print_info(f"{l}\n")
port = CFG[c_port]
rig = rm.open_resource(port)
rig.write(":FUNCtion:CURRent:DC") # ustawienie na IDC
#pomiar napięcie, jeśli jest obsługiwany drugi kanał to na drugim kanale
rig.write(":FUNCtion2:VOLTage:DC") # ustawienie 2 na VDC
self.print_info(f'Połączyłem się z RIGOLEM {port} \n')
self.rigs.append(rig)
self.but_connect["text"] = "Rozłącz"
except:
self.print_info(f'Nie mogłem połączyć się z portem {port}\n')
self.rig = []
def print_info(self, info):
self.info_text.insert(tk.END, info)
self.info_text.update()
self.info_text.see(tk.END)
self.info_text.edit_modified(0)
def graf(self):
if self.df.empty:
self.print_info("Brak danych\n".format())
return
if self.fig_plt is None:
self.fig_plt = Figure(figsize=(5, 2), dpi=100)
self.ax = self.fig_plt.add_subplot(111).plot(self.df,'-')
#self.fig_plt, self.ax = plt.subplots(figsize=(5, 2))
self.canvas = FigureCanvasTkAgg(self.fig_plt, master=self.tab_plt) # A tk.DrawingArea.
self.canvas.draw()
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
toolbar = NavigationToolbar2Tk(self.canvas, self.tab_plt)
toolbar.update()
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.fig_plt.clf()
self.fig_plt.add_subplot(111).plot(self.df,'-')
#cs = self.df.columns.to_list()
#self.fig_plt.legend(cs)
self.fig_plt.suptitle(CFG[c_title])
now = datetime.now()
self.fig_plt.text(0, 0, now, fontsize=8, ha='left', va='bottom', wrap=False)
ef0 = EngFormatter(sep="", places=0)
ef2 = EngFormatter(sep="", places=2)
#ax_list = self.fig_plt.axes
#print('ax_list=',ax_list)
#for ax in ax_list:
# print('ax=',ax)
ax = self.fig_plt.axes[0]
ax.yaxis.set_major_formatter(ef0)
ax.xaxis.set_major_formatter(ef0)
ax.set(xlabel = "U[V]", ylabel = "I[A]")
ax.grid()
ax.legend(self.df.columns, loc = 'best')
self.canvas.draw()
def save_df(self):
filename = fd.asksaveasfilename(filetypes=[("Plik CSV","*.csv")], defaultextension = "*.csv", initialfile=CFG[c_title])
if filename != '':
self.df.to_csv(filename, sep = ';', decimal = ',', index=True)
self.print_info(f"Zapisano dane do pliku {filename}\n")
def load_df(self):
filename = fd.askopenfilename(filetypes=[("Plik CSV","*.csv")], defaultextension = "*.csv")
if filename != '':
self.df = pd.read_csv(filename, sep = ';', decimal = ',', index_col=column_U)
self.print_info(f"Odczytano dane z pliku {filename}\n")
path = Path(filename)
directory = path.parent
name = os.path.splitext(path.name)[0]
self.title.delete(0, tk.END)
self.title.insert(0, name)
CFG[c_title] = name
self.graf()
def save_CFG(self):
self.get_data()
dir = os.path.dirname(os.path.realpath(__file__))
#base = os.path.basename(os.path.realpath(__file__))
#split= os.path.splitdrive(os.path.realpath(__file__))
#print(dir)
#print(base)
#print(split)
with open(dir+'\\'+ type(self).__name__+'.yaml', 'w') as file:
yaml.dump(CFG, file)
def send_cmd(self):
c = f'Ta={CFG[c_Ta]}_{column_I}'
pomiar = f'U={f(self.u)}V; I={f(self.i)}A'
self.print_info(f"Wysłano {c} {pomiar}\n")
self.df.at[self.u, c] = self.i
self.graf()
def odczyt_UI(self, rig):
u = float(rig.query(":FUNCtion2:VALUe2?"))
i = abs(float(rig.query(":FUNCtion2:VALUe1?")))
return u, i
def idle_time(self):
self.get_data()
self.after(100, self.idle_time)
def idle_1000ms(self):
self.get_data()
self.timestamp = pd.Timestamp.now()
if len(self.rigs) > 0:
self.u, self.i = self.odczyt_UI(self.rigs[0])
p = self.u*self.i
Tj = CFG[c_Ta] + p * CFG[c_Rth]
pomiar = f'U={f(self.u)}V I={f(self.i)}A P={f(p)}W Tj={f(Tj)}C'
self.pomiar["text"] = pomiar
if Tj > CFG[c_Tj_max]:
self.pomiar["fg"] = '#F00'
else:
self.pomiar["fg"] = '#00F'
self.after(1000, self.idle_1000ms)
if __name__ == "__main__":
Rigol(master=tk.Tk()).mainloop()