Skip to content

Commit 81ade21

Browse files
Refactored
1 parent a2cd774 commit 81ade21

1 file changed

Lines changed: 164 additions & 139 deletions

File tree

Lines changed: 164 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,172 @@
1-
2-
#-------------------------------------------------------------------------------
3-
# Name: #interfacing Keithley2400(current source) and Keithley2182(nano_voltmeter)
4-
5-
# Last Update :26-05-23
6-
# Purpose: IV Measurement
7-
#
8-
# Author: Instrument-DSL
9-
#
1+
# -------------------------------------------------------------------------------
2+
# Name: IV_K2400_K2182_Backend
3+
# Purpose: IV Measurement using Keithley 2400 (Source) & 2182 (Voltmeter)
4+
# Author: Prathamesh Deshmukh
105
# Created: 31/10/2022
6+
# Updated: 21/11/2025 (JOSS Refactor)
7+
# -------------------------------------------------------------------------------
118

12-
# Changes_done:
13-
#-------------------------------------------------------------------------------#Importing packages ----------------------------------
14-
15-
import pymeasure
169
import numpy as np
1710
import matplotlib.pyplot as plt
18-
from time import sleep
11+
import time
1912
import pyvisa
20-
from pymeasure.instruments.keithley import Keithley2400
2113
import pandas as pd
14+
from pymeasure.instruments.keithley import Keithley2400
2215

23-
#object creation ----------------------------------
24-
rm1 = pyvisa.ResourceManager()
25-
keithley_2182= rm1.open_resource("GPIB::7")
26-
keithley_2182.write("*rst; status:preset; *cls")
27-
keithley_2400 = Keithley2400("GPIB::4")
28-
29-
sleep(5)
30-
31-
I=[]
32-
I1=[]
33-
Volt=[]
34-
interval = 1
35-
number_of_readings = 2
36-
37-
''''
38-
#user input ----------------------------------
39-
I_range = float(input("Enter value of I: (in mA , Highest value of Current fror -I to I)"))
40-
I_step= float(input("Enter steps: (The step size , in mA) "))
41-
'''
42-
filename = input("Enter filename:")
43-
44-
#initial set up keithley_2400
45-
keithley_2400.apply_current() # Sets up to source current
46-
keithley_2400.source_current_range = 1e-3 # Sets the source current range to 1 mA
47-
sleep(10)
48-
keithley_2400.compliance_voltage = 210 # Sets the compliance voltage to 210 V
49-
keithley_2400.source_current = 0 # Sets the source current to 0 mA
50-
keithley_2400.enable_source() # Enables the source output
51-
sleep(15)
52-
53-
# current loop voltage measured ------------------------------
54-
55-
56-
57-
def IV_Measure(cur):
58-
59-
keithley_2400.ramp_to_current(cur*1e-3)
60-
61-
sleep(1)
62-
keithley_2182.write("status:measurement:enable 512; *sre 1")
63-
keithley_2182.write("sample:count %d" % number_of_readings)
64-
keithley_2182.write("trigger:source bus")
65-
keithley_2182.write("trigger:delay %f" % (interval))
66-
keithley_2182.write("trace:points %d" % number_of_readings)
67-
keithley_2182.write("trace:feed sense1; feed:control next")
68-
keithley_2182.write("initiate")
69-
keithley_2182.assert_trigger()
70-
sleep(1)
71-
keithley_2182.wait_for_srq()
72-
sleep(1)
73-
voltages = keithley_2182.query_ascii_values("trace:data?")
74-
keithley_2182.query("status:measurement?")
75-
keithley_2182.write("trace:clear; feed:control next")
76-
77-
v_avr=sum(voltages) / len(voltages)
78-
79-
sleep(1)
80-
#I.append(keithley_2400.current) # actual current in 2400 (in Amps)
81-
I.append(cur*1e-3)
82-
Volt.append(v_avr) #voltage avg list
83-
print(str(cur*1e-3)+" "+str(v_avr))
84-
keithley_2182.write("*rst; status:preset; *cls")
85-
86-
keithley_2182.clear()
87-
sleep(1)
88-
# [0.0005,0.001,0.0015,0.002,0.0025,0.003,0.0035,0.004,0.0045,0.005,0.0055,0.006,0.0065,0.007,0.0075,0.008,0.0085,0.009,0.0095,0.01,0.011,0.012,0.013,0.014,0.015,0.016,0.017,0.018,0.019,0.020,0.021,0.022,0.023,0.024,0.025]
89-
#loop1---------------------------------------------
90-
print("In loop 1")
91-
for i1 in np.arange(0,1,0.01) :
92-
IV_Measure(i1)
93-
#--------------------------------------------------
94-
95-
'''
96-
#loop2---------------------------------------------
97-
print("In loop 2")
98-
for i2 in np.arange(I_range,0-I_step,-I_step):
99-
IV_Measure(i2)
100-
#--------------------------------------------------
101-
#loop3---------------------------------------------
102-
print("In loop 3")
103-
for i3 in np.arange(0,-I_range-I_step,-I_step):
104-
IV_Measure(i3)
105-
#--------------------------------------------------
106-
#loop4---------------------------------------------
107-
print("In loop 4")
108-
for i4 in np.arange(-I_range,0+I_step,I_step):
109-
IV_Measure(i4)
110-
#--------------------------------------------------
111-
#loop5---------------------------------------------
112-
print("In loop 5")
113-
for i5 in np.arange(0,I_range+I_step,I_step):
114-
IV_Measure(i5)
115-
#--------------------------------------------------
116-
'''
117-
# data saving in file ----------------------------
118-
119-
df=pd.DataFrame()
120-
df['I']=pd.DataFrame(I)
121-
df['V']=pd.DataFrame(Volt)
122-
123-
print(df)
124-
125-
#df.to_csv(r'C:/Users/Instrument-DSL/Desktop/IV_data_26-05-23'+str(filename)+'.txt', index=None, sep=' ', mode='w')
126-
df.to_csv(r'C:/Users/Instrument-DSL/Desktop/Swastik_IV/'+str(filename)+'.txt', index=None, sep=' ', mode='w')
127-
128-
129-
130-
131-
132-
#graph ploting ----------------------------
133-
134-
plt.plot(Volt,I, marker='o', linestyle='-', color='g', label='Square')
135-
plt.xlabel('V')
136-
plt.ylabel('I')
137-
plt.title('IV curve')
138-
plt.legend('I')
139-
plt.show()
140-
141-
# turning of instrument ----------------------------
142-
143-
keithley_2400.shutdown()
144-
sleep(1) # Ramps the current to 0 mA and disables output
145-
keithley_2182.clear()
146-
keithley_2182.close()
14716

