upload files

This commit is contained in:
2025-06-02 09:32:52 +02:00
parent 196939c145
commit bf739f8101
8 changed files with 1015 additions and 0 deletions

279
MTB_Test.py Normal file
View File

@@ -0,0 +1,279 @@
# -*- coding: utf-8 -*-
"""
MTB Calibration + Qualification Script (v3)
-----------------------------------------
Auf Wunsch angepasste Zielgrenzen und Iterationen :
* **Kalibrierung**: |ppm| < 1000 als Ziel, max 5 Iterationen.
* **Qualifikation**: ``OK`` ≤ 10 000 ppm (≈1 nA @ 100 µA FS),
``COOL`` ≤ 5 000 ppm ; sonst ``FAIL``.
Weitere Features aus v2 bleiben unverändert.
"""
from __future__ import annotations
import sys, time, numpy as np, pyvisa, serial.tools.list_ports
from typing import Sequence
from pymeasure.instruments.keithley import Keithley2400, KeithleyDMM6500
from ModuleTestBox import ModuleTestBox
from sklearn.linear_model import LinearRegression
import json
# ---------- Gerätesuche -------------------------------------------------------
rm = pyvisa.ResourceManager()
def find_visa(hint: str) -> str:
for res in rm.list_resources():
if hint.lower() in res.lower():
return res
raise RuntimeError(f'VISA-Resource "{hint}" nicht gefunden')
def find_serial(hint: str) -> str:
for port, desc, _ in serial.tools.list_ports.comports():
if hint.lower() in desc.lower():
return port
raise RuntimeError(f'Serial-Port "{hint}" nicht gefunden')
# ---------- User-Prompt -------------------------------------------------------
def ask_user(prompt: str = "Last umgesteckt? <Enter>=OK, n=repeat, q=quit → ") -> str:
while True:
ans = input(prompt).strip().lower()
if ans in ("", "y", "yes"):
return "ok"
if ans in ("n", "no"):
return "repeat"
if ans == "q":
sys.exit("Abbruch durch Benutzer")
print("Bitte nur Enter, n oder q eingeben …")
# ---------- Mess-Hilfsfunktionen ---------------------------------------------
VOLTAGES = np.arange(10, 201, 10) # 10 … 200 V, 10 V-Step
VOLTAGES_FULLRANGE = np.arange(10, 1001, 10) # 1 … 100 V, 10 V-Step
CAL_TARGET = 100 # ppm-Band für Kalibrierung
CAL_MAX_ITERS = 5 # max. Iterationen
QUAL_LIMIT_COOL = 100 # ppm für "cool"
QUAL_LIMIT_OK = 500 # ppm für "ok" -> 1nA @ 2uA
SHUNT_OHM = 102.9807197e6 # Referenz-Widerstand (Ω)
SETTLING_S = 0.2 # Settling-Zeit pro Step
def sweep_currents(kei: Keithley2400, mtb: ModuleTestBox):
"""Sweep über VOLTAGES - liefert (I_ref, I_dut)."""
I_ref, I_dut = [], []
kei.enable_source()
for v in VOLTAGES:
kei.source_voltage = v
time.sleep(SETTLING_S)
I_ref.append(v / SHUNT_OHM)
I_dut.append(mtb.GetI() * -1e-12)
kei.disable_source()
return np.asarray(I_ref), np.asarray(I_dut)
def sweep_currents_fullRange(kei: Keithley2400, mtb: ModuleTestBox):
"""Sweep über VOLTAGES - liefert (I_ref, I_dut)."""
I_ref, I_dut = [], []
kei.enable_source()
for v in VOLTAGES_FULLRANGE:
kei.source_voltage = v
time.sleep(SETTLING_S)
I_ref.append(v / SHUNT_OHM)
I_dut.append(mtb.GetI() * -1e-12)
kei.disable_source()
return np.asarray(I_ref), np.asarray(I_dut)
def lineaer_fit(x: Sequence[float], y: Sequence[float]) -> tuple[float, float]:
"""Lineare Regression: y = m * x + b"""
x = np.asarray(x).reshape(-1, 1)
y = np.asarray(y).reshape(-1, 1)
model = LinearRegression()
model.fit(x, y)
m = model.coef_[0][0]
b = model.intercept_[0]
return m, b
def gain_ppm(I_ref: Sequence[float], I_dut: Sequence[float]) -> float:
slope, offset = np.polyfit(I_ref, I_dut, 1)
# print(f" Fit: {curve:.3e} * x² + {slope:.3e} * x + {offset:.3e}")
return (1.0 / slope - 1.0) * 1e6
# ---------- Hauptprogramm -----------------------------------------------------
dataToSave = []
if __name__ == "__main__":
serial_id = int(input("Enter the MTB Serial ID [default 100]: ") or "100")
print(f"Serial ID = {serial_id}")
with Keithley2400(find_serial("Prolific")) as kei, \
KeithleyDMM6500(find_visa("0x05E6::0x6500")) as dmm, \
ModuleTestBox(hints=["VID:PID=CAFE:4001"], verbose=False) as mtb:
print("Verbunden. FW:", mtb.GetFirmwareVersion())
if mtb.HV_IsLocked():
sys.exit("HV ist gesperrt - Abbruch")
mtb.SetBoardID(serial_id)
mtb.SetMeanCount(60)
dataToSave.append({"serial_id": serial_id, "firmware": mtb.GetFirmwareVersion()})
mtb.HV_SetAll(0)
kei.apply_voltage(compliance_current=11e-6) # 11 µA-Limit
dataChannels = []
for ch in range(1, 9):
print(f"\n=== Channel {ch} ===")
mtb.SelectChannel(ch)
mtb.HV_Enable(True)
mtb.AdjustScaleI(0)
# ------ Kalibrier-Loop ------
corr_ppm = 0.0
for it in range(CAL_MAX_ITERS):
try:
mtb.AdjustScaleI(round(corr_ppm))
except Exception as e:
print(f" Fehler bei der Kalibrierung: {e}")
break
I_ref, I_dut = sweep_currents(kei, mtb)
ppm = gain_ppm(I_ref, I_dut)
print(f" Iter {it}: gain error = {ppm:+.0f} ppm")
if abs(ppm) < CAL_TARGET:
break
corr_ppm += ppm
print(f"→ final gain error for channel {ch}: {corr_ppm:+.0f} ppm")
# ------ Qualifikations-Sweep ------
I_ref, I_dut = sweep_currents(kei, mtb)
ppm_chk = abs(gain_ppm(I_ref, I_dut))
if ppm_chk <= QUAL_LIMIT_COOL:
status = "COOL" # ≤5 k
elif ppm_chk <= QUAL_LIMIT_OK:
status = "OK" # ≤10 k
else:
status = "FAIL"
print(f"Qualification: {ppm_chk:.0f} ppm → {status}")
print("Doing full range sweep …")
I_ref, I_dut = sweep_currents_fullRange(kei, mtb)
kei.beep(1000, 0.5)
# ------ Benutzer Prompt ------
if ask_user() == "repeat":
print("Wiederhole Kanal …")
ch -= 1
continue
data = {
'v': VOLTAGES_FULLRANGE.tolist(),
'i_ref': I_ref.tolist(),
'i_dut': I_dut.tolist()
}
dataChannels.append({
"channel": ch,
"gain_error": corr_ppm,
"qualification": status,
"data": data
})
mtb.HV_SetAll(0)
print("\nAlle Kanäle bearbeitet. Kalibrierung beendet.")
dataToSave.append({"HVchannels": dataChannels})
print("Tests starten …")
print("Interlock aktivieren …")
while True:
if mtb.HV_IsLocked():
print("Interlock detected")
break
time.sleep(0.1)
print("Interlock Reset Button drücken")
print(mtb.HV_GetAll())
while True:
if mtb.HV_GetAll():
print("Interlock released")
break
time.sleep(0.1)
input("Interlock Reset Button gedrückt. <Enter> zum Fortfahren …")
print("Bias Test starten …")
dataChannels = []
dmm.measure_current(max_current=0.001)
dmm.current_nplc = 12
for ch in range(1, 9):
print(f"\n=== Channel {ch} ===")
mtb.SelectChannel(ch)
mtb.Bias_Enable(True)
voltages = range(18, 901, 9)
currents = []
dmmcurrent = []
failed = False
for v in voltages:
mtb.Bias_SetV(v / 1000)
time.sleep(SETTLING_S/2)
dmmcur = dmm.current
while dmmcur > 1:
dmmcur = dmm.current
dmmcurrent.append(dmmcur * 1e6)
current = mtb.Bias_GetI()
currents.append(current * 1e6)
if abs(current - dmmcur) >= (2e-6 + (v/300 * 1e-6)):
print(f" Bias-Test für Kanal {ch} fehlgeschlagen: {current:.3e} != {dmmcur:.3e}")
failed = True
mtb.Bias_SetV(0)
mtb.Bias_Enable(False)
if not failed:
print(f"Bias-Test für Kanal {ch} erfolgreich")
status = "OK"
else:
status = "FAIL"
kei.beep(1000, 0.5)
# ------ Benutzer Prompt ------
if ask_user() == "repeat":
print("Wiederhole Kanal …")
ch -= 1
continue
dataChannels.append({
"channel": ch,
"voltages": list(voltages),
"currents": currents,
"dmmcurrent": dmmcurrent,
"status": status
})
print("\nAlle Kanäle bearbeitet. Bias-Test beendet.")
dataToSave.append({"Biaschannels": dataChannels})
print("Tests abgeschlossen. Daten speichern …")
# check if file exists, if yes, append number to filename
i = 1
#save json data to file
filename = f"TestResults/MTB_Test_{serial_id}.json"
while True:
try:
with open(filename, "x") as f:
json.dump(dataToSave, f, indent=4)
break
except FileExistsError:
filename = f"TestResults/MTB_Test_{serial_id}_{i}.json"
i += 1
print("Daten gespeichert.")
print("Programm beendet.")

