upload files
This commit is contained in:
279
MTB_Test.py
Normal file
279
MTB_Test.py
Normal 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
75
MTB_UploadResults.py
Normal 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
344
ModuleTestBox.py
Normal 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
72
biastest.py
Normal 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
31
live_measurement.py
Normal 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
134
measurement.py
Normal 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
46
plot.py
Normal 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
34
relaistest.py
Normal 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
|
||||
Reference in New Issue
Block a user