From e4bb9eec32eaaa31630fa9f0f803c1ff1729e18c Mon Sep 17 00:00:00 2001
From: Nicolas Roisin <nicolas.roisin@uclouvain.be>
Date: Tue, 25 Feb 2025 17:55:21 +0100
Subject: [PATCH] new class file equipment

---
 .../__pycache__/equipment.cpython-311.pyc     | Bin 0 -> 2220 bytes
 equipment_control/cm110.py                    |  11 ++
 equipment_control/dmm.py                      |  11 ++
 equipment_control/equipment.py                |  57 +++++++++
 equipment_control/hp4145.py                   | 118 ++++++++++++++++++
 equipment_control/k24xx.py                    |  11 ++
 equipment_control/k4200.py                    |  96 ++++++++++++++
 7 files changed, 304 insertions(+)
 create mode 100644 equipment_control/__pycache__/equipment.cpython-311.pyc
 create mode 100644 equipment_control/cm110.py
 create mode 100644 equipment_control/dmm.py
 create mode 100644 equipment_control/hp4145.py
 create mode 100644 equipment_control/k24xx.py
 create mode 100644 equipment_control/k4200.py

diff --git a/equipment_control/__pycache__/equipment.cpython-311.pyc b/equipment_control/__pycache__/equipment.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..38619ad18dbf067ef14e2ca29461d31adc447a20
GIT binary patch
literal 2220
zcmah~&1)M+6rcT)WJ_x+u9JqumQ~jZUAMNeTUy1m4iuZ5VoE|U!e!a+PTY;9U1etF
zVxxc#IhfFc3k6?&avMw`^j`WOl(i68>?u%s=uO4#C8xeOlGY!I>FmsJ-^}~%yqS6L
z&HkwCg9OUlZ@;eBG(!HsPQO53l#`F(wMQJ{NM%wYGPz3}`6+P}N*+l)4mnT=IfT|N
zCE1Z45JUY7yQ_9@RJ{6MoO}%3Jwi!IatQKEiX)d)Xd1MHqdX<0q@x0+91U>5NdOKy
zNtibk4Xiw_xz!5wg6*51Tb$+}`Ya!h<?XV?IpwnvvEwy?Ih3)TEDGCoX&>INm%4la
zt32$ll+=kC%MN)!o=dBS9O-_QdM2aXuQ8iO!_}=#ms|0Gp+qXD<%f~XDpAsM9AI*e
z*7N!NtJ}+qYxjMZyWZNeZ`W`me9is_rsQk1zcthLy@2`U(~;TgR+KVL*K-5Y+#ZQH
zJlh{j!~8r4&>-!JcaA6KniF#`l1CH8)<khfZKcLSQBQEw=Z;2(0dX|HgRn>Hk|S;4
z7-2(oxlSITD9ZVx0G@maPkfD^;E@4OxrB#SMd|U>OL*!9o^}bZAGX3S&S@l8qh#P#
zs9y`BE8j9Vpk_c>#r3Sf^}Q&|sO6j+Npb2qCbVeaYpdp%%nGQHU<0rMb_HPs;Vpzw
zgsT9C!Z4L0WrJ=-+NM>m(Z08tj*9D?2Ix_b+Gy6STFk0YSjhI|sUI)n48sik2LSME
zdT4jx+0xEZ+nC*1Ze{YV%zL4zZ9N-~6q<S=)C=toi#_E+n87NPQ%!v;)Ti1DpY)XT
zVFoKGcc-73J7#-&uBS1=3|7><-_*xLeZ2j_d`~wWX0Ub?GL!=a!`0cbzoCOZ5YhAt
z2zw-m(}zGL1ZS8BARoqkwg4#^_u$jG9DsBf_m;_5H`jpoM1G>w$$lI`F#!aKTIKxg
z68UZn;~_vl_KG8&PjyK|jUlgY8()+uUMyASZ@Lc6TY1M7j`J+GmA81_V(TJrXALbH
z#7yRa<-tXuA}`V)$|{^)2Ro5gWv&+(YBxYe-xLGv8W7pr0372Su84d$YQ`>$2oua~
z-}?W^LEae7>j5;#pXsaNwdtRikJ9t4^n8b?19#fFh2vbYnJXUU7F)T+4w3ZR0=^&I
zRd&B@XGf2-GtKPGPxgz#QMTC17DG`xN}u>|g6JdR$Y)2HrB-Gs?ALBwobf&UJ9=?Z
z1OaZDGxy5=SsrM+<-Fg!-eF158L~*nNVCg6r-mfXmg5|~gUbrVnS|TEae7!6bp`m2
zj{`t@r800$9;YUosmY_%R4X+V_N&;>s*%Pk*n`|P&1ldxE51`Jqnt6#$2F_m(<DvP
z@ogB^fI!Pp()KG=%iD_7vg>WIAuuMQgH50`3Gkc<1H$hNqd))#H>UuN_<5-oLbdQR
zGt^jV4`my7+iAVA+)*VB-PVWHptjH<G2BRKcRD175lJf``66^zfX3nfmFWJ`qT&0!
zpWHpmv(_o=E^ZoEh?y@I{3`Wn_aK(o42+>8_zD07i6pg&-VjemeOCgLufuMlmA&;h
LD*kzcR+#w@#r+Yu

literal 0
HcmV?d00001

diff --git a/equipment_control/cm110.py b/equipment_control/cm110.py
new file mode 100644
index 0000000..b7eea32
--- /dev/null
+++ b/equipment_control/cm110.py
@@ -0,0 +1,11 @@
+import equipment
+import pyvisa
+import time
+import numpy as np
+
+class CM110(equipment.Equipment):
+    
+    """Class to control CM110 monochromator"""
+    model="CM110"
+    company="Spectral Product"
+    link="https://www.spectralproducts.com/CM110"
diff --git a/equipment_control/dmm.py b/equipment_control/dmm.py
new file mode 100644
index 0000000..b734264
--- /dev/null
+++ b/equipment_control/dmm.py
@@ -0,0 +1,11 @@
+import equipment
+import pyvisa
+import time
+import numpy as np
+
+class DMM(equipment.Equipment):
+    
+    """Class to control DMM multimeter"""
+    model="DMM7510, DMM6500 or K2000"
+    company="Keithley"
+    link="https://www.tek.com/en/products/keithley/benchtop-digital-multimeter"
diff --git a/equipment_control/equipment.py b/equipment_control/equipment.py
index e69de29..098c86c 100644
--- a/equipment_control/equipment.py
+++ b/equipment_control/equipment.py
@@ -0,0 +1,57 @@
+import pyvisa
+import datetime
+import numpy as np
+class Equipment(pyvisa.ResourceManager):
+
+    """Base for Equipment classes."""
+    
+    model = ""
+    company = ""
+    link = ""
+        
+    def __init__(self,rm,address):
+        self.pyvisa_resource = rm.open_resource(address)
+        
+        
+    def set_connection_parameter(self,key,value):
+        if key=="timeout":
+            self.pyvisa_resource.timeout=value
+        elif key=="write_termination":
+            self.pyvisa_resource.write_termination=value
+        elif key=="read_termination":
+            self.pyvisa_resource.read_termination=value                    
+        elif key=="send_end":
+            self.pyvisa_resource.send_end=value
+        elif key=="baud_rate":
+            self.pyvisa_resource.baud_rate =value
+        
+            
+    def set_connection_parameter_dic(self,connection_parameter):
+        
+        if isinstance(connection_parameter, dict):
+            for key, value in connection_parameter.items():
+                self.set_connection_parameter(key,value)
+        else:
+            print("Please provide a dictionnary as argument.")
+        
+        
+    def close_connection(self):
+        self.pyvisa_resource.close()
+
+    def write_in_file(self,file_path,data):
+        
+        # Create file and header
+        f = open(file_path, "a")
+        f.write("%s\n"%(datetime.datetime.now().strftime("%c")))
+        
+        shape=np.shape(data)
+        
+        if len(shape)==1:
+            for i in range(shape[0]):
+                f.write("%.6f\n"%(data[i]))
+        elif len(shape)==2:
+            for i in range(shape[0]):
+                for j in range(shape[1]):
+                    f.write("%.6f"%(data[i,j]))
+                f.write("\n")                
+        f.close()
diff --git a/equipment_control/hp4145.py b/equipment_control/hp4145.py
new file mode 100644
index 0000000..eebc037
--- /dev/null
+++ b/equipment_control/hp4145.py
@@ -0,0 +1,118 @@
+import equipment
+import pyvisa
+import time
+import numpy as np
+
+class HP4145(equipment.Equipment):
+    
+    """Class to control HP4145 semiconductor analyzer"""
+    model="HP4145"
+    company="Keysight"
+    link="https://www.keysight.com/dk/en/assets/9018-07935/service-manuals/9018-07935.pdf"
+    
+    def initialize_sweep(self, mode="voltage sweep",number_channel=4,smu_bias={"SMU2":0,"SMU3":0,"SMU4":0},
+                  smu_compliance={"SMU1":1e-6,"SMU2":1e-6,"SMU3":1e-6,"SMU4":1e-6},sweep_param={"start":0,"stop":0,"step":0},
+                  integration_mode="IT1",delay_time=0,hold_time=0):
+        
+        
+        """
+        mode:
+            - "voltage sweep": voltage sweep with SMU1 and current measurements on all the SMUs
+        """
+        self.mode=mode
+        self.number_channel=number_channel
+        self.sweep_param=sweep_param
+        self.smu_compliance=smu_compliance
+        self.smu_bias=smu_bias
+        self.integration_mode=integration_mode
+        self.delay_time=delay_time
+        self.hold_time=hold_time
+        
+        self.set_connection_parameter_dic({"timeout":10e3,"write_termination":'\r\n',"read_termination":'\r\n',"send_end":True})
+    
+        try:
+            self.pyvisa_resource.write("BC") # clear all buffer.
+            self.pyvisa_resource.write("DR1") # This command enables or disables service request for data ready when communications is set to GPIB.
+            self.pyvisa_resource.write("EC 1") # This command sets the condition to exit the test if compliance is reached.
+            
+            if mode =="voltage sweep":
+                
+                self.length_data=int(abs((sweep_param["stop"]-sweep_param["start"])/sweep_param["step"]))
+                self.pyvisa_resource.write("DE") # DE: Accesses SMU channel definition page.
+
+                self.pyvisa_resource.write("CH1, 'V1', 'I1', 1, 1") # 1/2/3: voltage/current/Common 1/2/3: VAR1/VAR2/constant
+                
+                for i in range(number_channel-1):
+                    self.pyvisa_resource.write("CH%d, 'V%d', 'I%d', 1, 3"%(i+2,i+2,i+2)) 
+
+                self.pyvisa_resource.write("SS")# Accesses source setup page
+                self.pyvisa_resource.write("VR1, %s, %s, %s, %s"%(sweep_param["start"],sweep_param["stop"],sweep_param["step"],sweep_param["compliance"])) # VR1 for linear sweep of VAR1 source function, vmin, vmax,vstep, compliance
+                self.pyvisa_resource.write("VC2, %.2f, %s"%(smu_bias[0],smu_compliance[0]))
+                self.pyvisa_resource.write("VC3, %.2f, %s"%(smu_bias[1],smu_compliance[1]))
+                self.pyvisa_resource.write("VC4, %.2f, %s"%(smu_bias[2],smu_compliance[2]))
+            
+                self.pyvisa_resource.write("HT %f"%hold_time) # Sets a hold time that delays the start of a sweep
+                self.pyvisa_resource.write("DT %f"%delay_time) # delay time: Sets the time to wait between when the output voltage is set and when the measurement is made in a sweep.
+                self.pyvisa_resource.write(integration_mode) # integration time, IT1/IT2/IT3 : short/medium/long
+    
+                self.pyvisa_resource.write("SM")
+                self.pyvisa_resource.write("DM2")
+                self.pyvisa_resource.write("LI 'I1','I2','I3','I4'")
+            # self.pyvisa_resource.write("DM1")
+            # self.pyvisa_resource.write("XN 'V1', 1, %.1f, %.1f"%(v_sweep_param["start"],v_sweep_param["stop"]))
+            # self.pyvisa_resource.write("YA 'I1',2, 1E-15, 1E-12")
+        except pyvisa.VisaIOError:
+            print("/!\ VisaIOError : timeout expired")
+            self.pyvisa_resource.close()
+        
+        return {"mode":mode, "number_channels":number_channel, "sweep_param":sweep_param}
+    
+    def launch_measurements(self):
+        
+        N_v=self.length_data
+        data={}
+        
+        if self.integration_mode=="IT1":
+            Nmax=50
+        elif self.integration_mode=="IT2":
+            Nmax=30
+        elif self.integration_mode=="IT3":
+            Nmax=10
+            
+            
+        if self.mode=="voltage sweep":
+            
+            v_list=np.round(np.arange(self.sweep_param["start"],self.sweep_param["stop"]+self.sweep_param["step"],self.sweep_param["step"]),6)
+
+            data=np.zeros((N_v,self.number_channel))
+            data[:,0]=v_list
+            
+            iteration=int(N_v/Nmax)+1
+            index=0
+            j=0
+            while iteration>0:
+                v_start=v_list[index]
+                if index+Nmax>N_v:
+                    v_end=v_list[-1]
+                else:
+                    v_end=v_list[index+Nmax-1]
+                    
+                self.pyvisa_resource.write("MD") # This command controls measurements.
+                self.pyvisa_resource.write("ME1") # Run a single trigger test and store readings in a cleared buffer: 1
+                time.sleep(10)
+
+                N=int(abs((v_end-v_start)/self.sweep_param["step"]))+1
+                
+                for i in range(self.number_channel):
+                    data_list=self.pyvisa_resource.query("DO 'I%d'"%(i+1))# Read measurements
+                    for  data_i in data_list.split(","):
+                        data[j,i+1]=float(data_i[1:])
+                        if i==0:
+                            j+=1
+                            
+                index+=Nmax
+                iteration-=1  
+                j+=1
+            
+        return data
+    
diff --git a/equipment_control/k24xx.py b/equipment_control/k24xx.py
new file mode 100644
index 0000000..1126aa9
--- /dev/null
+++ b/equipment_control/k24xx.py
@@ -0,0 +1,11 @@
+import equipment
+import pyvisa
+import time
+import numpy as np
+
+class K24XX(equipment.Equipment):
+    
+    """Class to control K2400 or k2450 SMU"""
+    model="K2400 or K2450"
+    company="Keithley"
+    link="https://www.tek.com/en/products/keithley/digital-multimeter/dmm7510"
diff --git a/equipment_control/k4200.py b/equipment_control/k4200.py
new file mode 100644
index 0000000..c590bfd
--- /dev/null
+++ b/equipment_control/k4200.py
@@ -0,0 +1,96 @@
+import equipment
+import pyvisa
+import time
+import numpy as np
+
+class K4200(equipment.Equipment):
+    
+    """Class to control HP4145 semiconductor analyzer"""
+    model="K4200"
+    company="Keysight"
+    link="https://www.tek.com/en/products/keithley/4200a-scs-parameter-analyzer"
+    
+    def initialize_sweep(self, mode="voltage sweep",number_channel=4,smu_bias={"SMU2":0,"SMU3":0,"SMU4":0},
+                  smu_compliance={"SMU1":1e-6,"SMU2":1e-6,"SMU3":1e-6,"SMU4":1e-6},sweep_param={"start":0,"stop":0,"step":0},
+                  integration_mode="IT1",delay_time=0,hold_time=0):
+        
+        
+        """
+        mode:
+            - "voltage sweep": voltage sweep with SMU1 and current measurements on all the SMUs
+        """
+        self.mode=mode
+        self.number_channel=number_channel
+        self.sweep_param=sweep_param
+        self.smu_compliance=smu_compliance
+        self.smu_bias=smu_bias
+        self.integration_mode=integration_mode
+        self.delay_time=delay_time
+        self.hold_time=hold_time
+        
+        self.set_connection_parameter_dic({"timeout":10e3,"write_termination":'\r\n',"read_termination":'\r\n',"send_end":True})
+    
+        try:
+            self.pyvisa_resource.write("BC") # clear all buffer.
+            self.pyvisa_resource.write("DR1") # This command enables or disables service request for data ready when communications is set to GPIB.
+            self.pyvisa_resource.write("EC 1") # This command sets the condition to exit the test if compliance is reached.
+            
+            if mode =="voltage sweep":
+                
+                self.length_data=int(abs((sweep_param["stop"]-sweep_param["start"])/sweep_param["step"]))
+                self.pyvisa_resource.write("DE") # DE: Accesses SMU channel definition page.
+
+                self.pyvisa_resource.write("CH1, 'V1', 'I1', 1, 1") # 1/2/3: voltage/current/Common 1/2/3: VAR1/VAR2/constant
+                
+                for i in range(number_channel-1):
+                    self.pyvisa_resource.write("CH%d, 'V%d', 'I%d', 1, 3"%(i+2,i+2,i+2)) 
+
+                self.pyvisa_resource.write("SS")# Accesses source setup page
+                self.pyvisa_resource.write("VR1, %s, %s, %s, %s"%(sweep_param["start"],sweep_param["stop"],sweep_param["step"],sweep_param["compliance"])) # VR1 for linear sweep of VAR1 source function, vmin, vmax,vstep, compliance
+                self.pyvisa_resource.write("VC2, %.2f, %s"%(smu_bias[0],smu_compliance[0]))
+                self.pyvisa_resource.write("VC3, %.2f, %s"%(smu_bias[1],smu_compliance[1]))
+                self.pyvisa_resource.write("VC4, %.2f, %s"%(smu_bias[2],smu_compliance[2]))
+            
+                self.pyvisa_resource.write("HT %f"%hold_time) # Sets a hold time that delays the start of a sweep
+                self.pyvisa_resource.write("DT %f"%delay_time) # delay time: Sets the time to wait between when the output voltage is set and when the measurement is made in a sweep.
+                self.pyvisa_resource.write(integration_mode) # integration time, IT1/IT2/IT3 : short/medium/long
+
+                self.pyvisa_resource.write("RS 7") # This command sets the measurement resolution for all channels of a SM in number of bits
+                self.pyvisa_resource.write("RG 1, 10E-12") # This command sets the measurement resolution for all channels of a SM in number of bits
+                self.pyvisa_resource.write("RG 2, 10E-12") # This command sets the lowest current range of the SMU to be used when measuring.
+    
+                self.pyvisa_resource.write("SM")
+                self.pyvisa_resource.write("DM2")
+                self.pyvisa_resource.write("LI 'I1','I2','I3','I4'")
+        except pyvisa.VisaIOError:
+            print("/!\ VisaIOError : timeout expired")
+            self.pyvisa_resource.close()
+        
+        return {"mode":mode, "number_channels":number_channel, "sweep_param":sweep_param}
+    
+    def launch_measurements(self):
+        
+        self.pyvisa_resource.write("MD") # This command controls measurements.
+        self.pyvisa_resource.write("ME1") # Run a single trigger test and store readings in a cleared buffer: 1
+        time.sleep(10)
+        N=self.length_data        
+        
+        if self.mode=="voltage sweep":
+            v_list=np.round(np.arange(self.sweep_param["start"],self.sweep_param["stop"]+self.sweep_param["step"],self.sweep_param["step"]),6)
+
+            data=np.zeros((N,self.number_channel))
+            data[:,0]=v_list
+
+            count=0               
+            while (count<N):
+                data_ch1=np.float32(self.pyvisa_resource.query("RD 'I1', %d"%(count+1)))
+                if (data_ch1!=0.):
+                    data[count,1]=data_ch1
+                    for i in range(self.number_channel-1):
+                        data[count,i+2]=np.float32(self.pyvisa_resource.query("RD 'I%d', %d"%(i+2,count+1)))
+                    count+=1
+                time.sleep(0.1)
+        return data
+    
+
+        
\ No newline at end of file
-- 
GitLab