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()