75
MTB_UploadResults.py Normal file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Restore calibration settings on MTB:
- Seriennummer eingeben
- Neueste JSON-Datei laden
- meanCount und AdjustScaleI pro Kanal anwenden
"""
import json
import glob
import os
import sys
import serial.tools.list_ports
from ModuleTestBox import ModuleTestBox
def find_latest_file(serial_id: int, folder: str = "TestResults") -> str:
pattern = os.path.join(folder, f"MTB_Test_{serial_id}*.json")
files = glob.glob(pattern)
if not files:
raise FileNotFoundError(f"Keine Dateien gefunden für Serial {serial_id!r}")
# sortiere nach Suffix (höchste Zahl zuletzt)
files.sort(key=lambda f: int(os.path.splitext(f)[0].rsplit('_',1)[-1]) if '_' in os.path.splitext(f)[0] else 0)
return files[-1]
def prompt_mean_count() -> int:
while True:
try:
mc = int(input("MeanCount nicht in Datei gefunden. Bitte eingeben (z.B. 60): "))
return mc
except ValueError:
print("Bitte eine ganze Zahl eingeben.")
def main():
serial_id = int(input("Seriennummer eingeben: ").strip())
try:
filename = find_latest_file(serial_id)
except FileNotFoundError as e:
print(e)
sys.exit(1)
print(f"Lade Datei: {filename}")
with open(filename, "r") as f:
data = json.load(f)
# Versuch, meanCount auszulesen
mean_count = None
for entry in data:
if "meanCount" in entry:
mean_count = entry["meanCount"]
break
if mean_count is None:
mean_count = prompt_mean_count()
print(f"Verwende meanCount = {mean_count}")
# Hole gain_error pro Kanal
hv_channels = next((e["HVchannels"] for e in data if "HVchannels" in e), None)
if hv_channels is None:
print("Keine HVchannels in der Datei gefunden.")
sys.exit(1)
# Verbinden und hochladen
print("Verbinde mit MTB …")
with ModuleTestBox(hints=["VID:PID=CAFE:4001"], verbose=False) as mtb:
mtb.SetBoardID(serial_id)
mtb.SetMeanCount(mean_count)
for ch_info in hv_channels:
ch = ch_info["channel"]
corr_ppm = ch_info["gain_error"]
print(f"Channel {ch}: AdjustScaleI({corr_ppm:+.0f})")
mtb.SelectChannel(ch)
mtb.AdjustScaleI(int(round(corr_ppm)))
print("Fertig. Alle Einstellungen geladen.")
if __name__ == "__main__":
main()

344
ModuleTestBox.py Normal file
View File

@@ -0,0 +1,344 @@
import numpy as np
import serial
import serial.tools.list_ports
class ModuleTestBox:
def __init__(self, port=None, hints=None, verbose=False):
self.ser = None # port handle
self.port = port # port name COMn
self.portdesc = ''
self.hints = hints
self.verbose = verbose
self.portlist = {} # port list { port: description }
self.UpdateComPortList()
def __enter__(self):
self._Connect()
return self
def __exit__(self, exc_type, exc_value, traceback):
self._Disconnect()
if exc_type is not None:
print(f"Exception {exc_type}') # occurred with value {exc_value}")
# except serial.SerialException:
return True
# Update the serial port list
def UpdateComPortList(self):
ports = serial.tools.list_ports.comports()
self.portlist = {}
if self.verbose: print('* serial ports found:')
for port, desc, hwid in sorted(ports):
self.portlist[port] = hwid
if self.verbose: print(f'{port:7s}: {desc}, {hwid}')
# Finds a specific serial port based on a keyword (hint) in the description
def FindComPort(self, hints):
for port, desc in self.portlist.items():
for hint in hints:
if hint in desc:
self.port = port
self.portdesc = desc
return port
self.port = None
return None
# Checks if there is a connection to a serial port
def IsConnected(self):
return self.ser is not None
# Connect to a serial port by specific port or a hint in the description
def _Connect(self):
if self.hints is not None:
self.FindComPort(self.hints)
if self.port is not None:
if self.verbose: print(f'* open {self.port:s} ({self.portdesc})')
self.ser = serial.Serial(self.port, 115200, timeout=0.5)
def _Disconnect(self):
if self.IsConnected():
self.ser.close()
def TestConnection(self):
testdata = [0x5A, 0xA5]
# Test connection PC <-> HV box
for x in testdata:
y = self.Echo(x)
if y != x:
print(f'Communication failed: {y:02X} should be {x:02X}')
return False
# Test ADC register read/write (repeat for all ADCs)
adc_ok = True
for channel in range(1, 9):
self.SelectChannel(channel)
for x in testdata:
self.ADC_RegisterWrite(0x13, x)
y = self.ADC_RegisterRead(0x13)
if y != x:
print(f'ADC Register IO failed: {y:02X} should be {x:02X}')
adc_ok = False
if self.verbose:
adc = self.GetADCversion()
print(f'CH{channel}: ADC TYPE:{adc["ver"]} REV:{adc["rev"]} ID:{adc["id"]}')
return adc_ok
def _PutStr(self, s):
self.ser.write(s.encode('ASCII'))
def _PutINT8(self, x):
self.ser.write(x.to_bytes(1, signed=True, byteorder='little'))
def _PutINT16(self, x):
self.ser.write(x.to_bytes(2, signed=True, byteorder='little'))
def _PutINT32(self, x):
self.ser.write(x.to_bytes(4, signed=True, byteorder='little'))
def _PutUINT8(self, x):
self.ser.write(x.to_bytes(1, signed=False, byteorder='little'))
def _PutUINT16(self, x):
self.ser.write(x.to_bytes(2, signed=False, byteorder='little'))
def _PutUINT32(self, x):
self.ser.write(x.to_bytes(4, signed=False, byteorder='little'))
def _GetUINT8(self):
bv = self.ser.read(size=1)
return int.from_bytes(bv, signed=False, byteorder='little')
def _GetUINT16(self):
bv = self.ser.read(size=2)
return int.from_bytes(bv, signed=False, byteorder='little')
def _GetUINT32(self):
bv = self.ser.read(size=4)
return int.from_bytes(bv, signed=False, byteorder='little')
def _GetINT32(self):
bv = self.ser.read(size=4)
return int.from_bytes(bv, signed=True, byteorder='little')
def _Get2INT32(self):
bv = self.ser.read(size=8)
i = int.from_bytes(bv[0:4], signed=True, byteorder='little')
u = int.from_bytes(bv[4:8], signed=True, byteorder='little')
return i, u
def _CommandReq(self, command):
self.ser.write(command.encode('ASCII'))
def _CommandRsp(self, retform):
n = sum(retform)
bv = self.ser.read(size=n)
# split return bytes value
pos = 0
ret = []
for size in retform:
ret.append(int.from_bytes(bv[pos:pos+size], signed=False, byteorder='little'))
pos += size
return ret
def _Request(self, command, retform):
self._CommandReq(command)
return self._CommandRsp(retform)
def GetFirmwareVersion(self):
self._CommandReq('S0')
n = self._GetUINT8()
d = [self._GetUINT8() for _ in range(n)]
return ''.join(chr(i) for i in d)
def GetBoardID(self):
self._CommandReq('SI')
return self._GetUINT32()
def SetBoardID(self, id):
self._CommandReq('SD')
self._PutUINT32(id)
def SelectChannel(self, n):
if n-1 in range(8):
self._CommandReq('12345678'[n-1])
def GetI(self):
self._CommandReq('I')
return self._GetINT32()
def GetV(self):
self._CommandReq('V')
return self._GetINT32()
def GetIV(self):
self._CommandReq('D')
return self._GetINT32(), self._GetINT32()
def ScanI(self, fs, n):
self._CommandReq('MI')
self._PutUINT8(fs)
self._PutUINT16(n)
N = self._GetUINT16()
i = np.zeros(N, dtype=int)
for k in range(N):
i[k] = self._GetINT32()
return i
def ScanV(self, fs, n):
self._CommandReq('MV')
self._PutUINT8(fs)
self._PutUINT16(n)
N = self._GetUINT16()
u = np.zeros(N, dtype=int)
for k in range(N):
u[k] = self._GetINT32()
return u
def ScanIV(self, fs, n):
self._CommandReq('MD')
self._PutUINT8(fs)
self._PutUINT16(n)
N = self._GetUINT16()
i = np.zeros(N, dtype=int)
u = np.zeros(N, dtype=int)
for k in range(N):
i[k], u[k] = self._Get2INT32()
return i, u
def GetError(self):
status = self._Request('SE', (2,))[0]
if status == 0: return 'Status Ok'
return f'Error {status:04X}'
def GetADCversion(self):
self._CommandReq('SV')
b = self.ser.read(size=8)
s = ('ADE9113', 'ADE9112', 'ADE9103')[b[1]] if b[1] <= 3 else '?'
if len(b) >= 8:
id = f'{b[2]:02X}{b[3]:02X}{b[4]:02X}{b[5]:02X}{b[6]:02X}{b[7]:02X}'
else:
id = '????'
adc = {'ver':s, 'rev':f'{b[0]:02X}', 'id':id}
return adc
def SetRawData(self, unit=False):
self._CommandReq('SU')
self._PutUINT8(1 if unit else 0)
def SetMeanCount(self, n):
self._CommandReq('SN')
self._PutUINT8(n)
def AdjustOffsetI(self):
self._CommandReq('SCI')
return self._GetINT32()
def AdjustOffsetV(self):
self._CommandReq('SCV')
return self._GetINT32()
def AdjustScaleI(self, value):
self._CommandReq('SAI')
self._PutINT32(value)
def AdjustScaleV(self, value):
self._CommandReq('SAV')
self._PutINT32(value)
def ADC_Transmit(self, txdata):
self._CommandReq('X1')
self._PutUINT32(txdata)
def ADC_Receive(self):
self._CommandReq('X2')
return self._GetUINT32()
def ADC_Transfer(self, txdata):
self._CommandReq('XT')
self._PutUINT32(txdata)
return self._GetUINT32()
def ADC_RegisterWrite(self, reg, value):
self._CommandReq('XW')
self._PutUINT8(reg)
self._PutUINT8(value)
def ADC_RegisterRead(self, reg):
self._CommandReq('XR')
self._PutUINT8(reg)
return self._CommandRsp((1,))[0]
def ADC_RegisterRead2(self, reg):
self._CommandReq('Xr')
self._PutUINT8(reg)
return self._CommandRsp((1,1))
def ReadADC_I(self):
self._CommandReq('XI')
return self._GetINT32()
def ReadADC_V(self):
self._CommandReq('XV')
return self._GetINT32()
def ReadADC_IV(self):
self._CommandReq('XD')
return self._Get2INT32()
def ADC_Init(self):
self._CommandReq('XC')
def ADC_Sleep(self, sleep=True):
self._CommandReq('XS')
self._PutINT8(1 if sleep else 0)
def AddCRC(self, d):
self._CommandReq('XX')
self._PutUINT32(d)
return self._CommandRsp((4,))[0]
def Echo(self, din):
self._CommandReq('XE')
self._PutUINT8(din)
return self._CommandRsp((1,))[0]
def Bias_Enable(self, on):
self._CommandReq('BC')
self._PutINT8(1 if on else 0)
def Bias_GetAll(self):
self._CommandReq('BS')
return self._CommandRsp((1,))[0]
def Bias_SetV(self, v=0.0):
self._CommandReq('BV')
self._PutINT16(int(v*1000))
def Bias_GetI(self):
self._CommandReq('BI')
return self._GetINT32()/1000000000.0
def HV_Enable(self, on):
self._CommandReq('LC')
self._PutINT8(1 if on else 0)
def HV_SetAll(self, on):
self._CommandReq('LA')
self._PutINT8(1 if on else 0)
def HV_GetAll(self):
self._CommandReq('LS')
return self._CommandRsp((1,))[0]
def HV_IsLocked(self):
self._CommandReq('SL')
return self._CommandRsp((1,))[0] != 0

72
biastest.py Normal file
View File

@@ -0,0 +1,72 @@
from ModuleTestBox import ModuleTestBox
import sys, time, numpy as np, pyvisa, serial.tools.list_ports
from pymeasure.instruments.keithley import KeithleyDMM6500
import matplotlib.pyplot as plt
from time import sleep
import pandas as pd
hints=["VID:PID=CAFE:4001"]
# ---------- Gerätesuche -------------------------------------------------------
rm = pyvisa.ResourceManager()
def find_visa(hint: str) -> str:
for res in rm.list_resources():
if hint.lower() in res.lower():
return res
raise RuntimeError(f'VISA-Resource "{hint}" nicht gefunden')
channel = []
ref = []
dmmcurrent = []
ch = 2
count = range(18, 901, 9)
with ModuleTestBox(hints=hints, verbose=True) as hvp, KeithleyDMM6500(find_visa(hint='0x05E6::0x6500')) as dmm:
hvp.SelectChannel(ch)
hvp.Bias_Enable(1)
# hvp.Bias_SetV(0.5)
R = 1223.6841 #load resistance in Ohm
dmm.measure_current(max_current=0.001)
dmm.current_nplc = 12
sleep(2)
for u in count:
hvp.Bias_SetV(u/1000)
sleep(0.1)
ref.append(u*0.9092396440596435 + 0)
dmmcur = dmm.current
while dmmcur > 1:
dmmcur = dmm.current
dmmcurrent.append(dmmcur * 1e6)
current = hvp.Bias_GetI() * 1e6
channel.append(current)
# print(channel)
hvp.Bias_SetV(0)
hvp.Bias_Enable(0)
data = pd.DataFrame({
'v': count,
'i_hv': channel,
'i_dmm': dmmcurrent,
'i_ref': ref
})
data.to_excel('data_biasTEST.xlsx', index=False)
plt.figure()
plt.title('BiasingBox current measurements')
plt.ylabel('current [uA]')
plt.xlabel('measurements')
plt.grid()
plt.plot(channel, label=f'CH{ch}', marker='o', markersize=2)
# plt.plot(ref, label=f'ref', marker='o', markersize=2)
plt.plot(dmmcurrent, label=f'dmm', marker='o', markersize=2)
plt.legend()
plt.show()

31
live_measurement.py Normal file
View File

@@ -0,0 +1,31 @@
from pymeasure.instruments.keithley import Keithley2400
import serial.tools.list_ports
import pandas as pd
from ModuleTestBox import ModuleTestBox
from time import sleep
import matplotlib.pyplot as plt
channel = 7
hints=["VID:PID=CAFE:4001"]
try:
with ModuleTestBox(hints=hints, verbose=True) as mtb:
print("Connected to Device")
mtb.SelectChannel(channel)
mtb.Bias_Enable(False)
mtb.HV_Enable(True)
# sleep(2)
while True:
current = mtb.GetI() / 1e6
print(current)
sleep(0.5)
except Exception as e:
print(e)
exit()

134
measurement.py Normal file
View File

@@ -0,0 +1,134 @@
from pymeasure.instruments.keithley import Keithley2400, KeithleyDMM6500
import serial.tools.list_ports
import pyvisa
import pandas as pd
from ModuleTestBox import ModuleTestBox
from time import sleep
import matplotlib.pyplot as plt
rm = pyvisa.ResourceManager()
def find_instr_device(hint):
"""Scan available serial ports and return the first port whose description
contains the given hint (case-insensitive). Raise ValueError if not found.
"""
ports = rm.list_resources()
for port in ports:
if hint.lower() in port.lower():
return port
raise ValueError(f"No valid port found for hint '{hint}'.")
def find_device(hint):
"""Scan available serial ports and return the first port whose description
contains the given hint (case-insensitive). Raise ValueError if not found.
"""
ports = serial.tools.list_ports.comports()
for port, desc, hwid in ports:
if hint.lower() in desc.lower():
return port
raise ValueError(f"No valid port found for hint '{hint}'.")
def plotData(data):
"""Plot the data contained in the given DataFrame."""
fig, ax = plt.subplots(2, 1)
ax[0].plot(data['v'], data['i_kei'], label='Keithley 2400')
ax[0].plot(data['v'], data['i_dmm'], label='Keithley DMM 6500')
# ax[0].plot(data['v'], data['i_hv'], label='HV port')
ax[0].plot(data['v'], data['i_calc'], label='Calculated')
ax[0].set_xlabel('Voltage (V)')
ax[0].set_ylabel('Current (A)')
ax[0].legend()
ax[1].plot(data['v'], data['i_kei'] - data['i_calc'], label='Difference KEI')
ax[1].plot(data['v'], data['i_dmm'] - data['i_calc'], label='Difference DMM')
# ax[1].plot(data['v'], data['i_hv'] - data['i_calc'], label='Difference HV')
ax[1].set_xlabel('Voltage (V)')
ax[1].set_ylabel('Current difference (A)')
ax[1].legend()
plt.show()
hints=["VID:PID=CAFE:4001"]
if __name__ == '__main__':
try:
with Keithley2400(find_device(hint='Prolific')) as kei:
with KeithleyDMM6500(find_instr_device(hint='0x05E6::0x6500')) as dmm:
with ModuleTestBox(hints=hints, verbose=True) as hv:
print("Connected to Keithley 2400 and HV port.")
if not hv.TestConnection():
print("HV port not connected.")
exit()
print(f'Firmware: {hv.GetFirmwareVersion()}')
print(f'HV: {"locked" if hv.HV_IsLocked() else "unlocked"}')
# perform a voltage ramp measurement with kei and measure current with kei and hv to compare
# and store the results in a pandas DataFrame
nSamples = 90
vStart = 0.0
vStop = 900.0
hv.HV_SetAll(0)
# configure HV port
hv.SelectChannel(1)
hv.Bias_Enable(False)
hv.HV_Enable(True)
# hv.SetMeanCount(60)
# hv.AdjustScaleI(0) # ppm
# hv.AdjustScaleV(-4512) # ppm
# perform voltage ramp measurement with kei
kei.apply_voltage(compliance_current=1.1e-5)
kei.source_voltage = vStart
kei.enable_source()
# kei.current_nplc = 10
kei.measure_current(10)
kei.current_range = 10e-6
dmm.measure_current(max_current=0.000001)
dmm.current_nplc = 12
values = []
print("Starting voltage ramp measurement...")
for i in range(nSamples):
v = vStart + i * (vStop - vStart) / nSamples
kei.source_voltage = v
sleep(0.1)
i_kei = kei.current
# i_kei = 0
# i_dmm = 0
i_dmm = dmm.current
# i_hv = hv.GetI() * -1e-12
i_hv = 0
while i_dmm > 1:
i_dmm = dmm.current
# i_calc = v / 100.003e6
# i_calc = v / 5068423720.2
i_calc = v / 102980719.7
values.append((v, i_kei, i_dmm, i_hv, i_calc))
kei.disable_source()
data = pd.DataFrame(values, columns=['v', 'i_kei', 'i_dmm', 'i_hv', 'i_calc'])
data['ppm'] = (data['i_calc'] - data['i_hv']) / data['i_calc'] * 1e6
print(data)
print(hv.GetError())
sample_cal = round(nSamples * 0.8)
print(f"Sample for calibration: {data['ppm'].iloc[sample_cal:sample_cal+50].mean()} ppm")
# save data to Excel file
data.to_excel('data.xlsx', index=False)
plotData(data)
except Exception as e:
print(f"Error during programm: {e}")

46
plot.py Normal file
View File

@@ -0,0 +1,46 @@
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
def plotData(data):
"""Plot the data contained in the given DataFrame."""
fig, ax = plt.subplots(2, 1)
ax[0].plot(data['v'], data['i_dmm'], label='Keithley')
ax[0].plot(data['v'], data['i_hv'], label='HV port')
ax[0].plot(data['v'], data['i_calc'], label='Calculated')
ax[0].set_xlabel('Voltage (V)')
ax[0].set_ylabel('Current (1 uA)')
ax[0].legend()
ax[1].plot(data['v'], data['i_dmm'] - data['i_calc'], label='Difference KEI')
ax[1].plot(data['v'], data['i_hv'] - data['i_calc'], label='Difference HV')
ax[1].set_xlabel('Voltage (V)')
ax[1].grid()
ax[1].set_ylabel('Current difference (uA)')
ax[1].legend()
plt.show()
#import excel file
data = pd.read_excel('data_biasTEST.xlsx')
# data['ppm'] = (data['i_kei'] - data['i_hv']) / data['i_kei'] * 1e6
# sample_cal = round(len(data) * 0.8)
# print(f"Sample for calibration: {data['ppm'].iloc[sample_cal:sample_cal+20].mean()} ppm")
# linear regression for i_kei
from sklearn.linear_model import LinearRegression
X = data['v'].values.reshape(-1, 1)
y = data['i_hv'].values
reg = LinearRegression().fit(X, y)
data['i_calc'] = reg.predict(X)
print(f"Linear regression: {reg.coef_[0]} A/V")
print(f"Linear regression: {reg.intercept_} A")
print(data)
plotData(data)

34
relaistest.py Normal file
View File

@@ -0,0 +1,34 @@
from ModuleTestBox import ModuleTestBox
from time import sleep
hints=["VID:PID=CAFE:4001"]
with ModuleTestBox(hints=hints, verbose=True) as hvp:
# hvp.SelectADC(1)
# hvp.Relais_SetCh(1)
# hvp.SelectADC(4)
# hvp.Relais_SetCh(1)
# hvp.SelectADC(8)
# hvp.Relais_SetCh(1)
while True:
try:
for x in range(1, 9):
hvp.SelectChannel(x)
hvp.Bias_Enable(1)
hvp.Bias_SetV(0)
# sleep(0.1)
# sleep(0.1)
print(hvp.Bias_GetAll())
input("Press enter to turn off Relais")
for x in range(1, 9):
hvp.SelectChannel(x)
hvp.Bias_Enable(0)
hvp.Bias_SetV(0)
# sleep(0.1)
print(hvp.Bias_GetAll())
except KeyboardInterrupt:
print("KeyboardInterrupt")
break