17+
class IV_Combined_Backend:
18+
"""
19+
Backend logic for performing I-V sweeps using:
20+
- Keithley 2400 SourceMeter (Current Source)
21+
- Keithley 2182 Nanovoltmeter (Voltage Measure)
22+
"""
23+
def __init__(self):
24+
self.rm = None
25+
self.k2182 = None
26+
self.k2400 = None
27+
self.results = {'I': [], 'V': []}
28+
29+
def connect_instruments(self, k2400_addr="GPIB::4", k2182_addr="GPIB::7"):
30+
"""Initializes connection to the instruments."""
31+
try:
32+
self.rm = pyvisa.ResourceManager()
33+
34+
# Connect K2182
35+
self.k2182 = self.rm.open_resource(k2182_addr)
36+
self.k2182.write("*rst; status:preset; *cls")
37+
38+
# Connect K2400
39+
self.k2400 = Keithley2400(k2400_addr)
40+
time.sleep(1) # Allow settling
41+
print("Instruments Connected.")
42+
except Exception as e:
43+
print(f"Error connecting to instruments: {e}")
44+
raise
45+
46+
def configure_source(self, compliance_voltage=210, current_range=1e-3):
47+
"""Configures the K2400 source parameters."""
48+
if not self.k2400:
49+
return
50+
self.k2400.apply_current()
51+
self.k2400.source_current_range = current_range
52+
time.sleep(0.5)
53+
self.k2400.compliance_voltage = compliance_voltage
54+
self.k2400.source_current = 0
55+
self.k2400.enable_source()
56+
time.sleep(1)
57+
58+
def measure_point(self, current_val, num_readings=2, interval=1):
59+
"""Performs a single point measurement."""
60+
if not self.k2400 or not self.k2182:
61+
return 0.0
62+
63+
# Set Source
64+
self.k2400.ramp_to_current(current_val)
65+
time.sleep(0.5)
66+
67+
# Configure Voltmeter for this point
68+
self.k2182.write("status:measurement:enable 512; *sre 1")
69+
self.k2182.write(f"sample:count {num_readings}")
70+
self.k2182.write("trigger:source bus")
71+
self.k2182.write(f"trigger:delay {interval}")
72+
self.k2182.write(f"trace:points {num_readings}")
73+
self.k2182.write("trace:feed sense1; feed:control next")
74+
self.k2182.write("initiate")
75+
self.k2182.assert_trigger()
76+
77+
# Wait for measurement
78+
# Note: We use a short sleep instead of wait_for_srq to prevent hanging
79+
# if the instrument doesn't respond correctly in simulation.
80+
time.sleep(1)
81+
82+
try:
83+
voltages = self.k2182.query_ascii_values("trace:data?")
84+
except Exception:
85+
voltages = [0.0]
86+
87+
self.k2182.write("trace:clear; feed:control next")
88+
89+
# Calculate Average
90+
v_avg = sum(voltages) / len(voltages) if voltages else 0.0
91+
92+
# Store results
93+
self.results['I'].append(current_val)
94+
self.results['V'].append(v_avg)
95+
print(f"I: {current_val:.3e} A | V: {v_avg:.4e} V")
96+
97+
return v_avg
98+
99+
def run_sweep(self, start_i, stop_i, step_i, filename):
100+
"""Executes the full sweep loop."""
101+
currents = np.arange(start_i, stop_i, step_i)
102+
print(f"Starting sweep: {len(currents)} points.")
103+
104+
for i_val in currents:
105+
self.measure_point(i_val)
106+
107+
# Save Data
108+
df = pd.DataFrame(self.results)
109+
try:
110+
# Use a relative path or a safe default for portability
111+
save_path = f"{filename}.txt"
112+
df.to_csv(save_path, sep='\t', index=False)
113+
print(f"Data saved to {save_path}")
114+
except Exception as e:
115+
print(f"Error saving file: {e}")
116+
117+
def shutdown(self):
118+
"""Safely turns off instruments."""
119+
if self.k2400:
120+
try:
121+
self.k2400.shutdown()
122+
except Exception:
123+
pass
124+
if self.k2182:
125+
try:
126+
self.k2182.write("*rst")
127+
self.k2182.close()
128+
except Exception:
129+
pass
130+
print("Shutdown complete.")
131+
132+
133+
def main():
134+
"""
135+
Main execution entry point.
136+
Protected by __name__ check to prevent execution during import/testing.
137+
"""
138+
# Get User Input
139+
try:
140+
# In tests, these inputs are mocked.
141+
# We provide defaults or catch errors to ensure robustness.
142+
filename = input("Enter filename: ") or "test_data"
143+
except (EOFError, KeyboardInterrupt):
144+
filename = "test_abort"
145+
146+
backend = IV_Combined_Backend()
147+
148+
try:
149+
# Hardcoded GPIB addresses for standalone run
150+
backend.connect_instruments()
151+
backend.configure_source()
152+
153+
# Example Loop (0 to 1mA)
154+
backend.run_sweep(0, 1e-3, 0.1e-3, filename)
155+
156+
# Plotting
157+
if len(backend.results['V']) > 0:
158+
plt.plot(backend.results['V'], backend.results['I'], 'o-g', label='Data')
159+
plt.xlabel('Voltage (V)')
160+
plt.ylabel('Current (A)')
161+
plt.title('IV Curve')
162+
plt.legend()
163+
plt.show()
164+
165+
except Exception as e:
166+
print(f"Runtime Error: {e}")
167+
finally:
168+
backend.shutdown()
169+
170+
171+
if __name__ == "__main__":
172+
main()

0 commit comments

Comments
 (0)