1+ """
2+ This example performs both I-V and C-V measurements on an n-MOSFET using the 4200A-SCS.
3+ It uses the SMUs, CVU, and CVIV by calling the EX command.
4+
5+ 1. CVIV Configure for SMU Measurements:
6+ Calls the cviv_configure user module by using the EX command. It configures the CVIV
7+ for connection to the SMUs.
8+
9+ 2. Vds-Id (Family of Curves):
10+ This example performs a family of curves measurement using the 4200A-SCS.
11+ It sweeps drain voltage (0-5 V) and steps gate voltage (1-4 V) using SMU2 and SMU3,
12+ while SMU1 holds source at 0 V. Data is segmented and saved in a Clarius-like format.
13+
14+ 3. CVIV Configure for CVU Measurements:
15+ Calls the cviv_configure user module by using the EX command. It configures the CVIV
16+ for connection to the CVU.
17+
18+ 4. cv-nmosfet:
19+ This example performs a C-V sweep on a nMOSFET from 5 V to -5 V using the 4200A-SCS CVU. It will output a
20+ csv file containing the C-V measurements.
21+ Relies on plotly to plot the capacitance and voltage measurements.
22+
23+ Device used: 4-terminal DUT (e.g., nMOSFET) in 8101-PIV fixture
24+ """
25+
26+ from instrcomms import Communications
27+ import pandas as pd
28+ import time
29+
30+ INST_RESOURCE_STR = "TCPIP0::192.0.2.0::1225::SOCKET" # Instrument resource string, obtained from NI MAX
31+ my4200 = Communications (INST_RESOURCE_STR ) # Opens the resource manager in PyVISA with the corresponding instrument resource string
32+ my4200 .connect () # Opens connections to the 4200A-SCS
33+ my4200 ._instrument_object .write_termination = "\0 " # Set PyVISA write terminator
34+ my4200 ._instrument_object .read_termination = "\0 " # Set PyVISA read terminator
35+
36+ my4200 .query ("UL" )
37+ # Call the cviv_configure user module for SMU measurements
38+ my4200 .query ("EX cvivulib cviv_configure (CVIV1, 1, 1, 1, 1, 1, Source, Drain, Gate, Bulk, IV, )" )
39+
40+ # This is a loop to check the status of the test
41+ # The SP command returns 0 or 1 when the test is done running
42+ while True :
43+ status = my4200 .query ("SP" )
44+
45+ # Continues loop until the test is complete
46+ # Casting the status string to int makes the comparison simpler since it ignores the termination characters
47+ if int (status ) in [0 , 1 ]:
48+ print ("Setup Complete." )
49+ break
50+
51+ # Continously prints the status of the test every second to the terminal
52+ print (f"Status: { status } " )
53+ time .sleep (1 )
54+
55+ # Start SMU family of curves measurement
56+ my4200 .query ("BC" ) # Clear all readings from the buffer
57+ my4200 .query ("DE" ) # Access the SMU channel definition page
58+ # Channel 1. Voltage Name = VS, Current Name = IS, Voltage Source Mode, Constant source function
59+ my4200 .query ("CH 1, 'VS', 'IS', 1, 3" )
60+ # Channel 2. Voltage Name = VD, Current Name = ID, Voltage Source Mode, VAR1 sweep source function
61+ my4200 .query ("CH 2, 'VD', 'ID', 1, 1" )
62+ # Channel 3. Voltage Name = VG, Current Name = IG, Voltage Source Mode, VAR2 sweep source function
63+ my4200 .query ("CH 3, 'VG', 'IG', 1, 2" )
64+ my4200 .query ("SS" ) # Access the source setup page
65+ # Setup VAR1 source function, linear sweep, 0 V to 5 V, 0.1 V steps, 100 mA current compliance (SMU2)
66+ my4200 .query ("VR 1, 0, 5, 0.1, 100e-3" )
67+ # Setup VAR2 step sweep, 1 V to 4 V, 4 total steps (step size 1 V), 100 mA current compliance (SMU3)
68+ my4200 .query ("VP 2, 1, 4, 100e-3" )
69+ # Configure constant voltage, SMU channel 1, 0 V output value, 100 mA current compliance
70+ my4200 .query ("VC 1, 0, 100e-3" )
71+ my4200 .query ("HT 0" ) # Set to a 0 second hold time
72+ my4200 .query ("DT 0.001" ) # Set to a 1 millisecond delay time
73+ my4200 .query ("IT2" ) # Set integration time to 1 PLC
74+ my4200 .query ("RS 5" ) # Sets the measurement resolution to 5 digits
75+ my4200 .query ("RG 1, 100e-9" ) # Set the lowest current range to be used on SMU 1 to 100 nA
76+ my4200 .query ("RG 2, 100e-9" ) # Set the lowest current range to be used on SMU 2 to 100 nA
77+ my4200 .query ("RG 3, 100e-9" ) # Set the lowest current range to be used on SMU 3 to 100 nA
78+ my4200 .query ("SM" ) # Access the measurement setup page
79+ my4200 .query ("DM2" ) # Selects the list display mode
80+ my4200 .query ("LI 'ID', 'VD','VG'" ) # Enable current function ID and voltage functions VD & VG in list display mode
81+ my4200 .query ("MD" ) # Access the measurement control page
82+ my4200 .query ("ME 1" ) # Maps channel 1
83+
84+ while True :
85+ status = my4200 .query ("SP" )
86+
87+ if int (status ) in [0 , 1 ]:
88+ print ("Measurement Complete." )
89+ break
90+
91+ print (f"Status: { status } " )
92+ time .sleep (1 )
93+
94+ # Specify what measurements to return
95+ var_returned = ["ID" , "VD" , "VG" ]
96+
97+ # Define the number of segments for each measurements
98+ num_segments = 4 # Total number of lines
99+ readings_per_seg = (51 ) # Divide total number of readings by segments to get the readings per segment
100+
101+
102+ # Set custom data names
103+ custom_data_names = {"VD" : "DrainV" , "ID" : "DrainI" , "VG" : "GateV" }
104+
105+
106+ # Function to retrieve measurements for each variable
107+ def retrieve_measurements (variable ):
108+ index = 1
109+ measurements = []
110+ while True :
111+ data = my4200 .query (f"RD '{ variable } ', { index } " )
112+ if data == "0" :
113+ break
114+ measurements .append (float (data ))
115+ index += 1
116+ return measurements
117+
118+
119+ # Store all measurements in a dictionary
120+ all_measurements = {}
121+ for variable in var_returned :
122+ all_measurements [variable ] = retrieve_measurements (variable )
123+
124+ # Split the measurements into segments
125+ seg_measurements = {} # Initalize segment measurements
126+ for (variables , meas ) in all_measurements .items ():
127+ seg_var_measurements = {} # Initialize segments for variable measurements
128+ for segment in range (1 , num_segments + 1 ):
129+ start_index = (segment - 1 ) * readings_per_seg # Set start index
130+ end_index = segment * readings_per_seg # Set end index
131+ seg_name = custom_data_names .get (variables , f"{ variables } ({ segment } )" ) # Set custom data names
132+ seg_var_measurements [f"{ seg_name } ({ segment } )" ] = meas [start_index :end_index ] # Set the measurements to return
133+ seg_measurements .update (seg_var_measurements ) # Append array for measurements
134+
135+ # Sort the columns in the order as shown in Clarius
136+ clarius_order = [
137+ "DrainI(1)" ,
138+ "DrainV(1)" ,
139+ "GateV(1)" ,
140+ "DrainI(2)" ,
141+ "DrainV(2)" ,
142+ "GateV(2)" ,
143+ "DrainI(3)" ,
144+ "DrainV(3)" ,
145+ "GateV(3)" ,
146+ "DrainI(4)" ,
147+ "DrainV(4)" ,
148+ "GateV(4)" ,
149+ ]
150+
151+ # Create a DataFrame from the segmented measurements
152+ df_smu = pd .DataFrame (seg_measurements )
153+
154+ # Reorder the DataFrame
155+ df_smu = df_smu [clarius_order ]
156+
157+ # Save the DataFrame into a CSV file
158+ df_smu .to_csv ("vds-id.csv" , index = False )
159+ print ("Data saved to CSV file" )
160+
161+ # Set the CVIV to CVU Measurements
162+
163+ my4200 .query ("UL" )
164+ # Call the cviv_configure user module for CVU measurements
165+ my4200 .query ("EX cvivulib cviv_configure (CVIV1, 1, 3, 3, 2, 3, Source, Drain, Gate, Bulk, CV, )" )
166+
167+ while True :
168+ status = my4200 .query ("SP" )
169+
170+ if int (status ) in [0 , 1 ]:
171+ print ("Setup Complete." )
172+ break
173+
174+ print (f"Status: { status } " )
175+ time .sleep (1 )
176+
177+ my4200 .query (":CVU:RESET" ) # Resets the CVU card
178+ my4200 .query (":CVU:MODE 1" ) # Sets the mode to System Mode
179+ my4200 .query (":CVU:MODEL 2" ) # Sets the measurement model to Cp, Gp
180+ my4200 .query (":CVU:SPEED 2" ) # Sets the measurement speed to quiet
181+ my4200 .query (":CVU:ACZ:RANGE 0" ) # Sets the ac measurement range to autorange
182+ my4200 .query (":CVU:FREQ 1E6" ) # Set the frequency to 1 MHz
183+ # Create a DC sweep, from 5 V to -5 V in 0.2 V steps, sample z measurements
184+ my4200 .query (":CVU:SWEEP:DCV 5, -5, -0.2" )
185+ my4200 .query (":CVU:DELAY:SWEEP 0.1" ) # Set the delay sweep for 100 ms
186+ my4200 .query (":CVU:TEST:RUN" ) # Starts the CVU test
187+
188+ while True :
189+ status = my4200 .query ("SP" )
190+
191+ if int (status ) in [0 , 1 ]:
192+ print ("Measurement Complete." )
193+ break
194+
195+ print (f"Status: { status } " )
196+ time .sleep (1 )
197+
198+ CpGp = my4200 .query (":CVU:DATA:Z?" ) # Queries readings of Cp-Gp
199+ Volt = my4200 .query (":CVU:DATA:VOLT?" ) # Queries readings of Voltage
200+
201+ # Assume CpGp and Volt are the raw strings returned from the instrument
202+ CpGpList = CpGp .strip ().split ("\n " ) # Split by newline
203+ VoltList = Volt .strip ().split ("\n " ) # Split by newline
204+
205+ # Extract only Cp values (before the semicolon)
206+ CapList = [line .split (";" )[0 ] for line in CpGpList ]
207+
208+ # Convert to float
209+ VoltList = [float (v ) for v in VoltList ]
210+ CapList = [float (c ) for c in CapList ]
211+
212+ df_cvu = pd .DataFrame ({"Voltage (V)" : VoltList , "Capacitance (F)" : CapList })
213+ df_cvu .to_csv ("cv-nmosfet.csv" , index = False )
214+ print ("Data saved to CSV file" )
215+
216+ my4200 .disconnect () # Close communications with the 4200A-SCS
0 commit comments