From 129a46d620fa2759fe50b3e0b5f1ac9f7140ddf6 Mon Sep 17 00:00:00 2001 From: Liz Makley <77294716+Little-LIZard@users.noreply.github.com> Date: Mon, 15 Sep 2025 17:45:56 -0400 Subject: [PATCH 1/3] MP5000 Examples Initial release --- .../Parallel_Testing.py | 1130 +++++++++++++++++ .../6_Ch_Parallel_Measurements/README.md | 10 + .../Parallel_Measure_With_USB_Export.tsp | 237 ++++ .../Exporting_Parallel_Data_To_USB/README.md | 21 + .../Application_Examples/README.md | 15 + .../2_Channel_Parallel/Parallel_Combo.tsp | 48 + .../PSU_Examples/2_Channel_Parallel/README.md | 16 + .../PSU_Examples/2_Channel_Series/README.md | 16 + .../2_Channel_Series/Series_Combo.tsp | 45 + .../Bipolar_Output/Bipolar_Demo.py | 40 + .../Bipolar_Output/Bipolar_Demo.tsp | 45 + .../PSU_Examples/Bipolar_Output/README.md | 15 + .../PSU_Examples/Linear_Sweep/Linear_Sweep.py | 100 ++ .../Linear_Sweep/Linear_Sweep.tsp | 65 + .../PSU_Examples/Linear_Sweep/README.md | 15 + .../PSU_Examples/OCP/OCP Demo.py | 34 + .../PSU_Examples/OCP/OCP Demo.tsp | 18 + .../PSU_Examples/OCP/README.md | 15 + .../PSU_Examples/OVP/OVP_Demo.py | 47 + .../PSU_Examples/OVP/OVP_Demo.tsp | 28 + .../PSU_Examples/OVP/README.md | 15 + .../PSU_Examples/Output_Sequence/README.md | 28 + .../Trigger Output Sequence Demo.py | 223 ++++ .../Trigger Output Sequence Demo.tsp | 150 +++ .../Trigger Output Sequence TSP Functions.py | 308 +++++ .../PSU_Examples/README.md | 31 + .../PSU_Examples/Slew_Rate/README.md | 15 + .../PSU_Examples/Slew_Rate/Slew Rate Demo.py | 39 + .../PSU_Examples/Slew_Rate/Slew Rate Demo.tsp | 45 + .../Modular_Precision_Test_System/README.md | 23 + .../2_Channel_Parallel/Parallel_SMU_Combo.py | 140 ++ .../2_Channel_Parallel/Parallel_SMU_Combo.tsp | 159 +++ .../SMU_Examples/2_Channel_Parallel/README.md | 18 + .../SMU_Examples/2_Channel_Series/README.md | 19 + .../2_Channel_Series/Series_SMU_Combo.py | 140 ++ .../2_Channel_Series/Series_SMU_Combo.tsp | 164 +++ .../BJT_Gummel/BJT_Gummel_VbIc.tsp | 319 +++++ .../BJT_Gummel/BJT_Gummel_Vblc.py | 278 ++++ .../SMU_Examples/BJT_Gummel/README.md | 15 + .../BJT_Vce-Ic/BJT_Vclec_Curve.py | 311 +++++ .../BJT_Vce-Ic/BJT_Vclec_Curve.tsp | 369 ++++++ .../SMU_Examples/BJT_Vce-Ic/README.md | 15 + .../DC_Pulse_LIV_VCSEL_Char/Laser_VSCEL.py | 248 ++++ .../DC_Pulse_LIV_VCSEL_Char/Laser_VSCEL.tsp | 450 +++++++ .../DC_Pulse_LIV_VCSEL_Char/README.md | 15 + .../LED_ConfigList_Example/LED_Sequence.py | 166 +++ .../LED_ConfigList_Example/LED_Sequence.tsp | 167 +++ .../LED_ConfigList_Example/README.md | 13 + .../Linear_Current_Sweep/README.md | 15 + .../Sweep_Current_Diode.py | 142 +++ .../Sweep_Current_Diode.tsp | 194 +++ .../Linear_Voltage_Sweep/README.md | 15 + .../Linear_Voltage_Sweep/Sweep_Voltage_Res.py | 154 +++ .../Sweep_Voltage_Res.tsp | 202 +++ .../MOSFET_Drain_Family_Curve.py | 308 +++++ .../MOSFET_Drain_Family_Curve.tsp | 363 ++++++ .../MOSFET_Drain_Family_Curves/README.md | 15 + .../MOSFET_Transfer_Curve.py | 299 +++++ .../MOSFET_Transfer_Curve.tsp | 351 +++++ .../MOSFET_Transfer_Curve/README.md | 15 + .../Current_Pulse_Generation.py | 123 ++ .../Current_Pulse_Generation.tsp | 91 ++ .../Pulse_Current_Waveform_Capture/README.md | 15 + .../LED_Pulse_Sweep.py | 243 ++++ .../LED_Pulse_Sweep.tsp | 241 ++++ .../Pulse_Linear_Current_Sweep/README.md | 15 + .../Pulse_Linear_Voltage_Sweep/README.md | 15 + .../Res_Volt_Pulse_Sweep.py | 236 ++++ .../Res_Volt_Pulse_Sweep.tsp | 235 ++++ .../Digital_Pulse_Gen.py | 153 +++ .../Digital_Pulse_Gen.tsp | 140 ++ .../Pulse_Voltage_Waveform_Capture/README.md | 15 + .../SMU_Examples/README.md | 52 + .../Sine_Waveform_Capture/README.md | 15 + .../Sinewave_Generation.py | 140 ++ .../Sinewave_Generation.tsp | 119 ++ 76 files changed, 9764 insertions(+) create mode 100644 Examples/Modular_Precision_Test_System/Application_Examples/6_Ch_Parallel_Measurements/Parallel_Testing.py create mode 100644 Examples/Modular_Precision_Test_System/Application_Examples/6_Ch_Parallel_Measurements/README.md create mode 100644 Examples/Modular_Precision_Test_System/Application_Examples/Exporting_Parallel_Data_To_USB/Parallel_Measure_With_USB_Export.tsp create mode 100644 Examples/Modular_Precision_Test_System/Application_Examples/Exporting_Parallel_Data_To_USB/README.md create mode 100644 Examples/Modular_Precision_Test_System/Application_Examples/README.md create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Parallel/Parallel_Combo.tsp create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Parallel/README.md create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Series/README.md create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Series/Series_Combo.tsp create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Bipolar_Output/Bipolar_Demo.py create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Bipolar_Output/Bipolar_Demo.tsp create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Bipolar_Output/README.md create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Linear_Sweep/Linear_Sweep.py create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Linear_Sweep/Linear_Sweep.tsp create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Linear_Sweep/README.md create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/OCP/OCP Demo.py create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/OCP/OCP Demo.tsp create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/OCP/README.md create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/OVP/OVP_Demo.py create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/OVP/OVP_Demo.tsp create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/OVP/README.md create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/README.md create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/Trigger Output Sequence Demo.py create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/Trigger Output Sequence Demo.tsp create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/Trigger Output Sequence TSP Functions.py create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/README.md create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Slew_Rate/README.md create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Slew_Rate/Slew Rate Demo.py create mode 100644 Examples/Modular_Precision_Test_System/PSU_Examples/Slew_Rate/Slew Rate Demo.tsp create mode 100644 Examples/Modular_Precision_Test_System/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Parallel/Parallel_SMU_Combo.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Parallel/Parallel_SMU_Combo.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Parallel/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Series/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Series/Series_SMU_Combo.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Series/Series_SMU_Combo.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Gummel/BJT_Gummel_VbIc.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Gummel/BJT_Gummel_Vblc.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Gummel/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Vce-Ic/BJT_Vclec_Curve.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Vce-Ic/BJT_Vclec_Curve.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Vce-Ic/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/DC_Pulse_LIV_VCSEL_Char/Laser_VSCEL.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/DC_Pulse_LIV_VCSEL_Char/Laser_VSCEL.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/DC_Pulse_LIV_VCSEL_Char/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/LED_ConfigList_Example/LED_Sequence.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/LED_ConfigList_Example/LED_Sequence.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/LED_ConfigList_Example/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Current_Sweep/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Current_Sweep/Sweep_Current_Diode.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Current_Sweep/Sweep_Current_Diode.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Voltage_Sweep/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Voltage_Sweep/Sweep_Voltage_Res.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Voltage_Sweep/Sweep_Voltage_Res.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Drain_Family_Curves/MOSFET_Drain_Family_Curve.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Drain_Family_Curves/MOSFET_Drain_Family_Curve.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Drain_Family_Curves/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Transfer_Curve/MOSFET_Transfer_Curve.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Transfer_Curve/MOSFET_Transfer_Curve.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Transfer_Curve/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Current_Waveform_Capture/Current_Pulse_Generation.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Current_Waveform_Capture/Current_Pulse_Generation.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Current_Waveform_Capture/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Current_Sweep/LED_Pulse_Sweep.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Current_Sweep/LED_Pulse_Sweep.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Current_Sweep/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Voltage_Sweep/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Voltage_Sweep/Res_Volt_Pulse_Sweep.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Voltage_Sweep/Res_Volt_Pulse_Sweep.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Voltage_Waveform_Capture/Digital_Pulse_Gen.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Voltage_Waveform_Capture/Digital_Pulse_Gen.tsp create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Voltage_Waveform_Capture/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Sine_Waveform_Capture/README.md create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Sine_Waveform_Capture/Sinewave_Generation.py create mode 100644 Examples/Modular_Precision_Test_System/SMU_Examples/Sine_Waveform_Capture/Sinewave_Generation.tsp diff --git a/Examples/Modular_Precision_Test_System/Application_Examples/6_Ch_Parallel_Measurements/Parallel_Testing.py b/Examples/Modular_Precision_Test_System/Application_Examples/6_Ch_Parallel_Measurements/Parallel_Testing.py new file mode 100644 index 0000000..2cae540 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/Application_Examples/6_Ch_Parallel_Measurements/Parallel_Testing.py @@ -0,0 +1,1130 @@ +# Used to connect to the instrument +import pyvisa as visa +# Used for arrays +import numpy as np +# Used for frontend +import tkinter as tk +from tkinter import * +from tkinter import ttk +# Used for simple delays +from time import sleep +# Used for plotting/displaying results +from matplotlib.figure import Figure +from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk) + +rm = visa.ResourceManager() + +######################################################################################### +# Function: check_SMU() +# Purpose: This function allows the user to check their instrument connection. A +# resource string is entered by the user and queried to ensure a working +# working connection. +######################################################################################### +def check_SMU(): + # Empty displayed text + string_idn.config(text = ' ') + # Retrieve resource string + resource_string = ip_entry.get() + # Attempt to open the instrument, query the idn, and display the results + try: + myMP5000 = rm.open_resource(resource_string, read_termination = '\n') + myMP5000.timeout = 20000 + myMP5000.write("*IDN?") + read = myMP5000.read() + print(read) + remove_term = read.replace('\n','') + string_idn.config(text = remove_term) + myMP5000.write("localnode.prompts = 0 \n") + myMP5000.close() + # If error occurs notify the user + except: + string_idn.config(text = "Error in opening instrument, check resource string") + + # Clear all figures and draw to screen + fig1.clf() + fig2.clf() + fig3.clf() + fig4.clf() + canvas1.draw() + canvas2.draw() + canvas3.draw() + canvas4.draw() + +######################################################################################### +# Function: load_script() +# Purpose: This function loads multiple functions to execute the various tests for +# each channel. These tests can be configured and run by the user from the +# screen. +######################################################################################### +def load_script(): + # Open the instrument + string_idn.config(text = ' ') + resource_string = ip_entry.get() + myMP5000 = rm.open_resource(resource_string, read_termination = '\n') + myMP5000.timeout = 20000 + # Create a command buffer to later write to the instrument + tsp_command = ["loadscript CombinedTestScripts", + #################################################################################################################### + # Function: configure_DrainFamilyCurve_VdId_TwoTriggers(infoSMU,dSMUsettings, gSMUsettings, meaSettings,tmName) + # Parameters: infoSMU A list containing the SMU slot/module locations + # {gate_slot, drain_slot, gate_channel, drain_channel} + # dSMUsettings A list containing the drain configuration settings + # {startVd, stopVd, noSwpPoints, rangeId, limitId} + # gSMUsettings A list containing the gate configuration settings + # {startVg, stopVg, noStpPoints, limitIg} + # meaSettings A list containing the measure configuration settings + # {nplc, mDelay} + # tmName A string to name the trigger model + # + # Purpose: This function performs VdId characterization on a MOSFET. It returns the results to be graphed to + # the screen. + #################################################################################################################### + "function configure_DrainFamilyCurve_VdId_TwoTriggers(infoSMU,dSMUsettings, gSMUsettings, meaSettings,tmName)", + # SMU channel assignment + " local gateSMU = slot[infoSMU[1]].smu[infoSMU[2]]", + " local drainSMU = slot[infoSMU[3]].smu[infoSMU[4]]", + " local smu_id = {gateSMU,drainSMU}", + # Configure both channels for a voltage sweep + " for i = 1, 2 do ", + " smu_id[i].reset()", + " smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE", + " smu_id[i].sense = smu_id[i].SENSE_2WIRE ", + " if smu_id[i] == gateSMU then", + " smu_id[i].source.rangev = math.max(math.abs(gSMUsettings[1]), math.abs(gSMUsettings[2]))", + " smu_id[i].source.limiti = gSMUsettings[4]", + " smu_id[i].measure.rangei = 1e-3", + " else", + " smu_id[i].source.rangev = math.max(math.abs(dSMUsettings[1]), math.abs(dSMUsettings[2]))", + " smu_id[i].source.limiti = dSMUsettings[5]", + " smu_id[i].measure.rangei = dSMUsettings[4]", + " end ", + " smu_id[i].source.levelv = 0", + " smu_id[i].measure.nplc = meaSettings[1]", + + " smu_id[i].defbuffer1.clear()", + " smu_id[i].defbuffer1.appendmode = 1", + " smu_id[i].defbuffer2.clear()", + " smu_id[i].defbuffer2.appendmode = 1", + " end", + + ############################################# Drain Trigger Model ############################################## + ################################################################################################################ + + # Configure trigger model source for linear voltage sweep + " drainSMU.trigger.source.linearv(dSMUsettings[1], dSMUsettings[2], dSMUsettings[3])", + # Set trigger model measurements to i/v and store in default buffers + " drainSMU.trigger.measure.iv(drainSMU.defbuffer1,drainSMU.defbuffer2)", + + " local trigger_drain = slot[infoSMU[3]].trigger.model", + " trigger_drain.create(tmName[2])", + + # Wait for notification to begin test + " trigger_drain.addblock.wait(tmName[2],\"Wait1\",trigger.generator[1].EVENT_ID)", + + # Notify gate that sweep has began + " trigger_drain.addblock.notify(tmName[2],\"notifyStep\",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY1)", + + # Wait for notification from gate that fate voltage has advanced + " trigger_drain.addblock.wait(tmName[2],\"waitStep\",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY2 )", + # Advance drain source voltage + " trigger_drain.addblock.source.action.step(tmName[2], \"drainSweep\", infoSMU[4])", + # Notify gate that drain voltage has advanced + " trigger_drain.addblock.notify(tmName[2],\"notifyMeasure\",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY3)", + # Measure drain i/v + " trigger_drain.addblock.measure(tmName[2], \"measure\", infoSMU[4], 1)", + + # Wait for notification that gate measurement has been taken + " trigger_drain.addblock.wait(tmName[2],\"waitMeasure\",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY4 )", + # Loop back to advancing drain voltage + " trigger_drain.addblock.branch.counter(tmName[2], \"branch-sweep\", \"drainSweep\", dSMUsettings[3])", + # Notify gate that drain voltage sweep has completed + " trigger_drain.addblock.notify(tmName[2],\"notifySweepDone\",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY5)", + # Loop back to notify gate to begin sweep + " trigger_drain.addblock.branch.counter(tmName[2], \"branch-step\", \"notifyStep\", gSMUsettings[3])", + " trigger_drain.addblock.source.action.bias(tmName[2], \"drainbiaszeo\", infoSMU[4])", + + ############################################## Gate Trigger Model ############################################## + ################################################################################################################ + + # Configure trigger model source for linear voltage sweep + " gateSMU.trigger.source.linearv(gSMUsettings[1], gSMUsettings[2], gSMUsettings[3])", + # Set trigger model measurements to i/v and store in default buffers + " gateSMU.trigger.measure.iv(gateSMU.defbuffer1,gateSMU.defbuffer2)", + + " local trigger_gate = slot[infoSMU[1]].trigger.model", + " trigger_gate.create(tmName[1]) ", + + # Wait for notification from drain to begin + " trigger_gate.addblock.wait(tmName[1],\"Wait1\",trigger.generator[1].EVENT_ID) ", + # Advance gate source voltage + " trigger_gate.addblock.wait(tmName[1],\"waitStep\",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY1 )", + # Notify drain that gate voltage has advanced + " trigger_gate.addblock.source.action.step(tmName[1], \"gateVoltage\", infoSMU[2])", + + # Wait for notification that drain source voltage has advanced + " trigger_gate.addblock.notify(tmName[1],\"notify\",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY2)", + # Measure gate i/v + " trigger_gate.addblock.wait(tmName[1],\"waitMeasure\",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY3 )", + # Notify drain that measurement has been taken + " trigger_gate.addblock.measure(tmName[1],\"measure\",infoSMU[2],1)", + # Loop back to wait for drain voltage to advance + " trigger_gate.addblock.notify(tmName[1],\"notify\",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY4)", + + # Wait for notification that drain voltage sweep has completed + " trigger_gate.addblock.branch.counter(tmName[1], \"branch-Measure\", \"waitMeasure\", dSMUsettings[3])", + # Loop back to wait for drain to begin + " trigger_gate.addblock.wait(tmName[1],\"waitSweepDone\",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY5 )", + " trigger_gate.addblock.branch.counter(tmName[1], \"branch-Step\", \"waitStep\", gSMUsettings[3])", + " trigger_gate.addblock.source.action.bias(tmName[1], \"gatebiaszero\", infoSMU[2])", + "end", + #################################################################################################################### + # Function: Pulse_Waveform_Capture_MSMU_MP5000(infoSMU,srcSettings,meaSettings,pulseSettings,triggerName) + # Parameters: infoSMU A list containing the SMU slot/module locations + # {slot_no, sweepV_channel} + # srcSettings A list containing the source configuration settings + # {startV, stopV, noPoints, limitI} + # meaSettings A list containing the measure configuration settings + # {measRangeV, measRangeI, remoteSense} + # pulseSettings A list containing the pulse parameters + # {pulsePeriod, pulseWidth, mDelay, apertureTime} + # triggerName A string to name the trigger model + # + # Purpose: This function configures a channel to generate a voltage source pulsed waveform. The function + # records current and voltage to display to the user. + #################################################################################################################### + "function Pulse_Waveform_Capture_MSMU_MP5000(infoSMU,srcSettings,meaSettings,pulseSettings,triggerName)", + # SMU channel assignment + " local sweepV_SMU = slot[infoSMU[1]].smu[infoSMU[2]]", + " sweepV_SMU.reset()", + # Source Settings + " sweepV_SMU.source.func = sweepV_SMU.FUNC_DC_VOLTAGE", + " sweepV_SMU.source.rangev = math.max(math.abs(srcSettings[1]), math.abs(srcSettings[2]))", + " sweepV_SMU.source.limiti = srcSettings[4]", + " sweepV_SMU.source.levelv = 0", + # Measure Settings + " sweepV_SMU.measure.rangev = meaSettings[1]", + " sweepV_SMU.measure.rangei = meaSettings[2]", + " sweepV_SMU.measure.aperture = pulseSettings[4]", + " sweepV_SMU.sense = sweepV_SMU.SENSE_2WIRE", + " sweepV_SMU.measure.autorangei = 0", + + " if meaSettings[3] == true then", + " sweepV_SMU.sense = sweepV_SMU.SENSE_4WIRE", + " else", + " sweepV_SMU.sense = sweepV_SMU.SENSE_2WIRE", + " end ", + # Buffer clear + " sweepV_SMU.defbuffer1.clear()", + " sweepV_SMU.defbuffer1.appendmode = 1", + " sweepV_SMU.defbuffer2.clear()", + " sweepV_SMU.defbuffer2.appendmode = 1", + # Calculate number of points + " local nPoints = math.ceil(((pulseSettings[1] * srcSettings[3]) + pulseSettings[1]) / pulseSettings[4])", + + # Configure trigger model source for linear voltage sweep + " sweepV_SMU.trigger.source.linearv(srcSettings[1], srcSettings[2], srcSettings[3])", + # Set trigger model to measure i/v and store in default buffers + " sweepV_SMU.trigger.measure.iv(sweepV_SMU.defbuffer1,sweepV_SMU.defbuffer2)", + + # Configure trigger model + " local triggerModel = slot[infoSMU[1]].trigger.model", + " triggerModel.create(triggerName) ", + # Wait for notification to begin + " triggerModel.addblock.wait(triggerName,\"Wait1_pulse\",trigger.generator[1].EVENT_ID)", + # Measureoverlapped allows measurements to be taken while trigger model is running in parallel + " triggerModel.addblock.measureoverlapped(triggerName, \"measure_IV\", infoSMU[2], nPoints)", + " triggerModel.addblock.delay.constant(triggerName, \"prepulse_delay\", pulseSettings[1]/2)", + # Advance to next voltage value in voltage sweep + " triggerModel.addblock.source.action.step(triggerName, \"sweepV_IV\", infoSMU[2])", + " triggerModel.addblock.delay.constant(triggerName, \"meas_pulse_width\", pulseSettings[2],\"sweepV_IV\")", + # Set source level to 0 + " triggerModel.addblock.source.action.bias(triggerName, \"pulse_bias\", infoSMU[2])", + " triggerModel.addblock.delay.constant(triggerName, \"meas_pulse_period\", pulseSettings[1],\"sweepV_IV\")", + # Loop through all voltage sweep values + " triggerModel.addblock.branch.counter(triggerName, \"branch-counter\", \"sweepV_IV\", srcSettings[3])", + " triggerModel.addblock.delay.constant(triggerName, \"postpulse_delay\", pulseSettings[1]) ", + "end ", + #################################################################################################################### + # Function: configure_DC_Laser_VCSEL_LIV_inTriggerModel(infoSMU,IF_Settings,PD_Settings, meaSettings,tmName) + # Parameters: infoSMU A list containing the SMU slot/module locations + # {IF_slot, IF_channel, PD_slot, PD_channel} + # IF_Settings A list containing the configuration settings for the IF channel + # {startIF, stopIF, noPoints, rangeI, limitV, measRangeV} + # PD_Settings A list containing the configuration settings for the PD channel + # {biasV_PD, srcRangeV_PD, measRangeI_PD} + # meaSettings A list containing the measure configuration settings + # {nplc, mDelay, remodeSense} + # tmName A list containing names for both trigger models + # {tm_name1, tm_name2} + # + # Purpose: This function configures two channels for an LIV test for a laser VCSEL. + #################################################################################################################### + "function configure_DC_Laser_VCSEL_LIV_inTriggerModel(infoSMU,IF_Settings,PD_Settings, meaSettings,tmName)", + # SMU channel assignment + " local IF_SMU = slot[infoSMU[1]].smu[infoSMU[2]]", + " local PD_SMU = slot[infoSMU[3]].smu[infoSMU[4]]", + " local smu_id = {IF_SMU, PD_SMU}", + + # Configure all channels + " for i = 1, 2 do", + " smu_id[i].reset()", + # Buffer clear + " smu_id[i].defbuffer1.clear()", + " smu_id[i].defbuffer1.appendmode = 1", + " smu_id[i].defbuffer2.clear()", + " smu_id[i].defbuffer2.appendmode = 1", + " if smu_id[i] == IF_SMU then", + # Source Settings + " smu_id[i].source.func = smu_id[i].FUNC_DC_CURRENT", + " smu_id[i].source.rangei = IF_Settings[4]", + " smu_id[i].source.leveli = 0", + " smu_id[i].source.limitv = IF_Settings[5]", + # Measure Settings + " smu_id[i].measure.rangev = IF_Settings[6]", + " smu_id[i].measure.rangei = IF_Settings[4]", + " else", + # Source Settings + " smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE", + " smu_id[i].source.rangev = PD_Settings[2]", + " smu_id[i].source.levelv = PD_Settings[1]", + # Measure Settings + " smu_id[i].measure.rangei = PD_Settings[3]", + " end", + + " smu_id[i].measure.nplc = meaSettings[1]", + " if meaSettings[3] == true then", + " smu_id[i].sense = smu_id[i].SENSE_4WIRE", + " else", + " smu_id[i].sense = smu_id[i].SENSE_2WIRE", + " end", + " end", + + ########################################### IF Channel Trigger Model ########################################### + ################################################################################################################ + + # Setup trigger model source for linear current sweep + " IF_SMU.trigger.source.lineari(IF_Settings[1], IF_Settings[2], IF_Settings[3])", + # Set trigger model to measure i/v and store in default buffers + " IF_SMU.trigger.measure.iv(IF_SMU.defbuffer1,IF_SMU.defbuffer2)", + + " local trigger_IF = slot[infoSMU[1]].trigger.model", + " trigger_IF.create(tmName[1]) ", + + # Wait for notification to begin + " trigger_IF.addblock.wait(tmName[1],\"Wait1_if\",trigger.generator[1].EVENT_ID) ", + + # Notify PD trigger model to begin + " trigger_IF.addblock.notify(tmName[1],\"notifyPDStep\",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY1)", + + # Wait for response from PD trigger model + " trigger_IF.addblock.wait(tmName[1],\"waitPDStep\",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY2 )", + # Advance to next source value + " trigger_IF.addblock.source.action.step(tmName[1], \"IF_sweep\", infoSMU[2])", + " trigger_IF.addblock.delay.constant(tmName[1],\"meaDelay\",meaSettings[2],\"IF_sweep\")", + # Notify PD trigger model that step and wait are complete + " trigger_IF.addblock.notify(tmName[1],\"notify\",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY3)", + # Measure i/v on source terminals + " trigger_IF.addblock.measure(tmName[1], \"measure\", infoSMU[2], 1)", + + # Wait for measurement completed notification + " trigger_IF.addblock.wait(tmName[1],\"wait\",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY4 )", + # Loop back to advancing source current + " trigger_IF.addblock.branch.counter(tmName[1], \"IF_branch\", \"IF_sweep\", IF_Settings[3])", + # Notify PD trigger model that current sweep is completed + " trigger_IF.addblock.notify(tmName[1],\"notifySweepDone\",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY5) ", + " trigger_IF.addblock.source.action.bias(tmName[1], \"IF_bias\", infoSMU[2])", + + ########################################### PD Channel Trigger Model ########################################### + ################################################################################################################ + + # Setup trigger model source for linear voltage sweep + # (voltage sweep is only one value so will stay constant through test) + " PD_SMU.trigger.source.linearv(PD_Settings[1], PD_Settings[1], 1)", + # Set trigger model to measure i/v and store in default buffers + " PD_SMU.trigger.measure.iv(PD_SMU.defbuffer1,PD_SMU.defbuffer2)", + + " local trigger_PD = slot[infoSMU[3]].trigger.model", + " trigger_PD.create(tmName[2])", + + # Wait for notification to begin + " trigger_PD.addblock.wait(tmName[2],\"Wait1\",trigger.generator[1].EVENT_ID) ", + + # Wait for initiation from source trigger model + " trigger_PD.addblock.wait(tmName[2],\"waitStep\",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY1 )", + # Advance to next source value + " trigger_PD.addblock.source.action.step(tmName[2], \"bias_PD\", infoSMU[4])", + # Notify source trigger model that source has been stepped + " trigger_PD.addblock.notify(tmName[2],\"notifyStep\",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY2)", + + # Wait for notification of completion from source trigger model + " trigger_PD.addblock.wait(tmName[2],\"waitMeas\",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY3 )", + " trigger_PD.addblock.delay.constant(tmName[2],\"meaDelay\",meaSettings[2])", + # Measure i/v on PD terminals + " trigger_PD.addblock.measure(tmName[2], \"measure\", infoSMU[4], 1)", + # Notify source trigger model that measurement is completed + " trigger_PD.addblock.notify(tmName[2],\"notify\",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY4)", + # Loop back aand wait for notification from source trigger model + " trigger_PD.addblock.branch.counter(tmName[2], \"branch-PD\", \"waitMeas\", IF_Settings[3])", + + # Wait for notification that source trigger model is completed + " trigger_PD.addblock.wait(tmName[2],\"waitPDDone\",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY5 )", + # Set source to 0 + " trigger_PD.addblock.source.action.bias(tmName[2], \"PDzero\", infoSMU[4])", + "end ", + #################################################################################################################### + # Function: configure_Sine_Waveform_Generate_MSMU_MP5000(noSlot, noCh, Vrms, numCycles, frequency, limitI,tm_Name) + # Parameters: noSlot Slot number of the module + # noCh Channel number to be used + # Vrms Vrms of generated sine wave + # numCycles Number of cycles to generate + # frequency Frequency of generated wave + # limitI Current limit for source + # tm_name Name for the trigger model + # + # Purpose: This function configures a channel to generate a sine wave according to user specifications. + #################################################################################################################### + "function configure_Sine_Waveform_Generate_MSMU_MP5000(settings, tm_Name)", + # SMU alais + " local noSlot = settings[1]", + " local noCh = settings[2]", + " local Vrms = settings[3]", + " local numCycles = settings[4]", + " local frequency = settings[5]", + " local limitI = settings[6]", + " local smu_ID = slot[noSlot].smu[noCh]", + # Calculate sinewave values + " local Vpp = Vrms * math.sqrt(2)", + " local sourceValues = {} ", + " local pointsPerCycle = 7200 / frequency", + " local timeInterval = 1/7200", + " local numDataPoints = pointsPerCycle * numCycles", + " local numReadings", + # Generate voltage sweep values + " for i=1, numDataPoints do", + " sourceValues[i] = (Vpp * math.sin(i * 2 * math.pi / pointsPerCycle))", + " end", + # Create reading buffers + " numReadings = 280 * numCycles", + " smu_ID.reset()", + " readingBuffer1 = smu_ID.makebuffer(5000)", + " readingBuffer2 = smu_ID.makebuffer(5000)", + + # Configure channel settings + " smu_ID.source.func = smu_ID.FUNC_DC_VOLTAGE", + " smu_ID.source.autorangev = smu_ID.OFF", + " smu_ID.source.rangev = 20", + " smu_ID.source.limiti = limitI", + " smu_ID.source.levelv = 0", + " smu_ID.source.output = 1", + " smu_ID.measure.aperture = 0.0001", + " smu_ID.measure.rangei = 100e-3", + " smu_ID.measure.rangev = 20", + + # Configure sweep voltage list + " smu_ID.trigger.source.listv(sourceValues)", + # Set trigger model readings for i/v and store in reading buffers + " smu_ID.trigger.measure.iv(readingBuffer1,readingBuffer2)", + + # Configure trigger model + " local triggermodel = slot[noSlot].trigger.model", + " triggermodel.create(tm_Name)", + # Wait for notification to begin + " triggermodel.addblock.wait(tm_Name,\"Wait1\",trigger.generator[1].EVENT_ID)", + # Measure overlapped allows for readings to be taken while triggermodel is executing in parallel + " triggermodel.addblock.measureoverlapped(tm_Name, \"measure3\", noCh, numReadings)", + " triggermodel.addblock.delay.constant(tm_Name, \"delay-init\", 2e-3)", + # Advance through voltage sweep + " triggermodel.addblock.source.action.step(tm_Name, \"sweep_step1\",noCh)", + " triggermodel.addblock.delay.constant(tm_Name, \"delay-on2\", timeInterval,\"sweep_step1\")", + # Loop through all voltage values + " triggermodel.addblock.branch.counter(tm_Name, \"branch-counter7\", \"sweep_step1\", numDataPoints)", + "end", + "endscript", + "CombinedTestScripts.run()"] + # Write all commands to the instrument + for cmd in tsp_command: + myMP5000.write(cmd + "\n") + myMP5000.close() + # Confirm to user that script is loaded + string_idn.config(text = "Test Script is loaded") + print("Test Script is loaded") + +######################################################################################### +# Function: run_test_drain_family() +# Purpose: This function runs the drain family test that was loaded previously. This +# handles retrieving user input, retrieving buffer data, and displaying +# result to the user. +######################################################################################### +def run_test_drain_family(): + # Clear figure + fig1.clf() + # Open instrument resource + resource_string = ip_entry.get() + myMP5000 = rm.open_resource(resource_string, read_termination = '\n') + myMP5000.timeout = 20000 + + # Retrieve location data from user + location_assignments = task1_assign_input.get()[1:-1].split(",") + slot_ga = int(location_assignments[0]) + ch_ga = int(location_assignments[1]) + slot_dr = int(location_assignments[2]) + ch_dr = int(location_assignments[3]) + # Retrieve Drain sweep parameters from user + drain_parameters = task1_drain_input.get()[1:-1].split(",") + startV = float(drain_parameters[0]) + stopV = float(drain_parameters[1]) + noPts = int(drain_parameters[2]) + # Retrieve Gate sweep parameters from user + gate_parameters = task1_gate_input.get()[1:-1].split(",") + noGaPts = int(gate_parameters[2]) + # Initiate and complete the test + tsp_command = [ + "configure_DrainFamilyCurve_VdId_TwoTriggers("+task1_assign_input.get()+","+task1_drain_input.get()+","+task1_gate_input.get()+",{0.05,0},{\"tm_gate\",\"tm_drain\"})\n", + f"slot[{slot_ga}].smu[{ch_ga}].source.output = 1 \n", + f"slot[{slot_dr}].smu[{ch_dr}].source.output = 1 \n", + "slot[1].trigger.model.initiate(\"tm_gate\")\n", + "slot[1].trigger.model.initiate(\"tm_drain\")\n", + "trigger.generator[1].assert()\n", + "waitcomplete()\n", + f"slot[{slot_ga}].smu[{ch_ga}].source.output = 0 \n", + f"slot[{slot_dr}].smu[{ch_dr}].source.output = 0 \n", + "slot[1].trigger.model.delete(\"tm_gate\")\n", + "slot[1].trigger.model.delete(\"tm_drain\")\n" + ] + for cmd in tsp_command: + myMP5000.write(cmd) + + # Retrieve drain voltage data + x_buffer = [] + for i in range(0,noPts): + x_buffer.append(float(i*((stopV-startV)/(noPts-1)))) + x_array = np.array(x_buffer) + + # Retrieve drain current data + y_buffer = [] + data = f"printbuffer(1,slot[{slot_dr}].smu[{ch_dr}].defbuffer1.n,slot[{slot_dr}].smu[{ch_dr}].defbuffer1)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + myMP5000.close() + + print("\n######## VdId Test ########") + print("Vd\t\tId") + + # Create plot for displaying data + plot1 = fig1.add_subplot() + plot1.grid() + plot1.set_title("Drain Family Curve Test",fontsize=14) + # Create plots for each sweep + for k in range (0, noGaPts): + for i in range((noPts*k),noPts*(k+1)): + y_buffer.append(float(readBuffer[i])) + y_array = np.array(y_buffer) + for i in range(0,noPts): + print(f"{"{:.5e}".format(x_array[i])}\t{y_array[i]}") + plot1.plot(x_array,y_array,'-o', color='blue') + y_buffer.clear() + canvas1.draw() + print("VdId test done") + print("###########################\n") + +######################################################################################### +# Function: liv_test() +# Purpose: This function runs the liv test that was loaded previously. This +# handles retrieving user input, retrieving buffer data, and displaying +# result to the user. +######################################################################################### +def liv_test(): + # Clear figure + fig2.clf() + # Open instrument connection + resource_string = ip_entry.get() + myMP5000 = rm.open_resource(resource_string, read_termination = '\n') + myMP5000.timeout = 20000 + + # Retrieve location data from user + location_assignments = task2_assign_input.get()[1:-1].split(",") + slot_ld = int(location_assignments[0]) + ch_ld = int(location_assignments[1]) + slot_pd = int(location_assignments[2]) + ch_pd = int(location_assignments[3]) + + # Initiate and complete the liv test + tsp_command = [ + "configure_DC_Laser_VCSEL_LIV_inTriggerModel("+task2_assign_input.get()+","+task2_ld_input.get()+","+task2_pd_input.get()+",{0.05,1e-3,false},{\"tm_laser\",\"tm_pd\"})\n", + f"slot[{slot_ld}].smu[{ch_ld}].source.output = 1\n", + f"slot[{slot_pd}].smu[{ch_pd}].source.output = 1\n", + f"slot[{slot_pd}].trigger.model.initiate(\"tm_pd\")\n", + f"slot[{slot_ld}].trigger.model.initiate(\"tm_laser\")\n", + "trigger.generator[1].assert()\n", + "waitcomplete()\n", + f"slot[{slot_ld}].smu[{ch_ld}].source.output = 0\n", + f"slot[{slot_pd}].smu[{ch_pd}].source.output = 0\n", + f"slot[{slot_pd}].trigger.model.delete(\"tm_pd\")\n", + f"slot[{slot_ld}].trigger.model.delete(\"tm_laser\")\n", + ] + for cmd in tsp_command: + myMP5000.write(cmd) + + print("\n################ LIV Test #################") + print("I_F\t\tV_F\t\tI_D") + + # Retrieve ld current data + x_buffer = [] + data = f"printbuffer(1,slot[{slot_ld}].smu[{ch_ld}].defbuffer1.n, slot[{slot_ld}].smu[{ch_ld}].defbuffer1)" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + x_buffer = [float(i) for i in readBuffer] + x_array = np.array(x_buffer) + # Retrieve ld voltage data + y_buffer = [] + data = f"printbuffer(1,slot[{slot_ld}].smu[{ch_ld}].defbuffer2.n,slot[{slot_ld}].smu[{ch_ld}].defbuffer2)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + y_buffer =[float(i) for i in readBuffer] + y_array = np.array(y_buffer) + # Retrieve pd current data + y2_buffer = [] + data = f"printbuffer(1,slot[{slot_pd}].smu[{ch_pd}].defbuffer1.n,slot[{slot_pd}].smu[{ch_pd}].defbuffer1)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + myMP5000.close() + y2_buffer =[float(i) for i in readBuffer] + y2_array = np.array(y2_buffer) + # Plot results + plot2 = fig2.add_subplot() + plot2.grid() + plot2.set_title("Laser/VCSEL L-I-V Test",fontsize=14) + plot2.plot(x_array,y_array,'-o',color='blue') + + plot22 = plot2.twinx() + plot22.plot(x_array,y2_array,'-o',color='red') + + canvas2.draw() + + for i in range(len(x_buffer)): + print("{:.5e}".format(x_buffer[i]), "{:.5e}".format(y_buffer[i]), "{:.5e}".format(y2_buffer[i]), sep="\t") + print("LIV test done") + print("###########################################\n") + +######################################################################################### +# Function: pulse_test() +# Purpose: This function runs the pulse test that was loaded previously. This +# handles retrieving user input, retrieving buffer data, and displaying +# result to the user. +######################################################################################### +def pulse_test(): + # Clear figure + fig3.clf() + # Open instrument connection + resource_string = ip_entry.get() + myMP5000 = rm.open_resource(resource_string, read_termination = '\n') + myMP5000.timeout = 20000 + + # Fetch module location data from user + location_assignments = task3_assign_input.get()[1:-1].split(",") + slot_no = int(location_assignments[0]) + chan_no = int(location_assignments[1]) + + # Initiate and complete pulse test + tsp_command = [ + "Pulse_Waveform_Capture_MSMU_MP5000("+task3_assign_input.get()+","+task3_src_input.get()+",{6, 0.1, false}, "+task3_pulse_input.get()+",\"TM_sweepV_pulse\")\n", + f"slot[{slot_no}].smu[{chan_no}].source.output = 1 \n", + f"slot[{slot_no}].trigger.model.initiate(\"TM_sweepV_pulse\")\n", + "delay(100e-3)", + "trigger.generator[1].assert()\n", + "waitcomplete()\n", + f"slot[{slot_no}].smu[{chan_no}].source.output = 0 \n", + f"slot[{slot_no}].trigger.model.delete(\"TM_sweepV_pulse\")\n" + ] + for cmd in tsp_command: + myMP5000.write(cmd) + + print("\n######## Pulse Test ########") + print("Time\t\tV") + + # Retrieve timestamp data + x_buffer = [] + data = f"printbuffer(1,slot[{slot_no}].smu[{chan_no}].defbuffer2.n,slot[{slot_no}].smu[{chan_no}].defbuffer2.timestamps)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + x_buffer = [float(i) for i in readBuffer] + # Retrieve voltage data + y_buffer = [] + data = f"printbuffer(1,slot[{slot_no}].smu[{chan_no}].defbuffer2.n,slot[{slot_no}].smu[{chan_no}].defbuffer2)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + y_buffer = [float(i) for i in readBuffer] + + # Plot data + plot3 = fig3.add_subplot() + plot3.grid() + plot3.set_title("Voltage Pulse Sweep",fontsize=14) + plot3.plot(x_buffer,y_buffer,linewidth = 3,color='magenta') + canvas3.draw() + + for i in range(len(x_buffer)): + print("{:.5e}".format(x_buffer[i]), "{:.5e}".format(y_buffer[i]), sep="\t") + myMP5000.close() + print("Pulse test done") + print("############################\n") + +######################################################################################### +# Function: sine_test() +# Purpose: This function runs the sine generation test that was loaded previously. +# This handles retrieving user input, retrieving buffer data, and +# displaying result to the user. +######################################################################################### +def sine_test(): + # Clear figure + fig4.clf() + # Open instrument connection + resource_string = ip_entry.get() + myMP5000 = rm.open_resource(resource_string, read_termination = '\n') + myMP5000.timeout = 20000 + + settings = task4_src_input.get()[1:-1].split(",") + slot_no = settings[0] + chan_no = settings[1] + + # Initiate and complete sine generation + tsp_command = [ + "configure_Sine_Waveform_Generate_MSMU_MP5000("+task4_src_input.get()+", \"TM_sine\")\n", + f"slot[{slot_no}].smu[{chan_no}].source.output = 1 \n", + f"slot[{slot_no}].trigger.model.initiate(\"TM_sine\")\n", + "delay(100e-3)", + "trigger.generator[1].assert()\n", + "waitcomplete()\n", + f"slot[{slot_no}].smu[{chan_no}].source.output = 0 \n", + f"slot[{slot_no}].trigger.model.delete(\"TM_sine\")\n" + ] + for cmd in tsp_command: + myMP5000.write(cmd) + + print("\n######## Sine Test #########") + print("Time\t\tV") + + # Retrieve timestamp data + x_buffer = [] + data = "printbuffer(1,readingBuffer2.n,readingBuffer2.timestamps)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + x_buffer = [float(i) for i in readBuffer] + # Retrieve voltage data + y_buffer = [] + data = "printbuffer(1,readingBuffer2.n,readingBuffer2)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + y_buffer = [float(i) for i in readBuffer] + # Plot results + plot4 = fig4.add_subplot() + plot4.grid() + plot4.set_title("Sine Generation",fontsize=14) + plot4.plot(x_buffer,y_buffer,linewidth = 3, color='darkblue') + canvas4.draw() + + for i in range(len(x_buffer)): + print("{:.5e}".format(x_buffer[i]), "{:.5e}".format(y_buffer[i]), sep="\t") + myMP5000.close() + print("Sine test done") + print("############################\n") + +######################################################################################### +# Function: plot() +# Purpose: This function runs and plots the results from all tests. +######################################################################################### +def plot(): + # Clear all figures + fig1.clf() + fig2.clf() + fig3.clf() + fig4.clf() + + # Open instrument connection + resource_string = ip_entry.get() + myMP5000 = rm.open_resource(resource_string, read_termination = '\n') + myMP5000.timeout = 20000 + + # Retrieve location data from user + task1_location_assignments = task1_assign_input.get()[1:-1].split(",") + slot_ga = int(task1_location_assignments[0]) + ch_ga = int(task1_location_assignments[1]) + slot_dr = int(task1_location_assignments[2]) + ch_dr = int(task1_location_assignments[3]) + # Retrieve Drain sweep parameters from user + drain_parameters = task1_drain_input.get()[1:-1].split(",") + startV = float(drain_parameters[0]) + stopV = float(drain_parameters[1]) + noPts = int(drain_parameters[2]) + # Retrieve Gate sweep parameters from user + gate_parameters = task1_gate_input.get()[1:-1].split(",") + noGaPts = int(gate_parameters[2]) + # Retrieve location data from user + task2_location_assignments = task2_assign_input.get()[1:-1].split(",") + slot_ld = int(task2_location_assignments[0]) + ch_ld = int(task2_location_assignments[1]) + slot_pd = int(task2_location_assignments[2]) + ch_pd = int(task2_location_assignments[3]) + # Fetch module location data from user + task3_location_assignments = task3_assign_input.get()[1:-1].split(",") + slot_no_3 = int(task3_location_assignments[0]) + chan_no_3 = int(task3_location_assignments[1]) + # Retrieve location data from user + task4_location_assignments = task4_src_input.get()[1:-1].split(",") + slot_no_4 = task4_location_assignments[0] + chan_no_4 = task4_location_assignments[1] + + + tsp_command = [ + "configure_DrainFamilyCurve_VdId_TwoTriggers("+task1_assign_input.get()+","+task1_drain_input.get()+","+task1_gate_input.get()+",{0.05,0},{\"tm_gate\",\"tm_drain\"})\n", + f"slot[{slot_ga}].smu[{ch_ga}].source.output = 1 \n", + f"slot[{slot_dr}].smu[{ch_dr}].source.output = 1 \n", + f"slot[{slot_ga}].trigger.model.initiate(\"tm_gate\")\n", + f"slot[{slot_dr}].trigger.model.initiate(\"tm_drain\")\n", + "configure_DC_Laser_VCSEL_LIV_inTriggerModel("+task2_assign_input.get()+","+task2_ld_input.get()+","+task2_pd_input.get()+",{0.05,1e-3,false},{\"tm_laser\",\"tm_pd\"})\n", + f"slot[{slot_ld}].smu[{ch_ld}].source.output = 1 \n", + f"slot[{slot_pd}].smu[{ch_pd}].source.output = 1 \n", + f"slot[{slot_pd}].trigger.model.initiate(\"tm_pd\")\n", + f"slot[{slot_ld}].trigger.model.initiate(\"tm_laser\")\n", + "Pulse_Waveform_Capture_MSMU_MP5000("+task3_assign_input.get()+","+task3_src_input.get()+",{6, 0.1, false},"+task3_pulse_input.get()+",\"TM_sweepV_pulse\")\n", + f"slot[{slot_no_3}].smu[{chan_no_3}].source.output = 1 \n", + f"slot[{slot_no_3}].trigger.model.initiate(\"TM_sweepV_pulse\")\n", + "configure_Sine_Waveform_Generate_MSMU_MP5000("+task4_src_input.get()+",\"TM_sine\")\n", + f"slot[{slot_no_4}].smu[{chan_no_4}].source.output = 1 \n", + f"slot[{slot_no_4}].trigger.model.initiate(\"TM_sine\")\n", + "trigger.generator[1].assert()\n", + "waitcomplete()\n", + f"slot[{slot_ga}].smu[{ch_ga}].source.output = 0 \n", + f"slot[{slot_dr}].smu[{ch_dr}].source.output = 0 \n", + f"slot[{slot_ga}].trigger.model.delete(\"tm_gate\")\n", + f"slot[{slot_dr}].trigger.model.delete(\"tm_drain\")\n" + ] + for cmd in tsp_command: + myMP5000.write(cmd) + print("Tests complete") + +################################################## VdId Test ################################################### +################################################################################################################ + + # Retrieve drain voltage data + x_buffer = [] + for i in range(0,noPts): + x_buffer.append(float(i*((stopV-startV)/(noPts-1)))) + x_array = np.array(x_buffer) + + # Retrieve drain current data + y_buffer = [] + data = f"printbuffer(1,slot[{slot_dr}].smu[{ch_dr}].defbuffer1.n,slot[{slot_dr}].smu[{ch_dr}].defbuffer1)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + + print("\n######## VdId Test ########") + print("Vd\t\tId") + + # Create plot for displaying data + plot1 = fig1.add_subplot() + plot1.grid() + plot1.set_title("Drain Family Curve Test",fontsize=14) + # Create plots for each sweep + for k in range (0, noGaPts): + for i in range((noPts*k),noPts*(k+1)): + y_buffer.append(float(readBuffer[i])) + y_array = np.array(y_buffer) + for i in range(0,noPts): + print(f"{"{:.5e}".format(x_array[i])}\t{y_array[i]}") + plot1.plot(x_array,y_array,'-o', color='blue') + y_buffer.clear() + canvas1.draw() + print("VdId test done") + print("###########################\n") + +################################################## LIV Test ################################################### +################################################################################################################ + + tsp_command = [ + f"slot[{slot_ld}].smu[{ch_ld}].source.output = 0 \n", + f"slot[{slot_pd}].smu[{ch_pd}].source.output = 0 \n", + f"slot[{slot_ld}].trigger.model.delete(\"tm_laser\")\n", + f"slot[{slot_pd}].trigger.model.delete(\"tm_pd\")\n" + ] + for cmd in tsp_command: + myMP5000.write(cmd) + + print("\n################ LIV Test #################") + print("I_F\t\tV_F\t\tI_D") + + # Retrieve ld current data + x_buffer = [] + data = f"printbuffer(1,slot[{slot_ld}].smu[{ch_ld}].defbuffer1.n, slot[{slot_ld}].smu[{ch_ld}].defbuffer1)" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + x_buffer = [float(i) for i in readBuffer] + x_array = np.array(x_buffer) + # Retrieve ld voltage data + y_buffer = [] + data = f"printbuffer(1,slot[{slot_ld}].smu[{ch_ld}].defbuffer2.n,slot[{slot_ld}].smu[{ch_ld}].defbuffer2)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + y_buffer =[float(i) for i in readBuffer] + y_array = np.array(y_buffer) + # Retrieve pd current data + y2_buffer = [] + data = f"printbuffer(1,slot[{slot_pd}].smu[{ch_pd}].defbuffer1.n,slot[{slot_pd}].smu[{ch_pd}].defbuffer1)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + y2_buffer =[float(i) for i in readBuffer] + y2_array = np.array(y2_buffer) + # Plot results + plot2 = fig2.add_subplot() + plot2.grid() + plot2.set_title("Laser/VCSEL L-I-V Test",fontsize=14) + plot2.plot(x_array,y_array,'-o',color='blue') + + plot22 = plot2.twinx() + plot22.plot(x_array,y2_array,'-o',color='red') + + canvas2.draw() + + for i in range(len(x_buffer)): + print("{:.5e}".format(x_buffer[i]), "{:.5e}".format(y_buffer[i]), "{:.5e}".format(y2_buffer[i]), sep="\t") + print("LIV test done") + print("###########################################\n") + +############################################# Pulse Waveform Test ############################################## +################################################################################################################ + + tsp_command = [ + f"slot[{slot_no_3}].smu[{chan_no_3}].source.output = 0 \n", + f"slot[{slot_no_3}].trigger.model.delete(\"TM_sweepV_pulse\")\n" + ] + for cmd in tsp_command: + myMP5000.write(cmd) + + print("\n######## Pulse Test ########") + print("Time\t\tV") + + # Retrieve timestamp data + x_buffer = [] + data = f"printbuffer(1,slot[{slot_no_3}].smu[{chan_no_3}].defbuffer2.n,slot[{slot_no_3}].smu[{chan_no_3}].defbuffer2.timestamps)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + x_buffer = [float(i) for i in readBuffer] + # Retrieve voltage data + y_buffer = [] + data = f"printbuffer(1,slot[{slot_no_3}].smu[{chan_no_3}].defbuffer2.n,slot[{slot_no_3}].smu[{chan_no_3}].defbuffer2)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + y_buffer = [float(i) for i in readBuffer] + + # Plot data + plot3 = fig3.add_subplot() + plot3.grid() + plot3.set_title("Voltage Pulse Sweep",fontsize=14) + plot3.plot(x_buffer,y_buffer,linewidth = 3,color='magenta') + canvas3.draw() + + for i in range(len(x_buffer)): + print("{:.5e}".format(x_buffer[i]), "{:.5e}".format(y_buffer[i]), sep="\t") + print("Pulse test done") + print("############################\n") + +############################################## Sine Waveform Test ############################################## +################################################################################################################ + + tsp_command = [ + f"slot[{slot_no_4}].smu[{chan_no_4}].source.output = 0 \n", + f"slot[{slot_no_4}].trigger.model.delete(\"TM_sine\")\n" + ] + for cmd in tsp_command: + myMP5000.write(cmd) + + print("\n######## Sine Test #########") + print("Time\t\tV") + + # Retrieve timestamp data + x_buffer = [] + data = "printbuffer(1,readingBuffer2.n,readingBuffer2.timestamps)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + x_buffer = [float(i) for i in readBuffer] + # Retrieve voltage data + y_buffer = [] + data = "printbuffer(1,readingBuffer2.n,readingBuffer2)\n" + readBuffer = myMP5000.query(data) + readBuffer = readBuffer.strip().split(",") + y_buffer = [float(i) for i in readBuffer] + # Plot results + plot4 = fig4.add_subplot() + plot4.grid() + plot4.set_title("Sine Generation",fontsize=14) + plot4.plot(x_buffer,y_buffer,linewidth = 3, color='darkblue') + canvas4.draw() + + for i in range(len(x_buffer)): + print("{:.5e}".format(x_buffer[i]), "{:.5e}".format(y_buffer[i]), sep="\t") + myMP5000.close() + print("Sine test done") + print("############################\n") + + +###################################### Generate UI ###################################### +######################################################################################### + +# Create Window +window = tk.Tk() +window.config(bg='grey') +window.geometry("1260x730") +s = ttk.Style() +s.theme_use('clam') +s.configure('new.TFrame',background = 'grey') +s.configure("TButton", background = 'darkgrey') + +# Create figure locations +content = ttk.Frame(window,style = 'new.TFrame') +content.grid(column=0, row=0) +fig1 = Figure(figsize = (5, 3), dpi = 100,facecolor='lightgrey') +fig2 = Figure(figsize = (5, 3), dpi = 100,facecolor='lightgrey') +fig3 = Figure(figsize = (5, 3), dpi = 100,facecolor='lightgrey') +fig4 = Figure(figsize = (5, 3), dpi = 100,facecolor='lightgrey') + + +frame1 = ttk.Frame(content) +frame2 = ttk.Frame(content) +frame3 = ttk.Frame(content) +frame4 = ttk.Frame(content) + +# Check VISA string settings +check_button = ttk.Button(master = content,command = check_SMU,text = "Check Inst") +check_button.grid(column=0,row=0,ipadx=10,sticky = "w") +# VISA entry box +ip_entry = ttk.Entry(content,width=15,foreground='black') +ip_entry.insert(0,'TCPIP0::192.168.0.2::5025::SOCKET') +ip_entry.grid(column=1, row=0, columnspan=1,sticky="w") + +# Loadscript button +loadscript = ttk.Button(content, command = load_script, text="LoadScript") +loadscript.grid(column=0,row=1,ipadx=10,sticky = "w") +# VISA output string +string_idn = ttk.Label(content,background='grey') +string_idn.grid(column=0,row=2,columnspan =3,sticky="w") +# Title string +text=ttk.Label(content,text="6CH SMU Parallel Test in Synchronization",foreground="white",background="grey",font=('Aerial bold',20)) +text.grid(column=4, row=0,rowspan = 2, columnspan = 15) + +# Run task one button +run_button_1= ttk.Button(master = content,command = run_test_drain_family,text = "Run Task1") +run_button_1.grid(column=0, row=3,ipadx=10, rowspan=1,sticky = "w") +# Task 1 configuration +task1_name = ttk.Label(content,text="Drain Family Curve",background='grey') +task1_name.grid(column=1, row=3,sticky="w") +task1_assign= ttk.Label(content,text="Assign {slot_ga,ch_ga,slot_dr,ch_dr}",background='grey') +task1_assign.grid(column=0, row=4,columnspan=2,sticky="w") +task1_assign_input= ttk.Entry(content,width=30) +task1_assign_input.insert(0,"{1,1,1,2}") +task1_assign_input.grid(column=0, row=5,columnspan=2,sticky="w") +task1_drain= ttk.Label(content,text="Drain {StartV,StopV,NoPts,Rng_Id,LimitId}",background='grey') +task1_drain.grid(column=0, row=6,columnspan=2,sticky="w") +task1_drain_input= ttk.Entry(content,width=30) +task1_drain_input.insert(0,"{0,2,41,100e-3,100e-3}") +task1_drain_input.grid(column=0, row=7,columnspan=2,sticky="w") +task1_gate= ttk.Label(content,text="Gate {StartV,StopV,NoPts,LimitIg}",background='grey') +task1_gate.grid(column=0, row=8,columnspan=2,sticky="w") +task1_gate_input= ttk.Entry(content,width=30) +task1_gate_input.insert(0,"{1.4,1.8,3,0.1}") +task1_gate_input.grid(column=0, row=9,columnspan=2,sticky="w") + +string_empty2 = ttk.Label(content,background='grey') +string_empty2.grid(column=0,row=10,sticky="w") + +# Run task two button +run_button_2= ttk.Button(master = content,command = liv_test,text = "Run Task2") +run_button_2.grid(column=0, row=11,ipadx=10, rowspan=1,sticky = "w") +# Task two configuration +task2_name = ttk.Label(content,text="LIV Test",background='grey') +task2_name.grid(column=1, row=11,sticky="w") +task2_assign= ttk.Label(content,text="Assign {slot_ld,ch_ld,slot_pd,ch_pd}",background='grey') +task2_assign.grid(column=0, row=12,columnspan=2,sticky="w") +task2_assign_input= ttk.Entry(content,width=30) +task2_assign_input.insert(0,"{2,1,2,2}") +task2_assign_input.grid(column=0, row=13,columnspan=2,sticky="w") +task2_ld= ttk.Label(content,text="LD {StartI,StopI,NoPts,RngI,limV,MeaRngV}",background='grey') +task2_ld.grid(column=0, row=14,columnspan=2,sticky="w") +task2_ld_input= ttk.Entry(content,width=30) +task2_ld_input.insert(0,"{0,100e-3,21,1,6,6}") +task2_ld_input.grid(column=0, row=15,columnspan=2,sticky="w") +task2_pd= ttk.Label(content,text="PD {BiasV,SrcRngV,MeaRngI}",background='grey') +task2_pd.grid(column=0, row=16,columnspan=2,sticky="w") +task2_pd_input= ttk.Entry(content,width=30) +task2_pd_input.insert(0,"{0,6,10e-3}") +task2_pd_input.grid(column=0, row=17,columnspan=2,sticky="w") + +string_empty3 = ttk.Label(content,background='grey') +string_empty3.grid(column=0,row=18,sticky="w") + +# Run task three button +run_button_3= ttk.Button(master = content,command = pulse_test,text = "Run Task3") +run_button_3.grid(column=0, row=19,ipadx=10, rowspan=1,sticky = "w") +# Task three configuration +task3_name = ttk.Label(content,text="Pulse Test",background='grey') +task3_name.grid(column=1, row=19,sticky="w") +task3_assign= ttk.Label(content,text="Assign {slot_smu,ch_smu}",background='grey') +task3_assign.grid(column=0, row=20,columnspan=2,sticky="w") +task3_assign_input= ttk.Entry(content,width=30) +task3_assign_input.insert(0,"{3,1}") +task3_assign_input.grid(column=0, row=21,columnspan=2,sticky="w") +task3_src= ttk.Label(content,text="Src Set {StartV,StopV,NoPts,LimI}",background='grey') +task3_src.grid(column=0, row=22,columnspan=2,sticky="w") +task3_src_input= ttk.Entry(content,width=30) +task3_src_input.insert(0,"{-5,5,11,0.1}") +task3_src_input.grid(column=0, row=23,columnspan=2,sticky="w") +task3_pulse= ttk.Label(content,text="Pulse {Period,Width,Delay,Intg}",background='grey') +task3_pulse.grid(column=0, row=24,columnspan=2,sticky="w") +task3_pulse_input= ttk.Entry(content,width=30) +task3_pulse_input.insert(0,"{5e-3,3e-3,1e-3,0.1e-3}") +task3_pulse_input.grid(column=0, row=25,columnspan=2,sticky="w") + +string_empty4 = ttk.Label(content,background='grey') +string_empty4.grid(column=0,row=26,sticky="w") + +# Run task four button +run_button_4= ttk.Button(master = content,command = sine_test,text = "Run Task4") +run_button_4.grid(column=0, row=27,ipadx=10, rowspan=1,sticky = "w") +# Task four configuration +task4_name = ttk.Label(content,text="Sine Test",background='grey') +task4_name.grid(column=1, row=27,sticky="w") +task4_src= ttk.Label(content,text="Input:slot,ch,Vrms,NoCyle,Freq,LimI",background='grey') +task4_src.grid(column=0, row=28,columnspan=2,sticky="w") +task4_src_input= ttk.Entry(content,width=30) +task4_src_input.insert(0,"{3, 2, 5, 3, 60, 0.1}") +task4_src_input.grid(column=0, row=29,columnspan=2,sticky="w") + +string_empty5 = ttk.Label(content,background='grey') +string_empty5.grid(column=0,row=30,sticky="w") +# Run all button +plot_button = ttk.Button(master = content,command = plot,text = "Run All") +plot_button.grid(column=0, row=31,ipadx=10, ipady=5, rowspan=2,sticky = "w") + +# Place frames +frame1.grid(column=4, row=3, columnspan=5, rowspan=15,sticky='nw') +frame2.grid(column=10, row=3, columnspan=5, rowspan=15,sticky='ne') +frame3.grid(column=4, row=16, columnspan=5, rowspan=15) +frame4.grid(column=10, row=16, columnspan=5, rowspan=15) + +# Test title +window.title('Parallel Test in Synchronization') + +canvas1 = FigureCanvasTkAgg(fig1, master = frame1) +canvas2 = FigureCanvasTkAgg(fig2, master = frame2) +canvas3 = FigureCanvasTkAgg(fig3, master = frame3) +canvas4 = FigureCanvasTkAgg(fig4, master = frame4) + +canvas1_widget = canvas1.get_tk_widget() +canvas2_widget = canvas2.get_tk_widget() +canvas3_widget = canvas3.get_tk_widget() +canvas4_widget = canvas4.get_tk_widget() +canvas1_widget.pack() +canvas2_widget.pack() +canvas3_widget.pack() +canvas4_widget.pack() + +# Start window +window.mainloop() diff --git a/Examples/Modular_Precision_Test_System/Application_Examples/6_Ch_Parallel_Measurements/README.md b/Examples/Modular_Precision_Test_System/Application_Examples/6_Ch_Parallel_Measurements/README.md new file mode 100644 index 0000000..d187d69 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/Application_Examples/6_Ch_Parallel_Measurements/README.md @@ -0,0 +1,10 @@ +# 6 Channel Parallel Measurements + +This example follows the Synchronizing Parallel Testing with the MP5000 Series application note. Creates a dashboard in Python to display measured data. + +## Required Modules +3 x MSMU60-2 + +## Available Languages +* Python + diff --git a/Examples/Modular_Precision_Test_System/Application_Examples/Exporting_Parallel_Data_To_USB/Parallel_Measure_With_USB_Export.tsp b/Examples/Modular_Precision_Test_System/Application_Examples/Exporting_Parallel_Data_To_USB/Parallel_Measure_With_USB_Export.tsp new file mode 100644 index 0000000..bb9d368 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/Application_Examples/Exporting_Parallel_Data_To_USB/Parallel_Measure_With_USB_Export.tsp @@ -0,0 +1,237 @@ +------------------------------------------------------------------------------------ +--- Function: setup_smu() +--- Description: This function initializes each SMU with the settings appropriate +--- to complete this test. +------------------------------------------------------------------------------------ +function setup_smus() + for slot_no=1, slot_count do + for chan_no=1, 2 do + local smu = slot[slot_no].smu[chan_no] + -- Reset SMU + smu.reset() + smu.defbuffer1.clear() + smu.defbuffer2.clear() + -- Source settings + smu.source.func = smu.FUNC_DC_VOLTAGE + smu.source.limiti = ilimit + smu.source.levelv = vlevel + smu.source.autorangev = smu.OFF + smu.source.rangev = vlevel + --Measure settings + smu.measure.autorangei = smu.OFF + smu.measure.rangei = ilimit + smu.measure.aperture = aperture + smu.trigger.measure.i(smu.defbuffer1) + end + end +end + +------------------------------------------------------------------------------------ +--- Function: sync_measurements_trigger_timer() +--- Description: This function performs measurements across multiple SMU channels, +--- up to six, in parallel. This is done with the use of a trigger timer which +--- allows for minimal drift in measurement timing. +------------------------------------------------------------------------------------ +function sync_measurements_trigger_timer() + + --local loopCt = 100 -- Used for fixed count scan + -- Trigger timer allows easy syncronization + local triggerTimer = trigger.timer[1] + --triggerTimer.count = loopCt -- Used for fixed count scan + triggerTimer.count = 0 -- Infinite timer events + triggerTimer.delay = measure_interval + triggerTimer.passthrough = false + triggerTimer.stimulus = trigger.generator[1].EVENT_ID + + -- Create identical trigger models for all channels + for slot_no=1, slot_count do + local triggerModel = slot[slot_no].trigger.model + for chan_no=1, 2 do + local tm_name = string.format("trigModel%d", chan_no) + -- Create trigger model + triggerModel.create(tm_name) + triggerModel.addblock.source.output(tm_name, "output-on", chan_no, 1) + triggerModel.addblock.wait(tm_name, "wait", trigger.timer[1].EVENT_ID) -- Wait for event from trigger timer + triggerModel.addblock.measure(tm_name, "measure", chan_no, 1) -- Take a single measurement (not ideal for fast measurements) + triggerModel.addblock.branch.event(tm_name, "abort", "output-off", trigger.generator[2].EVENT_ID) -- Check for abort event + --triggerModel.addblock.branch.counter(tm_name, "branch", "wait", loopCt) -- Used for fixed count scan + triggerModel.addblock.branch.always(tm_name, "branch", "wait") -- Loop back to wait + triggerModel.addblock.source.output(tm_name, "output-off", chan_no, 0) -- Output off and end + triggerModel.initiate(tm_name) + end + end + delay(100e-3) -- Allow time for trigger models to build + trigger.generator[1].assert() -- Begin the trigger timer +end +------------------------------------------------------------------------------------ +--- Function: sync_measurements_delay_constant() +--- Description: This function performs measurements across multiple SMU channels, +--- up to six, in parallel. Timing is achieved by making the first channel a master +--- which uses a delay constant and notifys the other channels when to measure. +--- This is less accurate than using a trigger timer and has a drift of roughly .5 +--- to .33 us per iteration. +------------------------------------------------------------------------------------ +function sync_measurements_delay_constant() + --local loopCt = 2000 -- Used for fixed count scan + -- Create trigger models for every channel + for slot_no=slot_count, 1, -1 do + local triggerModel = slot[slot_no].trigger.model + for chan_no=2, 1, -1 do + local tm_name = string.format("trigModel%d", chan_no) + -- Channel 1 of Slot 1 serves as master, notifying others when to take measurements + if slot_no == 1 and chan_no == 1 then + triggerModel.create(tm_name) + triggerModel.addblock.source.output(tm_name, "output-on", chan_no, 1) + triggerModel.addblock.notify(tm_name, "notify-slots", triggerModel.EVENT_NOTIFY1) -- Notify others to take a measurements + triggerModel.addblock.measure(tm_name, "measure", chan_no, 1) -- Take single measurement (not ideal for fast measurements) + triggerModel.addblock.branch.event(tm_name, "abort", "output-off", trigger.generator[2].EVENT_ID) -- Check for abort event + triggerModel.addblock.delay.constant(tm_name, "delay-constant", measure_interval, "notify-slots") -- Delay for constant time (introduces error, not ideal) + --triggerModel.addblock.branch.counter(tm_name, "branch", "notify-slots", loopCt) -- Used for fixed count scan + triggerModel.addblock.branch.always(tm_name, "branch", "notify-slots") -- Loop to notificataion + triggerModel.addblock.source.output(tm_name, "output-off", chan_no, 0) -- Turn output off and end test + delay(100e-3) + triggerModel.initiate(tm_name) + -- All other channels wait for notification from master to measure + else + triggerModel.create(tm_name) + triggerModel.addblock.source.output(tm_name, "output-on", chan_no, 1) + triggerModel.addblock.wait(tm_name, "wait-main", slot[1].trigger.model.EVENT_NOTIFY1) -- Wait for notificaiton from master + triggerModel.addblock.measure(tm_name, "measure", chan_no, 1) -- Take single measurement (not ideal for fast measurements) + triggerModel.addblock.branch.event(tm_name, "abort", "output-off", trigger.generator[2].EVENT_ID) -- Check for abort event + --triggerModel.addblock.branch.counter(tm_name, "branch", "wait-main", loopCt) -- Used for fixed count scan + triggerModel.addblock.branch.always(tm_name, "branch", "wait-main") -- Loop to wait + triggerModel.addblock.source.output(tm_name, "output-off", chan_no, 0) -- Turn output off and end test + triggerModel.initiate(tm_name) + end + end + end +end + +-- This function deletes the trigger models off each module +function delete_trigger_models() + for i=1, slot_count do + slot[i].trigger.model.delete("trigModel1") + slot[i].trigger.model.delete("trigModel2") + end +end +-- This function aborts all trigger models +function abort_trigger_models() + trigger.generator[2].assert() -- Send event to abort trigger models + delay(500e-3) -- Wait for all models to abort + for i=1, slot_count do + -- Turn outputs off + slot[i].smu[1].source.output = 0 + slot[i].smu[2].source.output = 0 + end +end + +-- This function prints contents of reading buffers in a table format +function print_reading_buffers() + if slot_count == 1 then + print("Time\t\t Ch1\t\t Ch2") + for i=1, slot[1].smu[1].defbuffer1.n do + print(slot[1].smu[1].defbuffer1.timestamps[i], + vlevel/slot[1].smu[1].defbuffer1[i], vlevel/slot[1].smu[2].defbuffer1[i] + ) + end + elseif slot_count == 2 then + print("Time\t\t Ch1\t\t Ch2\t\t Ch3\t\t Ch4") + for i=1, slot[1].smu[1].defbuffer1.n do + print(slot[1].smu[1].defbuffer1.timestamps[i], + vlevel/slot[1].smu[1].defbuffer1[i], vlevel/slot[1].smu[2].defbuffer1[i], + vlevel/slot[2].smu[1].defbuffer1[i], vlevel/slot[2].smu[2].defbuffer1[i] + ) + end + else + print("Time\t\t Ch1\t\t Ch2\t\t Ch3\t\t Ch4\t\t Ch5\t\t Ch6") + for i=1, slot[1].smu[1].defbuffer1.n do + print(slot[1].smu[1].defbuffer1.timestamps[i], + vlevel/slot[1].smu[1].defbuffer1[i], vlevel/slot[1].smu[2].defbuffer1[i], + vlevel/slot[2].smu[1].defbuffer1[i], vlevel/slot[2].smu[2].defbuffer1[i], + vlevel/slot[3].smu[1].defbuffer1[i], vlevel/slot[3].smu[2].defbuffer1[i] + ) + end + end +end + +-- This function exports all buffer data to a UBS drive +function export_to_usb() + local filename = "/usb1/exported_data.csv" -- Set file to be created, will overwrite if file exists + local fptr, err = io.open(filename, "w") -- Define a file pointer + if err == nil then -- Check for USB error + -- Write buffer data to USB in csv for easy analysis + if slot_count == 1 then + fptr:write("Time, Ch1, Ch2\n") + for i=1, slot[1].smu[1].defbuffer1.n do + local writeString = string.format("%f, %f, %f", + slot[1].smu[1].defbuffer1.timestamps[i], + vlevel/slot[1].smu[1].defbuffer1[i], vlevel/slot[1].smu[2].defbuffer1[i] + ) + fptr:write(writeString) + end + elseif slot_count == 2 then + fptr:write("Time, Ch1, Ch2, Ch3, Ch4\n") + for i=1, slot[1].smu[1].defbuffer1.n do + local writeString = string.format("%f, %f, %f, %f, %f\n", + slot[1].smu[1].defbuffer1.timestamps[i], + vlevel/slot[1].smu[1].defbuffer1[i], vlevel/slot[1].smu[2].defbuffer1[i], + vlevel/slot[2].smu[1].defbuffer1[i], vlevel/slot[2].smu[2].defbuffer1[i] + ) + fptr:write(writeString) + end + else + fptr:write("Time, Ch1, Ch2, Ch3, Ch4, Ch5, Ch6\n") + for i=1, slot[1].smu[1].defbuffer1.n do + local writeString = string.format("%f, %f, %f, %f, %f, %f, %f\n", + slot[1].smu[1].defbuffer1.timestamps[i], + vlevel/slot[1].smu[1].defbuffer1[i], vlevel/slot[1].smu[2].defbuffer1[i], + vlevel/slot[2].smu[1].defbuffer1[i], vlevel/slot[2].smu[2].defbuffer1[i], + vlevel/slot[3].smu[1].defbuffer1[i], vlevel/slot[3].smu[2].defbuffer1[i] + ) + fptr:write(writeString) + end + end + fptr:flush() + fptr:close() + else + print("Error in Opening USB File") + end +end + +------------------------------------------------------------------------------------ +--- THESE FUNCTIONS ARE INTENDED TO BE UTILIZED BY THE USER +------------------------------------------------------------------------------------ +-- Start the test +function start_scan() + delete_trigger_models() + setup_smus() + sync_measurements_trigger_timer() + --sync_measurements_delay_constant() +end +-- Pause to export data to USB +function pause_export_resume() + abort_trigger_models() + delete_trigger_models() + export_to_usb() + setup_smus() + sync_measurements_trigger_timer() + --sync_measurements_delay_constant() +end +-- End the test +function end_scan() + abort_trigger_models() +end + +------------------------------------------------------------------------------------ +--- CHANGE SCAN PARAMETERS +------------------------------------------------------------------------------------ + +slot_count = 1 +vlevel = 5 +ilimit = 100e-3 +measure_interval = 20e-3 +aperture = 10e-3 + +start_scan() +--pause_export_resume() +--end_scan() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/Application_Examples/Exporting_Parallel_Data_To_USB/README.md b/Examples/Modular_Precision_Test_System/Application_Examples/Exporting_Parallel_Data_To_USB/README.md new file mode 100644 index 0000000..deacb90 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/Application_Examples/Exporting_Parallel_Data_To_USB/README.md @@ -0,0 +1,21 @@ +# Exporting Parallel Measurements to USB Drive + +This script performs long term datalogging for parallel resistance measurements. The test can be stopped by the user occasionally to export measurements to a USB drive. + +There are two primary methods for parallel measurement provided in this script. The simpler and more accurate method utilizes a trigger timer. This sends an event to all channels notifying them to take a measurement. The second method uses delay constants. This is more complex and less accurate (.33us drift every iteration), however, it provides a look into how multiple trigger models can communicate and interact. This script is not designed for very fast measurements (<1ms), there are other methods that can be used to achieve such timing. + +## Required Modules +2 or 3 x MSMU60-2 + +## Available Languages +* TSP + +## Instructions +1. Begin by connecting to the mainframe using the instrument panel found on the left side of the visual studio code interface or running the TSP: Connect function and entering the IP address or VISA resource screen. +2. Update the scan parameters according to your device’s specifications. +3. There are three functions designed for user use: “start_scan()”, “pause_export_resume()”, and “end_scan()”. +* “start_scan()” will begin measuring across all channels according to your parameters. +* “pause_export_resume()” will abort all measuring, export resistance data from buffers to a USB drive connected to the front panel, then continue measuring. +* “end_scan()” will end the measurements without exporting to USB. +4. Connect sensors/resistive loads to all channels. The script is designed to work with an even number of channels. If using an odd number of channels simply do not connect the final channel. +5. When the script is run, it will begin measuring across all channels. Intermittently the user may want to export previous data to a USB. This can be done by sending “pause_export_resume()” to the mainframe through the TSP terminal. diff --git a/Examples/Modular_Precision_Test_System/Application_Examples/README.md b/Examples/Modular_Precision_Test_System/Application_Examples/README.md new file mode 100644 index 0000000..652e08a --- /dev/null +++ b/Examples/Modular_Precision_Test_System/Application_Examples/README.md @@ -0,0 +1,15 @@ +# Application Examples + +The examples in this directory work with the [MP5103 Mainframe and supported modules](https://www.tek.com/en/products/mp5000-series-modular-precision-test-system). These examples are application focused and may involve more than one module type or be convertible to any module type. + +*Note: before running any example - verify slot and channel used.* + +## Directory + +### **[6 Channel Parallel Measurements](./6_Ch_Parallel_Measurements/)** +This example follows the Synchronizing Parallel Testing with the MP5000 Series application note. Creates a dashboard in Python to display measured data. + +### **[Exporting Parallel Measurements to USB](./Exporting_Parallel_Data_To_USB/)** +This script performs long term datalogging for parallel resistance measurements. The test can be stopped by the user occaisionally to export measurements to a USB drive. . + + diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Parallel/Parallel_Combo.tsp b/Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Parallel/Parallel_Combo.tsp new file mode 100644 index 0000000..09462f3 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Parallel/Parallel_Combo.tsp @@ -0,0 +1,48 @@ +-- Alais SMU +local slot_no = 2 +local psu1 = slot[slot_no].psu[1] +local psu2 = slot[slot_no].psu[2] +-- Source settings +local v_level = 20 +local i_level = 300e-3 +local i_limit = 350e-3 +-- Reset psu's +psu1.reset() +psu2.reset() +-- Clear all buffers +psu1.defbuffer1.clear() +psu1.defbuffer2.clear() +psu2.defbuffer1.clear() +psu2.defbuffer2.clear() +-- Setup buffers for trigger model measurements +psu1.trigger.measure.iv(psu1.defbuffer1, psu1.defbuffer2) +psu2.trigger.measure.iv(psu2.defbuffer1, psu2.defbuffer2) + +-- Configure psu source settings +-- To avoid channels sinking eachother one channel +-- has a higher levelv and the other has a higher current limit +psu1.source.levelv = v_level+100e-3 +psu2.source.levelv = v_level +psu1.source.limiti = i_level/2 +psu2.source.limiti = i_level/2 + (i_limit-i_level) + +-- Create a trigger model to turn channels on/off at same time +local tm_name = "tm" +local triggerModel = slot[slot_no].trigger.model +triggerModel.create(tm_name) +triggerModel.addblock.source.output(tm_name, "outputs-on", {1,2}, 1) +triggerModel.addblock.delay.constant(tm_name, "delay1", 10e-3) +triggerModel.addblock.measure(tm_name, "measure", {1,2}, 20) +triggerModel.addblock.delay.constant(tm_name, "delay2", 10e-3) +triggerModel.addblock.source.output(tm_name, "outputs-off", {1,2}, 0) + +-- Initiate trigger model and delete +triggerModel.initiate(tm_name) +waitcomplete() +triggerModel.delete(tm_name) + +-- Display measurements to terminal +print("V1\t\t I1\t\t V2\t\t I2") +for i=1, psu1.defbuffer1.n do + print(psu1.defbuffer2[i], psu1.defbuffer1[i], psu2.defbuffer2[i], psu2.defbuffer1[i]) +end \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Parallel/README.md b/Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Parallel/README.md new file mode 100644 index 0000000..bd2f606 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Parallel/README.md @@ -0,0 +1,16 @@ +# Combining 2 PSU Channels in Parallel + +Combines channel sourcing in parallel to increase current capabilities. + +## Required Modules +1 x MPSU50-2ST + +## Available Languages +* TSP + +## Instructions +1. Begin by connecting to the mainframe using the instrument panel found on the left side of the visual studio code interface or running the TSP: Connect function and entering the IP address. +2. Connect CH1-LO to CH2-LO and CH1-HI to CH2-HI. Then connect the load to CH1. +3. Configure the slot number, voltage source level, current level, and current limit as desired, located at the top of the script. +* NOTE: To avoid one of the PSU’s sinking the other the source settings must be set carefully. One channel has a voltage level that is slightly higher than the target voltage and the other has a current slightly higher than target current. The source levels for both channels must fall under the power envelope for the module. +4. Run Parallel _Combo.tsp and view the current/voltage readings from both channels. diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Series/README.md b/Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Series/README.md new file mode 100644 index 0000000..134db90 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Series/README.md @@ -0,0 +1,16 @@ +# Combining 2 PSU Channels in Series + +Combines channel sourcing in series to increase voltage capabilities. + +## Required Modules +1 x MPSU50-2ST + +## Available Languages +* TSP + +## Instructions +1. Begin by connecting to the mainframe using the instrument panel found on the left side of the visual studio code interface or running the TSP: Connect function and entering the IP address. +2. Connect CH1-LO to CH2-HI. Then connect the load to CH1-HI and CH2-LO +3. Configure the slot number, voltage source level, and current limit as desired, located at the top of the script. +* NOTE: Each channel is set with a voltage level that is half the input voltage level and both channels are set with the current limit specified. These parameters must fall in the power envelope per channel of the module. +4. Run Series_Combo.tsp and view the current/voltage readings from both channels. diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Series/Series_Combo.tsp b/Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Series/Series_Combo.tsp new file mode 100644 index 0000000..83ba8fc --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/2_Channel_Series/Series_Combo.tsp @@ -0,0 +1,45 @@ +-- Alais SMU +local slot_no = 2 +local psu1 = slot[slot_no].psu[1] +local psu2 = slot[slot_no].psu[2] +-- Source settings +local v_level = 10 +local i_limit = 300e-3 +-- Reset psu's +psu1.reset() +psu2.reset() +-- Clear all buffers +psu1.defbuffer1.clear() +psu1.defbuffer2.clear() +psu2.defbuffer1.clear() +psu2.defbuffer2.clear() +-- Setup buffers for trigger model measurements +psu1.trigger.measure.iv(psu1.defbuffer1, psu1.defbuffer2) +psu2.trigger.measure.iv(psu2.defbuffer1, psu2.defbuffer2) + +-- Configure psu source settings to achieve a higher voltage level +psu1.source.levelv = v_level/2 +psu2.source.levelv = v_level/2 +psu1.source.limiti = i_limit +psu2.source.limiti = i_limit + +-- Create a trigger model to turn channels on/off at same time +local tm_name = "tm" +local triggerModel = slot[slot_no].trigger.model +triggerModel.create(tm_name) +triggerModel.addblock.source.output(tm_name, "outputs-on", {1,2}, 1) +triggerModel.addblock.delay.constant(tm_name, "delay1", 10e-3) +triggerModel.addblock.measure(tm_name, "measure", {1,2}, 10) +triggerModel.addblock.delay.constant(tm_name, "delay2", 10e-3) +triggerModel.addblock.source.output(tm_name, "outputs-off", {1,2}, 0) + +-- Initiate trigger model and delete +triggerModel.initiate(tm_name) +waitcomplete() +triggerModel.delete(tm_name) + +-- Display measurements to terminal +print("V1\t\t I1\t\t V2\t\t I2") +for i=1, psu1.defbuffer1.n do + print(psu1.defbuffer2[i], psu1.defbuffer1[i], psu2.defbuffer2[i], psu2.defbuffer1[i]) +end \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Bipolar_Output/Bipolar_Demo.py b/Examples/Modular_Precision_Test_System/PSU_Examples/Bipolar_Output/Bipolar_Demo.py new file mode 100644 index 0000000..649bcf4 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Bipolar_Output/Bipolar_Demo.py @@ -0,0 +1,40 @@ +import pyvisa +import time + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET')#TCPIP0::134.63.75.230::1394::SOCKET +inst.write_termination = "\n" # for using the sockets based implementation +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +print(inst.query("*IDN?")) +inst.write("reset()") # reset instrument + +#UPDATE THESE FOR DIFFERENT SLOT/CHANNEL +slot = "slot[2]" +channel = "psu[2]" + +# Configure Source limit and source level +inst.write(f"{slot}.{channel}.source.limiti = 1") +inst.write(f"{slot}.{channel}.source.levelv = 1.5") + +inst.write(f"{slot}.{channel}.source.output = 1") # Output on + +print(f"\nForward Bias Voltage at {float(inst.query(f"print({slot}.{channel}.measure.i())"))} A: {float(inst.query(f"print({slot}.{channel}.measure.v())"))} V") # Measure voltage output + +time.sleep(2) + +#UPDATE IF USING DIFFERENT DIODE +inst.write(f"{slot}.{channel}.source.limiti = 0.065") +inst.write(f"{slot}.{channel}.source.levelv = -25") # Change source voltage level + +print(f"Reverse Bias Voltage at {float(inst.query(f"print({slot}.{channel}.measure.i())"))} A: {float(inst.query(f"print({slot}.{channel}.measure.v())"))} V") # Measure voltage output + +time.sleep(2) + +inst.write(f"{slot}.{channel}.source.output = 0") # Turn off output + +inst.clear() +inst.close() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Bipolar_Output/Bipolar_Demo.tsp b/Examples/Modular_Precision_Test_System/PSU_Examples/Bipolar_Output/Bipolar_Demo.tsp new file mode 100644 index 0000000..46a6233 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Bipolar_Output/Bipolar_Demo.tsp @@ -0,0 +1,45 @@ +--[[ +Purpose: + +This demo showcases the bipolar capabilities of the MP5000 power supply unit (PSU). +Unlike many power supplies that require recabling (switching HI and LO) to transition +from sourcing positive to negative voltage, the MP5000 can achieve this seamlessly +due to its bipolar functionality. This demo illustrates that with its bipolar capabilities, +both the Forward Voltage and Reverse Zener Voltage can be tested without the need for recabling. +]] + +--//////////////////////////////////////////////////////////////////// + +-- UPDATE THE FOLLOWING +slotNumber = 2 -- The slot number where your PSU is located +slotChannel = 2 -- The channel you are using on your PSU + +--/////////////////////////////////////////////////////////////////// + +-- Begin by configuring the PSU for Forward Bias: +-- Set Voltage Source Level = 1.5 V +-- Set Current Limit = 1 A + +local psu = slot[slotNumber].psu[slotChannel] +psu.source.limiti = 1 +psu.source.levelv = 1.5 + +-- Turn output on +psu.source.output = 1 + +-- Store the voltage and then print out the voltage reading for 1.5 volts set as the Voltage Source Level +local voltageReading = psu.measure.v() +print("Set 1.5V Measured: " .. voltageReading) + + +--UPDATE IF USING DIFFERENT DIODE +psu.source.limiti = 0.065 -- Change current limit +psu.source.levelv = -25 -- Change Source Voltage Level + + +-- Store the voltage and then print out the voltage reading for -25 volts set as the Voltage Source Level +local voltageReading2 = psu.measure.v() +print("Set ".. psu.source.levelv .."V Measured: " .. voltageReading2) + +-- Turn off output +psu.source.output = 0 diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Bipolar_Output/README.md b/Examples/Modular_Precision_Test_System/PSU_Examples/Bipolar_Output/README.md new file mode 100644 index 0000000..0a1d147 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Bipolar_Output/README.md @@ -0,0 +1,15 @@ +# Using Bipolar Output Capabilities + +Demonstrates bipolar capabilities of the power supply module. + +Outputs a positive voltage and then a negative voltage with measurements. Settings are configured to characterize a 1N5357B Zener Diode. + +## Required Modules +1 x MPSU50-2ST + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust settings as needed and run code. diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Linear_Sweep/Linear_Sweep.py b/Examples/Modular_Precision_Test_System/PSU_Examples/Linear_Sweep/Linear_Sweep.py new file mode 100644 index 0000000..a46e0a4 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Linear_Sweep/Linear_Sweep.py @@ -0,0 +1,100 @@ +import pyvisa as visa +import matplotlib.pyplot as plt + +rm = visa.ResourceManager() # for purely pyvisa implementation pass '@py' +print(rm.list_resources()) +mypsu = rm.open_resource("TCPIP0::192.168.0.2::5025::SOCKET") + +mypsu.write_termination = "\n" # for using the sockets based implementation +mypsu.read_termination = "\n" +mypsu.send_end = True +mypsu.timeout = 10000 + +############################################################################################### +# Example code below is intended to demonstrate how to take voltage and current readings +# and increase voltage through trials + +# *** slot[modSlot].psu[modChan] defines the module "modSlot" and channel "modChan" for +# which the function is being called *** +############################################################################################### + +def set_channel(modSlot, modChan, volt, curr): + chan = f"slot[{modSlot}].psu[{modChan}]" + + mypsu.write(f"{chan}.source.levelv = {volt}") + mypsu.write(f"{chan}.source.limiti = {curr}") + +# mypsu.write(f"{chan}.source.output = slot[{modSlot}].psu.ON") ---Use when standalone function not nested in Sweep() + + voltVal = mypsu.query(f"print({chan}.measure.v())") # Aquire voltage/current measurements + currVal = mypsu.query(f"print({chan}.measure.i())") + +# mypsu.write(f"{chan}.source.output = slot[{modSlot}].psu.OFF") ---Use when standalone function not nested in Sweep() + return([voltVal, currVal]) + +############################################################################################### +# This function plots the data from the sweep + +# PARAMETERS - +# voltageReadings : list of voltage readings from sweep +# currentReadings : list of current readings from sweep +############################################################################################### + +def plot_results(voltageReadings, currentReadings): + + plt.scatter(voltageReadings, currentReadings) + + plt.xlabel("Voltage (V)") + plt.ylabel("Current (A)") + + plt.show() + +############################################################################################### +# The example below will perform a linear voltage sweep + +# PARAMETERS - +# modSlot : Module for given DUT +# modChan : Channel for given DUT +# start : starting value of voltage sweep +# stop : stoping value of voltage sweep +# step : step increment size +# setCurr : Programmed current limit +############################################################################################### + +def Sweep(modSlot, modChan, start, stop, step, setCurr): + # Alais psu + chan = f"slot[{modSlot}].psu[{modChan}]" + # Configure channel for test + vals = set_channel(modSlot, modChan, 0, setCurr) + mypsu.write(f"{chan}.source.output = slot[{modSlot}].psu.ON") + + # Create lists to contain readings + voltageReadings = [0] * (int((stop - start) / step) + 1) + currentReadings = [0] * (int((stop - start) / step) + 1) + + # Iterate through voltages in sweep, aquiring measurements along the way + v = start + print(f"Module {modSlot}/Channel {modChan}:") + print(f" ") + while (v <= stop): + print("Set Voltage:", v, "Set Current:", setCurr) + vals = set_channel(modSlot, modChan, v, setCurr) + mypsu.write("delay(1)") # Constant delay of one second + + print(f"Voltage Reading: {float(vals[0]):.3f} Current Reading: {float(vals[1]):.3f}") + print(f" ") + + #Add readings to lists for plotting + voltageReadings[int((v - start) / step)] = float(vals[0]) + currentReadings[int((v - start) / step)] = float(vals[1]) + + v += step + + mypsu.write(f"{chan}.source.output = slot[{modSlot}].psu.OFF") + plot_results(voltageReadings, currentReadings) + print("Done") + +Sweep(2, 2, 0.5, 5, 0.5, 1) #UPDATE THIS LINE IF YOU WISH TO CHANGE THE SWEEP + +mypsu.close() +rm.close() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Linear_Sweep/Linear_Sweep.tsp b/Examples/Modular_Precision_Test_System/PSU_Examples/Linear_Sweep/Linear_Sweep.tsp new file mode 100644 index 0000000..2168638 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Linear_Sweep/Linear_Sweep.tsp @@ -0,0 +1,65 @@ +--[[ + Example code below is intended to demonstrate how to take voltage and current readings + and increase voltage through trials. set_channel configures the PSU for this sweep. + + *** slot[modSlot].psu[modChan] defines the module slot "modSlot" and channel "modChan" for which the function is being called *** +]] +function set_channel(modSlot, modChan, volt, curr) + local psu = slot[modSlot].psu[modChan] + psu.source.levelv = volt -- sets voltage level + psu.source.limiti = curr -- sets current limit + + psu.source.output = slot[modSlot].psu.ON -- turns Output + + voltageVal = psu.measure.v() -- measures the voltage level + currentVal = psu.measure.i() -- measures the current level + + print("Module 1/Channel 1:\nVoltage Reading:", voltageVal, "\nCurrent Reading:", currentVal) -- return readings + + psu.source.output = slot[modSlot].psu.OFF -- turn output OFF +end + + +--[[ + The example below will perform a linear voltage sweep + + PARAMETERS - + modSlot : Module for given DUT + modChan : Channel for given DUT + start : starting value of voltage sweep + stop : stoping value of voltage sweep + step : step increment size + setCurr : Programmed current limit + +]] + +function doSweep(modSlot, modChan, start, stop, step, setCurr) + -- Alais PSU + local psu = slot[modSlot].psu[modChan] + -- Reset PSU + slot[modSlot].psu[modChan].reset() + -- Configure source settings and turn output on + psu.source.levelv = 0 + psu.source.limiti = setCurr + psu.source.output = slot[modSlot].psu.ON + + local voltageVal + local currentVal + + -- Iterate through linear sweep voltage values + for v = start, stop, step do + psu.source.levelv = v + delay(1) -- Constant 1 second delay + print(string.format("\n\nSet Voltage: %.3f Set Current: %.3f", v, setCurr)) -- Output settings + -- Take measurements + voltageVal = slot[modSlot].psu[modChan].measure.v() + currentVal = slot[modSlot].psu[modChan].measure.i() + print(string.format("Module %d/Channel %d:\nVoltage Reading: %.3f \nCurrent Reading: %.3f", modSlot, modChan, voltageVal, currentVal)) -- Output readings + end + -- End test + psu.source.output = slot[modSlot].psu.OFF + print("\nDone") +end + + +doSweep(2, 2, 0.5, 5, 0.5, 1) -- UPDATE THIS LINE IF YOU WISH TO CHANGE THE SWEEP \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Linear_Sweep/README.md b/Examples/Modular_Precision_Test_System/PSU_Examples/Linear_Sweep/README.md new file mode 100644 index 0000000..f50dd48 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Linear_Sweep/README.md @@ -0,0 +1,15 @@ +# Using Bipolar Output Capabilities + +Generates a linear voltage sweep while taking measurements. + +The default configuration performs a voltage sweep on module 2, channel 2, starting at 0.5 V and stopping at 5 V, with increments of 0.5 V and a current limit set to 1 A. Could outputs sweep setting and measurement at every sweep point. + +## Required Modules +1 x MPSU50-2ST + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust settings as needed and run code. diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/OCP/OCP Demo.py b/Examples/Modular_Precision_Test_System/PSU_Examples/OCP/OCP Demo.py new file mode 100644 index 0000000..9905533 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/OCP/OCP Demo.py @@ -0,0 +1,34 @@ +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET')#TCPIP0::134.63.75.230::1394::SOCKET +inst.write_termination = "\n" # for using the sockets based implementation +inst.read_termination = "\n" +#inst.send_end = True +inst.timeout = 10000 + +print(inst.query("*IDN?")) +inst.write("reset()") # reset instrument + +#CHANGE THESE FOR NEW SLOT/CHANNEL +slot = 2 +channel = 2 + +# Configure source limit, level, OCP level, and OCP enable +inst.write(f"slot[{slot}].psu[{channel}].source.limiti = 350e-3") +inst.write(f"slot[{slot}].psu[{channel}].source.levelv = 15") +inst.write(f"slot[{slot}].psu[{channel}].source.protect.leveli = 500e-3") +inst.write(f"slot[{slot}].psu[{channel}].source.protect.enablei = slot[{slot}].psu[{channel}].ENABLE") + +inst.write(f"slot[{slot}].psu[{channel}].source.output = 1") # Output On + +# Wait while output is on +outputOn = inst.query(f"print(slot[{slot}].psu[{channel}].source.output)") +while(outputOn == 'ON'): + outputOn = inst.query(f"print(slot[{slot}].psu[{channel}].source.output)") + +print(f"\nOCP Tripped: {inst.query(f"print(slot[{slot}].psu[{channel}].source.protect.trippedi)")}") # OCP Tripped query + + +inst.close() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/OCP/OCP Demo.tsp b/Examples/Modular_Precision_Test_System/PSU_Examples/OCP/OCP Demo.tsp new file mode 100644 index 0000000..b1a1cc9 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/OCP/OCP Demo.tsp @@ -0,0 +1,18 @@ + +--OCP Demo Script + +--This script is designed for an OCP (Over Current Protection) demo. +--It sets the source voltage and current limit, configures the OCP and OVP +--(Over Voltage Protection) values, and then turns on the PSU (Power Supply Unit). + + +-- UPDATE THE FOLLOWING +slotNumber = 2 -- The slot number where your PSU is located +slotChannel = 2 -- The channel you are using on your PSU + +slot[slotNumber].psu[slotChannel].source.levelv = 15 +slot[slotNumber].psu[slotChannel].source.limiti = 0.350 +slot[slotNumber].psu[slotChannel].source.protect.leveli = 0.5 +slot[slotNumber].psu[slotChannel].source.protect.levelv = 55 + +slot[slotNumber].psu[slotChannel].source.output = slot[slotNumber].psu.ON \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/OCP/README.md b/Examples/Modular_Precision_Test_System/PSU_Examples/OCP/README.md new file mode 100644 index 0000000..b5eae8d --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/OCP/README.md @@ -0,0 +1,15 @@ +# Demonstrating Overcurrent Protection (OCP) + +Configures PSU to intentionally trigger and demonstrate OCP. + +Settings assume a 50 ohm load gets shorted during operation, triggering OCP to shut the power supply off. + +## Required Modules +1 x MPSU50-2ST + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust settings as needed and run code. diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/OVP/OVP_Demo.py b/Examples/Modular_Precision_Test_System/PSU_Examples/OVP/OVP_Demo.py new file mode 100644 index 0000000..f5a7645 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/OVP/OVP_Demo.py @@ -0,0 +1,47 @@ +""" Demo Code """ +import pyvisa as visa +import time + +rm = visa.ResourceManager() # for purely pyvisa implementation pass '@py' +print(rm.list_resources()) +mypsu = rm.open_resource("TCPIP0::192.168.0.2::5025::SOCKET") + +mypsu.write_termination = "\n" # for using the sockets based implementation +mypsu.read_termination = "\n" +mypsu.send_end = True +mypsu.timeout = 10000 + +#UPDATE THIS TO CHANGE SLOT/CHANNEL +chan = "slot[2].psu[1]" + +v_start = 5.0 +v_end = 20 +i_start = 1 +i_end = 2 +ovp_level = 12 +ocp_level = 5.5 + +mypsu.write("reset()") + +#Configure Channel" +mypsu.write(f"{chan}.source.levelv = {v_start}") +mypsu.write(f"{chan}.source.limiti = {i_start}") + +#Configure Protect +mypsu.write(f"{chan}.source.protect.levelv = {ovp_level}") +mypsu.write(f"{chan}.source.protect.leveli = {ocp_level}") + +#delay +time.sleep(2.5) + +#turn output on +mypsu.write(f"{chan}.source.output = 1") + +#Configure Channel to higher level" +mypsu.write(f"{chan}.source.levelv = {v_end}") +mypsu.write(f"{chan}.source.limiti = {i_end}") + +mypsu.close() +rm.close() + + diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/OVP/OVP_Demo.tsp b/Examples/Modular_Precision_Test_System/PSU_Examples/OVP/OVP_Demo.tsp new file mode 100644 index 0000000..e64e03f --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/OVP/OVP_Demo.tsp @@ -0,0 +1,28 @@ +local psu = slot[2].psu[1] + +local v_start = 5.0 +local v_end = 20 +local i_start = 1 +local i_end = 2 +local ovp_level = 12 +local ocp_level = 5.5 + +reset() + +-- Configure Channel +psu.source.levelv = v_start +psu.source.limiti = i_start + +-- Configure Protect +psu.source.protect.levelv = ovp_level +psu.source.protect.leveli = ocp_level + +-- delay +delay(2.5) + +-- Turn output on to trigger OVP +psu.source.output = 1 + +-- Configure Channel to higher level" +psu.source.levelv = v_end +psu.source.limiti = i_end \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/OVP/README.md b/Examples/Modular_Precision_Test_System/PSU_Examples/OVP/README.md new file mode 100644 index 0000000..9c822aa --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/OVP/README.md @@ -0,0 +1,15 @@ +# Demonstrating Overvoltage Protection (OVP) + +Configures PSU to intentionally trigger and demonstrate OVP. + +Code assumes a 50 ohm load. + +## Required Modules +1 x MPSU50-2ST + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust settings as needed and run code. diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/README.md b/Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/README.md new file mode 100644 index 0000000..8f18532 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/README.md @@ -0,0 +1,28 @@ +# Demonstrating Output Sequencing of Multiple Channels + +Times turning output on and off from multiple channels with accurate delays utilizing trigger models. + +The code is currently configured to work with two PSU modules and four channels. The current test setup includes the following delays: +On Delays +* `onDelay[0]= 0` +* `onDelay[1] = 0.005` +* `onDelay[2] = 0.010` +* `onDelay[3] = 0.015` + +Off Delays +* `offDelay[0] = 0.015` +* `offDelay[1] = 0.010` +* `offDelay[2] = 0.005` +* `offDelay[3] = 0.0000` +These values can be updated at the bottom of the TSP file. Additionally, if you decide to include additional modules and channels, make sure to update the enable channel section accordingly. The enables list sets all channels to enabled by default. To disable just set the value at the corresponding index to false. For example, to disable channel one of slot one: `enables[0] = False` + +## Required Modules +2 x MPSU50-2ST + +## Available Languages +* TSP +* Python +* Python calling loaded TSP Scripts + +## Instructions +1. Adjust settings as needed and run code. diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/Trigger Output Sequence Demo.py b/Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/Trigger Output Sequence Demo.py new file mode 100644 index 0000000..947b3ff --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/Trigger Output Sequence Demo.py @@ -0,0 +1,223 @@ +""" + + *********************************************************** + *** Copyright 2025 Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + *********************************************************** + + Output_Sequence_Demo.py + + This script does output on and off sequencing using trigger models and sending individual TSP commands for a singular fully loaded MP5103 mainframe. +""" + +import pyvisa +import time + +numberChannels = 6 # number of channels configured for output on/off sequence + +# power supply alias names +chs = [None] * numberChannels +slots = [None] * (round(numberChannels / 2)) +for i in range(numberChannels): # configures channels starting from slot 1 to 3 + chs[i] = f"slot[{int(i/2)+1}].psu[{(i%2) + 1}]" + slots[int(i/2)] = f"slot[{int(i/2) + 1}]" + + +# enable channels (True = enabled, False = disabled) +enables = [ + True, # Channe1 1 + True, # Channel 2 + True, # Channel 3 + True, # Channel 4 + True, # Channel 5 + True # Channel 6 + ] + +def configureOutputOnSequence(instrument, delays, vLevel): + """ + configureOutputOnSequence configures a output on sequence for the enabled channels using a trigger model for each channel. + + :param instrument: PyVISA resource object + :param delayChannels: List of delays before each channel turns on. Min: 0s, Max: 2^34s + + :return: None + """ + + for i in range(numberChannels): + if (enables[i]): + instrument.write(f"{chs[i]}.trigger.source.listv({{{vLevel[i]}}})") # set turn on voltage level + instrument.write(f"{slots[int(i/2)]}.trigger.model.create(\"onCh{i+1}\")") # create trigger model + instrument.write(f"{slots[int(i/2)]}.trigger.model.addblock.wait(\"onCh{i+1}\",\"Wait\", trigger.generator[1].EVENT_ID)") # wait until trigger signal received + instrument.write(f"{slots[int(i/2)]}.trigger.model.addblock.delay.constant(\"onCh{i+1}\", \"delayTurnOn\", {delays[i]})") # delay before channel turn on + instrument.write(f"{slots[int(i/2)]}.trigger.model.addblock.source.action.step(\"onCh{i+1}\", \"chOn\", {(i%2)+1})") # go to turn on voltage level + +def outputsOn(instrument): + """ + outputsOn turns on the output for on all the enabled channels using the configured output on sequence. + + :param instrument: PyVISA resource object + :return: None + """ + + # turn on outputs to 0V then initiate trigger models + for i in range(numberChannels): + if(enables[i]): + instrument.write(f"{chs[i]}.source.levelv = 0") + instrument.write(f"{chs[i]}.source.output = 1") + instrument.write(f"{slots[int(i/2)]}.trigger.model.initiate(\"onCh{i+1}\")") + + instrument.write("trigger.generator[1].assert()") # trigger all the trigger models simulatenously + + instrument.query("*OPC?") # wait until all outputs are on + +def configureOutputOffSequence(instrument, delays): + """ + configureOutputOffSequence configures a output off sequence for the enabled channels using a trigger model for each channel. + + :param instrument (PyVISA): PyVISA resource object + :param delayChannels: List of delays before each channel turns off. Min: 0s, Max: 2^34s + + :return: None + """ + for i in range(numberChannels): + if (enables[i]): + instrument.write(f"{slots[int(i/2)]}.trigger.model.create(\"offCh{i+1}\")") # create trigger model + instrument.write(f"{slots[int(i/2)]}.trigger.model.addblock.wait(\"offCh{i+1}\",\"Wait\", trigger.generator[1].EVENT_ID)") # wait until trigger signal received + instrument.write(f"{slots[int(i/2)]}.trigger.model.addblock.delay.constant(\"offCh{i+1}\", \"delayTurnOn\", {delays[i]})") # delay before channel turn on + instrument.write(f"{slots[int(i/2)]}.trigger.model.addblock.source.output(\"offCh{i+1}\", \"chOff\", {(i%2)+1}, 0)") # turn off channel + +def outputsOff(instrument): + """ + outputsOn turns on the output for on all the enabled channels using the configured output off sequence. + + :param instrument: PyVISA resource object + :return: None + """ + + # Initiate output off seqeuence trigger models + for i in range(numberChannels): + if (enables[i]): + instrument.write(f"{slots[int(i/2)]}.trigger.model.initiate(\"offCh{i+1}\")") + + instrument.write("trigger.generator[1].assert()") # trigger all the trigger models simulatenously + + instrument.query("*OPC?") # wait until all outputs are off + +def cleanUpOutputSequence(instrument): + """ + cleanUpOutputSequence deletes any configured trigger models used for output sequencing. + + :param instrument: PyVISA resource object + :return: None + """ + for i in range(numberChannels): + if(enables[i]): + instrument.write(f"{slots[int(i/2)]}.trigger.model.delete(\"onCh{i+1}\")") + instrument.write(f"{slots[int(i/2)]}.trigger.model.delete(\"offCh{i+1}\")") + +def errorCheck(instrument): + """ + cleakErrors prints any errors present in the error queue. + + :param instrument: PyVISA resource object + :return: None + """ + + errorCount = int(float(instrument.query("print(errorqueue.count)"))) # get number of errors + if(errorCount > 0): + print(f"Error Queue Count: {errorCount}") + for i in range(0, errorCount): # print all errors + print(instrument.query(f"print({i}, errorqueue.next())")) + +def main(): + + resourceString = "TCPIP0::134.63.75.188::5025::SOCKET" # instrument resource string + + # configure VISA connection + rm = pyvisa.ResourceManager() + instrument = rm.open_resource(resourceString) + instrument.timeout = 10000 # instrument timeout time + if "SOCKET" in resourceString: + instrument.write_termination = "\n" + instrument.read_termination = "\n" + instrument.send_end = True + + # voltage level for each channel + vLevel = [ + 5, # Channe1 1 + 5, # Channel 2 + 5, # Channel 3 + 5, # Channel 4 + 5, # Channel 5 + 5 # Channel 6 + ] + + # current limit for each chanel + iLimit = [ + 5, # Channe1 1 + 5, # Channel 2 + 5, # Channel 3 + 5, # Channel 4 + 5, # Channel 5 + 5 # Channel 6 + ] + + # slew rate for each channel in V/s + slewRate = [ + 10000, # Channe1 1 + 10000, # Channel 2 + 10000, # Channel 3 + 10000, # Channel 4 + 10000, # Channel 5 + 10000 # Channel 6 + ] + + # set current limit and slew rate + for i in range(numberChannels): + if(enables[i]): + instrument.write(f"{chs[i]}.source.limiti = {iLimit[i]}") + instrument.write(f"{chs[i]}.source.slewratev = {slewRate[i]}") + + # output sequence channel on delays in seconds + onDelays = [ + 0.000, # Channe1 1 + 0.005, # Channel 2 + 0.010, # Channel 3 + 0.015, # Channel 4 + 0.020, # Channel 5 + 0.025 # Channel 6 + ] + + # output sequence channel off delays in seconds + offDelays = [ + 0.025, # Channe1 1 + 0.020, # Channel 2 + 0.015, # Channel 3 + 0.010, # Channel 4 + 0.005, # Channel 5 + 0.000 # Channel 6 + ] + + instrument.timeout = 1000+1000*max(instrument.timeout/1000, max(onDelays), max(offDelays)) # set the timeout to 1 second greater than the highest on/off delay + + # cofigure output sequence + configureOutputOnSequence(instrument, onDelays, vLevel) # configure output on sequence + configureOutputOffSequence(instrument, offDelays) # configure output off sequence + + outputsOn(instrument) # turn outputs on using output on sequence + + # run any tests while device is powered + minOnTime = 0.005 # minimum time between channel turn on sequence and off sequence + time.sleep(minOnTime) + + outputsOff(instrument) # turn outputs off using output off sequence + + cleanUpOutputSequence(instrument) # clean up output sequence information stored on instrument + + errorCheck(instrument) # check for errors generated + + instrument.clear() # clear connection + instrument.close() # close VISA session + rm.close() # close resource manager session + +main() # run main program diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/Trigger Output Sequence Demo.tsp b/Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/Trigger Output Sequence Demo.tsp new file mode 100644 index 0000000..a0e1f21 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/Trigger Output Sequence Demo.tsp @@ -0,0 +1,150 @@ +--[[ +6Ch_Output_Sequence.tsp + +This script performs output on and off sequencing using trigger models +]] + +-- Set the number of channels you want to use +-- Will include channels from slot 1 first then 2 etc. +-- +-- If you wish to skip earlier channels (Ex only use slots 2/3) +-- Be sure to include these in the channel count then simply disable them +-- By updating the enables list +local Nchannels = 4 + +-- PSU alias names +local chs = {} +local slots = {} +for i = 1, Nchannels, 1 do + chs[i] = slot[math.ceil(i/2)].psu[2-math.mod(i,2)] + slots[math.ceil(i/2)] = slot[math.ceil(i/2)] +end + +-- Enable channels (True = enabled, False = disabled) +local enables = {true, true, true, true, false, false} + +----------------------------------------------------------------------------------------- +-- configureOutputOnSequence configures a output on sequence for the enabled channels +-- using a trigger model for each channel. +-- +-- :param delays: List of delays before each channel turns on. Min: 0s, Max: 2^34s +-- +-- :return: None +----------------------------------------------------------------------------------------- +local function configureOutputOnSequence(delays) + -- Create a trigger model for each channel to turn on the output after a specified delay + for i = 1, Nchannels, 1 do + if enables[i] then + slots[math.ceil(i/2)].trigger.model.delete("onCh"..i) -- Delete trigger models from previous tests + slots[math.ceil(i/2)].trigger.model.create("onCh"..i) -- Create trigger model + slots[math.ceil(i/2)].trigger.model.addblock.wait("onCh"..i,"Wait", trigger.generator[1].EVENT_ID) -- Wait for trigger generator event + slots[math.ceil(i/2)].trigger.model.addblock.delay.constant("onCh"..i, "delayTurnOn", delays[i]) -- Delay before turn on + slots[math.ceil(i/2)].trigger.model.addblock.source.output("onCh"..i, "chOn", 2-math.mod(i,2), 1) -- urn on channel + end + end +end + +----------------------------------------------------------------------------------------- +-- outputsOn Turns on the output for on all the enabled channels using the +-- configured output on sequence. +-- +-- :return: None +----------------------------------------------------------------------------------------- +local function outputsOn() + -- Initiate output on seqeuence trigger models + for i = 1, Nchannels, 1 do + if enables[i] then + slots[math.ceil(i/2)].trigger.model.initiate("onCh"..i) + end + end + -- Assert trigger to turn outputs on + trigger.generator[1].assert() + + waitcomplete() + +end + +----------------------------------------------------------------------------------------- +-- configureOutputOffSequence configures a output off sequence for the enabled channels +-- using a trigger model for each channel. +-- +-- :param delayChannels: List of delays before each channel turns off. Min: 0s, Max: 2^34s +-- +-- :return: None +----------------------------------------------------------------------------------------- +local function configureOutputOffSequence(delays) + -- Create a trigger model for each channel to turn off the output after a specified delay + for i = 1, Nchannels, 1 do + if enables[i] then + slots[math.ceil(i/2)].trigger.model.delete("offCh"..i) + slots[math.ceil(i/2)].trigger.model.create("offCh"..i) + slots[math.ceil(i/2)].trigger.model.addblock.wait("offCh"..i,"Wait", trigger.generator[1].EVENT_ID) + slots[math.ceil(i/2)].trigger.model.addblock.delay.constant("offCh"..i, "delayTurnOn", delays[i]) -- delay before turn off + slots[math.ceil(i/2)].trigger.model.addblock.source.output("offCh"..i, "chOff", 2-math.mod(i,2), 0) -- turn off channel + end + end +end + +----------------------------------------------------------------------------------------- +-- outputsOff turns on the output for off all the enabled channels using the +-- configured output off sequence. +-- +-- :return: None +----------------------------------------------------------------------------------------- +local function outputsOff() + -- Initiate output on seqeuence trigger models + for i = 1, Nchannels, 1 do + if enables[i] then + slots[math.ceil(i/2)].trigger.model.initiate("offCh"..i) + end + end + -- Assert trigger to turn outputs off + trigger.generator[1].assert() + + waitcomplete() +end + +-- Set voltage level and current limit for all channels +local v_level = 5 +local i_limit = 5 +local slewrate = 10000 + +for i = 1, Nchannels, 1 do + if enables[i] then + chs[i].source.levelv = v_level + chs[i].source.limiti = i_limit + chs[i].source.slewratev = slewrate + end +end + +-- On Delays +local onDelays = { + 0.000, + 0.005, + 0.010, + 0.015, + 0.000, + 0.000 +} + +-- Off Delays +local offDelays = { + 0.015, + 0.010, + 0.005, + 0.000, + 0.000, + 0.000 +} + +local onTime = 0.01 -- Time for Channels to be On + +configureOutputOnSequence(onDelays) -- Configure output on sequence + +configureOutputOffSequence(offDelays) -- Configure output off sequence + +outputsOn() -- Turn outputs on using trigger models + +delay(onTime) + +outputsOff() -- Turn outputs off using trigger models diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/Trigger Output Sequence TSP Functions.py b/Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/Trigger Output Sequence TSP Functions.py new file mode 100644 index 0000000..15a127f --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Output_Sequence/Trigger Output Sequence TSP Functions.py @@ -0,0 +1,308 @@ +""" + + *********************************************************** + *** Copyright 2025 Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + *********************************************************** + + Output_Sequence_TSP_Functions_Demo.py + + This script does output on and off sequencing using trigger models and TSP functions for a singular fully loaded MP5103 mainframe. +""" + +import pyvisa +import time + +numberChannels = 6 # number of channels configured for outut on/off sequence + +# power supply alias names +chs = [None] * numberChannels +slots = [None] * (round(numberChannels / 2)) +for i in range(numberChannels): # configures channels starting from slot 1 to 3 + chs[i] = f"slot[{int(i/2)+1}].psu[{(i%2) + 1}]" + slots[int(i/2)] = f"slot[{int(i/2) + 1}]" + +# enable channels (True = enabled, False = disabled) +enables = [ + True, # Channe1 1 + True, # Channel 2 + True, # Channel 3 + True, # Channel 4 + True, # Channel 5 + True # Channel 6 + ] + +def sendOutputSequenceFunctions(instrument): + """ + sendOutputSequenceFunctions sends a TSP script that contains TSP functions to call for output sequencing. + Note: This function is only required to be called once when configuring the test for the first time. + + :param instrument: PyVISA resource object + :return: None + """ + + instrument.write( + """loadscript outputSequence + + function configureOutputOnSequence(enables, slots, chs, delays, vLevel) + + -- configure channel trigger models + for i = 1, table.getn(enables), 1 do + if enables[i] then + chs[i].trigger.source.listv({vLevel[i]}) -- set turn on voltage level + slots[math.ceil(i/2)].trigger.model.delete("onCh"..i) + slots[math.ceil(i/2)].trigger.model.create("onCh"..i) + slots[math.ceil(i/2)].trigger.model.addblock.wait("onCh"..i,"Wait", trigger.generator[1].EVENT_ID) -- wait for trigger event + slots[math.ceil(i/2)].trigger.model.addblock.delay.constant("onCh"..i, "delayTurnOn", delays[i]) -- delay before turn on + slots[math.ceil(i/2)].trigger.model.addblock.source.action.step("onCh"..i, "chOn", 2-math.mod(i,2)) -- go to turn on voltage level + end + end + end + + -- Turns all channels on + function outputsOn(enables, slots, chs) + + -- turn on outputs to 0V then initiate trigger models + for i = 1, table.getn(enables), 1 do + if enables[i] then + chs[i].source.levelv = 0 + chs[i].source.output = 1 + slots[math.ceil(i/2)].trigger.model.initiate("onCh"..i) + end + end + + trigger.generator[1].assert() -- trigger all configured channels to begin output on sequencing + end + + -- Configure output off sequence + function configureOutputOffSequence(enables, slots, delays) + + -- configure channel trigger models + for i = 1, table.getn(enables), 1 do + if enables[i] then + slots[math.ceil(i/2)].trigger.model.delete("offCh"..i) + slots[math.ceil(i/2)].trigger.model.create("offCh"..i) + slots[math.ceil(i/2)].trigger.model.addblock.wait("offCh"..i,"Wait", trigger.generator[1].EVENT_ID) -- wait for trigger event + slots[math.ceil(i/2)].trigger.model.addblock.delay.constant("offCh"..i, "delayTurnOn", delays[i]) -- delay before turn off + slots[math.ceil(i/2)].trigger.model.addblock.source.output("offCh"..i, "chOff", 2-math.mod(i,2), 0) -- turn off channel + end + end + end + + -- Turns all channels off + function outputsOff(enables, slots) + + -- initiate trigger models + for i = 1, table.getn(enables), 1 do + if enables[i] then + slots[math.ceil(i/2)].trigger.model.initiate("offCh"..i) + end + end + + trigger.generator[1].assert() -- trigger all configured channels to begin output off sequencing + end + + -- Deletes any configured trigger models used for output sequencing. + function cleanUpOutputSequence(enables, slots) + + -- delete trigger models + for i = 1, table.getn(enables), 1 do + if enables[i] then + slots[math.ceil(i/2)].trigger.model.delete("onCh"..i) + slots[math.ceil(i/2)].trigger.model.delete("offCh"..i) + end + end + end + endscript + outputSequence.run()""") + +def configureOutputOnSequence(instrument, delays, vLevel): + """ + configureOutputOnSequence configures a output on sequence for the enabled channels using a trigger model for each channel. + + :param instrument: PyVISA resource object + :param delayChannels: List of delays before each channel turns on. Min: 0s, Max: 2^34s + + :return: None + """ + + instrument.write(f"configureOutputOnSequence({pythonListToLua(enables)}, {pythonListToLua(slots)}, {pythonListToLua(chs)}, {pythonListToLua(delays)}, {pythonListToLua(vLevel)})") # configure output on sequence + +def outputsOn(instrument): + """ + outputsOn turns on the output for on all the enabled channels using the configured output on sequence. + + :param instrument: PyVISA resource object + :return: None + """ + + instrument.write(f"outputsOn({pythonListToLua(enables)}, {pythonListToLua(slots)}, {pythonListToLua(chs)})") # turn outputs on in configured sequence + + instrument.query("*OPC?") # wait until all outputs are on + +def configureOutputOffSequence(instrument, delays): + """ + configureOutputOffSequence configures a output off sequence for the enabled channels using a trigger model for each channel. + + :param instrument (PyVISA): PyVISA resource object + :param delayChannels: List of delays before each channel turns off. Min: 0s, Max: 2^34s + + :return: None + """ + + instrument.write(f"configureOutputOffSequence({pythonListToLua(enables)}, {pythonListToLua(slots)}, {pythonListToLua(delays)})") # configure output off sequence + +def outputsOff(instrument): + """ + outputsOn turns on the output for on all the enabled channels using the configured output off sequence. + + :param instrument: PyVISA resource object + :return: None + """ + + instrument.write(f"outputsOff({pythonListToLua(enables)}, {pythonListToLua(slots)}, {pythonListToLua(chs)})") # turn outputs off in configured sequence + + instrument.query("*OPC?") # wait until all outputs are off + +def cleanUpOutputSequence(instrument): + """ + cleanUpOutputSequence deletes any configured trigger models used for output sequencing. + + :param instrument: PyVISA resource object + :return: None + """ + + instrument.write(f"cleanUpOutputSequence({pythonListToLua(enables)}, {pythonListToLua(slots)})") # delete trigger models once test completed + +def errorCheck(instrument): + """ + cleakErrors prints any errors present in the error queue. + + :param instrument: PyVISA resource object + :return: None + """ + + errorCount = int(float(instrument.query("print(errorqueue.count)"))) # get number of errors + if(errorCount > 0): + print(f"Error Queue Count: {errorCount}") + for i in range(0, errorCount): # print all errors + print(instrument.query(f"print({i}, errorqueue.next())")) + +def pythonListToLua(pythonList): + """ + pythonListToLua converts a python list to a lua list + + :param pythonList: Python list + :return: string containing lua list + """ + + def pythonToLua(element): + """ + pythonToLua converts a python list element to equivalent lua list element + + :param pythonList: Python list + :return: string for one element in lua list + """ + if element is True: + return "true" + elif element is False: + return "false" + else: + return str(element) + + # Join elements into a comma-separated string + return "{" + ", ".join(pythonToLua(element) for element in pythonList) + "}" + +def main(): + + resourceString = "TCPIP0::192.168.0.2::5025::SOCKET" # instrument resource string + + # configure VISA connection + rm = pyvisa.ResourceManager() + instrument = rm.open_resource(resourceString) + instrument.timeout = 10000 # instrument timeout time + if "SOCKET" in resourceString: + instrument.write_termination = "\n" + instrument.read_termination = "\n" + instrument.send_end = True + + # voltage level for each channel + vLevel = [ + 5, # Channe1 1 + 5, # Channel 2 + 5, # Channel 3 + 5, # Channel 4 + 5, # Channel 5 + 5 # Channel 6 + ] + + # current limit for each chanel + iLimit = [ + 5, # Channe1 1 + 5, # Channel 2 + 5, # Channel 3 + 5, # Channel 4 + 5, # Channel 5 + 5 # Channel 6 + ] + + # slew rate for each channel in V/s + slewRate = [ + 10000, # Channe1 1 + 10000, # Channel 2 + 10000, # Channel 3 + 10000, # Channel 4 + 10000, # Channel 5 + 10000 # Channel 6 + ] + + # set current limit and slew rate + for i in range(numberChannels): + if(enables[i]): + instrument.write(f"{chs[i]}.source.limiti = {iLimit[i]}") + instrument.write(f"{chs[i]}.source.slewratev = {slewRate[i]}") + + # output sequence channel on delays in seconds + onDelays = [ + 0.000, # Channe1 1 + 0.005, # Channel 2 + 0.010, # Channel 3 + 0.015, # Channel 4 + 0.020, # Channel 5 + 0.025 # Channel 6 + ] + + # output sequence channel off delays in seconds + offDelays = [ + 0.025, # Channe1 1 + 0.020, # Channel 2 + 0.015, # Channel 3 + 0.010, # Channel 4 + 0.005, # Channel 5 + 0.000 # Channel 6 + ] + + instrument.timeout = 1000+1000*max(instrument.timeout/1000, max(onDelays), max(offDelays)) # set the timeout to 1 second greater than the highest delay + + # cofigure output sequence + sendOutputSequenceFunctions(instrument) # configure TSP functions for output sequencing + configureOutputOnSequence(instrument, onDelays, vLevel) # configure output on sequence + configureOutputOffSequence(instrument, offDelays) # configure output off sequence + + outputsOn(instrument) # turn outputs on using output on sequence + + # run any tests while device is powered + minOnTime = 0.005 # minimum time between channel turn on sequence and off sequence + time.sleep(minOnTime) + + outputsOff(instrument) # turn outputs off using output off sequence + + cleanUpOutputSequence(instrument) # clean up output sequence information stored on instrument + + errorCheck(instrument) # check for errors generated + + instrument.clear() # clear connection + instrument.close() # close VISA session + rm.close() # close resource manager session + +main() # run main program diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/README.md b/Examples/Modular_Precision_Test_System/PSU_Examples/README.md new file mode 100644 index 0000000..7bbcb3a --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/README.md @@ -0,0 +1,31 @@ +# Power Supply Unit (PSU) Module Examples + +The examples in this directory work with the [MP5103 Mainframe and Supported PSU Modules](https://www.tek.com/en/products/mp5000-series-modular-precision-test-system). + +*Note: before running any example - verify slot and channel used.* + +## Directory + +### **[2 Channels In Parallel](./2_Channel_Parallel/)** +Combines channel sourcing in parallel to increase current capabilities. + +### **[2 Channels in Series](./2_Channels_Series/)** +Combines channel sourcing in series to increase voltage capabilities. + +### **[Bipolar Output](./Bipolar_Output/)** +Demonstrates bipolar capabilities of the power supply module. + +### **[Linear Sweep](./Linear_Sweep/)** +Generates a linear voltage sweep while taking measurements. + +### **[Overcurrent Protection (OCP)](./OCP/)** +Configures PSU to intentionally trigger and demonstrate OCP. + +### **[Output Sequencing](./Output_Sequence/)** +Times turning output on and off from multiple channels with accurate delays utilizing trigger models. + +### **[Overvoltage Protection (OVP)](./OVP/)** +Configures PSU to intentionally trigger and demonstrate OVP. + +### **[Slew Rate](./Slew_Rate/)** +Toggles outputs using different slew rates. \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Slew_Rate/README.md b/Examples/Modular_Precision_Test_System/PSU_Examples/Slew_Rate/README.md new file mode 100644 index 0000000..597401b --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Slew_Rate/README.md @@ -0,0 +1,15 @@ +# Controlling Slew Rate + +Toggles outputs using different slew rates. + +This code demonstrates the difference in slew rates of the unit and illustrates how the slew rate can be applied in various situations. The current output on the oscilloscope displays a comparison between a slew rate of 1 and a slew rate of 10,000, highlighting the effectiveness of the slew rate command. + +## Required Modules +1 x MPSU50-2ST + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust settings as needed and run code. diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Slew_Rate/Slew Rate Demo.py b/Examples/Modular_Precision_Test_System/PSU_Examples/Slew_Rate/Slew Rate Demo.py new file mode 100644 index 0000000..cc055be --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Slew_Rate/Slew Rate Demo.py @@ -0,0 +1,39 @@ +######################################################################################### +# This script toggles outputs with varying slewrates to demonstrate this feature. +# This is best paired with a scope to capture the effects. +######################################################################################### + +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +inst.write_termination = "\n" # for using the sockets based implementation +inst.read_termination = "\n" +#inst.send_end = True +inst.timeout = 10000 + +print(inst.query("*IDN?")) +inst.write("reset()") # Reset instrument + +# Alais PSU, change to change slot/channel +module = "slot[2].psu[1]" + +# Configure source i-limit, v-level +inst.write(f"{module}.source.limiti = 1") +inst.write(f"{module}.source.levelv = 5") + +# Slew Rate of 1 v/s +inst.write(f"{module}.source.slewratev = 1") +inst.write(f"{module}.source.output = 1") # Output On +inst.write("delay(1)") +inst.write(f"{module}.source.output = 0") # Output Off +inst.write("delay(1)") + +# Slew Rate of 10000 v/s +inst.write(f"{module}.source.slewratev = 10000") +inst.write(f"{module}.source.output = 1") # Output On +inst.write("delay(1)") +inst.write(f"{module}.source.output = 0") # Output Off + +inst.close() # Close instrument connection \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/Slew_Rate/Slew Rate Demo.tsp b/Examples/Modular_Precision_Test_System/PSU_Examples/Slew_Rate/Slew Rate Demo.tsp new file mode 100644 index 0000000..89f9991 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/Slew_Rate/Slew Rate Demo.tsp @@ -0,0 +1,45 @@ +----------------------------------------------------------------------------------------- +-- This script toggles outputs with varying slewrates to demonstrate this feature. +-- This is best paired with a scope to capture the effects. +----------------------------------------------------------------------------------------- +--- +reset() + +-- Alais PSU +local moduleSlot = slot[2] +local module = moduleSlot.psu[1] + +-- Set voltage and current limits for each channel +module.source.levelv = 5.0 +module.source.limiti = 1.0 + +-- Slewrate of 1 v/s +module.source.slewratev = 1 + + +-- Turn on each channel with delays +delay(0.0) -- Delay for chan_1 +module.source.output = moduleSlot.psu.ON + +-- Wait for 1 seconds +delay(1) + +-- Turn off each channel +module.source.output = moduleSlot.psu.OFF + +-- Set voltage and current limits again +module.source.levelv = 5.0 +module.source.limiti = 1.0 + +-- Slewrate of 10000 v/s +module.source.slewratev = 10000 + +-- Turn on output with delays again +delay(1) +module.source.output = moduleSlot.psu.ON + +-- Wait for 1 seconds +delay(1) + +-- Turn off outputs +module.source.output = moduleSlot.psu.OFF diff --git a/Examples/Modular_Precision_Test_System/README.md b/Examples/Modular_Precision_Test_System/README.md new file mode 100644 index 0000000..64e28aa --- /dev/null +++ b/Examples/Modular_Precision_Test_System/README.md @@ -0,0 +1,23 @@ +# MP5000 Series Modular Precision Test System + +The examples in this directory work with the [MP5103 Mainframe and supported modules](https://www.tek.com/en/products/mp5000-series-modular-precision-test-system). Examples are divided into subdirectories for module type, and more generic application examples. Many examples can be converted between module types. Each example contains Python code and TSP Scripts if applicable. + +## Available Modules + +### Power Supply Unit (PSU) Modules +* MPSU50-ST: 50W, 50 V/5 A, 2 Channels + +### Source Measure Unit (SMU) Modules +* MSMU60-2: 30W, 60 V/1.5 A, 2 Channels + +## Directory + +### **[Application Examples:](./Application_Examples/)** +Covers application focused code that may be applicable to any module or may involve more than one module type. + +### **[PSU Examples:](./PSU_Examples/)** +Examples using PSU functionality or applications. + +### **[SMU Examples:](./SMU_Examples/)** +Examples using SMU functionality or applications. + diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Parallel/Parallel_SMU_Combo.py b/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Parallel/Parallel_SMU_Combo.py new file mode 100644 index 0000000..73b2731 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Parallel/Parallel_SMU_Combo.py @@ -0,0 +1,140 @@ +################################################################################# +# +# Script File: Parallel_SMU_Combo.py +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# voltage sweeping. The purpose is show that you can perform voltage sweeping +# with two channel SMU combination in parallel to increase current. Each SMU +# assigned to one terminal for the high and the other for the low terminal. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 2 channel SMU (MSMU Series) +# 1 Resistive load +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +################################################################################# + +def plotResults(x, y): + import matplotlib.pyplot as pt + + pt.plot(x, y, 'o-', color="blue") + + pt.ylabel("Measured Current (A)") + pt.xlabel("Supplied Voltage (V)") + pt.grid(True) + pt.title("Parallel Voltage Sweep") + + pt.tight_layout() + pt.show() + +import pyvisa +import time + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# Source Settings +startV = 0 +stopV = 10 +noPoints = 6 +limitI = 0.2 # total current for all channels +# Measure Settings +measRangeV = 12 +measRangeI = 100e-3 # same range setting for each range +autoRangeI = True +nplc = 1 +mDelay = 0 +remoteSense = False + +# No more than two channels should be combined +infoSMU = [ + f"slot[1].smu[1]", + f"slot[1].smu[2]" +] + +for i in infoSMU: + # Reset each channel + inst.write(f"{i}.reset()") + inst.write(f"{i}.defbuffer1.clear()") + inst.write(f"{i}.defbuffer1.appendmode = 1") + inst.write(f"{i}.defbuffer2.clear()") + inst.write(f"{i}.defbuffer2.appendmode = 1") + # Source Settings + inst.write(f"{i}.source.func = {i}.FUNC_DC_VOLTAGE") + inst.write(f"{i}.source.rangev = {max(abs(startV), abs(stopV))}") + inst.write(f"{i}.source.limiti = {limitI / len(infoSMU)}") # Divide current limit among channels + inst.write(f"{i}.source.levelv = 0") + # Measure Settings + inst.write(f"{i}.measure.rangev = {measRangeV}") + inst.write(f"{i}.measure.nplc = {nplc}") + if autoRangeI: + inst.write(f"{i}.measure.autorangei = 1") + else: + inst.write(f"{i}.measure.autorangei = 0") + inst.write(f"{i}.measure.rangei = {measRangeI}") + if remoteSense: + inst.write(f"{i}.sense = {i}.SENSE_4WIRE") + else: + inst.write(f"{i}.sense = {i}.SENSE_2WIRE") + inst.write(f"{i}.source.output = 1") + +print("I_total","V",sep="\t\t") +# Calculate change in voltage for each iteration +deltaV = (stopV - startV) / (noPoints - 1) + +for j in range(noPoints): + # Set source voltage for each channel + for i in infoSMU: + inst.write(f"{i}.source.levelv = {startV + (j * deltaV)}") + time.sleep(mDelay) + # Measire i/v on each channel and store in default buffers + for i in infoSMU: + inst.write(f"{i}.measure.iv({i}.defbuffer1, {i}.defbuffer2)") + +currentReadings = [0.0] * noPoints +voltageReadings = [0.0] * noPoints + +# Retrieve data from buffers +for i in infoSMU: + inst.write(f"{i}.source.output = 0") + defBuffer1 = inst.query(f"printbuffer(1, {i}.defbuffer1.n, {i}.defbuffer1)").split(",") + defBuffer2 = inst.query(f"printbuffer(1, {i}.defbuffer2.n, {i}.defbuffer2)").split(",") + # Sum currents and voltages + currentReadings = [x + float(y) for x, y in zip(currentReadings, defBuffer1)] + voltageReadings = [x + float(y) for x, y in zip(voltageReadings, defBuffer2)] +# Divide voltage among currents +voltageReadings = [i/len(infoSMU) for i in voltageReadings] +# Display results +for i in range(noPoints): + print(f"{currentReadings[i]:.5e}", + f"{voltageReadings[i]:.5e}", + sep="\t") + +inst.clear() +inst.close() + +plotResults(voltageReadings, currentReadings) \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Parallel/Parallel_SMU_Combo.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Parallel/Parallel_SMU_Combo.tsp new file mode 100644 index 0000000..a7d845f --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Parallel/Parallel_SMU_Combo.tsp @@ -0,0 +1,159 @@ +--[[ +################################################################################ + +Script File: Parallel_SMU_Combo.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + voltage sweeping. The purpose is show that you can perform voltage sweeping + with two channel SMU combination in parallel to increase current. Each SMU + assigned to one terminal for the high and the other for the low terminal. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 2 channel SMU (MSMU Series) + 1 Resistive load + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * DC_Vsweep_2CH_Combination_inParallel(infoSMU,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense}) + +Example Usage: + * DC_Vsweep_2CH_Combination_inParallel(infoSMU,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense}) + +################################################################################ +--]] +function DC_Vsweep_2CH_Combination_inParallel(infoSMU,srcSettings,meaSettings) + function configSMU(assignSMU) + self = {} + local total_channel = table.getn(assignSMU) + print("total channel: ".. total_channel) + function self.sourceSetting(param,value) + -- Assign source settings for all channels + for i=1, total_channel do + if param == "limiti" then + -- Divide current limit between channels + assignSMU[i][1].source[param] = value/total_channel + else + assignSMU[i][1].source[param] = value + end + end + end + function self.measSetting(param,value) + -- Assign measure settings for all channels + for i=1, total_channel do + assignSMU[i][1].measure[param] = value + end + end + function self.generalSetting(param,value) + -- Assign settings for all channels + for i=1, total_channel do + assignSMU[i][1][param] = value + end + end + function self.measureiv() + -- Measure i/v on all channels + local read_v, read_i + local sum_v = 0 + local sum_i = 0 + for i=1, total_channel do + read_i , read_v = assignSMU[i][1].measure.iv() + -- Sum i/v measurements + sum_v = sum_v + read_v + sum_i = sum_i + read_i + end + -- Divide voltage among channels + return sum_i, sum_v/total_channel + end + function self.reset() + -- Reset all channels + for i=1, total_channel do + assignSMU[i][1].reset() + assignSMU[i][1].defbuffer1.clear() + assignSMU[i][1].defbuffer1.appendmode = 1 + assignSMU[i][1].defbuffer2.clear() + assignSMU[i][1].defbuffer2.appendmode = 1 + end + end + return self + end + + combineSMU = configSMU(infoSMU) + + -- Source Settings + combineSMU.reset() + combineSMU.sourceSetting("func",slot[1].smu[1].FUNC_DC_VOLTAGE) + combineSMU.sourceSetting("rangev",math.max(math.abs(srcSettings[1]), math.abs(srcSettings[2]))) + combineSMU.sourceSetting("limiti",srcSettings[4]) + combineSMU.sourceSetting("levelv",0) + + -- Measure Settings + combineSMU.measSetting("rangev",meaSettings[1]) + combineSMU.measSetting("nplc",meaSettings[4]) + + if meaSettings[3] == true then + combineSMU.measSetting("autorangei",1) + else + combineSMU.measSetting("autorangei",0) + combineSMU.measSetting("rangei",meaSettings[2]) + end + if meaSettings[6] == true then + combineSMU.generalSetting("sense",slot[1].smu[1].SENSE_4WIRE) + else + combineSMU.generalSetting("sense",slot[1].smu[1].SENSE_2WIRE) + end + + -- Calcualte change in voltage for each iteration + local deltaV = (srcSettings[2] - srcSettings[1])/ (srcSettings[3] - 1) + -- Turn outputs on + combineSMU.sourceSetting("output",1) + combineSMU.sourceSetting("output",1) + + print("I_total\t\t V") + for j = 1, srcSettings[3] , 1 do + -- Set voltage source level on all channels + combineSMU.sourceSetting("levelv", srcSettings[1] + ((j-1) * deltaV)) + delay(meaSettings[5]) + -- Measure i/v on all channels + print(combineSMU.measureiv()) + end + -- Turn outputs off + combineSMU.sourceSetting("output",0) + +end + +-- No more than two channels should be combined +local infoSMU = { } + infoSMU[1] = {slot[1].smu[1],1,1} + infoSMU[2] = {slot[1].smu[2],1,2} + +-- Source Settings +local startV = 0 +local stopV = 10 +local noPoints = 6 +local limitI = 0.2 -- Total current for all channels +-- Measure Settings +local measRangeV = 12 +local measRangeI = 100e-3 -- Same range setting for each range +local autoRangeI = true +local nplc = 1 +local mDelay = 0 +local remoteSense = false + +DC_Vsweep_2CH_Combination_inParallel(infoSMU,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense}) \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Parallel/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Parallel/README.md new file mode 100644 index 0000000..7deff27 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Parallel/README.md @@ -0,0 +1,18 @@ +# Combining 2 SMU Channels in Parallel + +Combines channel sourcing in parallel to increase current capabilities. + +By default, the scripts set each channel to a current limit of 100 mA however the output is measured at nearly 200 mA. The output current has been combined in parallel to extend the current capabilities of each channel. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Begin by connecting to the mainframe using the instrument panel found on the left side of the visual studio code interface or running the TSP: Connect function and entering the IP address. +2. Connect CH1 LO to CH2 LO and CH1 HI to CH2 HI. +3. Connect leads from CH2 HI/LO to the 10 kΩ. +4. Run Code diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Series/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Series/README.md new file mode 100644 index 0000000..bc48107 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Series/README.md @@ -0,0 +1,19 @@ +# Combining 2 SMU Channels in Series + +Combines channel sourcing in series to increase voltage capabilities. + +By default, the scripts sweep each channel from 0-10V. However, the total voltage reads from 0-20V. + + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Begin by connecting to the mainframe using the instrument panel found on the left side of the visual studio code interface or running the TSP: Connect function and entering the IP address. +2. Connect CH1 LO to CH2 LO and CH1 HI to CH2 HI. +3. Connect leads from CH2 HI/LO to the 10 kΩ. +4. Run Code diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Series/Series_SMU_Combo.py b/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Series/Series_SMU_Combo.py new file mode 100644 index 0000000..b0156fd --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Series/Series_SMU_Combo.py @@ -0,0 +1,140 @@ +################################################################################# +# +# Script File: Case1_DC_Current_Sweep_Diode_MSMU_MP5000.py +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# voltage sweeping. The purpose is show that you can perform voltage sweeping +# with two channel SMU combination in series to increase voltage. One SMU +# assigned to one terminal for the low and the low of the same channel is tied to +# the low of the other SMU. lastly the high of the other SMU is assigned to the +# other side of DUT. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 2 channel SMU (MSMU Series) +# 1 Resistive load +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +################################################################################# + +def plotResults(x, y): + import matplotlib.pyplot as pt + + pt.plot(x, y, 'o-', color="blue") + + pt.ylabel("Measured Current (A)") + pt.xlabel("Supplied Voltage (V)") + pt.grid(True) + pt.title("Series Voltage Sweep") + + pt.tight_layout() + pt.show() + +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# Source Settings +startV = 0 +stopV = 20 # total voltage from all channels +noPoints = 5 +limitI = 0.5 +# Measure Settings +measRangeV = 20 # same range setting for each channels +measRangeI = 100e-3 +autoRangeI = True +nplc = 1 +mDelay = 0 +remoteSense = False + +# No more than two channels should be combined +infoSMU = [ + f"slot[1].smu[1]", + f"slot[1].smu[2]" +] + +for i in infoSMU: + # Reset channels + inst.write(f"{i}.reset()") + inst.write(f"{i}.defbuffer1.clear()") + inst.write(f"{i}.defbuffer1.appendmode = 1") + inst.write(f"{i}.defbuffer2.clear()") + inst.write(f"{i}.defbuffer2.appendmode = 1") + # Src Settings + inst.write(f"{i}.source.func = {i}.FUNC_DC_VOLTAGE") + inst.write(f"{i}.source.rangev = {max(abs(startV / len(infoSMU)), abs(stopV / len(infoSMU)))}") + inst.write(f"{i}.source.limiti = {limitI}") + inst.write(f"{i}.source.levelv = 0") + # Measure Settings + inst.write(f"{i}.measure.rangev = {measRangeV}") + inst.write(f"{i}.measure.nplc = {nplc}") + if autoRangeI: + inst.write(f"{i}.measure.autorangei = 1") + else: + inst.write(f"{i}.measure.autorangei = 0") + inst.write(f"{i}.measure.rangei = {measRangeI}") + if remoteSense: + inst.write(f"{i}.measure.sense = {i}.SENSE_4WIRE") + else: + inst.write(f"{i}.measure.sense = {i}.SENSE_2WIRE") + inst.write(f"{i}.source.output = 1") + +print("I","V_total",sep="\t\t") +# Calculate change in voltage for each iteration +deltaV = (stopV - startV) / (noPoints - 1) + +for j in range(noPoints): + # Set voltage level on each channel + for i in infoSMU: + inst.write(f"{i}.source.levelv = {(startV + (j * deltaV)) / len(infoSMU)}") + inst.write(f"delay({mDelay})") + # Measure i/v on each channel + for i in infoSMU: + inst.write(f"{i}.measure.iv({i}.defbuffer1, {i}.defbuffer2)") + +currentReadings = [0.0] * noPoints +voltageReadings = [0.0] * noPoints + +for i in infoSMU: + inst.write(f"{i}.source.output = 0") + inst.write(f"{i}.source.output = 0") + # Retrieve buffer data + defBuffer1 = inst.query(f"printbuffer(1, {i}.defbuffer1.n, {i}.defbuffer1)").split(",") + defBuffer2 = inst.query(f"printbuffer(1, {i}.defbuffer2.n, {i}.defbuffer2)").split(",") + # Sum currents and voltages + currentReadings = [x + float(y) for x, y in zip(currentReadings, defBuffer1)] + voltageReadings = [x + float(y) for x, y in zip(voltageReadings, defBuffer2)] +# Divide currents among channels +currentReadings = [i/len(infoSMU) for i in currentReadings] +# Display results +for i in range(noPoints): + print(f"{currentReadings[i]:.5e}", f"{voltageReadings[i]:.5e}", sep="\t") + +inst.clear() +inst.close() + +plotResults(voltageReadings, currentReadings) \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Series/Series_SMU_Combo.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Series/Series_SMU_Combo.tsp new file mode 100644 index 0000000..33e326f --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/2_Channel_Series/Series_SMU_Combo.tsp @@ -0,0 +1,164 @@ +--[[ +################################################################################ + +Script File: Case1_DC_Current_Sweep_Diode_MSMU_MP5000.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + voltage sweeping. The purpose is show that you can perform voltage sweeping + with two channel SMU combination in series to increase voltage. One SMU + assigned to one terminal for the low and the low of the same channel is tied to + the low of the other SMU. lastly the high of the other SMU is assigned to the + other side of DUT. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 2 channel SMU (MSMU Series) + 1 Resistive load + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * DC_Vsweep_2CH_Combination_inSeries(infoSMU,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense}) + +Example Usage: + * DC_Vsweep_2CH_Combination_inSeries(infoSMU,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense}) + +################################################################################ +--]] +function DC_Vsweep_2CH_Combination_inSeries(infoSMU,srcSettings,meaSettings) + function configSMU(assignSMU) + self = {} + local total_channel = table.getn(assignSMU) + print("total channel: ".. total_channel) + function self.sourceSetting(param,value) + -- Assign source settings for all channels + for i=1, total_channel do + if param == "levelv" then + if value == 0 then + assignSMU[i][1].source[param] = 0 + else + assignSMU[i][1].source[param] = value/total_channel + end + else + assignSMU[i][1].source[param] = value + end + end + end + function self.measSetting(param,value) + -- Assign measure settings for all channels + for i=1, total_channel do + assignSMU[i][1].measure[param] = value + end + end + function self.generalSetting(param,value) + -- Assign settings for all channels + for i=1, total_channel do + assignSMU[i][1][param] = value + end + end + function self.measureiv() + -- Measure i/v on all channels + local read_v, read_i + local sum_v = 0 + local sum_i = 0 + for i=1, total_channel do + read_i , read_v = assignSMU[i][1].measure.iv() + -- Sum i/v measurements + sum_v = sum_v + read_v + sum_i = sum_i + read_i + end + -- Divide current among channels + return sum_i/total_channel, sum_v + end + function self.reset() + -- Reset all channels + for i=1, total_channel do + assignSMU[i][1].reset() + assignSMU[i][1].defbuffer1.clear() + assignSMU[i][1].defbuffer1.appendmode = 1 + assignSMU[i][1].defbuffer2.clear() + assignSMU[i][1].defbuffer2.appendmode = 1 + end + end + return self + end + + combineSMU = configSMU(infoSMU) + local total_channel = table.getn(infoSMU) + -- Source Settings + combineSMU.reset() + combineSMU.sourceSetting("func",slot[1].smu[1].FUNC_DC_VOLTAGE) + combineSMU.sourceSetting("rangev",math.max(math.abs(srcSettings[1]/total_channel), math.abs(srcSettings[2]/total_channel))) + combineSMU.sourceSetting("limiti",srcSettings[4]) + combineSMU.sourceSetting("levelv",0) + + -- Measure Settings + combineSMU.measSetting("rangev",meaSettings[1]) + combineSMU.measSetting("nplc",meaSettings[4]) + + if meaSettings[3] == true then + combineSMU.measSetting("autorangei",1) + else + combineSMU.measSetting("autorangei",0) + combineSMU.measSetting("rangei",meaSettings[2]) + end + if meaSettings[6] == true then + combineSMU.generalSetting("sense",slot[1].smu[1].SENSE_4WIRE) + else + combineSMU.generalSetting("sense",slot[1].smu[1].SENSE_2WIRE) + end + + -- Calculate change in voltage for each iteration + local deltaV = (srcSettings[2] - srcSettings[1])/ (srcSettings[3] - 1) + -- Turn outputs on + combineSMU.sourceSetting("output",1) + combineSMU.sourceSetting("output",1) + + print("I\t\t V_total") + for j = 1, srcSettings[3] , 1 do + -- Set voltage level on all channels + combineSMU.sourceSetting("levelv", srcSettings[1] + ((j-1) * deltaV)) + delay(meaSettings[5]) + -- Measure i/v on all channels + print(combineSMU.measureiv()) + end + -- Turn outputs off + combineSMU.sourceSetting("output",0) + + end + +-- No more than two channels should be combined +local infoSMU = { } +infoSMU[1] = {slot[1].smu[1],1,1} +infoSMU[2] = {slot[1].smu[2],1,2} + +-- Source Settings +local startV = 0 +local stopV = 20 -- Total voltage from all channels +local noPoints = 5 +local limitI = 0.2 +-- Measure Settings +local measRangeV = 20 -- Same range setting for each channels +local measRangeI = 100e-3 +local autoRangeI = true +local nplc = 1 +local mDelay = 0 +local remoteSense = false + +DC_Vsweep_2CH_Combination_inSeries(infoSMU,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense}) \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Gummel/BJT_Gummel_VbIc.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Gummel/BJT_Gummel_VbIc.tsp new file mode 100644 index 0000000..1e81e25 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Gummel/BJT_Gummel_VbIc.tsp @@ -0,0 +1,319 @@ +--[[ +################################################################################ + +Script File: BJT_Gummel_Vblc.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + gummel curve test for n-type bipolar junction transitor devices. + The purpose is show that you can perform the semiductor device gummel characterization + with the SMUs As written, two channels of SMU are assigned to the collector/base. + LOs from the two channels are tied together to emitter. There are three different + scripts for the same test. One is operation in run time environment, the other + is single trigger model for both channels in the same slot. The third is another trigger + model example for each channel in different slot or in the same slot in the trigger + trigger model. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 2 channel SMUs + 1 BJT Transistor + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * Gummel_VbIc_RunTimeEnv({base_slot,base_channel,collecor_slot,collector_channel},{startVbVc,stopVbVc,noSwpPoints,limitIbIc},{nplc,mDelay}) + * Gummel_VbIc_TriggerInOneSlot({common_slot,base_channel,collector_channel},{startVbVc,stopVbVc,noSwpPoints,limitIbIc},{nplc,mDelay},{"tm_vbic"}) + * Gummel_VbIc_TwoTriggers({base_slot,base_channel,collecor_slot,collector_channel},{startVbVc,stopVbVc,noSwpPoints,limitIbIc},{nplc,mDelay},{"tm_base", "tm_collector"}) + +Example Usage: + * Gummel_VbIc_RunTimeEnv({base_slot,base_channel,collecor_slot,collector_channel},{startVbVc,stopVbVc,noSwpPoints,limitIbIc},{nplc,mDelay}) + * Gummel_VbIc_TriggerInOneSlot({common_slot,base_channel,collector_channel},{startVbVc,stopVbVc,noSwpPoints,limitIbIc},{nplc,mDelay},{"tm_vbic"}) + * Gummel_VbIc_TwoTriggers({base_slot,base_channel,collecor_slot,collector_channel},{startVbVc,stopVbVc,noSwpPoints,limitIbIc},{nplc,mDelay},{"tm_base", "tm_collector"}) + +################################################################################ +--]] +function Gummel_VbIc_RunTimeEnv(infoSMU,bcSMUsettings, meaSettings) + -- SMU channel assignment + local baseSMU = slot[infoSMU[1]].smu[infoSMU[2]] + local collectorSMU = slot[infoSMU[3]].smu[infoSMU[4]] + local smu_id = {baseSMU,collectorSMU} + -- Setup both channels for a voltage sweep + for i = 1, 2 do + smu_id[i].reset() + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].sense = smu_id[i].SENSE_2WIRE + smu_id[i].source.rangev = math.max(math.abs(bcSMUsettings[1]), math.abs(bcSMUsettings[2])) + smu_id[i].source.limiti = bcSMUsettings[4] + smu_id[i].source.levelv = 0 + smu_id[i].measure.nplc = meaSettings[1] + smu_id[i].measure.autorangei = 1 + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + end + -- Calculate the change in voltage for both the base and collector + local deltaVg = (bcSMUsettings[2] - bcSMUsettings[1])/ (bcSMUsettings[3] - 1) + -- Turn outputs on + baseSMU.source.output = 1 + baseSMU.source.output = 1 + collectorSMU.source.output = 1 + collectorSMU.source.output = 1 + + print("Vb\t\t Ib\t\t Vce\t\t Ic") + -- Perform voltage sweep on both channels + for j = 1, bcSMUsettings[3] , 1 do + -- Increase voltage level on both channels + collectorSMU.source.levelv = bcSMUsettings[1] + ((j-1) * deltaVg) + baseSMU.source.levelv = bcSMUsettings[1] + ((j-1) * deltaVg) + -- Runtime delay + delay(meaSettings[2]) + -- Measure i/v on both channels and store in default buffers + baseSMU.measure.iv(baseSMU.defbuffer1, baseSMU.defbuffer2) + collectorSMU.measure.iv(collectorSMU.defbuffer1, collectorSMU.defbuffer2) + -- Display results + print(baseSMU.defbuffer2[j],baseSMU.defbuffer1[j],collectorSMU.defbuffer2[j],collectorSMU.defbuffer1[j]) + end + + baseSMU.source.output = 0 + collectorSMU.source.output = 0 + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + errorQUEUE() +end + +function Gummel_VbIc_TriggerInOneSlot(infoSMU,bcSMUsettings, meaSettings,tmName) + -- SMU channel assignment + local baseSMU = slot[infoSMU[1]].smu[infoSMU[2]] + local collectorSMU = slot[infoSMU[1]].smu[infoSMU[3]] + local smu_id = {baseSMU,collectorSMU} + -- Configure both channels for a voltage sweep + for i = 1, 2 do + smu_id[i].reset() + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].sense = smu_id[i].SENSE_2WIRE + smu_id[i].source.rangev = math.max(math.abs(bcSMUsettings[1]), math.abs(bcSMUsettings[2])) + smu_id[i].source.limiti = bcSMUsettings[4] + smu_id[i].source.levelv = 0 + smu_id[i].measure.nplc = meaSettings[1] + smu_id[i].measure.autorangei = 1 + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + end + + -- Setup linear voltage sweep for both channels + collectorSMU.trigger.source.linearv(bcSMUsettings[1], bcSMUsettings[2], bcSMUsettings[3]) + baseSMU.trigger.source.linearv(bcSMUsettings[1], bcSMUsettings[2], bcSMUsettings[3]) + -- Set trigger models to measure i/v and store in default buffers + baseSMU.trigger.measure.iv(baseSMU.defbuffer1,baseSMU.defbuffer2) + collectorSMU.trigger.measure.iv(collectorSMU.defbuffer1,collectorSMU.defbuffer2) + +--############################################## Trigger model ###############################################-- +--############################################################################################################-- + + local triggerModel = slot[infoSMU[1]].trigger.model + triggerModel.create(tmName[1]) + + -- Advance both channels to the next voltage value -- Loop 1 + triggerModel.addblock.source.action.step(tmName[1], "collectorVoltage", infoSMU[3]) --##-- + triggerModel.addblock.source.action.step(tmName[1], "baseVoltage", infoSMU[2]) --##-- + triggerModel.addblock.delay.constant(tmName[1],"meaDelay",meaSettings[2]) --##-- + -- Measure i/v --##-- + triggerModel.addblock.measure(tmName[1], "measure", infoSMU[2], 1) --##-- + triggerModel.addblock.measure(tmName[1], "measure2", infoSMU[3], 1) --##-- + -- Loop for every value in voltage sweeps --##-- + triggerModel.addblock.branch.counter(tmName[1], "branch-counter", "collectorVoltage", bcSMUsettings[3]) --##-- + +--############################################################################################################-- + + -- Initiate trigger model, delete when completed + baseSMU.source.output = 1 + collectorSMU.source.output = 1 + triggerModel.initiate(tmName[1]) + waitcomplete() + baseSMU.source.output = 0 + collectorSMU.source.output = 0 + triggerModel.delete(tmName[1]) + + -- Display results from buffers + print("Vb\t\t Ib\t\t Vce\t\t Ic") + for j = 1, baseSMU.defbuffer1.n , 1 do + print(baseSMU.defbuffer2[j],baseSMU.defbuffer1[j],collectorSMU.defbuffer2[j],collectorSMU.defbuffer1[j]) + end + + -- Check for errors and dipslay + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + errorQUEUE() +end + + +function Gummel_VbIc_TwoTriggers(infoSMU,bcSMUsettings, meaSettings,tmName) + -- SMU channel assignment + local baseSMU = slot[infoSMU[1]].smu[infoSMU[2]] + local collectorSMU = slot[infoSMU[3]].smu[infoSMU[4]] + local smu_id = {baseSMU,collectorSMU} + -- Configure both channels for a voltage sweep + for i = 1, 2 do + smu_id[i].reset() + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].sense = smu_id[i].SENSE_2WIRE + smu_id[i].source.rangev = math.max(math.abs(bcSMUsettings[1]), math.abs(bcSMUsettings[2])) + smu_id[i].source.limiti = bcSMUsettings[4] + smu_id[i].source.levelv = 0 + smu_id[i].measure.nplc = meaSettings[1] + smu_id[i].measure.autorangei = 1 + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + end + +--############################################ Base Trigger model ############################################-- +--############################################################################################################-- + + -- Set source values for linear voltage sweep + baseSMU.trigger.source.linearv(bcSMUsettings[1], bcSMUsettings[2], bcSMUsettings[3]) + -- Set trigger model measurements to i/v and store in default buffers + baseSMU.trigger.measure.iv(baseSMU.defbuffer1,baseSMU.defbuffer2) + + local trigger_base = slot[infoSMU[1]].trigger.model + trigger_base.create(tmName[1]) + + -- Notify collector model that sweep has begun -- Loop 1 + trigger_base.addblock.notify(tmName[1],"notifyCollectorStep",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY1) --##-- + -- Advance source to next voltage value --##-- + trigger_base.addblock.source.action.step(tmName[1], "baseVoltage", infoSMU[2]) --##-- + trigger_base.addblock.delay.constant(tmName[1],"meaDelay",meaSettings[2]) --##-- + -- Measure i/v on base --##-- + trigger_base.addblock.measure(tmName[1], "measure", infoSMU[2], 1) --##-- + --##-- + -- Wait for notification that collector model finished measurement --##-- + trigger_base.addblock.wait(tmName[1],"waitCollectorStep",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY2 ) --##-- + -- Loop to notify collector model of next iteration --##-- + trigger_base.addblock.branch.counter(tmName[1], "branch-gate", "notifyCollectorStep", bcSMUsettings[3]) --##-- + -- Notify collector model that voltage sweep has completed + trigger_base.addblock.notify(tmName[1],"notifySweepDone",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY5) + trigger_base.addblock.source.action.bias(tmName[1],"basezero", infoSMU[2]) + +--######################################### Collector Trigger model ##########################################-- +--############################################################################################################-- + + -- Set source values for linear voltage sweep + collectorSMU.trigger.source.linearv(bcSMUsettings[1], bcSMUsettings[2], bcSMUsettings[3]) + -- Set trigger model measurements to i/v and store in default buffers + collectorSMU.trigger.measure.iv(collectorSMU.defbuffer1,collectorSMU.defbuffer2) + + local trigger_collector = slot[infoSMU[3]].trigger.model + trigger_collector.create(tmName[2]) + + -- Wait for notification from base to begin sweep -- Loop 1 + trigger_collector.addblock.wait(tmName[2],"waitStep",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY1 ) --##-- + -- Advance source to next voltage value --##-- + trigger_collector.addblock.source.action.step(tmName[2], "collectorVoltage", infoSMU[4]) --##-- + trigger_collector.addblock.delay.constant(tmName[2],"meaDelay",meaSettings[2]) --##-- + -- Measure i/v on collector --##-- + trigger_collector.addblock.measure(tmName[2], "measure", infoSMU[4], 1) --##-- + -- Notify base model that measurment has been completed --##-- + trigger_collector.addblock.notify(tmName[2],"notifyStep",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY2) --##-- + -- Loop to wait for base notification --##-- + trigger_collector.addblock.branch.counter(tmName[2], "branch-drain", "waitStep", bcSMUsettings[3]) --##-- + + -- Wait for notification that sweep has been completed + trigger_collector.addblock.wait(tmName[2],"waitSweepDone",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY5 ) + trigger_collector.addblock.source.action.bias(tmName[2], "collectorzero", infoSMU[4]) + +--############################################################################################################-- + + -- Initiate trigger models, delete when completed + baseSMU.source.output = 1 + baseSMU.source.output = 1 + collectorSMU.source.output = 1 + collectorSMU.source.output = 1 + + trigger_collector.initiate(tmName[2]) + trigger_base.initiate(tmName[1]) + waitcomplete() + baseSMU.source.output = 0 + collectorSMU.source.output = 0 + + trigger_collector.delete(tmName[2]) + trigger_base.delete(tmName[1]) + + -- Display results from buffers + print("Vb\t\t Ib\t\t Vce\t\t Ic") + for j = 1, baseSMU.defbuffer1.n , 1 do + print(baseSMU.defbuffer2[j],baseSMU.defbuffer1[j],collectorSMU.defbuffer2[j],collectorSMU.defbuffer1[j]) + end + + -- Check for errors and + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + errorQUEUE() +end + +-- information of SMU +local base_slot = 1 +local collector_slot = 1 +local common_slot = 1 +local base_channel = 1 +local collector_channel = 2 + +-- Base & Collector Channel Settings +local startVbVc = 0 +local stopVbVc = 1 +local noSwpPoints = 41 +local limitIbIc = 500e-3 +-- Measure Settings for both channels +local nplc = 1 +local mDelay = 0 + +--Gummel_VbIc_RunTimeEnv({base_slot,base_channel,collector_slot,collector_channel},{startVbVc,stopVbVc,noSwpPoints,limitIbIc},{nplc,mDelay}) + +Gummel_VbIc_TriggerInOneSlot({common_slot,base_channel,collector_channel},{startVbVc,stopVbVc,noSwpPoints,limitIbIc},{nplc,mDelay},{"tm_vbic"}) + +--Gummel_VbIc_TwoTriggers({base_slot,base_channel,collector_slot,collector_channel},{startVbVc,stopVbVc,noSwpPoints,limitIbIc},{nplc,mDelay},{"tm_base", "tm_collector"}) + + + diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Gummel/BJT_Gummel_Vblc.py b/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Gummel/BJT_Gummel_Vblc.py new file mode 100644 index 0000000..5a9b4fe --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Gummel/BJT_Gummel_Vblc.py @@ -0,0 +1,278 @@ +################################################################################# +# +# Script File: BJT_Gummel_Vblc.py +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# gummel curve test for n-type bipolar junction transitor devices. +# The purpose is show that you can perform the semiductor device gummel characterization +# with the SMUs As written, two channels of SMU are assigned to the collector/base. +# LOs from the two channels are tied together to emitter. There are three different +# scripts for the same test. One is operation in run time environment, the other +# is single trigger model for both channels in the same slot. The third is another trigger +# model example for each channel in different slot or in the same slot in the trigger +# trigger model. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 2 channel SMUs +# 1 BJT Transistor +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +################################################################################# + +def plotResults(x, y1, y2): + import matplotlib.pyplot as pt + + pt.plot(x, y1, color="blue") + pt.plot(x, y2, color="orange") + + pt.ylabel("Current (A)") + pt.xlabel("Base Voltage (V)") + pt.xlim(.05, max(x)) + + pt.grid(True) + pt.title("BJT Gummel Plot") + pt.yscale('log') + + pt.show() + +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# For using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# Information of SMU +base_slot = 1 +collector_slot = 1 +common_slot = 1 +base_channel = 1 +collector_channel = 2 + +# Base & Collector Channel Settings +startVbVc = 0 +stopVbVc = 1 +noSwpPoints = 41 +limitIbIc = 500e-3 +# Measure Settings for both channels +nplc = 1 +mDelay = 0 + +tm_name1 = "tm_vbic" +tm_name2 = "tm_base" +tm_name3 = "tm_collector" + +def Gummel_VbIc_TriggerInOneSlot(): + baseSMU = f"slot[{common_slot}].smu[{base_channel}]" + collectorSMU = f"slot[{common_slot}].smu[{collector_channel}]" + smu_id = [baseSMU, collectorSMU] + # Configure both channels for a voltage sweep + for i in smu_id: + inst.write(f"{i}.reset()") + inst.write(f"{i}.source.func = {i}.FUNC_DC_VOLTAGE") + inst.write(f"{i}.sense = {i}.SENSE_2WIRE") + inst.write(f"{i}.source.rangev = {max(abs(startVbVc), abs(stopVbVc))}") + inst.write(f"{i}.source.limiti = {limitIbIc}") + inst.write(f"{i}.source.levelv = 0") + inst.write(f"{i}.measure.nplc = {nplc}") + inst.write(f"{i}.measure.autorangei = 1") + inst.write(f"{i}.defbuffer1.clear()") + inst.write(f"{i}.defbuffer1.appendmode = 1") + inst.write(f"{i}.defbuffer2.clear()") + inst.write(f"{i}.defbuffer2.appendmode = 1") + + # Setup linear voltage sweep for both channels + inst.write(f"{collectorSMU}.trigger.source.linearv({startVbVc}, {stopVbVc}, {noSwpPoints})") + inst.write(f"{baseSMU}.trigger.source.linearv({startVbVc}, {stopVbVc}, {noSwpPoints})") + # Set trigger models to measure i/v and store in default buffers + inst.write(f"{baseSMU}.trigger.measure.iv({baseSMU}.defbuffer1, {baseSMU}.defbuffer2)") + inst.write(f"{collectorSMU}.trigger.measure.iv({collectorSMU}.defbuffer1, {collectorSMU}.defbuffer2)") + +########################################################### Trigger model ############################################################ +###################################################################################################################################### + + triggerModel = f"slot[{common_slot}].trigger.model" + inst.write(f"{triggerModel}.create(\"{tm_name1}\")") + + # Advance both channels to the next volatge value ## Loop 1 + inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name1}\", \"collectorVoltage\", {collector_channel})") ##--## + inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name1}\", \"baseVoltage\", {base_channel})") ##--## + inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name1}\", \"meaDelay\", {mDelay})") ##--## + # Measure i/v ##--## + inst.write(f"{triggerModel}.addblock.measure(\"{tm_name1}\", \"measure\", {base_channel}, 1)") ##--## + inst.write(f"{triggerModel}.addblock.measure(\"{tm_name1}\", \"measure2\", {collector_channel}, 1)") ##--## + # Loop for every value in volgate sweeps ##--## + inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name1}\", \"branch-counter\", \"collectorVoltage\", {noSwpPoints})") ##--## + +###################################################################################################################################### + + # Initate trigger model, delete when finished + inst.write(f"{baseSMU}.source.output = 1") + inst.write(f"{collectorSMU}.source.output = 1") + inst.write(f"{triggerModel}.initiate(\"{tm_name1}\")") + inst.write("waitcomplete()") + inst.write(f"{baseSMU}.source.output = 0") + inst.write(f"{collectorSMU}.source.output = 0") + inst.write(f"{triggerModel}.delete(\"{tm_name1}\")") + + # Fetch data from buffers + defBuffer1 = inst.query(f"printbuffer(1, {baseSMU}.defbuffer1.n, {baseSMU}.defbuffer1)").split(",") + defBuffer1 = [float(x) for x in defBuffer1] + defBuffer2 = inst.query(f"printbuffer(1, {baseSMU}.defbuffer2.n, {baseSMU}.defbuffer2)").split(",") + defBuffer2 = [float(x) for x in defBuffer2] + defBuffer3 = inst.query(f"printbuffer(1, {collectorSMU}.defbuffer1.n, {collectorSMU}.defbuffer1)").split(",") + defBuffer3 = [float(x) for x in defBuffer3] + defBuffer4 = inst.query(f"printbuffer(1, {collectorSMU}.defbuffer2.n, {collectorSMU}.defbuffer2)").split(",") + defBuffer4 = [float(x) for x in defBuffer4] + + # Display results + print("Vb","Ib","Vce","Ic",sep="\t\t") + for i in range(len(defBuffer1)): + print(f"{defBuffer2[i]:.5e}", + f"{defBuffer1[i]:.5e}", + f"{defBuffer4[i]:.5e}", + f"{defBuffer3[i]:.5e}", + sep="\t" + ) + + inst.clear() + inst.close() + + plotResults(defBuffer2, defBuffer1, defBuffer3) + +def Gummel_VbIc_TwoTriggers(): + baseSMU = f"slot[{base_slot}].smu[{base_channel}]" + collectorSMU = f"slot[{collector_slot}].smu[{collector_channel}]" + smu_id = [baseSMU, collectorSMU] + # Configure both channels for a voltage sweep + for i in smu_id: + inst.write(f"{i}.reset()") + inst.write(f"{i}.source.func = {i}.FUNC_DC_VOLTAGE") + inst.write(f"{i}.sense = {i}.SENSE_2WIRE") + inst.write(f"{i}.source.rangev = {max(abs(startVbVc), abs(stopVbVc))}") + inst.write(f"{i}.source.limiti = {limitIbIc}") + inst.write(f"{i}.source.levelv = 0") + inst.write(f"{i}.measure.nplc = {nplc}") + inst.write(f"{i}.measure.autorangei = 1") + inst.write(f"{i}.defbuffer1.clear()") + inst.write(f"{i}.defbuffer1.appendmode = 1") + inst.write(f"{i}.defbuffer2.clear()") + inst.write(f"{i}.defbuffer2.appendmode = 1") + +######################################################### Base trigger model ######################################################### +###################################################################################################################################### + + # Set source values for linear voltage sweep + inst.write(f"{baseSMU}.trigger.source.linearv({startVbVc}, {stopVbVc}, {noSwpPoints})") + # Set trigger model measurements to i/v and store in default buffers + inst.write(f"{baseSMU}.trigger.measure.iv({baseSMU}.defbuffer1, {baseSMU}.defbuffer2)") + + trigger_base = f"slot[{base_slot}].trigger.model" + inst.write(f"{trigger_base}.create(\"{tm_name2}\")") + + # Notify collector model that sweep has begun ## Loop 1 + inst.write(f"{trigger_base}.addblock.notify(\"{tm_name2}\",\"notifyCollectorStep\",slot[{base_slot}].trigger.model.EVENT_NOTIFY1)") ##--## + # Advance source to next voltage value ##--## + inst.write(f"{trigger_base}.addblock.source.action.step(\"{tm_name2}\",\"baseVoltage\",{base_channel})") ##--## + inst.write(f"{trigger_base}.addblock.delay.constant(\"{tm_name2}\",\"meaDelay\",{mDelay})") ##--## + # Measure i/v on base ##--## + inst.write(f"{trigger_base}.addblock.measure(\"{tm_name2}\",\"measure\",{base_channel},1)") ##--## + ##--## + # Wait for notification that collector model finished measurement ##--## + inst.write(f"{trigger_base}.addblock.wait(\"{tm_name2}\",\"waitCollectorStep\",slot[{collector_slot}].trigger.model.EVENT_NOTIFY2)") ##--## + # Loop to notify collecto model of next iteration ##--## + inst.write(f"{trigger_base}.addblock.branch.counter(\"{tm_name2}\",\"branch-gate\",\"notifyCollectorStep\",{noSwpPoints})") ##--## + # Notify collector model that voltage sweep has completed + inst.write(f"{trigger_base}.addblock.notify(\"{tm_name2}\",\"notifySweepDone\",slot[{base_slot}].trigger.model.EVENT_NOTIFY3)") + inst.write(f"{trigger_base}.addblock.source.action.bias(\"{tm_name2}\",\"basezero\",{base_channel})") + +####################################################### Collector trigger model ###################################################### +###################################################################################################################################### + + # Set source values for linear voltage sweep + inst.write(f"{collectorSMU}.trigger.source.linearv({startVbVc}, {stopVbVc}, {noSwpPoints})") + # Set trigger model measurements to i/v and store in default buffers + inst.write(f"{collectorSMU}.trigger.measure.iv({collectorSMU}.defbuffer1, {collectorSMU}.defbuffer2)") + + trigger_collector = f"slot[{collector_slot}].trigger.model" + inst.write(f"{trigger_collector}.create(\"{tm_name3}\")") + + # Wait for notification from base to begin sweep ## Loop 1 + inst.write(f"{trigger_collector}.addblock.wait(\"{tm_name3}\",\"waitStep\",slot[{base_slot}].trigger.model.EVENT_NOTIFY1)") ##--## + # Advance source to next voltage value ##--## + inst.write(f"{trigger_collector}.addblock.source.action.step(\"{tm_name3}\",\"collectorVoltage\",{collector_channel})") ##--## + inst.write(f"{trigger_collector}.addblock.delay.constant(\"{tm_name3}\",\"meaDelay\",{mDelay})") ##--## + # Measure i/v on collector ##--## + inst.write(f"{trigger_collector}.addblock.measure(\"{tm_name3}\",\"measure\",{collector_channel},1)") ##--## + # Notify vase model that measurement has been completed ##--## + inst.write(f"{trigger_collector}.addblock.notify(\"{tm_name3}\",\"notifyStep\",slot[{collector_slot}].trigger.model.EVENT_NOTIFY2)") ##--## + # Loop to wait for base notification ##--## + inst.write(f"{trigger_collector}.addblock.branch.counter(\"{tm_name3}\",\"branch-drain\",\"waitStep\",{noSwpPoints})") ##--## + + # Wait for notification that sweep has been completed + inst.write(f"{trigger_collector}.addblock.wait(\"{tm_name3}\",\"waitSweepDone\",slot[{base_slot}].trigger.model.EVENT_NOTIFY3)") + inst.write(f"{trigger_collector}.addblock.source.action.bias(\"{tm_name3}\",\"collectorzero\",{collector_channel})") + +###################################################################################################################################### + + # Initiate trigger models, delete when completed + inst.write(f"{baseSMU}.source.output = 1") + inst.write(f"{collectorSMU}.source.output = 1") + inst.write(f"{trigger_collector}.initiate(\"{tm_name3}\")") + inst.write(f"{trigger_base}.initiate(\"{tm_name2}\")") + inst.write("waitcomplete()") + inst.write(f"{baseSMU}.source.output = 0") + inst.write(f"{collectorSMU}.source.output = 0") + inst.write(f"{trigger_base}.delete(\"{tm_name2}\")") + inst.write(f"{trigger_collector}.delete(\"{tm_name3}\")") + + # Fetch data from buffers + defBuffer1 = inst.query(f"printbuffer(1, {baseSMU}.defbuffer1.n, {baseSMU}.defbuffer1)").split(",") + defBuffer1 = [float(x) for x in defBuffer1] + defBuffer2 = inst.query(f"printbuffer(1, {baseSMU}.defbuffer2.n, {baseSMU}.defbuffer2)").split(",") + defBuffer2 = [float(x) for x in defBuffer2] + defBuffer3 = inst.query(f"printbuffer(1, {collectorSMU}.defbuffer1.n, {collectorSMU}.defbuffer1)").split(",") + defBuffer3 = [float(x) for x in defBuffer3] + defBuffer4 = inst.query(f"printbuffer(1, {collectorSMU}.defbuffer2.n, {collectorSMU}.defbuffer2)").split(",") + defBuffer4 = [float(x) for x in defBuffer4] + + # Display results + print("Vb","Ib","Vce","Ic",sep="\t\t") + for i in range(len(defBuffer1)): + print(f"{defBuffer2[i]:.5e}", + f"{defBuffer1[i]:.5e}", + f"{defBuffer4[i]:.5e}", + f"{defBuffer3[i]:.5e}", + sep="\t" + ) + + inst.clear() + inst.close() + + plotResults(defBuffer2, defBuffer1, defBuffer3) + +Gummel_VbIc_TriggerInOneSlot() +#Gummel_VbIc_TwoTriggers() + diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Gummel/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Gummel/README.md new file mode 100644 index 0000000..d8be55d --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Gummel/README.md @@ -0,0 +1,15 @@ +# BJT Gummel Characterization + +Two channels are used to perform voltage sweeps for a Gummel characterization of a BJT. Measurements are recorded throughout the test. + +NOTE: There are two functions provided. “Gummel_VbIc_TriggerInOneSlot” handles 2 channel on one SMU and “Gummel_VbIc_TwoTriggers” handles using two SMUs in different slots. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Vce-Ic/BJT_Vclec_Curve.py b/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Vce-Ic/BJT_Vclec_Curve.py new file mode 100644 index 0000000..49977f0 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Vce-Ic/BJT_Vclec_Curve.py @@ -0,0 +1,311 @@ +################################################################################# +# +# Script File: BJT_Vclec_Curve.py +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# drain family of curve test for MOSFET devices. The purpose is show that +# you can perform the semiductor device characterization with the SMUs +# As written, two channels of SMU are assigned to the base/collector and the two +# LOs from the two channels are tied together to the emitter. There are three different +# scripts for the same test. One is operation in run time environment, the other +# is single trigger model for both channels in the same slot. The third is another trigger +# model example for each channel in different slot or in the same slot in the trigger +# trigger model. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 2 channel SMUs +# 1 BJT transistor +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +################################################################################# + +def plotResults(x, y, numPoints, numSeries): + import matplotlib.pyplot as pt + + for i in range(numSeries): + pt.plot(x[i*numPoints:i*numPoints+numPoints], y[i*numPoints:i*numPoints+numPoints], '-', color="blue") + pt.ylabel("Collector Current (A)") + pt.xlabel("Collector Voltage (V)") + pt.grid(True) + pt.title("BJT Family of Curves") + + pt.show() + +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 20000 + +# information of SMU +base_slot = 1 +collector_slot = 1 +common_slot = 1 +base_channel = 1 +collector_channel = 2 +# Collector Channel Settings +startVc = 0 +stopVc = 2 +noSwpPoints = 41 +rangeIc = 250e-3 +limitIc = 250e-3 +# Base Channel Settings +startIb = 3.0e-3 +stopIb = 10.0e-3 +noStpPoints = 5 +limitVb = 6 +# Measure Settings for both channels +nplc = 1 +mDelay = 0 + +tm_name1 = "tm_vcic" +tm_name2 = "tm_base" +tm_name3 = "tm_collector" + +def OutputCurve_VceIc_TriggerInOneSlot(): + baseSMU = f"slot[{common_slot}].smu[{base_channel}]" + collectorSMU = f"slot[{common_slot}].smu[{collector_channel}]" + smu_id = [baseSMU, collectorSMU] + # Configure both channel settings + for i in range(2): + inst.write(f"{smu_id[i]}.reset()") + # Set base to perform current sweep + if smu_id[i] == baseSMU: + inst.write(f"{smu_id[i]}.source.func = {smu_id[i]}.FUNC_DC_CURRENT") + inst.write(f"{smu_id[i]}.source.rangei = {max(abs(startIb), abs(stopIb))}") + inst.write(f"{smu_id[i]}.source.limitv = {limitVb}") + # Set collector to perform voltage sweep + else: + inst.write(f"{smu_id[i]}.source.func = {smu_id[i]}.FUNC_DC_VOLTAGE") + inst.write(f"{smu_id[i]}.source.rangev = {max(abs(startVc), abs(stopVc))}") + inst.write(f"{smu_id[i]}.source.limiti = {limitIc}") + inst.write(f"{smu_id[i]}.measure.rangei = {rangeIc}") + + inst.write(f"{smu_id[i]}.sense = {smu_id[i]}.SENSE_2WIRE") + inst.write(f"{smu_id[i]}.measure.nplc = {nplc}") + + inst.write(f"{smu_id[i]}.defbuffer1.clear()") + inst.write(f"{smu_id[i]}.defbuffer1.appendmode = 1") + inst.write(f"{smu_id[i]}.defbuffer2.clear()") + inst.write(f"{smu_id[i]}.defbuffer2.appendmode = 1") + # Configure voltage and current sweeps + inst.write(f"{collectorSMU}.trigger.source.linearv({startVc}, {stopVc}, {noSwpPoints})") + # Configure trigger model measurements to i/v and store in default buffers + inst.write(f"{collectorSMU}.trigger.measure.iv({collectorSMU}.defbuffer1, {collectorSMU}.defbuffer2)") + inst.write(f"{baseSMU}.trigger.source.lineari({startIb}, {stopIb}, {noStpPoints})") + inst.write(f"{baseSMU}.trigger.measure.iv({baseSMU}.defbuffer1, {baseSMU}.defbuffer2)") + +########################################################## Trigger model ########################################################### +#################################################################################################################################### + + triggerModel = f"slot[{common_slot}].trigger.model" + inst.write(f"{triggerModel}.create(\"{tm_name1}\")") + # Advance base voltage to next value in sweep ## Loop 2 + inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name1}\", \"baseVoltage\", {base_channel})") ##--## + # Advance collector current to next value in sweep ## Loop 1 ##--## + inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name1}\", \"collectorVoltage\", {collector_channel})") ##--## ##--## + # Measure both channels i/v ##--## ##--## + inst.write(f"{triggerModel}.addblock.measure(\"{tm_name1}\", \"measure\", {base_channel}, 1)") ##--## ##--## + inst.write(f"{triggerModel}.addblock.measure(\"{tm_name1}\", \"measure2\", {collector_channel}, 1)") ##--## ##--## + # Loop back to advancing collector voltage ##--## ##--## + inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name1}\", \"branch-collector\", \"collectorVoltage\", {noSwpPoints})") ##--## ##--## + # Loop back to advancing base current ##--## + inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name1}\", \"branch-base\", \"baseVoltage\", {noStpPoints})") ##--## + # Reset sources to 0 + inst.write(f"{triggerModel}.addblock.source.action.bias(\"{tm_name1}\", \"basebiaszeo\", {base_channel})") + inst.write(f"{triggerModel}.addblock.source.action.bias(\"{tm_name1}\", \"collectorbiaszeo\", {collector_channel})") + +#################################################################################################################################### + + # Initiate trigger model, delete when completed + inst.write(f"{baseSMU}.source.output = 1") + inst.write(f"{collectorSMU}.source.output = 1") + + inst.write(f"{triggerModel}.initiate(\"{tm_name1}\")") + inst.write("waitcomplete()") + + inst.write(f"{baseSMU}.source.output = 0") + inst.write(f"{collectorSMU}.source.output = 0") + inst.write(f"{triggerModel}.delete(\"{tm_name1}\")") + + # Fetch contents of buffer + defBuffer1 = inst.query(f"printbuffer(1, {baseSMU}.defbuffer1.n, {baseSMU}.defbuffer1)").split(",") + defBuffer1 = [float(x) for x in defBuffer1] + defBuffer2 = inst.query(f"printbuffer(1, {baseSMU}.defbuffer2.n, {baseSMU}.defbuffer2)").split(",") + defBuffer2 = [float(x) for x in defBuffer2] + defBuffer3 = inst.query(f"printbuffer(1, {collectorSMU}.defbuffer1.n, {collectorSMU}.defbuffer1)").split(",") + defBuffer3 = [float(x) for x in defBuffer3] + defBuffer4 = inst.query(f"printbuffer(1, {collectorSMU}.defbuffer2.n, {collectorSMU}.defbuffer2)").split(",") + defBuffer4 = [float(x) for x in defBuffer4] + + # Display results + print("Vb","Ib","Vce","Ic",sep="\t\t") + for i in range(len(defBuffer1)): + print(f"{defBuffer2[i]:.5e}", + f"{defBuffer1[i]:.5e}", + f"{defBuffer4[i]:.5e}", + f"{defBuffer3[i]:.5e}", + sep="\t" + ) + inst.clear() + inst.close() + plotResults(defBuffer4, defBuffer3, noSwpPoints, noStpPoints) + +def OutputCurve_VceIc_TwoTriggers(): + baseSMU = f"slot[{base_slot}].smu[{base_channel}]" + collectorSMU = f"slot[{collector_slot}].smu[{collector_channel}]" + smu_id = [baseSMU, collectorSMU] + # Configure channel settings + for i in range(2): + inst.write(f"{smu_id[i]}.reset()") + # Set base channel to perform current sweep + if smu_id[i] == baseSMU: + inst.write(f"{smu_id[i]}.source.func = {smu_id[i]}.FUNC_DC_CURRENT") + inst.write(f"{smu_id[i]}.source.rangei = {max(abs(startIb), abs(stopIb))}") + inst.write(f"{smu_id[i]}.source.limitv = {limitVb}") + # Set collector channel to perform voltage sweep + else: + inst.write(f"{smu_id[i]}.source.func = {smu_id[i]}.FUNC_DC_VOLTAGE") + inst.write(f"{smu_id[i]}.source.rangev = {max(abs(startVc), abs(stopVc))}") + inst.write(f"{smu_id[i]}.source.limiti = {limitIc}") + inst.write(f"{smu_id[i]}.measure.rangei = {rangeIc}") + + inst.write(f"{smu_id[i]}.sense = {smu_id[i]}.SENSE_2WIRE") + inst.write(f"{smu_id[i]}.measure.nplc = {nplc}") + + inst.write(f"{smu_id[i]}.defbuffer1.clear()") + inst.write(f"{smu_id[i]}.defbuffer1.appendmode = 1") + inst.write(f"{smu_id[i]}.defbuffer2.clear()") + inst.write(f"{smu_id[i]}.defbuffer2.appendmode = 1") + +##################################################### Collector trigger model ###################################################### +#################################################################################################################################### + + # Configure trigger model for a linear voltage sweep + inst.write(f"{collectorSMU}.trigger.source.linearv({startVc}, {stopVc}, {noSwpPoints})") + # Set trigger model measurements to i/v and store in default buffers + inst.write(f"{collectorSMU}.trigger.measure.iv({collectorSMU}.defbuffer1, {collectorSMU}.defbuffer2)") + + trigger_collector = f"slot[{collector_slot}].trigger.model" + inst.write(f"{trigger_collector}.create(\"{tm_name3}\") ") + + # Notify base model that collector model has begun ## Loop 2 + inst.write(f"{trigger_collector}.addblock.notify(\"{tm_name3}\",\"notifyStep\",slot[{collector_slot}].trigger.model.EVENT_NOTIFY1)") ##--## + ##--## + # Wait for notification that base current has been advanced ##--## + inst.write(f"{trigger_collector}.addblock.wait(\"{tm_name3}\",\"waitStep\",slot[{base_slot}].trigger.model.EVENT_NOTIFY2 )") ##--## + # Advance voltage source value ## Loop 1 ##--## + inst.write(f"{trigger_collector}.addblock.source.action.step(\"{tm_name3}\",\"collectorSweep\",{collector_channel})") ##--## ##--## + # Notify vase that voltage sweep has been advanced ##--## ##--## + inst.write(f"{trigger_collector}.addblock.notify(\"{tm_name3}\",\"notifyMeasure\",slot[{collector_slot}].trigger.model.EVENT_NOTIFY3)") ##--## ##--## + # Measure collector channel i/v ##--## ##--## + inst.write(f"{trigger_collector}.addblock.measure(\"{tm_name3}\",\"measure\",{collector_channel}, 1)") ##--## ##--## + ##--## ##--## + # Wait for notification from vase that measurement has been taken ##--## ##--## + inst.write(f"{trigger_collector}.addblock.wait(\"{tm_name3}\",\"waitMeasure\",slot[{base_slot}].trigger.model.EVENT_NOTIFY4 )") ##--## ##--## + # Loop back to advance voltage value ##--## ##--## + inst.write(f"{trigger_collector}.addblock.branch.counter(\"{tm_name3}\",\"branch-sweep\",\"collectorSweep\",{noSwpPoints})") ##--## ##--## + # Notify base that voltage sweep has completed ##--## + inst.write(f"{trigger_collector}.addblock.notify(\"{tm_name3}\",\"notifySweepDone\",slot[{collector_slot}].trigger.model.EVENT_NOTIFY5)") ##--## + # Loop back to notify base that sweep has begun ##--## + inst.write(f"{trigger_collector}.addblock.branch.counter(\"{tm_name3}\",\"branch-step\",\"notifyStep\",{noStpPoints})") ##--## + inst.write(f"{trigger_collector}.addblock.source.action.bias(\"{tm_name3}\",\"collectorbiaszeo\",{collector_channel})") + +####################################################### Base trigger model ######################################################### +#################################################################################################################################### + + # Configure trigger model for a linear current sweep + inst.write(f"{baseSMU}.trigger.source.lineari({startIb}, {stopIb}, {noStpPoints})") + # Set trigger model measurements i/v and store in default buffers + inst.write(f"{baseSMU}.trigger.measure.iv({baseSMU}.defbuffer1,{baseSMU}.defbuffer2)") + + + trigger_base = f"slot[{base_slot}].trigger.model" + inst.write(f"{trigger_base}.create(\"{tm_name2}\")") + + # Wait for notification that collector model has started ## Loop 2 + inst.write(f"{trigger_base}.addblock.wait(\"{tm_name2}\",\"waitStep\",slot[{collector_slot}].trigger.model.EVENT_NOTIFY1 )") ##--## + # Advance current source value ##--## + inst.write(f"{trigger_base}.addblock.source.action.step(\"{tm_name2}\",\"baseVoltage\",{base_channel})") ##--## + # Notify collector that current has been advanced ##--## + inst.write(f"{trigger_base}.addblock.notify(\"{tm_name2}\",\"notify\",slot[{base_slot}].trigger.model.EVENT_NOTIFY2)") ##--## + ##--## + # Wait for notification from collector that measurement has been taken ## Loop 1 ##--## + inst.write(f"{trigger_base}.addblock.wait(\"{tm_name2}\",\"waitMeasure\",slot[{collector_slot}].trigger.model.EVENT_NOTIFY3 )") ##--## ##--## + # Measure base channel i/v ##--## ##--## + inst.write(f"{trigger_base}.addblock.measure(\"{tm_name2}\",\"measure\",{base_channel},1)") ##--## ##--## + # Notify collector that measurement has been taken ##--## ##--## + inst.write(f"{trigger_base}.addblock.notify(\"{tm_name2}\",\"notify\",slot[{base_slot}].trigger.model.EVENT_NOTIFY4)") ##--## ##--## + # Loop back to wait for notification from collector ##--## ##--## + inst.write(f"{trigger_base}.addblock.branch.counter(\"{tm_name2}\",\"branch-Measure\",\"waitMeasure\",{noSwpPoints})") ##--## ##--## + ##--## + # Wait for notification that collector voltage sweep has been completed ##--## + inst.write(f"{trigger_base}.addblock.wait(\"{tm_name2}\",\"waitSweepDone\",slot[{collector_slot}].trigger.model.EVENT_NOTIFY5 )") ##--## + # Loop back to wait for notification from collector and advance current ##--## + inst.write(f"{trigger_base}.addblock.branch.counter(\"{tm_name2}\",\"branch-Step\",\"waitStep\",{noStpPoints})") ##--## + inst.write(f"{trigger_base}.addblock.source.action.bias(\"{tm_name2}\",\"gatebiaszero\",{base_channel})") + +#################################################################################################################################### + + # Initiate trigger models, delete when finished + inst.write(f"{baseSMU}.source.output = 1") + inst.write(f"{collectorSMU}.source.output = 1") + inst.write(f"{trigger_base}.initiate(\"{tm_name2}\")") + inst.write(f"{trigger_collector}.initiate(\"{tm_name3}\")") + + inst.write(f"waitcomplete()") + + inst.write(f"{baseSMU}.source.output = 0") + inst.write(f"{collectorSMU}.source.output = 0") + + inst.write(f"{trigger_base}.delete(\"{tm_name2}\")") + inst.write(f"{trigger_collector}.delete(\"{tm_name3}\")") + + # Fetch contents of buffer + defBuffer1 = inst.query(f"printbuffer(1, {baseSMU}.defbuffer1.n, {baseSMU}.defbuffer1)").split(",") + defBuffer1 = [float(x) for x in defBuffer1] + defBuffer2 = inst.query(f"printbuffer(1, {baseSMU}.defbuffer2.n, {baseSMU}.defbuffer2)").split(",") + defBuffer2 = [float(x) for x in defBuffer2] + defBuffer3 = inst.query(f"printbuffer(1, {collectorSMU}.defbuffer1.n, {collectorSMU}.defbuffer1)").split(",") + defBuffer3 = [float(x) for x in defBuffer3] + defBuffer4 = inst.query(f"printbuffer(1, {collectorSMU}.defbuffer2.n, {collectorSMU}.defbuffer2)").split(",") + defBuffer4 = [float(x) for x in defBuffer4] + + # Display results + print("Vb","Ib","Vce","Ic",sep="\t\t") + for i in range(len(defBuffer1)): + print(f"{defBuffer2[i]:.5e}", + f"{defBuffer1[i]:.5e}", + f"{defBuffer4[i]:.5e}", + f"{defBuffer3[i]:.5e}", + sep="\t" + ) + inst.clear() + inst.close() + plotResults(defBuffer4, defBuffer3, noSwpPoints, noStpPoints) + +OutputCurve_VceIc_TriggerInOneSlot() +#OutputCurve_VceIc_TwoTriggers() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Vce-Ic/BJT_Vclec_Curve.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Vce-Ic/BJT_Vclec_Curve.tsp new file mode 100644 index 0000000..e68a2e7 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Vce-Ic/BJT_Vclec_Curve.tsp @@ -0,0 +1,369 @@ +--[[ +################################################################################ + +Script File: BJT_Vclec_Curve.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + drain family of curve test for MOSFET devices. The purpose is show that + you can perform the semiductor device characterization with the SMUs + As written, two channels of SMU are assigned to the base/collector and the two + LOs from the two channels are tied together to the emitter. There are three different + scripts for the same test. One is operation in run time environment, the other + is single trigger model for both channels in the same slot. The third is another trigger + model example for each channel in different slot or in the same slot in the trigger + trigger model. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 2 channel SMUs + 1 BJT transistor + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * OutputCurve_VceIc_RunTimeEnv({base_slot,collector_slot,base_channel,collector_channel},{startVc,stopVc,noSwpPoints,rangeIc,limitIc},{startIb,stopIb,noStpPoints,limitVb},{nplc,mDelay}) + * OutputCurve_VceIc_TriggerInOneSlot({common_slot,base_channel,collector_channel},{startVc,stopVc,noSwpPoints,rangeIc,limitIc},{startIb,stopIb,noStpPoints,limitVb},{nplc,mDelay},{"tm_vcic"}) + * OutputCurve_VceIc_TwoTriggers({base_slot,collector_slot,base_channel,collector_channel},{startVc,stopVc,noSwpPoints,rangeIc,limitIc},{startIb,stopIb,noStpPoints,limitVb},{nplc,mDelay},{"tm_base","tm_collector"}) + +Example Usage: + * OutputCurve_VceIc_RunTimeEnv({base_slot,collector_slot,base_channel,collector_channel},{startVc,stopVc,noSwpPoints,rangeIc,limitIc},{startIb,stopIb,noStpPoints,limitVb},{nplc,mDelay}) + * OutputCurve_VceIc_TriggerInOneSlot({common_slot,base_channel,collector_channel},{startVc,stopVc,noSwpPoints,rangeIc,limitIc},{startIb,stopIb,noStpPoints,limitVb},{nplc,mDelay},{"tm_vcic"}) + * OutputCurve_VceIc_TwoTriggers({base_slot,collector_slot,base_channel,collector_channel},{startVc,stopVc,noSwpPoints,rangeIc,limitIc},{startIb,stopIb,noStpPoints,limitVb},{nplc,mDelay},{"tm_base","tm_collector"}) + +################################################################################ +--]] +function OutputCurve_VceIc_RunTimeEnv(infoSMU,cSMUsettings, bSMUsettings, meaSettings) + -- SMU channel assignment + local baseSMU = slot[infoSMU[1]].smu[infoSMU[2]] + local collectorSMU = slot[infoSMU[3]].smu[infoSMU[4]] + local smu_id = {baseSMU,collectorSMU} + + -- Configure both channel settings + for i = 1, 2 do + smu_id[i].reset() + -- Base set to perform current sweep + if smu_id[i] == baseSMU then + smu_id[i].source.func = smu_id[i].FUNC_DC_CURRENT + smu_id[i].source.rangei = math.max(math.abs(bSMUsettings[1]), math.abs(bSMUsettings[2])) + smu_id[i].source.limitv = bSMUsettings[4] + -- Collector to perform voltage sweep + else + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].source.rangev = math.max(math.abs(cSMUsettings[1]), math.abs(cSMUsettings[2])) + smu_id[i].source.limiti = cSMUsettings[5] + smu_id[i].measure.rangei = cSMUsettings[4] + end + + smu_id[i].sense = smu_id[i].SENSE_2WIRE + smu_id[i].measure.nplc = meaSettings[1] + + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + end + + -- Calculate the change in voltage/current for the two terminals + local deltaVd = (cSMUsettings[2] - cSMUsettings[1])/ (cSMUsettings[3] - 1) + local deltaIg = (bSMUsettings[2] - bSMUsettings[1])/ (bSMUsettings[3] - 1) + + -- Turn outputs on + baseSMU.source.output = 1 + baseSMU.source.output = 1 + + collectorSMU.source.output = 1 + collectorSMU.source.output = 1 + + -- Perform current sweep on collector, voltage sweep on base + print("Vb\t\t Ib\t\t Vce\t\t Ic") + for gateLoop = 1, bSMUsettings[3], 1 do + -- Set base current to next value in sweep + baseSMU.source.leveli = bSMUsettings[1] + ((gateLoop -1) * deltaIg) + for drainLoop = 1, cSMUsettings[3] , 1 do + -- Set collector voltage to next value in sweep + collectorSMU.source.levelv = cSMUsettings[1] + ((drainLoop-1) * deltaVd) + delay(meaSettings[2]) + -- Measure i/v on both channels and store in default buffers + baseSMU.measure.iv(baseSMU.defbuffer1,baseSMU.defbuffer2) + collectorSMU.measure.iv(collectorSMU.defbuffer1,collectorSMU.defbuffer2) + -- Display results + print(baseSMU.defbuffer2[(cSMUsettings[3]*(gateLoop-1)) + drainLoop ],baseSMU.defbuffer1[(cSMUsettings[3]*(gateLoop-1)) + drainLoop],collectorSMU.defbuffer2[(cSMUsettings[3]*(gateLoop-1)) + drainLoop],collectorSMU.defbuffer1[(cSMUsettings[3]*(gateLoop-1)) + drainLoop]) + end + end + baseSMU.source.output = 0 + collectorSMU.source.output = 0 + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + errorQUEUE() +end + +function OutputCurve_VceIc_TriggerInOneSlot(infoSMU,cSMUsettings, bSMUsettings, meaSettings,tmName) + -- SMU channel assignment + local baseSMU = slot[infoSMU[1]].smu[infoSMU[2]] + local collectorSMU = slot[infoSMU[1]].smu[infoSMU[3]] + local smu_id = {baseSMU,collectorSMU} + -- Configure both channel settings + for i = 1, 2 do + smu_id[i].reset() + -- Set base to perform current sweep + if smu_id[i] == baseSMU then + smu_id[i].source.func = smu_id[i].FUNC_DC_CURRENT + smu_id[i].source.rangei = math.max(math.abs(bSMUsettings[1]), math.abs(bSMUsettings[2])) + smu_id[i].source.limitv = bSMUsettings[4] + -- Set collector to perform voltage sweep + else + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].source.rangev = math.max(math.abs(cSMUsettings[1]), math.abs(cSMUsettings[2])) + smu_id[i].source.limiti = cSMUsettings[5] + smu_id[i].measure.rangei = cSMUsettings[4] + end + + smu_id[i].sense = smu_id[i].SENSE_2WIRE + smu_id[i].measure.nplc = meaSettings[1] + + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + end + -- Configure voltage and current sweeps + collectorSMU.trigger.source.linearv(cSMUsettings[1], cSMUsettings[2], cSMUsettings[3]) + -- Configure trigger model measurements to i/v and store in default buffers + collectorSMU.trigger.measure.iv(collectorSMU.defbuffer1,collectorSMU.defbuffer2) + baseSMU.trigger.source.lineari(bSMUsettings[1], bSMUsettings[2], bSMUsettings[3]) + baseSMU.trigger.measure.iv(baseSMU.defbuffer1,baseSMU.defbuffer2) + +--############################################## Trigger model ###############################################-- +--############################################################################################################-- + + local triggerModel = slot[infoSMU[1]].trigger.model + triggerModel.create(tmName[1]) + -- Advance base voltage to next value in sweep -- Loop 2 + triggerModel.addblock.source.action.step(tmName[1], "baseVoltage", infoSMU[2]) --##-- + -- Advance collector current to next value in sweep -- Loop 1 --##-- + triggerModel.addblock.source.action.step(tmName[1], "collectorVoltage", infoSMU[3]) --##-- --##-- + -- Measure both channels i/v --##-- --##-- + triggerModel.addblock.measure(tmName[1], "measure", infoSMU[2], 1) --##-- --##-- + triggerModel.addblock.measure(tmName[1], "measure2", infoSMU[3], 1) --##-- --##-- + -- Loop back to advancing collector voltage --##-- --##-- + triggerModel.addblock.branch.counter(tmName[1], "branch-collector", "collectorVoltage", cSMUsettings[3]) --##-- --##-- + -- Loop back to advancing base current --##-- + triggerModel.addblock.branch.counter(tmName[1], "branch-base", "baseVoltage", bSMUsettings[3]) --##-- + -- Reset sources to 0 + triggerModel.addblock.source.action.bias(tmName[1], "basebiaszeo", infoSMU[2]) + triggerModel.addblock.source.action.bias(tmName[1], "collectorbiaszeo", infoSMU[3]) + +--############################################################################################################-- + + -- Initiate trigger model, delete when completed + baseSMU.source.output = 1 + collectorSMU.source.output = 1 + + triggerModel.initiate(tmName[1]) + waitcomplete() + + baseSMU.source.output = 0 + collectorSMU.source.output = 0 + triggerModel.delete(tmName[1]) + + -- Display contents of buffers + print("Vb\t\t Ib\t\t Vce\t\t Ic") + for j = 1, collectorSMU.defbuffer1.n , 1 do + print(baseSMU.defbuffer2[j],baseSMU.defbuffer1[j],collectorSMU.defbuffer2[j],collectorSMU.defbuffer1[j]) + end + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + errorQUEUE() +end + +function OutputCurve_VceIc_TwoTriggers(infoSMU,cSMUsettings, bSMUsettings, meaSettings,tmName) + -- SMU channel assignment + local baseSMU = slot[infoSMU[1]].smu[infoSMU[3]] + local collectorSMU = slot[infoSMU[2]].smu[infoSMU[4]] + local smu_id = {baseSMU,collectorSMU} + -- Configure channel settings + for i = 1, 2 do + smu_id[i].reset() + -- Set base channel to perform current sweep + if smu_id[i] == baseSMU then + smu_id[i].source.func = smu_id[i].FUNC_DC_CURRENT + smu_id[i].source.rangei = math.max(math.abs(bSMUsettings[1]), math.abs(bSMUsettings[2])) + smu_id[i].source.limitv = bSMUsettings[4] + -- Set collector channel to perform voltage sweep + else + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].source.rangev = math.max(math.abs(cSMUsettings[1]), math.abs(cSMUsettings[2])) + smu_id[i].source.limiti = cSMUsettings[5] + smu_id[i].measure.rangei = cSMUsettings[4] + end + + smu_id[i].sense = smu_id[i].SENSE_2WIRE + smu_id[i].measure.nplc = meaSettings[1] + + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + end + +--######################################### Collector trigger model ##########################################-- +--############################################################################################################-- + + -- Configure trigger model for a linear voltage sweep + collectorSMU.trigger.source.linearv(cSMUsettings[1], cSMUsettings[2], cSMUsettings[3]) + -- Set trigger model measurements to i/v and store in default buffers + collectorSMU.trigger.measure.iv(collectorSMU.defbuffer1,collectorSMU.defbuffer2) + + local trigger_collector = slot[infoSMU[2]].trigger.model + trigger_collector.create(tmName[2]) + + -- Notify base model that collector model has began -- Loop 2 + trigger_collector.addblock.notify(tmName[2],"notifyStep",slot[infoSMU[2]].trigger.model.EVENT_NOTIFY1) --##-- + --##-- + -- Wait for notification that base current has been advanced --##-- + trigger_collector.addblock.wait(tmName[2],"waitStep",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY2 ) --##-- + -- Advance voltage source value -- Loop 1 --##-- + trigger_collector.addblock.source.action.step(tmName[2], "collectorSweep", infoSMU[4]) --##-- --##-- + -- Notify base that voltage sweep has been advanced --##-- --##-- + trigger_collector.addblock.notify(tmName[2],"notifyMeasure",slot[infoSMU[2]].trigger.model.EVENT_NOTIFY3) --##-- --##-- + -- Measure collector channel i/v --##-- --##-- + trigger_collector.addblock.measure(tmName[2], "measure", infoSMU[4], 1) --##-- --##-- + --##-- --##-- + -- Wait for notification from base that mesurement has been taken --##-- --##-- + trigger_collector.addblock.wait(tmName[2],"waitMeasure",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY4 ) --##-- --##-- + -- Loop back to advance voltage value --##-- --##-- + trigger_collector.addblock.branch.counter(tmName[2], "branch-sweep", "collectorSweep", cSMUsettings[3]) --##-- --##-- + -- Notify base that voltage sweep has completed --##-- + trigger_collector.addblock.notify(tmName[2],"notifySweepDone",slot[infoSMU[2]].trigger.model.EVENT_NOTIFY5) --##-- + -- Loop back to notify base that sweep has begun --##-- + trigger_collector.addblock.branch.counter(tmName[2], "branch-step", "notifyStep", bSMUsettings[3]) --##-- + trigger_collector.addblock.source.action.bias(tmName[2], "collectorbiaszeo", infoSMU[4]) + +--############################################ Base trigger model ############################################-- +--############################################################################################################-- + + -- Configure trigger model for a linear current sweep + baseSMU.trigger.source.lineari(bSMUsettings[1], bSMUsettings[2], bSMUsettings[3]) + -- Set trigger model measurements i/v and store in default buffers + baseSMU.trigger.measure.iv(baseSMU.defbuffer1,baseSMU.defbuffer2) + + local trigger_base = slot[infoSMU[1]].trigger.model + trigger_base.create(tmName[1]) + + -- Wait for notification that collector model has started -- Loop 2 + trigger_base.addblock.wait(tmName[1],"waitStep",slot[infoSMU[2]].trigger.model.EVENT_NOTIFY1 ) --##-- + -- Advance current source value --##-- + trigger_base.addblock.source.action.step(tmName[1], "baseVoltage", infoSMU[3]) --##-- + -- Notify collector that current has been advanced --##-- + trigger_base.addblock.notify(tmName[1],"notify",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY2) --##-- + --##-- + -- Wait for notificaiton from collector that measurement has been taken -- Loop 1 --##-- + trigger_base.addblock.wait(tmName[1],"waitMeasure",slot[infoSMU[2]].trigger.model.EVENT_NOTIFY3 ) --##-- --##-- + -- Measure base channel i/v --##-- --##-- + trigger_base.addblock.measure(tmName[1],"measure",infoSMU[3],1) --##-- --##-- + -- Notify collector that measurement has been taken --##-- --##-- + trigger_base.addblock.notify(tmName[1],"notify",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY4) --##-- --##-- + -- Loop back to wait for notification from collector --##-- --##-- + trigger_base.addblock.branch.counter(tmName[1], "branch-Measure", "waitMeasure", cSMUsettings[3]) --##-- --##-- + --##-- + -- Wait for notification that collector voltage sweep has completed --##-- + trigger_base.addblock.wait(tmName[1],"waitSweepDone",slot[infoSMU[2]].trigger.model.EVENT_NOTIFY5 ) --##-- + -- Loop back to wait for notification from collector and advance current --##-- + trigger_base.addblock.branch.counter(tmName[1], "branch-Step", "waitStep", bSMUsettings[3]) --##-- + trigger_base.addblock.source.action.bias(tmName[1], "gatebiaszero", infoSMU[3]) + +--############################################################################################################-- + + -- Initiate trigger models, delete when finished + baseSMU.source.output = 1 + collectorSMU.source.output = 1 + trigger_base.initiate(tmName[1]) + trigger_collector.initiate(tmName[2]) + + waitcomplete() + + baseSMU.source.output = 0 + collectorSMU.source.output = 0 + + trigger_base.delete(tmName[1]) + trigger_collector.delete(tmName[2]) + + -- Display results from default buffers + print("Vb\t\t Ib\t\t Vce\t\t Ic") + for j = 1, collectorSMU.defbuffer1.n , 1 do + print(baseSMU.defbuffer2[j],baseSMU.defbuffer1[j],collectorSMU.defbuffer2[j],collectorSMU.defbuffer1[j]) + end + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + errorQUEUE() +end + +-- Information of SMU +local base_slot = 1 +local collector_slot= 1 +local common_slot = 1 +local base_channel = 1 +local collector_channel = 2 +-- Collector Channel Settings +local startVc = 0 +local stopVc = 2 +local noSwpPoints = 41 +local rangeIc = 250e-3 +local limitIc = 250e-3 +-- Base Channel Settings +local startIb = 3.0e-3 +local stopIb = 10.0e-3 +local noStpPoints = 5 +local limitVb = 6 +-- Measure Settings for both channels +local nplc = 1 +local mDelay = 0 + +--OutputCurve_VceIc_RunTimeEnv({base_slot,collector_slot,base_channel,collector_channel},{startVc,stopVc,noSwpPoints,rangeIc,limitIc},{startIb,stopIb,noStpPoints,limitVb},{nplc,mDelay}) + +OutputCurve_VceIc_TriggerInOneSlot({common_slot,base_channel,collector_channel},{startVc,stopVc,noSwpPoints,rangeIc,limitIc},{startIb,stopIb,noStpPoints,limitVb},{nplc,mDelay},{"tm_vcic"}) + +--OutputCurve_VceIc_TwoTriggers({base_slot,collector_slot,base_channel,collector_channel},{startVc,stopVc,noSwpPoints,rangeIc,limitIc},{startIb,stopIb,noStpPoints,limitVb},{nplc,mDelay},{"tm_base","tm_collector"}) + diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Vce-Ic/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Vce-Ic/README.md new file mode 100644 index 0000000..1765522 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/BJT_Vce-Ic/README.md @@ -0,0 +1,15 @@ +# BJT Vce-Ic Characterization + +Two channels are used to perform a voltage and current sweep for a Vce-Ic characterization of a BJT. Measurements are recorded + +NOTE: There are two functions provided. “OutputCurve_VceIc_TriggerInOneSlot” handles 2 channels on one SMU and “OutputCurve_VceIc_TwoTriggers” handles using two channels on different slots. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/DC_Pulse_LIV_VCSEL_Char/Laser_VSCEL.py b/Examples/Modular_Precision_Test_System/SMU_Examples/DC_Pulse_LIV_VCSEL_Char/Laser_VSCEL.py new file mode 100644 index 0000000..ad35fe7 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/DC_Pulse_LIV_VCSEL_Char/Laser_VSCEL.py @@ -0,0 +1,248 @@ +################################################################################# +# +# Script File: Laser_VSCEL.py +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# current sweeping. The purpose is show that you can perform a laser/VCLSE device +# characterization with the SMUs. As written, one channel of SMU is assigned to +# the anode for the high and cathode for the low terminal. the high of the other SMU +# is assigned to the high of a PD anode and the low is to cathod. One channels performs +# current sweeping on the laser/or VCSEL measuring voltage and current. The other channel +# keeps a constanst voltage bias measurement current of the PD device. This test provides +# three different scripts. One is DC sweep measurement in run time environment. The second +# is still DC test tested in trigger model. The third is a pulse test in trigger model. +# This feature gives a lot of convience and speed in LED sequence test. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 2 channel SMU +# 1 Laser/VSCEL unit +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +################################################################################# + +def plotResults(x, y1, y2): + import matplotlib.pyplot as pt + + fig, ax1 = pt.subplots() + + ax1.plot(x, y1, '-', color="tab:blue") + ax1.set_xlabel("IF (A)") + ax1.set_ylabel("VF (V)", color="tab:blue") + ax1.tick_params(axis='y', labelcolor="tab:blue") + + ax2 = ax1.twinx() + + ax2.plot(x, y2, '-', color="tab:red") + ax2.set_ylabel("I_PD (A)", color="tab:red") + ax2.tick_params(axis='y', labelcolor="tab:red") + + fig.suptitle("L-I_V Test") + fig.tight_layout() + pt.show() + +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# information of SMU +IF_slot = 1 +IF_channel = 1 +PD_slot = 1 +PD_channel = 2 + +# IF Settings +startIF = 0 +stopIF = 100e-3 +noPoints = 60 +rangeI = 1000e-3 +limitV = 6 +measRangeV = 6 + +# PD Source Measure Settings +biasV_PD = 0 +srcRangeV_PD = 6 +measRangeI_PD = 10e-3 +# Mesaure Settings +nplc = 1 +mDelay = 1e-3 +remoteSense = False + +# Pulse Settings +pulsePeriod = 5e-3 # pulsePeriod should be longer than pulseWidth + mDelay + apertureTime +pulseWidth = 3e-3 +apertureTime = 1e-3 + +# Trigger model names +tm_name1 = "tm_if" +tm_name2 = "tm_pd" + +IF_smu = f"slot[{IF_slot}].smu[{IF_channel}]" +PD_smu = f"slot[{PD_slot}].smu[{PD_channel}]" +smu_id = [IF_smu, PD_smu] + +def Pulse_Laser_VCSEL_LIV_inTriggerModel(): + for i in smu_id: + inst.write(f"{i}.reset()") + + # Buffer clear + inst.write(f"{i}.defbuffer1.clear()") + inst.write(f"{i}.defbuffer1.appendmode = 1") + inst.write(f"{i}.defbuffer2.clear()") + inst.write(f"{i}.defbuffer2.appendmode = 1") + + if i == IF_smu: + # Source settings + inst.write(f"{i}.source.func = {i}.FUNC_DC_CURRENT") + inst.write(f"{i}.source.rangei = {rangeI}") + inst.write(f"{i}.source.leveli = 0") + inst.write(f"{i}.source.limitv = {limitV}") + # Measure settings + inst.write(f"{i}.measure.rangev = {measRangeV}") + inst.write(f"{i}.measure.rangei = {rangeI}") + else: + # Source settings + inst.write(f"{i}.source.func = {i}.FUNC_DC_VOLTAGE") + inst.write(f"{i}.source.rangev = {srcRangeV_PD}") + inst.write(f"{i}.source.levelv = {biasV_PD}") + # Measure settings + inst.write(f"{i}.measure.rangei = {measRangeI_PD}") + + inst.write(f"{i}.measure.aperture = {apertureTime}") + + if remoteSense: + inst.write(f"{i}.sense = {i}.SENSE_4WIRE") + else: + inst.write(f"{i}.sense = {i}.SENSE_2WIRE") + +################################################# Source channel trigger model ################################################# +################################################################################################################################ + + inst.write(f"{IF_smu}.trigger.source.lineari({startIF}, {stopIF}, {noPoints})") + inst.write(f"{IF_smu}.trigger.measure.iv({IF_smu}.defbuffer1, {IF_smu}.defbuffer2)") + + # Configure trigger model + trigger_IF = f"slot[{IF_slot}].trigger.model" + inst.write(f"{trigger_IF}.create(\"{tm_name1}\")") + + # Notify PD trigger model to begin + inst.write(f"{trigger_IF}.addblock.notify(\"{tm_name1}\", \"notifyPDStep\", slot[{IF_slot}].trigger.model.EVENT_NOTIFY1)") + + # Wait for PD channel to advance to next source value + inst.write(f"{trigger_IF}.addblock.wait(\"{tm_name1}\", \"waitPDStep\", slot[{PD_slot}].trigger.model.EVENT_NOTIFY2)") + # Advance to next current value in sweep # Loop 1 + inst.write(f"{trigger_IF}.addblock.source.action.step(\"{tm_name1}\", \"IF_sweep\", {IF_channel})") ##--## + inst.write(f"{trigger_IF}.addblock.delay.constant(\"{tm_name1}\", \"meaDelay\", {mDelay}, \"IF_sweep\")") ##--## + # Notify PD trigger model that current value has advanced ##--## + inst.write(f"{trigger_IF}.addblock.notify(\"{tm_name1}\", \"notify\", slot[{IF_slot}].trigger.model.EVENT_NOTIFY3)") ##--## + # Measure source channel i/v ##--## + inst.write(f"{trigger_IF}.addblock.measure(\"{tm_name1}\", \"measure\", {IF_channel}, 1)") ##--## + ##--## + # Wait for notification that PD channel has been measured ##--## + inst.write(f"{trigger_IF}.addblock.wait(\"{tm_name1}\", \"wait\", slot[{PD_slot}].trigger.model.EVENT_NOTIFY4)") ##--## + inst.write(f"{trigger_IF}.addblock.delay.constant(\"{tm_name1}\", \"pulse_width\", {pulseWidth}, \"IF_sweep\")") ##--## + # Set source value back to 0 ##--## + inst.write(f"{trigger_IF}.addblock.source.action.bias(\"{tm_name1}\", \"IF_off\", {IF_channel})") ##--## + inst.write(f"{trigger_IF}.addblock.delay.constant(\"{tm_name1}\", \"pulse_period\", {pulsePeriod}, \"IF_sweep\")") ##--## + # Loop back to advance source current ##--## + inst.write(f"{trigger_IF}.addblock.branch.counter(\"{tm_name1}\", \"IF_branch\", \"IF_sweep\", {noPoints})") ##--## + # Notify PD channel that sweep has completed + inst.write(f"{trigger_IF}.addblock.notify(\"{tm_name1}\", \"notifySweepDone\", slot[{IF_slot}].trigger.model.EVENT_NOTIFY5)") + inst.write(f"{trigger_IF}.addblock.source.action.bias(\"{tm_name1}\", \"IF_bias\", {IF_channel})") + +################################################### PD channel trigger model ################################################### +################################################################################################################################ + + inst.write(f"{PD_smu}.trigger.source.linearv({biasV_PD}, {biasV_PD}, 1)") + inst.write(f"{PD_smu}.trigger.measure.iv({PD_smu}.defbuffer1, {PD_smu}.defbuffer2)") + + trigger_PD = f"slot[{PD_slot}].trigger.model" + inst.write(f"{trigger_PD}.create(\"{tm_name2}\")") + + # Wait for source trigger model to begin + inst.write(f"{trigger_PD}.addblock.wait(\"{tm_name2}\", \"waitStep\", slot[{IF_slot}].trigger.model.EVENT_NOTIFY1)") + # Advance to next source value in sweep + inst.write(f"{trigger_PD}.addblock.source.action.step(\"{tm_name2}\", \"bias_PD\", {PD_channel})") + # Notify source that voltage value has been advanced + inst.write(f"{trigger_PD}.addblock.notify(\"{tm_name2}\", \"notifyStep\", slot[{PD_slot}].trigger.model.EVENT_NOTIFY2)") + + # Wait for source trigger model to advance source current # Loop 1 + inst.write(f"{trigger_PD}.addblock.wait(\"{tm_name2}\", \"waitMeas\", slot[{IF_slot}].trigger.model.EVENT_NOTIFY3)") ##--## + # Measure i/v on PD channel ##--## + inst.write(f"{trigger_PD}.addblock.measure(\"{tm_name2}\", \"measure\", {PD_channel}, 1)") ##--## + # Notify source trigger model that measurement has been completed ##--## + inst.write(f"{trigger_PD}.addblock.notify(\"{tm_name2}\", \"notify\", slot[{PD_slot}].trigger.model.EVENT_NOTIFY4)") ##--## + # Loop back to wait for notification from source trigger model ##--## + inst.write(f"{trigger_PD}.addblock.branch.counter(\"{tm_name2}\", \"branch-PD\", \"waitMeas\", {noPoints})") ##--## + + # Wait for nootification from source that the sweep has been completed + inst.write(f"{trigger_PD}.addblock.wait(\"{tm_name2}\", \"waitPDDone\", slot[{IF_slot}].trigger.model.EVENT_NOTIFY5)") + inst.write(f"{trigger_PD}.addblock.source.action.bias(\"{tm_name2}\", \"PDzero\", {PD_channel})") + +################################################################################################################################ + + # Initiate trigger models, delete when completed + inst.write(f"{IF_smu}.source.output = 1") + inst.write(f"{PD_smu}.source.output = 1") + inst.write(f"{PD_smu}.source.levelv = {biasV_PD}") + + inst.write(f"{trigger_PD}.initiate(\"{tm_name2}\")") + inst.write(f"{trigger_IF}.initiate(\"{tm_name1}\")") + + inst.write("waitcomplete()") + + inst.write(f"{IF_smu}.source.output = 0") + inst.write(f"{PD_smu}.source.output = 0") + + inst.write(f"{trigger_PD}.delete(\"{tm_name1}\")") + inst.write(f"{trigger_IF}.delete(\"{tm_name2}\")") + + # Retrieve buffer data + defBuffer1 = inst.query(f"printbuffer(1, {IF_smu}.defbuffer1.n, {IF_smu}.defbuffer1)").split(",") + defBuffer1 = [float(x) for x in defBuffer1] + defBuffer2 = inst.query(f"printbuffer(1, {IF_smu}.defbuffer2.n, {IF_smu}.defbuffer2)").split(",") + defBuffer2 = [float(x) for x in defBuffer2] + defBuffer3 = inst.query(f"printbuffer(1, {PD_smu}.defbuffer1.n, {PD_smu}.defbuffer1)").split(",") + defBuffer3 = [float(x) for x in defBuffer3] + defBuffer4 = inst.query(f"printbuffer(1, {PD_smu}.defbuffer2.n, {PD_smu}.defbuffer2)").split(",") + defBuffer4 = [float(x) for x in defBuffer4] + + # Display results + print("IF","VF","I_PD","V_PD",sep="\t\t") + for i in range(len(defBuffer1)): + print(f"{defBuffer1[i]:.5e}", + f"{defBuffer2[i]:.5e}", + f"{defBuffer3[i]:.5e}", + f"{defBuffer4[i]:.5e}", + sep="\t" + ) + inst.clear() + inst.close() + plotResults(defBuffer1, defBuffer2, [-1 * x for x in defBuffer3]) + + +Pulse_Laser_VCSEL_LIV_inTriggerModel() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/DC_Pulse_LIV_VCSEL_Char/Laser_VSCEL.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/DC_Pulse_LIV_VCSEL_Char/Laser_VSCEL.tsp new file mode 100644 index 0000000..cdf238c --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/DC_Pulse_LIV_VCSEL_Char/Laser_VSCEL.tsp @@ -0,0 +1,450 @@ + +--[[ +################################################################################ + +Script File: Laser_VSCEL.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + current sweeping. The purpose is show that you can perform a laser/VCLSE device + characterization with the SMUs. As written, one channel of SMU is assigned to + the anode for the high and cathode for the low terminal. the high of the other SMU + is assigned to the high of a PD anode and the low is to cathod. One channels performs + current sweeping on the laser/or VCSEL measuring voltage and current. The other channel + keeps a constanst voltage bias measurement current of the PD device. This test provides + three different scripts. One is DC sweep measurement in run time environment. The second + is still DC test tested in trigger model. The third is a pulse test in trigger model. + This feature gives a lot of convience and speed in LED sequence test. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 2 channel SMU + 1 Laser/VSCEL unit + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * DC_Laser_VCSEL_LIV_RunTimeEnv({IF_slot,IF_channel,PD_slot,PD_channel},{startIF,stopIF,noPoints, rangeI, limitV,measRangeV},{biasV_PD,srcRangeV_PD,measRangeI_PD},{nplc,mDelay, remoteSense}) + * DC_Laser_VCSEL_LIV_inTriggerModel({IF_slot,IF_channel,PD_slot,PD_channel},{startIF,stopIF,noPoints, rangeI, limitV,measRangeV},{biasV_PD,srcRangeV_PD,measRangeI_PD},{nplc,mDelay, remoteSense},{"tm_if","tm_pd"}) + * Pulse_Laser_VCSEL_LIV_inTriggerModel({IF_slot,IF_channel,PD_slot,PD_channel},{startIF,stopIF,noPoints, rangeI, limitV,measRangeV},{biasV_PD,srcRangeV_PD,measRangeI_PD},{nplc,remoteSense},{pulsePeriod,pulseWidth,mDelay,apertureTime},{"tm_if","tm_pd"}) + +Example Usage: + * DC_Laser_VCSEL_LIV_RunTimeEnv({1,1,1,2},{0,100e-3,11, 100e-3, 6,6},{0,6,6},{1,0, false}) + * DC_Laser_VCSEL_LIV_inTriggerModel({1,1,1,2},{0,100e-3,11, 100e-3, 6,6},{0,6,6},{1,0, false},{"tm_if","tm_pd"}) + * Pulse_Laser_VCSEL_LIV_inTriggerModel({1,1,1,2},{0,100e-3,11, 100e-3, 6,6},{0,6,6},{0,false},{5e-3,3e-3,0,1e-3},{"tm_if","tm_pd"}) + +################################################################################ +--]] + +function DC_Laser_VCSEL_LIV_RunTimeEnv(infoSMU,IF_Settings,PD_Settings, meaSettings) + -- SMU channel assignment + local IF_SMU = slot[infoSMU[1]].smu[infoSMU[2]] + local PD_SMU = slot[infoSMU[3]].smu[infoSMU[4]] + local smu_id = {IF_SMU, PD_SMU} + + -- Configure all channels + for i = 1, 2 do + smu_id[i].reset() + -- Buffer clear + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + if smu_id[i] == IF_SMU then + -- Source Settings + smu_id[i].source.func = smu_id[i].FUNC_DC_CURRENT + smu_id[i].source.rangei = IF_Settings[4] + smu_id[i].source.leveli = 0 + smu_id[i].source.limitv = IF_Settings[5] + -- Measure Settings + smu_id[i].measure.rangev = IF_Settings[6] + smu_id[i].measure.rangei = IF_Settings[4] + else + -- Source Settings + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].source.rangev = PD_Settings[2] + smu_id[i].source.levelv = PD_Settings[1] + -- Measure Settings + smu_id[i].measure.rangei = PD_Settings[3] + end + + smu_id[i].measure.nplc = meaSettings[1] + if meaSettings[3] == true then + smu_id[i].sense = smu_id[i].SENSE_4WIRE + else + smu_id[i].sense = smu_id[i].SENSE_2WIRE + end + end + + -- Calculate current change per iteration + local deltaIF = (IF_Settings[2] - IF_Settings[1])/ (IF_Settings[3] - 1) + IF_SMU.source.output = 1 + IF_SMU.source.output = 1 + PD_SMU.source.output = 1 + PD_SMU.source.output = 1 + PD_SMU.source.levelv = PD_Settings[1] + + print("IF\t\t VF\t\t I_PD \t\t V_PD") + for j = 1, IF_Settings[3] , 1 do + -- Set current source level + IF_SMU.source.leveli = IF_Settings[1] + ((j-1) * deltaIF) + delay(meaSettings[2]) + -- Measure i/v for both Source and PD, store in default buffers + IF_SMU.measure.iv(IF_SMU.defbuffer1, IF_SMU.defbuffer2) + PD_SMU.measure.iv(PD_SMU.defbuffer1, PD_SMU.defbuffer2) + -- Display results + print(IF_SMU.defbuffer1[j],IF_SMU.defbuffer2[j],PD_SMU.defbuffer1[j],PD_SMU.defbuffer2[j]) + end + + IF_SMU.source.output = 0 + PD_SMU.source.output = 0 + + -- Display all errors + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end +end + +function DC_Laser_VCSEL_LIV_inTriggerModel(infoSMU,IF_Settings,PD_Settings, meaSettings,tmName) + -- SMU channel assignment + local IF_SMU = slot[infoSMU[1]].smu[infoSMU[2]] + local PD_SMU = slot[infoSMU[3]].smu[infoSMU[4]] + local smu_id = {IF_SMU, PD_SMU} + + -- Configure all channels + for i = 1, 2 do + smu_id[i].reset() + -- Buffer clear + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + if smu_id[i] == IF_SMU then + -- Source Settings + smu_id[i].source.func = smu_id[i].FUNC_DC_CURRENT + smu_id[i].source.rangei = IF_Settings[4] + smu_id[i].source.leveli = 0 + smu_id[i].source.limitv = IF_Settings[5] + -- Measure Settings + smu_id[i].measure.rangev = IF_Settings[6] + smu_id[i].measure.rangei = IF_Settings[4] + else + -- Source Settings + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].source.rangev = PD_Settings[2] + smu_id[i].source.levelv = PD_Settings[1] + -- Measure Settings + smu_id[i].measure.rangei = PD_Settings[3] + end + + smu_id[i].measure.nplc = meaSettings[1] + if meaSettings[3] == true then + smu_id[i].sense = smu_id[i].SENSE_4WIRE + else + smu_id[i].sense = smu_id[i].SENSE_2WIRE + end + end + +--####################################### Source channel trigger model #######################################-- +--############################################################################################################-- + + -- Setup trigger model source for linear current sweep + IF_SMU.trigger.source.lineari(IF_Settings[1], IF_Settings[2], IF_Settings[3]) + -- Set trigger model to measure i/v and store in default buffers + IF_SMU.trigger.measure.iv(IF_SMU.defbuffer1,IF_SMU.defbuffer2) + + local trigger_IF = slot[infoSMU[1]].trigger.model + trigger_IF.create(tmName[1]) + + -- Notify PD trigger model to begin + trigger_IF.addblock.notify(tmName[1],"notifyPDStep",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY1) + + -- Wait for response from PD trigger model + trigger_IF.addblock.wait(tmName[1],"waitPDStep",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY2 ) + -- Advance to next source value --Loop 1 + trigger_IF.addblock.source.action.step(tmName[1], "IF_sweep", infoSMU[2]) --##-- + trigger_IF.addblock.delay.constant(tmName[1],"meaDelay",meaSettings[2],"IF_sweep") --##-- + -- Notify PD trigger model that step and wait are complete --##-- + trigger_IF.addblock.notify(tmName[1],"notify",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY3) --##-- + -- Measure iv on source terminals --##-- + trigger_IF.addblock.measure(tmName[1], "measure", infoSMU[2], 1) --##-- + --##-- + -- Wait for measurement completed notification --##-- + trigger_IF.addblock.wait(tmName[1],"wait",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY4 ) --##-- + -- Loop back to advancing source current --##-- + trigger_IF.addblock.branch.counter(tmName[1], "IF_branch", "IF_sweep", IF_Settings[3]) --##-- + -- Notify PD trigger model that current sweep is completed + trigger_IF.addblock.notify(tmName[1],"notifySweepDone",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY5) + trigger_IF.addblock.source.action.bias(tmName[1], "IF_bias", infoSMU[2]) + +--######################################### PD channel trigger model #########################################-- +--############################################################################################################-- + + -- Setup trigger model source for linear voltage sweep + -- (voltage sweep is only one value so will stay constant through test) + PD_SMU.trigger.source.linearv(PD_Settings[1], PD_Settings[1], 1) + -- Set trigger model to measure i/v and store in default buffers + PD_SMU.trigger.measure.iv(PD_SMU.defbuffer1,PD_SMU.defbuffer2) + + local trigger_PD = slot[infoSMU[3]].trigger.model + trigger_PD.create(tmName[2]) + + -- Wait for initiation from source trigger model + trigger_PD.addblock.wait(tmName[2],"waitStep",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY1 ) + -- Advance to next source value + trigger_PD.addblock.source.action.step(tmName[2], "bias_PD", infoSMU[4]) + -- Notify source trigger model that source has been stepped + trigger_PD.addblock.notify(tmName[2],"notifyStep",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY2) + + -- Wait for notification of completion from source trigger model --Loop 1 + trigger_PD.addblock.wait(tmName[2],"waitMeas",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY3 ) --##-- + trigger_PD.addblock.delay.constant(tmName[2],"meaDelay",meaSettings[2]) --##-- + -- Measure iv on PD terminals --##-- + trigger_PD.addblock.measure(tmName[2], "measure", infoSMU[4], 1) --##-- + -- Notify source trigger model that measurement is completed --##-- + trigger_PD.addblock.notify(tmName[2],"notify",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY4) --##-- + -- Loop back and wait for notification from source trigger model --##-- + trigger_PD.addblock.branch.counter(tmName[2], "branch-PD", "waitMeas", IF_Settings[3]) --##-- + + -- Wait for notification that source trigger model is completed + trigger_PD.addblock.wait(tmName[2],"waitPDDone",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY5 ) + -- Set source to 0 + trigger_PD.addblock.source.action.bias(tmName[2], "PDzero", infoSMU[4]) + +--############################################################################################################-- + + -- Initiate trigger models, delete when completed + IF_SMU.source.output = 1 + IF_SMU.source.output = 1 + PD_SMU.source.output = 1 + PD_SMU.source.output = 1 + PD_SMU.source.levelv = PD_Settings[1] + + trigger_PD.initiate(tmName[2]) + trigger_IF.initiate(tmName[1]) + + waitcomplete() + IF_SMU.source.output = 0 + PD_SMU.source.output = 0 + + trigger_IF.delete(tmName[1]) + trigger_PD.delete(tmName[2]) + + -- Display default buffer readings + print("IF\t\t VF\t\t I_PD \t\t V_PD") + for j = 1, IF_SMU.defbuffer1.n , 1 do + print(IF_SMU.defbuffer1[j],IF_SMU.defbuffer2[j],PD_SMU.defbuffer1[j],PD_SMU.defbuffer2[j]) + end + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end +end + +function Pulse_Laser_VCSEL_LIV_inTriggerModel(infoSMU,IF_Settings,PD_Settings, meaSettings,pulseSettings,tmName) + -- SMU channel assignment + local IF_SMU = slot[infoSMU[1]].smu[infoSMU[2]] + local PD_SMU = slot[infoSMU[3]].smu[infoSMU[4]] + local smu_id = {IF_SMU, PD_SMU} + + for i = 1, 2 do + smu_id[i].reset() + -- Buffer clear + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + if smu_id[i] == IF_SMU then + -- Source Settings + smu_id[i].source.func = smu_id[i].FUNC_DC_CURRENT + smu_id[i].source.rangei = IF_Settings[4] + smu_id[i].source.leveli = 0 + smu_id[i].source.limitv = IF_Settings[5] + -- Measure Settings + smu_id[i].measure.rangev = IF_Settings[6] + smu_id[i].measure.rangei = IF_Settings[4] + else + -- Source Settings + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].source.rangev = PD_Settings[2] + smu_id[i].source.levelv = PD_Settings[1] + -- Measure Settings + smu_id[i].measure.rangei = PD_Settings[3] + end + + smu_id[i].measure.aperture = pulseSettings[3] + if meaSettings[2] == true then + smu_id[i].sense = smu_id[i].SENSE_4WIRE + else + smu_id[i].sense = smu_id[i].SENSE_2WIRE + end + end + +--####################################### Source channel trigger model #######################################-- +--############################################################################################################-- + + -- Configure trigger model for linear current sweep + IF_SMU.trigger.source.lineari(IF_Settings[1], IF_Settings[2], IF_Settings[3]) + -- Set trigger model to measure i/v and store in default buffers + IF_SMU.trigger.measure.iv(IF_SMU.defbuffer1,IF_SMU.defbuffer2) + + local trigger_IF = slot[infoSMU[1]].trigger.model + trigger_IF.create(tmName[1]) + + -- Notify PD trigger model to begin + trigger_IF.addblock.notify(tmName[1],"notifyPDStep",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY1) + + -- Wait for PD channel to advance to next source value + trigger_IF.addblock.wait(tmName[1],"waitPDStep",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY2 ) + -- Advance to next current value in sweep --Loop 1 + trigger_IF.addblock.source.action.step(tmName[1], "IF_sweep", infoSMU[2]) --##-- + trigger_IF.addblock.delay.constant(tmName[1],"meaDelay",meaSettings[1],"IF_sweep") --##-- + -- Notify PD trigger model that current value has advanced --##-- + trigger_IF.addblock.notify(tmName[1],"notify",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY3) --##-- + -- Measure source channel i/v --##-- + trigger_IF.addblock.measure(tmName[1], "measure", infoSMU[2], 1) --##-- + --##-- + -- Wait for notification that PD channel has been measured --##-- + trigger_IF.addblock.wait(tmName[1],"wait",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY4 ) --##-- + trigger_IF.addblock.delay.constant(tmName[1],"pulse_width",pulseSettings[2],"IF_sweep") --##-- + -- Set source value back to 0 --##-- + trigger_IF.addblock.source.action.bias(tmName[1], "IF_off", infoSMU[2]) --##-- + trigger_IF.addblock.delay.constant(tmName[1],"pulse_period",pulseSettings[1],"IF_sweep") --##-- + -- Loop back to advance source current --##-- + trigger_IF.addblock.branch.counter(tmName[1], "IF_branch", "IF_sweep", IF_Settings[3]) --##-- + -- Notify PD channel that sweep has completed + trigger_IF.addblock.notify(tmName[1],"notifySweepDone",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY5) + trigger_IF.addblock.source.action.bias(tmName[1], "IF_bias", infoSMU[2]) + +--######################################### PD channel trigger model #########################################-- +--############################################################################################################-- + + -- Setup trigger model source for linear voltage sweep + -- (voltage sweep is only one value so will stay constant through test) + PD_SMU.trigger.source.linearv(PD_Settings[1], PD_Settings[1], 1) + -- Set trigger model to measure i/v and store in default buffers + PD_SMU.trigger.measure.iv(PD_SMU.defbuffer1,PD_SMU.defbuffer2) + + local trigger_PD = slot[infoSMU[3]].trigger.model + trigger_PD.create(tmName[2]) + + -- Wait for source trigger model to begin + trigger_PD.addblock.wait(tmName[2],"waitStep",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY1 ) + -- Advance to next source value in sweep + trigger_PD.addblock.source.action.step(tmName[2], "bias_PD", infoSMU[4]) + -- Notify source that voltage value has been advanced + trigger_PD.addblock.notify(tmName[2],"notifyStep",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY2) + + -- Wait for source trigger model to advance source current --Loop 1 + trigger_PD.addblock.wait(tmName[2],"waitMeas",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY3 ) --##-- + -- Measure i/v on PD channel --##-- + trigger_PD.addblock.measure(tmName[2], "measure", infoSMU[4], 1) --##-- + -- Notify source trigger model that measurement has been completed --##-- + trigger_PD.addblock.notify(tmName[2],"notify",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY4) --##-- + -- Loop back to wait for notification from source trigger model --##-- + trigger_PD.addblock.branch.counter(tmName[2], "branch-PD", "waitMeas", IF_Settings[3]) --##-- + + -- Wait for notification from source that the sweep has been completed + trigger_PD.addblock.wait(tmName[2],"waitPDDone",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY5 ) + trigger_PD.addblock.source.action.bias(tmName[2], "PDzero", infoSMU[4]) + +--############################################################################################################-- + + -- Initiate trigger model, delete when finished + IF_SMU.source.output = 1 + IF_SMU.source.output = 1 + PD_SMU.source.output = 1 + PD_SMU.source.output = 1 + PD_SMU.source.levelv = PD_Settings[1] + + trigger_PD.initiate(tmName[2]) + trigger_IF.initiate(tmName[1]) + + waitcomplete() + IF_SMU.source.output = 0 + PD_SMU.source.output = 0 + + trigger_IF.delete(tmName[1]) + trigger_PD.delete(tmName[2]) + + -- Display contents of measurement buffers + print("IF\t\t VF\t\t I_PD \t\t V_PD") + for j = 1, IF_SMU.defbuffer1.n , 1 do + print(IF_SMU.defbuffer1[j],IF_SMU.defbuffer2[j],PD_SMU.defbuffer1[j],PD_SMU.defbuffer2[j]) + end + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end +end + +-- information of SMU +local IF_slot = 1 +local IF_channel = 1 +local PD_slot = 1 +local PD_channel = 2 + +-- IF Settings +local startIF = 0 +local stopIF = 100e-3 +local noPoints = 60 +local rangeI = 1000e-3 +local limitV = 6 +local measRangeV = 6 + +-- PD Source Measure Settings +local biasV_PD = 0 +local srcRangeV_PD = 6 +local measRangeI_PD = 10e-3 +-- Mesaure Settings +local nplc = 1 +local mDelay = 1e-3 +local remoteSense = false + +-- Pulse Settings +local pulsePeriod = 5e-3 -- pulsePeriod should be longer than pulseWidth + mDelay + apertureTime +local pulseWidth = 3e-3 +local apertureTime = 1e-3 + + +--DC_Laser_VCSEL_LIV_RunTimeEnv({IF_slot,IF_channel,PD_slot,PD_channel},{startIF,stopIF,noPoints, rangeI, limitV,measRangeV},{biasV_PD,srcRangeV_PD,measRangeI_PD},{nplc,mDelay, remoteSense}) +--DC_Laser_VCSEL_LIV_inTriggerModel({IF_slot,IF_channel,PD_slot,PD_channel},{startIF,stopIF,noPoints, rangeI, limitV,measRangeV},{biasV_PD,srcRangeV_PD,measRangeI_PD},{nplc,mDelay, remoteSense},{"tm_if","tm_pd"}) +Pulse_Laser_VCSEL_LIV_inTriggerModel({IF_slot,IF_channel,PD_slot,PD_channel},{startIF,stopIF,noPoints, rangeI, limitV,measRangeV},{biasV_PD,srcRangeV_PD,measRangeI_PD},{mDelay,remoteSense},{pulsePeriod,pulseWidth,apertureTime},{"tm_if","tm_pd"}) + + + diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/DC_Pulse_LIV_VCSEL_Char/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/DC_Pulse_LIV_VCSEL_Char/README.md new file mode 100644 index 0000000..4399a02 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/DC_Pulse_LIV_VCSEL_Char/README.md @@ -0,0 +1,15 @@ +# DC/Pulse LIV Characterization for VCSEL + +Two channels are used to perform a voltage and current sweep for an LIV characterization of a VCSEL. Measurements are recorded throughout the test. + +NOTE: There are three functions provided, “DC_Laser_VCSEL_LIV_RunTimeEnv” is the simplest however as it does not utilize a trigger model timings are less accurate. “DC_Laser_VCSEL_LIV_inTriggerModel” Uses a trigger model but is slower than “Pulse_Laser_VCSEL_LIV_inTriggerModel” which is used by default. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/LED_ConfigList_Example/LED_Sequence.py b/Examples/Modular_Precision_Test_System/SMU_Examples/LED_ConfigList_Example/LED_Sequence.py new file mode 100644 index 0000000..750116d --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/LED_ConfigList_Example/LED_Sequence.py @@ -0,0 +1,166 @@ +################################################################################# +# +# Script File: LED_Sequence.tsp +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# current sweeping. The purpose is show that you can perform a LED device +# with the SMU. As written, one channel of SMU is assigned to the anode for +# the high and cathode for the low terminal. This script shows the benefit of +# configlist feature. All the attributes of source and measurement are configured +# in configlist prior to the trigger. Then trigger recalls the configlist and run. +# This feature gives a lot of convience and speed in LED sequence test. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 1 channel SMU +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +################################################################################# + +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +IF_slot = 1 +IF_Channel = 1 +smu = f"slot[{IF_slot}].smu[{IF_Channel}]" +triggerModel = f"slot[{IF_slot}].trigger.model" + +triggerName = "tm_led" +configName = "cl_led" + +# test_items stores the various settings to create a config list +test_items = [ +# Mode, srcBias, srcRng, mDelay, mTime, Limit, mRng + [0, 0.000001, 0.00001, 0.01, 0.001, 1, 6], + [0, 0.00001, 0.0001, 0.005, 0.001, 3, 6], + [0, 0.35, 1, 0.001, 0.001, 5, 6], + [1, -7, 20, 0.005, 0.001, 0.00005, 0.001], + [0, 0.000001, 0.00001, 0.003, 0.001, 1, 6], + [0, 0.350, 1, 0.003, 0.001, 10, 20], + [0, 0.000001, 0.00001, 0.003, 0.001, 1, 6], + [1, -40, 60, 0.01, 0.0001, 0.0005, 0.01], + [1, -20, 20, 0.01, 0.0001, 0.0005, 0.01], + [0, 0.00001, 0.0001, 0.003, 0.001, 3, 6], + [0, 0.030, 1, 0.003, 0.001, 10, 20], + [0, 0.00001, 0.0001, 0.003, 0.001, 3, 6], + [1, -24, 60, 0.005, 0.0001, 0.005, 0.001], + [1, -7, 20, 0.005, 0.0001, 0.005, 0.001], + [0, 0.000001, 0.00001, 0.01, 0.001, 1, 6], + [0, 0.00001, 0.0001, 0.005, 0.001, 3, 6], + [0, 0.35, 1, 0.001, 0.001, 5, 6], + [1, 7, 20, 0.005, 0.0001, 0.005, 0.001] +] + +# Initialize SMU +def initSMU(): + inst.write(f"{smu}.reset()") + inst.write(f"{smu}.source.func = {smu}.OUTPUT_DCVOLTS") + inst.write(f"{smu}.source.rangev = 20") + inst.write(f"{smu}.source.levelv = 0") + inst.write(f"{smu}.source.limiti = 1e-6") + + inst.write(f"{smu}.measure.rangei = 1e-6") + inst.write(f"{smu}.measure.rangev = 20") + inst.write(f"{smu}.measure.aperture = 5e-3") + inst.write(f"{smu}.defbuffer1.clear()") + inst.write(f"{smu}.defbuffer2.clear()") + +# Create a configlist from the table +def setConfigList(): + # Create configlist + inst.write(f"{smu}.configlist.create(\"{configName}\")") + for i in range(len(test_items)): + seqTable = test_items[i] + # Setup SMU with settings from table + if seqTable[0] == 0: + inst.write(f"{smu}.source.func = {smu}.FUNC_DC_CURRENT") + inst.write(f"{smu}.source.rangei = {seqTable[2]}") + inst.write(f"{smu}.source.leveli = {seqTable[1]}") + inst.write(f"{smu}.source.limitv = {seqTable[5]}") + + inst.write(f"{smu}.measure.rangev = {seqTable[6]}") + inst.write(f"{smu}.measure.rangei = {seqTable[2]}") + inst.write(f"{smu}.measure.delay = {seqTable[3]}") + inst.write(f"{smu}.measure.aperture = {seqTable[4]}") + elif seqTable[0] == 1: + inst.write(f"{smu}.source.func = {smu}.OUTPUT_DCVOLTS") + inst.write(f"{smu}.source.rangev = {seqTable[2]}") + inst.write(f"{smu}.source.levelv = {seqTable[1]}") + inst.write(f"{smu}.source.limiti = {seqTable[5]}") + + inst.write(f"{smu}.measure.rangev = {seqTable[2]}") + inst.write(f"{smu}.measure.rangei = {seqTable[6]}") + inst.write(f"{smu}.measure.delay = {seqTable[3]}") + inst.write(f"{smu}.measure.aperture = {seqTable[4]}") + # Store the current settings in configlist at index i + inst.write(f"{smu}.configlist.store(\"{configName}\", {i+1})") + +# Create and initialize trigger model +def setTriggerModel(): + inst.write(f"{smu}.trigger.measure.iv({smu}.defbuffer1, {smu}.defbuffer2)") + inst.write(f"{triggerModel}.create(\"{triggerName}\")") + # Set SMU settings to next item in configlist + inst.write(f"{triggerModel}.addblock.configlist.next(\"{triggerName}\", \"recallBlockName\", {IF_Channel}, \"{configName}\")") + # Take i/v measurements + inst.write(f"{triggerModel}.addblock.measure(\"{triggerName}\", \"measBlockname\", {IF_Channel}, 1)") + # Loop through all items in configlist + inst.write(f"{triggerModel}.addblock.branch.counter(\"{triggerName}\", \"branch-counter\", \"recallBlockName\", {len(test_items)})") + +# Execute test +setConfigList() +setTriggerModel() +initSMU() + +inst.write(f"{smu}.source.levelv = 0") +inst.write(f"{smu}.source.output = 1") + +inst.write(f"{triggerModel}.initiate(\"{triggerName}\")") +inst.write(f"waitcomplete()") + +print("Test Done") +inst.write(f"{smu}.source.output = 0") + +inst.write(f"{triggerModel}.delete(\"{triggerName}\")") +inst.write(f"{smu}.configlist.delete(\"{configName}\")") + +# Restieve buffer data +defBuffer1 = inst.query(f"printbuffer(1, {smu}.defbuffer1.n, {smu}.defbuffer1)") +defBuffer1 = defBuffer1.split(",") +defBuffer2 = inst.query(f"printbuffer(1, {smu}.defbuffer2.n, {smu}.defbuffer2)") +defBuffer2 = defBuffer2.split(",") + +# Display results +print("Current","Voltage",sep="\t") +for i in range(len(defBuffer1)): + print(defBuffer1[i].lstrip(), + defBuffer2[i].lstrip(), + sep="\t" + ) + +inst.clear() +inst.close() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/LED_ConfigList_Example/LED_Sequence.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/LED_ConfigList_Example/LED_Sequence.tsp new file mode 100644 index 0000000..d1d340b --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/LED_ConfigList_Example/LED_Sequence.tsp @@ -0,0 +1,167 @@ + +--[[ +################################################################################ + +Script File: LED_Sequence.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + current sweeping. The purpose is show that you can perform a LED device + with the SMU. As written, one channel of SMU is assigned to the anode for + the high and cathode for the low terminal. This script shows the benefit of + configlist feature. All the attributes of source and measurement are configured + in configlist prior to the trigger. Then trigger recalls the configlist and run. + This feature gives a lot of convience and speed in LED sequence test. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 1 channel SMU + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * LED_SequenceTest_MSMU_MP5000({IF_slot,IF_channel},{"tm_led","cl_led"}) +Example Usage: + * LED_SequenceTest_MSMU_MP5000({1,1},{"tm_led","cl_led"}) + +################################################################################ +--]] + + +function LED_SequenceTest_MSMU_MP5000(infoSMU,triggerSettings) + local smu_ID = slot[infoSMU[1]].smu[infoSMU[2]] + local triggerName = triggerSettings[1] + local configName = triggerSettings[2] + + local test_items = {} + -- test_items stores the various settings to create a config list + -- Mode, srcBias, srcRng, mDelay, mTime, Limit, mRng + test_items[1] = {0, 0.000001, 0.00001, 0.01, 0.001, 1, 6} + test_items[2] = {0, 0.00001, 0.0001, 0.005, 0.001, 3, 6} + test_items[3] = {0, 0.35, 1, 0.001, 0.001, 5, 6} + test_items[4] = {1, -7, 20, 0.005, 0.001, 0.00005, 0.001} + test_items[5] = {0, 0.000001, 0.00001, 0.003, 0.001, 1, 6} + test_items[6] = {0, 0.350, 1, 0.003, 0.001, 10, 20} + test_items[7] = {0, 0.000001, 0.00001, 0.003, 0.001, 1, 6} + test_items[8] = {1, -40, 60, 0.01, 0.0001, 0.0005, 0.01} + test_items[9] = {1, -20, 20, 0.01, 0.0001, 0.0005, 0.01} + test_items[10] = {0, 0.00001, 0.0001, 0.003, 0.001, 3, 6} + test_items[11] = {0, 0.030, 1, 0.003, 0.001, 10, 20} + test_items[12] = {0, 0.00001, 0.0001, 0.003, 0.001, 3, 6} + test_items[13] = {1, -24, 60, 0.005, 0.0001, 0.005, 0.001} + test_items[14] = {1, -7, 20, 0.005, 0.0001, 0.005, 0.001} + test_items[15] = {0, 0.000001, 0.00001, 0.01, 0.001, 1, 6} + test_items[16] = {0, 0.00001, 0.0001, 0.005, 0.001, 3, 6} + test_items[17] = {0, 0.35, 1, 0.001, 0.001, 5, 6} + test_items[18] = {1, 7, 20, 0.005, 0.0001, 0.005, 0.001} + + -- Initialize SMU + function initSMU() + smu_ID.reset() + smu_ID.source.func = smu_ID.OUTPUT_DCVOLTS + smu_ID.source.rangev = 20 + smu_ID.source.levelv = 0 + smu_ID.source.limiti = 1e-6 + + smu_ID.measure.rangei = 1e-6 + smu_ID.measure.rangev = 20 + smu_ID.measure.aperture = 5e-3 + smu_ID.defbuffer1.clear() + smu_ID.defbuffer2.clear() + end + + -- Create a configlist from the table + function setConfiglist() + seqCount = table.getn(test_items) + local seqTable + -- Create configlist + smu_ID.configlist.create(configName) + + for i = 1, seqCount do + seqTable = test_items[i] + -- Setup SMU with settings from table + if seqTable[1] == 0 then + smu_ID.source.func = smu_ID.FUNC_DC_CURRENT + smu_ID.source.rangei = seqTable[3] + smu_ID.source.leveli = seqTable[2] + smu_ID.source.limitv = seqTable[6] + + smu_ID.measure.rangev = seqTable[7] + smu_ID.measure.rangei = seqTable[3] + smu_ID.measure.delay = seqTable[4] + smu_ID.measure.aperture = seqTable[5] + + elseif seqTable[1] == 1 then + smu_ID.source.func = smu_ID.OUTPUT_DCVOLTS + smu_ID.source.rangev = seqTable[3] + smu_ID.source.levelv = seqTable[2] + smu_ID.source.limiti = seqTable[6] + + smu_ID.measure.rangev = seqTable[3] + smu_ID.measure.rangei = seqTable[7] + smu_ID.measure.delay = seqTable[4] + smu_ID.measure.aperture = seqTable[5] + end + -- Store the current settings in configlist at index i + smu_ID.configlist.store(configName,i) + end + end + + -- Create and initialize trigger model + function setTriggerModel() + smu_ID.trigger.measure.iv(smu_ID.defbuffer1,smu_ID.defbuffer2) + triggerModel = slot[infoSMU[1]].trigger.model + triggerModel.create(triggerName) + -- Set SMU settings to next item in configlist + triggerModel.addblock.configlist.next(triggerName, "recallBlockName", infoSMU[2], configName) -- recall configlist which was already configured + -- Take i/v measurements + triggerModel.addblock.measure(triggerName, "measBlockname", infoSMU[2],1) -- make measurement with configured condition + -- Loop through all items in configlist + triggerModel.addblock.branch.counter(triggerName, "branch-counter", "recallBlockName", seqCount) -- re-interate recalling the configlist list recall + end + + -- Execute test + setConfiglist() + setTriggerModel() + initSMU() + + smu_ID.source.levelv = 0 + smu_ID.source.output = 1 + + triggerModel.initiate(triggerName) + waitcomplete() + + print("Test Done") + smu_ID.source.output = 0 + + triggerModel.delete(triggerName) + smu_ID.configlist.delete(triggerSettings[2]) + + -- Display measurements from default buffers + print("Current\t\t Voltage") + for j = 1, smu_ID.defbuffer1.n , 1 do + print(smu_ID.defbuffer1[j],smu_ID.defbuffer2[j]) + end + +end + +-- information of SMU +local IF_slot = 1 +local IF_channel = 1 + +LED_SequenceTest_MSMU_MP5000({IF_slot,IF_channel},{"tm_led","cl_led"}) \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/LED_ConfigList_Example/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/LED_ConfigList_Example/README.md new file mode 100644 index 0000000..d800a10 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/LED_ConfigList_Example/README.md @@ -0,0 +1,13 @@ +# LED Characterization with Config Lists + +Create a large configuration list, iterate through the list and take measurements when the settings are applied. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Current_Sweep/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Current_Sweep/README.md new file mode 100644 index 0000000..96e44cb --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Current_Sweep/README.md @@ -0,0 +1,15 @@ +# Linear Current Sweep + +Configures and executes a linear current sweep while measuring voltage. + +NOTE: There are two functions provided that are nearly identical except for how they manage timing. “DC_Current_Sweep_Diode_RunTimeEnv” uses the delay() function for timing while “DC_Current_Sweep_Diode_inTriggerModel” uses the trigger model. The trigger model is used by default as the timing is more accurate. To change this simply change which function call is commented at the bottom of the script. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Current_Sweep/Sweep_Current_Diode.py b/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Current_Sweep/Sweep_Current_Diode.py new file mode 100644 index 0000000..6921e04 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Current_Sweep/Sweep_Current_Diode.py @@ -0,0 +1,142 @@ + +################################################################################# +# +# Script File: Sweep_Current_Diode.py +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# current sweeping. The purpose is show that you can perform a diode device +# characterization with the SMUs. As written, one channel of SMU is +# assigned to the anode for the high and cathode for the low terminal. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 1 channel SMU (MSMU Series) +# 1 Diode +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +################################################################################# + +def plotResults(x, y): + import matplotlib.pyplot as pt + + pt.plot(x, y, "o-") + pt.xlabel("Supplied Current (A)") + pt.ylabel("Measured Voltage (V)") + pt.title("Diode Current Sweep") + + pt.show() + +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# information of SMU +slot_no = 1 +IF_channel = 1 +# Source Settings +startIF = 0 +stopIF = 12e-3 +noPoints = 31 +rangeI = 100e-3 +limitV = 3 +# Measure Settings +measRangeV = 6 +nplc = 1 +mDelay = 0 +remoteSense = False +tm_name = "TM_Diode" + +# Channel assignment +smu = f"slot[{slot_no}].smu[{IF_channel}]" +inst.write(f"{smu}.reset()") + +# Source settings +inst.write(f"{smu}.source.func = {smu}.FUNC_DC_CURRENT") +inst.write(f"{smu}.source.rangei = {rangeI}") +inst.write(f"{smu}.source.leveli = 0") +inst.write(f"{smu}.source.limitv = {limitV}") +# Measure settings +inst.write(f"{smu}.measure.rangev = {measRangeV}") +inst.write(f"{smu}.measure.rangei = {rangeI}") +inst.write(f"{smu}.measure.nplc = {nplc}") +inst.write(f"{smu}.measure.autorangei = 1") + +if remoteSense: + inst.write(f"{smu}.sense = {smu}.SENSE_4WIRE") +else: + inst.write(f"{smu}.sense = {smu}.SENSE_2WIRE") + +# Buffer clear +inst.write(f"{smu}.defbuffer1.clear()") +inst.write(f"{smu}.defbuffer1.appendmode = 1") +inst.write(f"{smu}.defbuffer2.clear()") +inst.write(f"{smu}.defbuffer2.appendmode = 1") + +# Configure trigger model source for a linear current sweep +inst.write(f"{smu}.trigger.source.lineari({startIF}, {stopIF}, {noPoints})") +# Set trigger model current and voltage measurements to be stored in the default buffers +inst.write(f"{smu}.trigger.measure.iv({smu}.defbuffer1, {smu}.defbuffer2)") + +# Configure trigger model +triggerModel = f"slot[{slot_no}].trigger.model" +inst.write(f"{triggerModel}.create(\"{tm_name}\")") +# Set the source level to next value in linear current sweep +inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name}\", \"IF_sweep\", {IF_channel})") +# Delay in trigger model is very accurate +inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"IF_delay\", {mDelay})") +# Measure iv and store in default buffers +inst.write(f"{triggerModel}.addblock.measure(\"{tm_name}\", \"VF_measure\", {IF_channel}, 1)") +# Loop for a set number of iterations +inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name}\", \"IF_branch\", \"IF_sweep\", {noPoints})") +# Return source to 0 +inst.write(f"{triggerModel}.addblock.source.action.bias(\"{tm_name}\", \"IF_bias\", {IF_channel})") + +# Execute sweep +inst.write(f"{smu}.source.output = 1") +# Initiate trigger model, delete when finished +inst.write(f"{triggerModel}.initiate(\"{tm_name}\")") +inst.write(f"waitcomplete()") +inst.write(f"{smu}.source.output = 0") +inst.write(f"{triggerModel}.delete(\"{tm_name}\")") + +# Retrieve defbuffers +defbuffer1 = inst.query(f"printbuffer(1, {smu}.defbuffer1.n, {smu}.defbuffer1)").split(",") +defbuffer1 = [float(x) for x in defbuffer1] +defbuffer2 = inst.query(f"printbuffer(1, {smu}.defbuffer2.n, {smu}.defbuffer2)").split(",") +defbuffer2 = [float(x) for x in defbuffer2] + +# Display readings to terminal +print("IF","VF",sep="\t\t") +for i in range(len(defbuffer1)): + print(f"{defbuffer1[i]:.5e}", + f"{defbuffer2[i]:.5e}", + sep="\t" + ) + +inst.clear() +inst.close() + +plotResults(defbuffer1, defbuffer2) \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Current_Sweep/Sweep_Current_Diode.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Current_Sweep/Sweep_Current_Diode.tsp new file mode 100644 index 0000000..fba82de --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Current_Sweep/Sweep_Current_Diode.tsp @@ -0,0 +1,194 @@ +--[[ +################################################################################ + +Script File: Sweep_Current_Diode.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + current sweeping. The purpose is show that you can perform a diode device + characterization with the SMUs. As written, one channel of SMU is + assigned to the anode for the high and cathode for the low terminal. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 1 channel SMU (MSMU Series) + 1 Diode + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * DC_Current_Sweep_Diode_RunTimeEnv({slot_no,IF_channel},{startIF, stopIF,noPoints, rangeI, limitV}, {measRangeV,nplc,mDelay,remoteSense}) + * DC_Current_Sweep_Diode_inTriggerModel({slot_no,IF_channel},{startIF, stopIF,noPoints, rangeI, limitV}, {measRangeV,nplc,mDelay,remoteSense},triggerName) + +Example Usage: + * DC_Current_Sweep_Diode_RunTimeEnv({1,1},{0,300e-3,31 , 1000e-3, 6}, {6, 1, 0, false}) + * DC_Current_Sweep_Diode_inTriggerModel({1,1},{0,300e-3,31, 1000e-3, 6}, {6, 1, 0,false},"TM_Diode") +################################################################################ +--]] + +function DC_Current_Sweep_Diode_RunTimeEnv(infoSMU,srcSettings,meaSettings) + -- SMU channel assignment + local IF_SMU = slot[infoSMU[1]].smu[infoSMU[2]] + IF_SMU.reset() + -- Source Settings + IF_SMU.source.func = IF_SMU.FUNC_DC_CURRENT + IF_SMU.source.rangei = srcSettings[4] + IF_SMU.source.leveli = 0 + IF_SMU.source.limitv = srcSettings[5] + -- Measure Settings + IF_SMU.measure.rangev = meaSettings[1] + IF_SMU.measure.rangei = srcSettings[4] + IF_SMU.measure.nplc = meaSettings[2] + IF_SMU.measure.autorangei = 1 + if meaSettings[4] == true then + IF_SMU.sense = IF_SMU.SENSE_4WIRE + else + IF_SMU.sense = IF_SMU.SENSE_2WIRE + end + -- Buffer clear + IF_SMU.defbuffer1.clear() + IF_SMU.defbuffer1.appendmode = 1 + IF_SMU.defbuffer2.clear() + IF_SMU.defbuffer2.appendmode = 1 + + -- Current step size + local deltaIF = (srcSettings[2] - srcSettings[1])/ (srcSettings[3] - 1) + IF_SMU.source.output = 1 + + -- Sweep through current values + print("IF\t\t VF") + for j = 1, srcSettings[3] , 1 do + -- Set current level + IF_SMU.source.leveli = srcSettings[1] + ((j-1) * deltaIF) + -- Timing is handled with a runtime delay() will be less accurate than a trigger model + delay(meaSettings[3]) + -- Measure current/voltage and store in default buffers + IF_SMU.measure.iv(IF_SMU.defbuffer1, IF_SMU.defbuffer2) + -- Display last readings + print(IF_SMU.defbuffer1[j],IF_SMU.defbuffer2[j]) + end + + IF_SMU.source.output = 0 + + -- Check for and display errors + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + +end + +function DC_Current_Sweep_Diode_inTriggerModel(infoSMU,srcSettings,meaSettings,triggerName) + -- SMU channel assignment + local IF_SMU = slot[infoSMU[1]].smu[infoSMU[2]] + IF_SMU.reset() + -- Source Settings + IF_SMU.source.func = IF_SMU.FUNC_DC_CURRENT + IF_SMU.source.rangei = srcSettings[4] + IF_SMU.source.leveli = 0 + IF_SMU.source.limitv = srcSettings[5] + -- Measure Settings + IF_SMU.measure.rangev = meaSettings[1] + IF_SMU.measure.rangei = srcSettings[4] + IF_SMU.measure.nplc = meaSettings[2] + IF_SMU.measure.autorangei = 1 + if meaSettings[4] == true then + IF_SMU.sense = IF_SMU.SENSE_4WIRE + else + IF_SMU.sense = IF_SMU.SENSE_2WIRE + end + -- Buffer clear + IF_SMU.defbuffer1.clear() + IF_SMU.defbuffer1.appendmode = 1 + IF_SMU.defbuffer2.clear() + IF_SMU.defbuffer2.appendmode = 1 + + -- Configure trigger model source for a linear current sweep + IF_SMU.trigger.source.lineari(srcSettings[1], srcSettings[2], srcSettings[3]) + -- Set trigger model current and voltage measurements to be stored in the default buffers + IF_SMU.trigger.measure.iv(IF_SMU.defbuffer1,IF_SMU.defbuffer2) + + -- Configure trigger model + local triggerModel = slot[infoSMU[1]].trigger.model + triggerModel.create(triggerName) + -- Set source level to next value in linear current sweep + triggerModel.addblock.source.action.step(triggerName, "IF_sweep", infoSMU[2]) + -- Delay handled in trigger model, very accurate + triggerModel.addblock.delay.constant(triggerName, "IF_delay", meaSettings[3]) + -- Measure iv and store in default buffers + triggerModel.addblock.measure(triggerName, "VF_measure", infoSMU[2], 1) + -- Loop for a set number of iterations + triggerModel.addblock.branch.counter(triggerName, "IF_branch", "IF_sweep", srcSettings[3]) + -- Return source to 0 + triggerModel.addblock.source.action.bias(triggerName, "IF_bias", infoSMU[2]) + + + IF_SMU.source.output = 1 + -- Initiate trigger model, delete when done + triggerModel.initiate(triggerName) + waitcomplete() + IF_SMU.source.output = 0 + triggerModel.delete(triggerName) + + -- Display data from measurement buffers + print("IF\t\t VF") + for j = 1, IF_SMU.defbuffer1.n , 1 do + print(IF_SMU.defbuffer1[j],IF_SMU.defbuffer2[j]) + end + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + +end + +-- information of SMU +local slot_no = 1 +local IF_channel = 1 +-- Source Settings +local startIF = 0 +local stopIF = 300e-3 +local noPoints = 31 +local rangeI = 1000e-3 +local limitV = 6 +-- Measure Settings +local measRangeV = 6 +local nplc = 1 +local mDelay = 0 +local remoteSense = false + +local tm_name = "TM_Diode" + +--DC_Current_Sweep_Diode_RunTimeEnv({slot_no,IF_channel},{startIF,stopIF,noPoints, rangeI, limitV},{measRangeV,nplc,mDelay, remoteSense}) + +-- Trigger model is used by default for greater accuracy +DC_Current_Sweep_Diode_inTriggerModel({slot_no,IF_channel},{startIF,stopIF,noPoints, rangeI, limitV},{measRangeV,nplc,mDelay, remoteSense},tm_name) + +errorQUEUE() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Voltage_Sweep/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Voltage_Sweep/README.md new file mode 100644 index 0000000..55d9c62 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Voltage_Sweep/README.md @@ -0,0 +1,15 @@ +# Linear Voltage Sweep + +Configures and executes a linear voltage sweep while measuring current. + +NOTE: There are two functions provided that are nearly identical except for how they manage timing. “DC_Voltage_Sweep_Resistor_RunTimeEnv” uses the delay() function for timing while “DC_Voltage_Sweep_Resistor_inTriggerModel” uses the trigger model. The trigger model is used by default as the timing is more accurate. To change this simply change which function call is commented at the bottom of the script. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Voltage_Sweep/Sweep_Voltage_Res.py b/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Voltage_Sweep/Sweep_Voltage_Res.py new file mode 100644 index 0000000..4220506 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Voltage_Sweep/Sweep_Voltage_Res.py @@ -0,0 +1,154 @@ +################################################################################# +# +# Script File: Sweep_Voltage_Res.tsp +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# voltage sweeping. The purpose is show that you can perform a resistor +# device characterization with the SMUs. As written, one channel of SMU is +# assigned to one terminal for the high and the other for the low terminal. +# For the same purpose, SMU support it in run time evironment and in trigger model. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 1 channel SMU (MSMU Series) +# 1 Resistive load +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +# Functions created by this script: +# * DC_Voltage_Sweep_Resistor_RunTimeEnv({slot_no,smu_channel},,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense}) +# * DC_Voltage_Sweep_Resistor_inTriggerModel({slot_no,smu_channel},,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense},tm_name) +# +# Example Usage: +# * DC_Voltage_Sweep_Resistor_RunTimeEnv({1,1},-5,5,101,1,6, 1, 1, 1,0) +# * DC_Voltage_Sweep_Resistor_inTriggerModel({1,1},-5,5,101,1,6, 1, 1, 1,0,"TM_sweepV") +# +################################################################################# + +def plotResults(x, y): + import matplotlib.pyplot as pt + pt.scatter(x, y, label="R") + pt.xlabel("Supplied voltage (V)") + pt.ylabel("Resistance (Ω)") + pt.title("Resistor Characterization") + pt.grid(True) + pt.show() + +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# information of SMU +slot_no = 1 +sweepV_channel = 1 +# Source Settings +startV = -5 +stopV = 5 +noPoints = 100 +limitI = 1 +# Measure Settings +measRangeV = 6 +measRangeI = 0.100 +autoRangeI = True +nplc = 1 +mDelay = 0 +remoteSense = False +tm_name = "TM_sweepV" + +# Channel assignment +smu = f"slot[{slot_no}].smu[{sweepV_channel}]" +inst.write(f"{smu}.reset()") + +# Source settings +inst.write(f"{smu}.source.func = {smu}.FUNC_DC_VOLTAGE") +inst.write(f"{smu}.source.rangev = {max(abs(startV), abs(stopV))}") +inst.write(f"{smu}.source.limiti = {limitI}") +inst.write(f"{smu}.source.levelv = 0") +# Measure settings +inst.write(f"{smu}.measure.rangev = {measRangeV}") +inst.write(f"{smu}.measure.nplc = {nplc}") + +if autoRangeI: + inst.write(f"{smu}.measure.autorangei = 1") +else: + inst.write(f"{smu}.measure.autorangei = 1") + inst.write(f"{smu}.measure.rangei = {measRangeI}") + +if remoteSense: + inst.write(f"{smu}.sense = {smu}.SENSE_4WIRE") +else: + inst.write(f"{smu}.sense = {smu}.SENSE_2WIRE") + +# Buffer clear +inst.write(f"{smu}.defbuffer1.clear()") +inst.write(f"{smu}.defbuffer1.appendmode = 1") +inst.write(f"{smu}.defbuffer2.clear()") +inst.write(f"{smu}.defbuffer2.appendmode = 1") + +# Setup trigger model source levels for linear voltage sweep +inst.write(f"{smu}.trigger.source.linearv({startV}, {stopV}, {noPoints})") +# Set trigger model iv measurements to store in default buffers +inst.write(f"{smu}.trigger.measure.iv({smu}.defbuffer1, {smu}.defbuffer2)") + +# Configure trigger model +triggerModel = f"slot[{slot_no}].trigger.model" +inst.write(f"{triggerModel}.create(\"{tm_name}\")") +# Step soruce level to next value in sweep +inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name}\", \"sweepV_IV\", {sweepV_channel})") +# Constant delay in trigger model is very accurate +inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"IV_delay\", {mDelay})") +# Measure iv and store in buffers +inst.write(f"{triggerModel}.addblock.measure(\"{tm_name}\", \"measure_IV\", {sweepV_channel}, 1)") +# Loop for each value in sweep +inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name}\", \"branch-counter\", \"sweepV_IV\", {noPoints})") + +# Execute sweep, delete trigger model when finished +inst.write(f"{smu}.source.output = 1") +inst.write(f"{triggerModel}.initiate(\"{tm_name}\")") +inst.write(f"waitcomplete()") +inst.write(f"{smu}.source.output = 0") +inst.write(f"{triggerModel}.delete(\"{tm_name}\")") + +# Retrieve defbuffers +defbuffer1 = inst.query(f"printbuffer(1, {smu}.defbuffer1.n, {smu}.defbuffer1)").split(",") +defbuffer1 = [float(x) for x in defbuffer1] +defbuffer2 = inst.query(f"printbuffer(1, {smu}.defbuffer2.n, {smu}.defbuffer2)").split(",") +defbuffer2 = [float(x) for x in defbuffer2] +resists = [x/y for x,y in zip(defbuffer2, defbuffer1)] + +# Display readings to terminal +print("V","I","R",sep="\t\t") +for i in range(len(defbuffer1)): + print(f"{defbuffer2[i]:.5e}", + f"{defbuffer1[i]:.5e}", + f"{resists[i]:.5e}", + sep="\t" + ) + +inst.clear() +inst.close() + +plotResults(defbuffer2, resists) \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Voltage_Sweep/Sweep_Voltage_Res.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Voltage_Sweep/Sweep_Voltage_Res.tsp new file mode 100644 index 0000000..da39fa7 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Linear_Voltage_Sweep/Sweep_Voltage_Res.tsp @@ -0,0 +1,202 @@ +--[[ +################################################################################ + +Script File: Sweep_Voltage_Res.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + voltage sweeping. The purpose is show that you can perform a resistor + device characterization with the SMUs. As written, one channel of SMU is + assigned to one terminal for the high and the other for the low terminal. + For the same purpose, SMU support it in run time evironment and in trigger model. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 1 channel SMU (MSMU Series) + 1 Resistive load + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * DC_Voltage_Sweep_Resistor_RunTimeEnv({slot_no,smu_channel},,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense}) + * DC_Voltage_Sweep_Resistor_inTriggerModel({slot_no,smu_channel},,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense},tm_name) + +Example Usage: + * DC_Voltage_Sweep_Resistor_RunTimeEnv({1,1},-5,5,101,1,6, 1, 1, 1,0) + * DC_Voltage_Sweep_Resistor_inTriggerModel({1,1},-5,5,101,1,6, 1, 1, 1,0,"TM_sweepV") + +################################################################################ +--]] +function DC_Voltage_Sweep_Resistor_RunTimeEnv(infoSMU,srcSettings,meaSettings) + -- SMU channel assignment + local sweepV_SMU = slot[infoSMU[1]].smu[infoSMU[2]] + sweepV_SMU.reset() + -- Source Settings + sweepV_SMU.source.func = sweepV_SMU.FUNC_DC_VOLTAGE + sweepV_SMU.source.rangev = math.max(math.abs(srcSettings[1]), math.abs(srcSettings[2])) + sweepV_SMU.source.limiti = srcSettings[4] + sweepV_SMU.source.levelv = 0 + -- Measure Settings + sweepV_SMU.measure.rangev = meaSettings[1] + sweepV_SMU.measure.nplc = meaSettings[4] + sweepV_SMU.sense = sweepV_SMU.SENSE_2WIRE + if meaSettings[3] == true then + sweepV_SMU.measure.autorangei = 1 + else + sweepV_SMU.measure.autorangei = 0 + sweepV_SMU.measure.ranagei = meaSettings[2] + end + + if meaSettings[6] == true then + sweepV_SMU.sense = sweepV_SMU.SENSE_4WIRE + else + sweepV_SMU.sense = sweepV_SMU.SENSE_2WIRE + end + -- Buffer clear + sweepV_SMU.defbuffer1.clear() + sweepV_SMU.defbuffer1.appendmode = 1 + sweepV_SMU.defbuffer2.clear() + sweepV_SMU.defbuffer2.appendmode = 1 + + -- Voltage change each cycle + local deltaV = (srcSettings[2] - srcSettings[1])/ (srcSettings[3] - 1) + + sweepV_SMU.source.output = 1 + + -- Iterate through voltage values + print("V\t\t I\t\t R") + for j = 1, srcSettings[3] , 1 do + sweepV_SMU.source.levelv = srcSettings[1] + ((j-1) * deltaV) + delay(meaSettings[5]) + -- Measure iv and store in default buffers, display results + sweepV_SMU.measure.iv(sweepV_SMU.defbuffer1, sweepV_SMU.defbuffer2) + print(sweepV_SMU.defbuffer2[j],sweepV_SMU.defbuffer1[j],(sweepV_SMU.defbuffer2[j]/sweepV_SMU.defbuffer1[j])) + end + + sweepV_SMU.source.output = 0 + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end +end + + +function DC_Voltage_Sweep_Resistor_inTriggerModel(infoSMU,srcSettings,meaSettings,triggerName) + -- SMU channel assignment + local sweepV_SMU = slot[infoSMU[1]].smu[infoSMU[2]] + sweepV_SMU.reset() + -- Source Settings + sweepV_SMU.source.func = sweepV_SMU.FUNC_DC_VOLTAGE + sweepV_SMU.source.rangev = math.max(math.abs(srcSettings[1]), math.abs(srcSettings[2])) + sweepV_SMU.source.limiti = srcSettings[4] + sweepV_SMU.source.levelv = 0 + -- Measure Settings + sweepV_SMU.measure.rangev = meaSettings[1] + sweepV_SMU.measure.nplc = meaSettings[4] + sweepV_SMU.sense = sweepV_SMU.SENSE_2WIRE + if meaSettings[3] == true then + sweepV_SMU.measure.autorangei = 1 + else + sweepV_SMU.measure.autorangei = 0 + sweepV_SMU.measure.rangei = meaSettings[2] + end + + if meaSettings[6] == true then + sweepV_SMU.sense = sweepV_SMU.SENSE_4WIRE + else + sweepV_SMU.sense = sweepV_SMU.SENSE_2WIRE + end + -- Buffer clear + sweepV_SMU.defbuffer1.clear() + sweepV_SMU.defbuffer1.appendmode = 1 + sweepV_SMU.defbuffer2.clear() + sweepV_SMU.defbuffer2.appendmode = 1 + + -- Setup trigger model source levels for linear voltage sweep + sweepV_SMU.trigger.source.linearv(srcSettings[1], srcSettings[2], srcSettings[3]) + -- Set trigger model iv measurements to store in default buffers + sweepV_SMU.trigger.measure.iv(sweepV_SMU.defbuffer1,sweepV_SMU.defbuffer2) + + -- Configure trigger model + local triggerModel = slot[infoSMU[1]].trigger.model + triggerModel.create(triggerName) + -- Step source level to next value in sweep + triggerModel.addblock.source.action.step(triggerName, "sweepV_IV", infoSMU[2]) + -- Constant delay in trigger model is very accurate + triggerModel.addblock.delay.constant(triggerName, "IV_delay", meaSettings[5]) + -- Measure iv and store in buffers + triggerModel.addblock.measure(triggerName, "measure_IV", infoSMU[2], 1) + -- Loop for each value in sweep + triggerModel.addblock.branch.counter(triggerName, "branch-counter", "sweepV_IV", srcSettings[3]) + + sweepV_SMU.source.output = 1 + + -- Initiate trigger model and delete when complete + triggerModel.initiate(triggerName) + waitcomplete() + sweepV_SMU.source.output = 0 + triggerModel.delete(triggerName) + + -- Read measurement buffers and display + print("V\t\t I\t\t R") + for j = 1, sweepV_SMU.defbuffer1.n , 1 do + print(sweepV_SMU.defbuffer2[j],sweepV_SMU.defbuffer1[j],(sweepV_SMU.defbuffer2[j]/sweepV_SMU.defbuffer1[j])) + end + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end +end + +-- SMU channel Settings +local slot_no = 1 +local sweepV_channel= 1 +-- Source Settings +local startV = -5 +local stopV = 5 +local noPoints = 101 +local limitI = 1 +-- Measure Settings +local measRangeV = 6 +local measRangeI = 100e-3 +local autoRangeI = true +local nplc = 1 +local mDelay = 0 +local remoteSense = false + +local tm_name = "TM_sweepV" + +-- DC_Voltage_Sweep_Resistor_RunTimeEnv({slot_no, sweepV_channel},{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense}) + +-- Trigger model is used for greater accuracy +DC_Voltage_Sweep_Resistor_inTriggerModel({slot_no, sweepV_channel},{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense},tm_name) +errorQUEUE() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Drain_Family_Curves/MOSFET_Drain_Family_Curve.py b/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Drain_Family_Curves/MOSFET_Drain_Family_Curve.py new file mode 100644 index 0000000..1b8e5c9 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Drain_Family_Curves/MOSFET_Drain_Family_Curve.py @@ -0,0 +1,308 @@ +################################################################################# +# +# Script File: MOSFET_Drain_Family_Curve.tsp +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# drain famility of curve test for MOSFET devices. The purpose is show that +# you can perform the semiductor device characterization with the SMUs +# As written, two channels of SMU are assigned to the gate, drain, and the two +# LOs from the two channels are tied together to source. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 2 channel SMUs +# 1 MOSFET +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +################################################################################# + +def plotResults(x, y, numPoints, numSeries): + import matplotlib.pyplot as pt + + for i in range(numSeries): + pt.plot(x[i*numPoints:i*numPoints+numPoints], y[i*numPoints:i*numPoints+numPoints], '-', color="blue") + pt.ylabel("Drain Current (A)") + pt.xlabel("Drain Voltage (V)") + pt.grid(True) + pt.title("MOSFET Family of Curves") + + pt.tight_layout() + pt.show() + +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# information of SMU +gate_slot = 1 +drain_slot = 1 +common_slot = 1 +gate_channel = 1 +drain_channel = 2 +# Drain Channel Settings +startVd = 0 +stopVd = 2 +noSwpPoints = 41 +rangeId = 100e-3 +limitId = 100e-3 +# Gate Channel Settings +startVg = 1.4 +stopVg = 1.8 +noStpPoints = 3 +limitIg = 0.1 +# Measure Settings for both channels +nplc = 1 +mDelay = 0 + +tm_name1 = "tm_vdid" +tm_name2 = "tm_gate" +tm_name3 = "tm_drain" + +def DrainFamilyCurve_VdId_TriggerInOneSlot(): + gateSMU = f"slot[{common_slot}].smu[{gate_channel}]" + drainSMU = f"slot[{common_slot}].smu[{drain_channel}]" + smu_id = [gateSMU, drainSMU] + # Configure both channels for a voltage sweep + for i in smu_id: + inst.write(f"{i}.reset()") + inst.write(f"{i}.source.func = {i}.FUNC_DC_VOLTAGE") + inst.write(f"{i}.sense = {i}.SENSE_2WIRE") + + if i == gateSMU: + inst.write(f"{i}.source.rangev = {max(abs(startVg), abs(stopVg))}") + inst.write(f"{i}.source.limiti = {limitIg}") + else: + inst.write(f"{i}.source.rangev = {max(abs(startVd), abs(stopVd))}") + inst.write(f"{i}.source.limiti = {limitId}") + inst.write(f"{i}.measure.rangei = {rangeId}") + + inst.write(f"{i}.source.levelv = 0") + inst.write(f"{i}.measure.nplc = {nplc}") + inst.write(f"{i}.defbuffer1.clear()") + inst.write(f"{i}.defbuffer1.appendmode = 1") + inst.write(f"{i}.defbuffer2.clear()") + inst.write(f"{i}.defbuffer2.appendmode = 1") + + # Set both trigger model sources to a linear voltage sweep + inst.write(f"{drainSMU}.trigger.source.linearv({startVd}, {stopVd}, {noSwpPoints})") + # Set both trigger model measurements to i/v and store in default buffers + inst.write(f"{drainSMU}.trigger.measure.iv({drainSMU}.defbuffer1, {drainSMU}.defbuffer2)") + inst.write(f"{gateSMU}.trigger.source.linearv({startVg}, {stopVg}, {noStpPoints})") + inst.write(f"{gateSMU}.trigger.measure.iv({gateSMU}.defbuffer1, {gateSMU}.defbuffer2)") + +################################################ Trigger model ################################################# +################################################################################################################ + + triggerModel = f"slot[{common_slot}].trigger.model" + inst.write(f"{triggerModel}.create(\"{tm_name1}\")") + + # Advance gate voltage to next value ## Loop 2 + inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name1}\", \"gateVoltage\", {gate_channel})") ##--## + # Advance drain voltage to next value ## Loop 1 ##--## + inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name1}\", \"drainVoltage\", {drain_channel})") ##--## ##--## + # Measure i/v on both channels ##--## ##--## + inst.write(f"{triggerModel}.addblock.measure(\"{tm_name1}\", \"measure\", {gate_channel}, 1)") ##--## ##--## + inst.write(f"{triggerModel}.addblock.measure(\"{tm_name1}\", \"measure2\", {drain_channel}, 1)") ##--## ##--## + # Loop back to advance drain voltage ##--## ##--## + inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name1}\", \"branch-drain\", \"drainVoltage\", {noSwpPoints})") ##--## ##--## + # Loop back to advance gate voltage ##--## + inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name1}\", \"branch-gate\", \"gateVoltage\", {noStpPoints})") ##--## + # Set sources to 0 + inst.write(f"{triggerModel}.addblock.source.action.bias(\"{tm_name1}\", \"gatebiaszeo\", {gate_channel})") + inst.write(f"{triggerModel}.addblock.source.action.bias(\"{tm_name1}\", \"drainbiaszeo\", {drain_channel})") + +################################################################################################################ + + # Initiate trigger model and delete when completed + inst.write(f"{gateSMU}.source.output = 1") + inst.write(f"{drainSMU}.source.output = 1") + + inst.write(f"{triggerModel}.initiate(\"{tm_name1}\")") + inst.write(f"waitcomplete()") + + inst.write(f"{gateSMU}.source.output = 0") + inst.write(f"{drainSMU}.source.output = 0") + inst.write(f"{triggerModel}.delete(\"{tm_name1}\")") + + # Fetch buffer data + defBuffer1 = inst.query(f"printbuffer(1, {gateSMU}.defbuffer1.n, {gateSMU}.defbuffer1)").split(",") + defBuffer1 = [float(x) for x in defBuffer1] + defBuffer2 = inst.query(f"printbuffer(1, {gateSMU}.defbuffer2.n, {gateSMU}.defbuffer2)").split(",") + defBuffer2 = [float(x) for x in defBuffer2] + defBuffer3 = inst.query(f"printbuffer(1, {drainSMU}.defbuffer1.n, {drainSMU}.defbuffer1)").split(",") + defBuffer3 = [float(x) for x in defBuffer3] + defBuffer4 = inst.query(f"printbuffer(1, {drainSMU}.defbuffer2.n, {drainSMU}.defbuffer2)").split(",") + defBuffer4 = [float(x) for x in defBuffer4] + + # Display results + print("Vg","Ig","Vd","Id", sep="\t\t") + for i in range(len(defBuffer1)): + print(f"{defBuffer2[i]:.5e}", + f"{defBuffer1[i]:.5e}", + f"{defBuffer4[i]:.5e}", + f"{defBuffer3[i]:.5e}", + sep="\t" + ) + + inst.clear() + inst.close() + + plotResults(defBuffer4, defBuffer3, noSwpPoints, noStpPoints) + +def DrainFamilyCurve_VdId_TwoTriggers(): + gateSMU = f"slot[{gate_slot}].smu[{gate_channel}]" + drainSMU = f"slot[{drain_slot}].smu[{drain_channel}]" + smu_id = [gateSMU, drainSMU] + # Configure both channels for a voltage sweep + for i in smu_id: + inst.write(f"{i}.reset()") + inst.write(f"{i}.source.func = {i}.FUNC_DC_VOLTAGE") + inst.write(f"{i}.sense = {i}.SENSE_2WIRE") + + if i == gateSMU: + inst.write(f"{i}.source.rangev = {max(abs(startVg), abs(stopVg))}") + inst.write(f"{i}.source.limiti = {limitIg}") + else: + inst.write(f"{i}.source.rangev = {max(abs(startVd), abs(stopVd))}") + inst.write(f"{i}.source.limiti = {limitId}") + inst.write(f"{i}.measure.rangei = {rangeId}") + + inst.write(f"{i}.source.levelv = 0") + inst.write(f"{i}.measure.nplc = {nplc}") + + inst.write(f"{i}.defbuffer1.clear()") + inst.write(f"{i}.defbuffer1.appendmode = 1") + inst.write(f"{i}.defbuffer2.clear()") + inst.write(f"{i}.defbuffer2.appendmode = 1") + +############################################# Drain Trigger model ############################################## +################################################################################################################ + + # Configure trigger model for linear voltage sweep + inst.write(f"{drainSMU}.trigger.source.linearv({startVd}, {stopVd}, {noSwpPoints})") + # Set trigger model measurements to i/v and store in default buffers + inst.write(f"{drainSMU}.trigger.measure.iv({drainSMU}.defbuffer1, {drainSMU}.defbuffer2)") + + trigger_drain = f"slot[{drain_slot}].trigger.model" + inst.write(f"{trigger_drain}.create(\"{tm_name3}\")") + + # Notify gate that sweep has began ## Loop 2 + inst.write(f"{trigger_drain}.addblock.notify(\"{tm_name3}\",\"notifyStep\",slot[{drain_slot}].trigger.model.EVENT_NOTIFY1) ") ##--## + ##--## + # Wait for notification from gate that gate voltage has advanced ##--## + inst.write(f"{trigger_drain}.addblock.wait(\"{tm_name3}\",\"waitStep\",slot[{gate_slot}].trigger.model.EVENT_NOTIFY2 )") ##--## + # Advance drain source voltage ## Loop 1 ##--## + inst.write(f"{trigger_drain}.addblock.source.action.step(\"{tm_name3}\", \"drainSweep\", {drain_channel})") ##--## ##--## + # Notify gate that drain voltage has advanced ##--## ##--## + inst.write(f"{trigger_drain}.addblock.notify(\"{tm_name3}\",\"notifyMeasure\",slot[{drain_slot}].trigger.model.EVENT_NOTIFY3)") ##--## ##--## + # Measure drain i/v ##--## ##--## + inst.write(f"{trigger_drain}.addblock.measure(\"{tm_name3}\", \"measure\", {drain_channel}, 1)") ##--## ##--## + ##--## ##--## + # Wait for notification that gate measurement has been taken ##--## ##--## + inst.write(f"{trigger_drain}.addblock.wait(\"{tm_name3}\",\"waitMeasure\",slot[{gate_slot}].trigger.model.EVENT_NOTIFY4 )") ##--## ##--## + # Loop back to advancing drain voltage ##--## ##--## + inst.write(f"{trigger_drain}.addblock.branch.counter(\"{tm_name3}\", \"branch-sweep\", \"drainSweep\", {noSwpPoints})") ##--## ##--## + # Notify gate that drain voltage sweep has completed ##--## + inst.write(f"{trigger_drain}.addblock.notify(\"{tm_name3}\",\"notifySweepDone\",slot[{drain_slot}].trigger.model.EVENT_NOTIFY5)") ##--## + # Loop back to notify gate to begin sweep ##--## + inst.write(f"{trigger_drain}.addblock.branch.counter(\"{tm_name3}\", \"branch-step\", \"notifyStep\", {noStpPoints})") ##--## + inst.write(f"{trigger_drain}.addblock.source.action.bias(\"{tm_name3}\", \"drainbiaszeo\", {drain_channel})") + +############################################## Gate Trigger model ############################################## +################################################################################################################ + + inst.write(f"{gateSMU}.trigger.source.linearv({startVg}, {stopVg}, {noStpPoints})") + inst.write(f"{gateSMU}.trigger.measure.iv({gateSMU}.defbuffer1, {gateSMU}.defbuffer2)") + + trigger_gate = f"slot[{gate_slot}].trigger.model" + inst.write(f"{trigger_gate}.create(\"{tm_name2}\")") + + # Wait for notification from drain to begin ## Loop 2 + inst.write(f"{trigger_gate}.addblock.wait(\"{tm_name2}\",\"waitStep\",slot[{drain_slot}].trigger.model.EVENT_NOTIFY1 )") ##--## + # Advance gate source voltage ##--## + inst.write(f"{trigger_gate}.addblock.source.action.step(\"{tm_name2}\", \"gateVoltage\", {gate_channel})") ##--## + # Notify drain that gate voltage has advanced ##--## + inst.write(f"{trigger_gate}.addblock.notify(\"{tm_name2}\",\"notify\",slot[{gate_slot}].trigger.model.EVENT_NOTIFY2)") ##--## + ##--## + # Wait for notification that drain source voltage has advanced ## Loop 1 ##--## + inst.write(f"{trigger_gate}.addblock.wait(\"{tm_name2}\",\"waitMeasure\",slot[{drain_slot}].trigger.model.EVENT_NOTIFY3 )") ##--## ##--## + # Measure gate i/v ##--## ##--## + inst.write(f"{trigger_gate}.addblock.measure(\"{tm_name2}\",\"measure\",{gate_channel}, 1)") ##--## ##--## + # Notify drain that measurement has been taken ##--## ##--## + inst.write(f"{trigger_gate}.addblock.notify(\"{tm_name2}\",\"notify\",slot[{gate_slot}].trigger.model.EVENT_NOTIFY4)") ##--## ##--## + # Loop back to wait for drain voltage to advance ##--## ##--## + inst.write(f"{trigger_gate}.addblock.branch.counter(\"{tm_name2}\", \"branch-Measure\", \"waitMeasure\", {noSwpPoints})") ##--## ##--## + ##--## + # Wait for notification that drain voltage sweep has completed ##--## + inst.write(f"{trigger_gate}.addblock.wait(\"{tm_name2}\",\"waitSweepDone\",slot[{drain_slot}].trigger.model.EVENT_NOTIFY5 )") ##--## + # Loop back to wait for drain to begin ##--## + inst.write(f"{trigger_gate}.addblock.branch.counter(\"{tm_name2}\", \"branch-Step\", \"waitStep\", {noStpPoints})") ##--## + inst.write(f"{trigger_gate}.addblock.source.action.bias(\"{tm_name2}\", \"gatebiaszero\", {gate_channel})") + +################################################################################################################ + + # Initiate trigger models and delete when completed + inst.write(f"{gateSMU}.source.output = 1") + inst.write(f"{drainSMU}.source.output = 1") + inst.write(f"{trigger_gate}.initiate(\"{tm_name2}\")") + inst.write(f"{trigger_drain}.initiate(\"{tm_name3}\")") + + inst.write("waitcomplete()") + + # Fetch buffer data + defBuffer1 = inst.query(f"printbuffer(1, {gateSMU}.defbuffer1.n, {gateSMU}.defbuffer1)").split(",") + defBuffer1 = [float(x) for x in defBuffer1] + defBuffer2 = inst.query(f"printbuffer(1, {gateSMU}.defbuffer2.n, {gateSMU}.defbuffer2)").split(",") + defBuffer2 = [float(x) for x in defBuffer2] + defBuffer3 = inst.query(f"printbuffer(1, {drainSMU}.defbuffer1.n, {drainSMU}.defbuffer1)").split(",") + defBuffer3 = [float(x) for x in defBuffer3] + defBuffer4 = inst.query(f"printbuffer(1, {drainSMU}.defbuffer2.n, {drainSMU}.defbuffer2)").split(",") + defBuffer4 = [float(x) for x in defBuffer4] + + # Display results + print("Vg","Ig","Vd","Id", sep="\t\t") + for i in range(len(defBuffer1)): + print(f"{defBuffer2[i]:.5e}", + f"{defBuffer1[i]:.5e}", + f"{defBuffer4[i]:.5e}", + f"{defBuffer3[i]:.5e}", + sep="\t" + ) + + inst.write(f"{gateSMU}.source.output = 0") + inst.write(f"{drainSMU}.source.output = 0") + + inst.write(f"{trigger_drain}.delete(\"{tm_name3}\")") + inst.write(f"{trigger_gate}.delete(\"{tm_name2}\")") + + inst.clear() + inst.close() + + plotResults(defBuffer4, defBuffer3, noSwpPoints, noStpPoints) + +DrainFamilyCurve_VdId_TriggerInOneSlot() +#DrainFamilyCurve_VdId_TwoTriggers() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Drain_Family_Curves/MOSFET_Drain_Family_Curve.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Drain_Family_Curves/MOSFET_Drain_Family_Curve.tsp new file mode 100644 index 0000000..d4d57b5 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Drain_Family_Curves/MOSFET_Drain_Family_Curve.tsp @@ -0,0 +1,363 @@ +--[[ +################################################################################ + +Script File: MOSFET_Drain_Family_Curve.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + drain famility of curve test for MOSFET devices. The purpose is show that + you can perform the semiductor device characterization with the SMUs + As written, two channels of SMU are assigned to the gate, drain, and the two + LOs from the two channels are tied together to source. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 2 channel SMUs + 1 MOSFET + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * DrainFamilyCurve_VdId_RunTimeEnv({gate_slot,gate_channel,drain_slot,drain_channel},{startVd,stopVd,noSwpPoints,rangeId,limitId},{startVg,stopVg,noStpPoints,limitIg},{nplc,mDelay}) + * DrainFamilyCurve_VdId_TriggerInOneSlot({common_slot,gate_channel,drain_channel},{startVd,stopVd,noSwpPoints,rangeId,limitId},{startVg,stopVg,noStpPoints,limitIg},{nplc,mDelay},{"tm_vdid"}) + * DrainFamilyCurve_VdId_TwoTriggers({gate_slot,gate_channel,drain_slot,drain_channel},{startVd,stopVd,noSwpPoints,rangeId,limitId},{startVg,stopVg,noStpPoints,limitIg},{nplc,mDelay},{"tm_gate","tm_drain"}) + +Example Usage: + * DrainFamilyCurve_VdId_RunTimeEnv({1,1,1,2},{0,2,41,0.1,0.1},{1.4,1.8,3,0.1},{1,0}) + * DrainFamilyCurve_VdId_TriggerInOneSlot({1,1,2},{0,2,41,0.1,0.1},{1.4,1.8,3,0.1},{1,0},{"tm_vdid"}) + * DrainFamilyCurve_VdId_TwoTriggers({1,1,1,2},{0,2,41,0.1,0.1},{1.4,1.8,3,0.1},{1,0},{"tm_gate","tm_drain"}) + +################################################################################ +--]] +function DrainFamilyCurve_VdId_RunTimeEnv(infoSMU,dSMUsettings, gSMUsettings, meaSettings) + -- SMU channel assignment + local gateSMU = slot[infoSMU[1]].smu[infoSMU[2]] + local drainSMU = slot[infoSMU[3]].smu[infoSMU[4]] + local smu_id = {gateSMU,drainSMU} + -- Configure both channels for a voltage sweep + for i = 1, 2 do + smu_id[i].reset() + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].sense = smu_id[i].SENSE_2WIRE + + if smu_id[i] == gateSMU then + smu_id[i].source.rangev = math.max(math.abs(gSMUsettings[1]), math.abs(gSMUsettings[2])) + smu_id[i].source.limiti = gSMUsettings[4] + + else + smu_id[i].source.rangev = math.max(math.abs(dSMUsettings[1]), math.abs(dSMUsettings[2])) + smu_id[i].source.limiti = dSMUsettings[5] + smu_id[i].measure.rangei = dSMUsettings[4] + end + smu_id[i].source.levelv = 0 + smu_id[i].measure.nplc = meaSettings[1] + + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + end + + -- Calculate the voltage step for both channels + local deltaVd = (dSMUsettings[2] - dSMUsettings[1])/ (dSMUsettings[3] - 1) + local deltaVg = (gSMUsettings[2] - gSMUsettings[1])/ (gSMUsettings[3] - 1) + + gateSMU.source.output = 1 + gateSMU.source.output = 1 + + drainSMU.source.output = 1 + drainSMU.source.output = 1 + + print("Vg\t\t Ig\t\t Vd\t\t Id") + -- Execute both sweeps + for gateLoop = 1, gSMUsettings[3], 1 do + -- Advance gate voltage + gateSMU.source.levelv = gSMUsettings[1] + ((gateLoop -1) * deltaVg) + for drainLoop = 1, dSMUsettings[3] , 1 do + -- Advance drain voltage + drainSMU.source.levelv = dSMUsettings[1] + ((drainLoop-1) * deltaVd) + delay(meaSettings[2]) + -- Measure i/vo n both channels, store in default buffers + gateSMU.measure.iv(gateSMU.defbuffer1,gateSMU.defbuffer2) + drainSMU.measure.iv(drainSMU.defbuffer1,drainSMU.defbuffer2) + -- Display results + print(gateSMU.defbuffer2[(dSMUsettings[3]*(gateLoop-1)) + drainLoop ],gateSMU.defbuffer1[(dSMUsettings[3]*(gateLoop-1)) + drainLoop],drainSMU.defbuffer2[(dSMUsettings[3]*(gateLoop-1)) + drainLoop],drainSMU.defbuffer1[(dSMUsettings[3]*(gateLoop-1)) + drainLoop]) + end + end + gateSMU.source.output = 0 + drainSMU.source.output = 0 + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + errorQUEUE() +end + +function DrainFamilyCurve_VdId_TriggerInOneSlot(infoSMU,dSMUsettings, gSMUsettings, meaSettings,tmName) + -- SMU channel assignment + local gateSMU = slot[infoSMU[1]].smu[infoSMU[2]] + local drainSMU = slot[infoSMU[1]].smu[infoSMU[3]] + local smu_id = {gateSMU,drainSMU} + -- Configure both channels for a voltage sweep + for i = 1, 2 do + smu_id[i].reset() + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].sense = smu_id[i].SENSE_2WIRE + + if smu_id[i] == gateSMU then + smu_id[i].source.rangev = math.max(math.abs(gSMUsettings[1]), math.abs(gSMUsettings[2])) + smu_id[i].source.limiti = gSMUsettings[4] + + else + smu_id[i].source.rangev = math.max(math.abs(dSMUsettings[1]), math.abs(dSMUsettings[2])) + smu_id[i].source.limiti = dSMUsettings[5] + smu_id[i].measure.rangei = dSMUsettings[4] + end + smu_id[i].source.levelv = 0 + smu_id[i].measure.nplc = meaSettings[1] + + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + end + + -- Set both trigger model sources to a linear voltage sweep + drainSMU.trigger.source.linearv(dSMUsettings[1], dSMUsettings[2], dSMUsettings[3]) + -- Set both trigger model measurements to i/v and store in default buffers + drainSMU.trigger.measure.iv(drainSMU.defbuffer1,drainSMU.defbuffer2) + gateSMU.trigger.source.linearv(gSMUsettings[1], gSMUsettings[2], gSMUsettings[3]) + gateSMU.trigger.measure.iv(gateSMU.defbuffer1,gateSMU.defbuffer2) + +--############################################## Trigger model ###############################################-- +--############################################################################################################-- + + local triggerModel = slot[infoSMU[1]].trigger.model + triggerModel.create(tmName[1]) + + -- Advance gate voltage to next value -- Loop 2 + triggerModel.addblock.source.action.step(tmName[1], "gateVoltage", infoSMU[2]) --##-- + -- Advance drain voltage to next value -- Loop 1 --##-- + triggerModel.addblock.source.action.step(tmName[1], "drainVoltage", infoSMU[3]) --##-- --##-- + -- Measure i/v on both channels --##-- --##-- + triggerModel.addblock.measure(tmName[1], "measure", infoSMU[2], 1) --##-- --##-- + triggerModel.addblock.measure(tmName[1], "measure2", infoSMU[3], 1) --##-- --##-- + -- Loop back to advance drain voltage --##-- --##-- + triggerModel.addblock.branch.counter(tmName[1], "branch-drain", "drainVoltage", dSMUsettings[3]) --##-- --##-- + -- Loop back to advance gate voltage --##-- + triggerModel.addblock.branch.counter(tmName[1], "branch-gate", "gateVoltage", gSMUsettings[3]) --##-- + -- Set sources to 0 + triggerModel.addblock.source.action.bias(tmName[1], "gatebiaszeo", infoSMU[2]) + triggerModel.addblock.source.action.bias(tmName[1], "drainbiaszeo", infoSMU[3]) + +--############################################################################################################-- + + -- Initiate trigger model, delete when completed + gateSMU.source.output = 1 + drainSMU.source.output = 1 + + triggerModel.initiate(tmName[1]) + waitcomplete() + + gateSMU.source.output = 0 + drainSMU.source.output = 0 + triggerModel.delete(tmName[1]) + + -- Display results from buffers + print("Vg\t\t Ig\t\t Vd\t\t Id") + for j = 1, drainSMU.defbuffer1.n , 1 do + print(gateSMU.defbuffer2[j],gateSMU.defbuffer1[j],drainSMU.defbuffer2[j],drainSMU.defbuffer1[j]) + end + + ;-- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + errorQUEUE() +end + +function DrainFamilyCurve_VdId_TwoTriggers(infoSMU,dSMUsettings, gSMUsettings, meaSettings,tmName) + -- SMU channel assignment + local gateSMU = slot[infoSMU[1]].smu[infoSMU[2]] + local drainSMU = slot[infoSMU[3]].smu[infoSMU[4]] + local smu_id = {gateSMU,drainSMU} + -- Configure both channels for a voltage sweep + for i = 1, 2 do + smu_id[i].reset() + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].sense = smu_id[i].SENSE_2WIRE + + if smu_id[i] == gateSMU then + smu_id[i].source.rangev = math.max(math.abs(gSMUsettings[1]), math.abs(gSMUsettings[2])) + smu_id[i].source.limiti = gSMUsettings[4] + + else + smu_id[i].source.rangev = math.max(math.abs(dSMUsettings[1]), math.abs(dSMUsettings[2])) + smu_id[i].source.limiti = dSMUsettings[5] + smu_id[i].measure.rangei = dSMUsettings[4] + end + smu_id[i].source.levelv = 0 + smu_id[i].measure.nplc = meaSettings[1] + + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + end + +--########################################### Drain Trigger model ############################################-- +--############################################################################################################-- + + -- Configure trigger model source for linear voltage sweep + drainSMU.trigger.source.linearv(dSMUsettings[1], dSMUsettings[2], dSMUsettings[3]) + -- Set trigger model measurements to i/v and store in default buffers + drainSMU.trigger.measure.iv(drainSMU.defbuffer1,drainSMU.defbuffer2) + + local trigger_drain = slot[infoSMU[3]].trigger.model + trigger_drain.create(tmName[2]) + + -- Notify gate that sweep has began -- Loop 2 + trigger_drain.addblock.notify(tmName[2],"notifyStep",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY1) --##-- + --##-- + -- Wait for notification from gate that gate voltage has advanced --##-- + trigger_drain.addblock.wait(tmName[2],"waitStep",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY2 ) --##-- + -- Advance drain source voltage -- Loop 1 --##-- + trigger_drain.addblock.source.action.step(tmName[2], "drainSweep", infoSMU[4]) --##-- --##-- + -- Notify gate that drain voltage has advanced --##-- --##-- + trigger_drain.addblock.notify(tmName[2],"notifyMeasure",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY3) --##-- --##-- + -- Measure drain i/v --##-- --##-- + trigger_drain.addblock.measure(tmName[2], "measure", infoSMU[4], 1) --##-- --##-- + --##-- --##-- + -- Wait for notification that gate measurement has been taken --##-- --##-- + trigger_drain.addblock.wait(tmName[2],"waitMeasure",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY4 ) --##-- --##-- + -- Loop back to advancing drain voltage --##-- --##-- + trigger_drain.addblock.branch.counter(tmName[2], "branch-sweep", "drainSweep", dSMUsettings[3]) --##-- --##-- + -- Notify gate that drain voltage sweep has completed --##-- + trigger_drain.addblock.notify(tmName[2],"notifySweepDone",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY5) --##-- + -- Loop back to notify gate to begin sweep --##-- + trigger_drain.addblock.branch.counter(tmName[2], "branch-step", "notifyStep", gSMUsettings[3]) --##-- + trigger_drain.addblock.source.action.bias(tmName[2], "drainbiaszeo", infoSMU[4]) + +--############################################ Gate Trigger model ############################################-- +--############################################################################################################-- + + -- Configure trigger model source for linear voltage sweep + gateSMU.trigger.source.linearv(gSMUsettings[1], gSMUsettings[2], gSMUsettings[3]) + -- Set trigger model measurements to i/v and store in default buffers + gateSMU.trigger.measure.iv(gateSMU.defbuffer1,gateSMU.defbuffer2) + + local trigger_gate = slot[infoSMU[1]].trigger.model + trigger_gate.create(tmName[1]) + + -- Wait for notification from drain to begin -- Loop 2 + trigger_gate.addblock.wait(tmName[1],"waitStep",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY1 ) --##-- + -- Advance gate source voltage --##-- + trigger_gate.addblock.source.action.step(tmName[1], "gateVoltage", infoSMU[2]) --##-- + -- Notify drain that gate voltage has advanced --##-- + trigger_gate.addblock.notify(tmName[1],"notify",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY2) --##-- + --##-- + -- Wait for notification that drain source voltage has advanced -- Loop 1 --##-- + trigger_gate.addblock.wait(tmName[1],"waitMeasure",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY3 ) --##-- --##-- + -- Measure gate i/v --##-- --##-- + trigger_gate.addblock.measure(tmName[1],"measure",infoSMU[2],1) --##-- --##-- + -- Notify drain that measurement has been taken --##-- --##-- + trigger_gate.addblock.notify(tmName[1],"notify",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY4) --##-- --##-- + -- Loop back to wait for drain voltage to advance --##-- --##-- + trigger_gate.addblock.branch.counter(tmName[1], "branch-Measure", "waitMeasure", dSMUsettings[3]) --##-- --##-- + --##-- + -- Wait for notification that drain voltage sweep has completed --##-- + trigger_gate.addblock.wait(tmName[1],"waitSweepDone",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY5 ) --##-- + -- Loop back to wait for drain to begin --##-- + trigger_gate.addblock.branch.counter(tmName[1], "branch-Step", "waitStep", gSMUsettings[3]) --##-- + trigger_gate.addblock.source.action.bias(tmName[1], "gatebiaszero", infoSMU[2]) + +--############################################################################################################-- + + -- Initiate trigger models and delete when completed + gateSMU.source.output = 1 + drainSMU.source.output = 1 + trigger_gate.initiate(tmName[1]) + trigger_drain.initiate(tmName[2]) + + waitcomplete() + + -- Display results from default buffers + print("Vg\t\t Ig\t\t Vd\t\t Id") + for j = 1, drainSMU.defbuffer1.n , 1 do + print(gateSMU.defbuffer2[j],gateSMU.defbuffer1[j],drainSMU.defbuffer2[j],drainSMU.defbuffer1[j]) + end + + gateSMU.source.output = 0 + drainSMU.source.output = 0 + + trigger_gate.delete(tmName[1]) + trigger_drain.delete(tmName[2]) + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + errorQUEUE() +end + +-- information of SMU +local gate_slot = 1 +local drain_slot = 1 +local common_slot = 1 +local gate_channel = 1 +local drain_channel = 2 +-- Drain Channel Settings +local startVd = 0 +local stopVd = 2 +local noSwpPoints = 41 +local rangeId = 100e-3 +local limitId = 100e-3 +-- Gate Channel Settings +local startVg = 1.4 +local stopVg = 1.8 +local noStpPoints = 3 +local limitIg = 0.1 +-- Measure Settings for both channels +local nplc = 1 +local mDelay = 0 + +-- DrainFamilyCurve_VdId_RunTimeEnv({gate_slot,gate_channel,drain_slot,drain_channel},{startVd,stopVd,noSwpPoints,rangeId,limitId},{startVg,stopVg,noStpPoints,limitIg},{nplc,mDelay}) + +DrainFamilyCurve_VdId_TriggerInOneSlot({common_slot,gate_channel,drain_channel},{startVd,stopVd,noSwpPoints,rangeId,limitId},{startVg,stopVg,noStpPoints,limitIg},{nplc,mDelay},{"tm_vdid"}) + +--DrainFamilyCurve_VdId_TwoTriggers({gate_slot,gate_channel,drain_slot,drain_channel},{startVd,stopVd,noSwpPoints,rangeId,limitId},{startVg,stopVg,noStpPoints,limitIg},{nplc,mDelay},{"tm_gate","tm_drain"}) + diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Drain_Family_Curves/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Drain_Family_Curves/README.md new file mode 100644 index 0000000..0120114 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Drain_Family_Curves/README.md @@ -0,0 +1,15 @@ +# MOSFET Drain Family of Curves + +Two channels are used to perform voltage sweeps for a family of curves characterization of a MOSFET. Measurements are recorded throughout the test. + +NOTE: There are two functions provided. “DrainFamilyCurve_VdId_TriggerInOneSlot” handles 2 channels on 1 SMU and “DrainFamilyCurve_VdId_TriggerInOneSlot” handles using two channels on different slots. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Transfer_Curve/MOSFET_Transfer_Curve.py b/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Transfer_Curve/MOSFET_Transfer_Curve.py new file mode 100644 index 0000000..106b82a --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Transfer_Curve/MOSFET_Transfer_Curve.py @@ -0,0 +1,299 @@ +################################################################################ +# +# Script File: MOSFET_Transfer_Curve.tsp +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# drain famility of curve test for MOSFET devices. The purpose is show that +# you can perform the semiductor device characterization with the SMUs +# As written, two channels of SMU are assigned to the gate, drain, and the two +# LOs from the two channels are tied together to source. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 2 channel SMUs +# 1 MOSFET +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +################################################################################ + +def plotResults(x, y): + import matplotlib.pyplot as pt + + pt.plot(x, y, '-', color="blue") + pt.ylabel("Drain Current (A)") + pt.xlabel("Gate Voltage (V)") + pt.grid(True) + pt.title("MOSFET Transfer Curve") + + pt.tight_layout() + pt.show() + +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# information of SMU +gate_slot = 1 +drain_slot = 1 +common_slot = 1 +gate_channel = 1 +drain_channel = 2 +# Drain Channel Settings +Vd = 1 +limitId = 0.1 +# Gate Channel Settings +startVg = 0 +stopVg = 2.2 +noSwpPoints = 45 +limitIg = 0.1 +# Measure Settings for both channels +nplc = 1 +mDelay = 0 + +tm_name1 = "tm_vgid" +tm_name2 = "tm_gate" +tm_name3 = "tm_drain" + +def TransferCurve_VgId_TriggerInOneSlot(): + gateSMU = f"slot[{common_slot}].smu[{gate_channel}]" + drainSMU = f"slot[{common_slot}].smu[{drain_channel}]" + smu_id = [gateSMU, drainSMU] + # Configure both channels for a voltage output + for i in smu_id: + inst.write(f"{i}.reset()") + inst.write(f"{i}.source.func = {i}.FUNC_DC_VOLTAGE") + inst.write(f"{i}.sense = {i}.SENSE_2WIRE") + if i == gateSMU: + inst.write(f"{i}.source.rangev = {max(abs(startVg), abs(stopVg))}") + inst.write(f"{i}.source.limiti = {limitIg}") + else: + inst.write(f"{i}.source.rangev = {Vd}") + inst.write(f"{i}.measure.rangev = {Vd}") + inst.write(f"{i}.source.limiti = {limitId}") + + inst.write(f"{i}.source.levelv = 0") + inst.write(f"{i}.measure.nplc = {nplc}") + inst.write(f"{i}.measure.autorangei = 1") + + inst.write(f"{i}.defbuffer1.clear()") + inst.write(f"{i}.defbuffer1.appendmode = 1") + inst.write(f"{i}.defbuffer2.clear()") + inst.write(f"{i}.defbuffer2.appendmode = 1") + + # Set both trigger model sources to a linear voltage sweep + inst.write(f"{drainSMU}.trigger.source.linearv({Vd}, {Vd}, 2)") + # Set both trigger model measurements to i/v and store in default buffers + inst.write(f"{gateSMU}.trigger.source.linearv({startVg}, {stopVg}, {noSwpPoints})") + inst.write(f"{gateSMU}.trigger.measure.iv({gateSMU}.defbuffer1, {gateSMU}.defbuffer2)") + inst.write(f"{drainSMU}.trigger.measure.iv({drainSMU}.defbuffer1, {drainSMU}.defbuffer2)") + +################################################ Trigger model ################################################# +################################################################################################################ + + triggerModel = f"slot[{common_slot}].trigger.model" + inst.write(f"{triggerModel}.create(\"{tm_name1}\")") + + # Advance drain voltage to next value ## Loop 1 + inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name1}\", \"drainVoltage\", {drain_channel})") ##--## + # Advance gate voltage to next value ##--## + inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name1}\", \"gateVoltage\", {gate_channel})") ##--## + inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name1}\",\"meaDelay\", {mDelay})") ##--## + # Measure i/v on both channels ##--## + inst.write(f"{triggerModel}.addblock.measure(\"{tm_name1}\", \"measure\", {gate_channel}, 1)") ##--## + inst.write(f"{triggerModel}.addblock.measure(\"{tm_name1}\", \"measure2\", {drain_channel}, 1)") ##--## + # Loop through all sweep values ##--## + inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name1}\", \"branch-counter\", \"gateVoltage\", {noSwpPoints})") ##--## + +################################################################################################################ + + # Initiate trigger model, delete when finished + inst.write(f"{gateSMU}.source.output = 1") + inst.write(f"{drainSMU}.source.output = 1") + + inst.write(f"{triggerModel}.initiate(\"{tm_name1}\")") + inst.write(f"waitcomplete()") + + # Retrieve buffer data + defBuffer1 = inst.query(f"printbuffer(1, {gateSMU}.defbuffer1.n, {gateSMU}.defbuffer1)").split(",") + defBuffer1 = [float(x) for x in defBuffer1] + defBuffer2 = inst.query(f"printbuffer(1, {gateSMU}.defbuffer2.n, {gateSMU}.defbuffer2)").split(",") + defBuffer2 = [float(x) for x in defBuffer2] + defBuffer3 = inst.query(f"printbuffer(1, {drainSMU}.defbuffer1.n, {drainSMU}.defbuffer1)").split(",") + defBuffer3 = [float(x) for x in defBuffer3] + defBuffer4 = inst.query(f"printbuffer(1, {drainSMU}.defbuffer2.n, {drainSMU}.defbuffer2)").split(",") + defBuffer4 = [float(x) for x in defBuffer4] + + # Display results + print("Vg","Ig","Vd","Id", sep="\t\t") + for i in range(len(defBuffer1)): + print(f"{defBuffer2[i]:.5e}", + f"{defBuffer1[i]:.5e}", + f"{defBuffer4[i]:.5e}", + f"{defBuffer3[i]:.5e}", + sep="\t" + ) + + inst.write(f"{gateSMU}.source.output = 0") + inst.write(f"{drainSMU}.source.output = 0") + + inst.write(f"{triggerModel}.delete(\"{tm_name1}\")") + + inst.clear() + inst.close() + + plotResults(defBuffer2, defBuffer3) + +def TransferCurve_VgId_TwoTriggers(): + gateSMU = f"slot[{gate_slot}].smu[{gate_channel}]" + drainSMU = f"slot[{drain_slot}].smu[{drain_channel}]" + smu_id = [gateSMU, drainSMU] + + for i in smu_id: + inst.write(f"{i}.reset()") + inst.write(f"{i}.source.func = {i}.FUNC_DC_VOLTAGE") + inst.write(f"{i}.sense = {i}.SENSE_2WIRE") + # Configure both channels for voltage output + if i == gateSMU: + inst.write(f"{i}.source.rangev = {max(abs(startVg), abs(stopVg))}") + inst.write(f"{i}.source.limiti = {limitIg}") + else: + inst.write(f"{i}.source.rangev = {Vd}") + inst.write(f"{i}.source.limiti = {limitId}") + inst.write(f"{i}.source.levelv = 0") + inst.write(f"{i}.measure.nplc = {nplc}") + inst.write(f"{i}.measure.autorangei = 1") + inst.write(f"{i}.defbuffer1.clear()") + inst.write(f"{i}.defbuffer1.appendmode = 1") + inst.write(f"{i}.defbuffer2.clear()") + inst.write(f"{i}.defbuffer2.appendmode = 1") + +############################################## Gate Trigger model ############################################## +################################################################################################################ + + # Configure gate source for linear voltage sweep + inst.write(f"{gateSMU}.trigger.source.linearv({startVg}, {stopVg}, {noSwpPoints})") + # Set trigger model to measure i/v and store in default buffer + inst.write(f"{gateSMU}.trigger.measure.iv({gateSMU}.defbuffer1, {gateSMU}.defbuffer2)") + + trigger_gate = f"slot[{gate_slot}].trigger.model" + inst.write(f"{trigger_gate}.create(\"{tm_name2}\")") + + # Notify drain that sweep has began + inst.write(f"{trigger_gate}.addblock.notify(\"{tm_name2}\",\"notifyDrainStep\",slot[{gate_slot}].trigger.model.EVENT_NOTIFY1)") + + # Wait for notification that drain source voltage has advanced + inst.write(f"{trigger_gate}.addblock.wait(\"{tm_name2}\",\"waitDrainStep\",slot[{drain_slot}].trigger.model.EVENT_NOTIFY2)") + # Advance gate voltage source level ## Loop 1 + inst.write(f"{trigger_gate}.addblock.source.action.step(\"{tm_name2}\",\"gateVoltage\",{gate_channel})") ##--## + # Notify drain that source voltage has advanced ##--## + inst.write(f"{trigger_gate}.addblock.notify(\"{tm_name2}\",\"notify\",slot[{gate_slot}].trigger.model.EVENT_NOTIFY3)") ##--## + # Measure drain i/v ##--## + inst.write(f"{trigger_gate}.addblock.measure(\"{tm_name2}\",\"measure\",{gate_channel},1)") ##--## + ##--## + # Wait for measurement completed notification from drain ##--## + inst.write(f"{trigger_gate}.addblock.wait(\"{tm_name2}\",\"wait\",slot[{drain_slot}].trigger.model.EVENT_NOTIFY4)") ##--## + # Loop back to advance source voltage ##--## + inst.write(f"{trigger_gate}.addblock.branch.counter(\"{tm_name2}\",\"branch-gate\",\"gateVoltage\",{noSwpPoints})") ##--## + # Notify drain that sweep has completed + inst.write(f"{trigger_gate}.addblock.notify(\"{tm_name2}\",\"notifySweepDone\",slot[{gate_slot}].trigger.model.EVENT_NOTIFY5)") + inst.write(f"{trigger_gate}.addblock.source.action.bias(\"{tm_name2}\",\"gatezero\",{gate_channel})") + +############################################# Drain Trigger model ############################################## +################################################################################################################ + + # Configure drain source to maintain constant voltage + inst.write(f"{drainSMU}.trigger.source.linearv({Vd}, {Vd}, 1)") + # Set trigger model to measure i/v and store in default buffers + inst.write(f"{drainSMU}.trigger.measure.iv({drainSMU}.defbuffer1,{drainSMU}.defbuffer2)") + + trigger_drain = f"slot[{drain_slot}].trigger.model" + inst.write(f"{trigger_drain}.create(\"{tm_name3}\")") + + # Wait for notification that sweep has begun + inst.write(f"{trigger_drain}.addblock.wait(\"{tm_name3}\",\"waitStep\",slot[{gate_slot}].trigger.model.EVENT_NOTIFY1)") + # Advance source voltage level + inst.write(f"{trigger_drain}.addblock.source.action.step(\"{tm_name3}\",\"drainVoltage\",{drain_channel})") + # Notify gate that source voltage has advanced + inst.write(f"{trigger_drain}.addblock.notify(\"{tm_name3}\",\"notifyStep\",slot[{drain_slot}].trigger.model.EVENT_NOTIFY2)") + + # Wait for notificaiton that gate voltage source has advanced ## Loop 1 + inst.write(f"{trigger_drain}.addblock.wait(\"{tm_name3}\",\"waitMeas\",slot[{gate_slot}].trigger.model.EVENT_NOTIFY3)") ##--## + inst.write(f"{trigger_drain}.addblock.delay.constant(\"{tm_name3}\",\"meaDelay\",{mDelay})") ##--## + # Measure drain i/v ##--## + inst.write(f"{trigger_drain}.addblock.measure(\"{tm_name3}\",\"measure\",{drain_channel},1)") ##--## + # Notify gate that measurement has been taken ##--## + inst.write(f"{trigger_drain}.addblock.notify(\"{tm_name3}\",\"notify\",slot[{drain_slot}].trigger.model.EVENT_NOTIFY4)") ##--## + # Loop back to wait block ##--## + inst.write(f"{trigger_drain}.addblock.branch.counter(\"{tm_name3}\",\"branch-drain\",\"waitMeas\",{noSwpPoints})") ##--## + + # Wait for notificaiton that sweep has completed + inst.write(f"{trigger_drain}.addblock.wait(\"{tm_name3}\",\"waitSweepDone\",slot[{gate_slot}].trigger.model.EVENT_NOTIFY5)") + inst.write(f"{trigger_drain}.addblock.source.action.bias(\"{tm_name3}\",\"drainzero\",{drain_channel})") + +################################################################################################################ + + # Initiate trigger models, delete when completed + inst.write(f"{gateSMU}.source.output = 1") + inst.write(f"{drainSMU}.source.output = 1") + + inst.write(f"{trigger_drain}.initiate(\"{tm_name3}\")") + inst.write(f"{trigger_gate}.initiate(\"{tm_name2}\")") + inst.write("waitcomplete()") + + # Retrieve buffer data + defBuffer1 = inst.query(f"printbuffer(1, {gateSMU}.defbuffer1.n, {gateSMU}.defbuffer1)").split(",") + defBuffer1 = [float(x) for x in defBuffer1] + defBuffer2 = inst.query(f"printbuffer(1, {gateSMU}.defbuffer2.n, {gateSMU}.defbuffer2)").split(",") + defBuffer2 = [float(x) for x in defBuffer2] + defBuffer3 = inst.query(f"printbuffer(1, {drainSMU}.defbuffer1.n, {drainSMU}.defbuffer1)").split(",") + defBuffer3 = [float(x) for x in defBuffer3] + defBuffer4 = inst.query(f"printbuffer(1, {drainSMU}.defbuffer2.n, {drainSMU}.defbuffer2)").split(",") + defBuffer4 = [float(x) for x in defBuffer4] + + # Display results + print("Vg","Ig","Vd","Id", sep="\t\t") + for i in range(len(defBuffer1)): + print(f"{defBuffer2[i]:.5e}", + f"{defBuffer1[i]:.5e}", + f"{defBuffer4[i]:.5e}", + f"{defBuffer3[i]:.5e}", + sep="\t" + ) + + inst.write(f"{gateSMU}.source.output = 0") + inst.write(f"{drainSMU}.source.output = 0") + + inst.write(f"{trigger_gate}.delete(\"{tm_name2}\")") + inst.write(f"{trigger_drain}.delete(\"{tm_name3}\")") + + inst.clear() + inst.close() + + plotResults(defBuffer2, defBuffer3) + +#TransferCurve_VgId_TriggerInOneSlot() +TransferCurve_VgId_TwoTriggers() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Transfer_Curve/MOSFET_Transfer_Curve.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Transfer_Curve/MOSFET_Transfer_Curve.tsp new file mode 100644 index 0000000..0a2f1f2 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Transfer_Curve/MOSFET_Transfer_Curve.tsp @@ -0,0 +1,351 @@ +--[[ +################################################################################ + +Script File: MOSFET_Transfer_Curve.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + drain famility of curve test for MOSFET devices. The purpose is show that + you can perform the semiductor device characterization with the SMUs + As written, two channels of SMU are assigned to the gate, drain, and the two + LOs from the two channels are tied together to source. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 2 channel SMUs + 1 MOSFET + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * TransferCurve_VgId_RunTimeEnv({gate_slot,gate_channel,drain_slot,drain_channel},{Vd,limitId},{startVg,stopVg,noSwpPoints,limitIg},{nplc,mDelay}) + * TransferCurve_VgId_TriggerOneSlot({common_slot,gate_channel,drain_channel},{Vd,limitId},{startVg,stopVg,noSwpPoints,limitIg},{nplc,mDelay}) + * TransferCurve_VgId_TwoTriggers({gate_slot,gate_channel,drain_slot,drain_channel},{Vd,limitId},{startVg,stopVg,noSwpPoints,limitIg},{nplc,mDelay}) + +Example Usage: + * TransferCurve_VgId_RunTimeEnv({1,1,1,2},{1,0.1},{0,2.2,45,0.1},{1,0}) + * TransferCurve_VgId_TriggerOneSlot({1,1,2},{1,0.1},{0,2.2,45,0.1},{1,0},{"tm_vgid"}) + * TransferCurve_VgId_TwoTriggers({1,1,1,1},{1,0.1},{0,2.2,45,0.1},{1,0},{"tm_gate","tm_drain"}) + +################################################################################ +--]] +function TransferCurve_VgId_RunTimeEnv(infoSMU,dSMUsettings, gSMUsettings, meaSettings) + -- SMU channel assignment + local gateSMU = slot[infoSMU[1]].smu[infoSMU[2]] + local drainSMU = slot[infoSMU[3]].smu[infoSMU[4]] + local smu_id = {gateSMU,drainSMU} + -- Set both channels for voltage output + for i = 1, 2 do + smu_id[i].reset() + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].sense = smu_id[i].SENSE_2WIRE + if smu_id[i] == gateSMU then + smu_id[i].source.rangev = math.max(math.abs(gSMUsettings[1]), math.abs(gSMUsettings[2])) + smu_id[i].source.limiti = gSMUsettings[4] + else + smu_id[i].source.rangev = dSMUsettings[1] + smu_id[i].source.limiti = dSMUsettings[2] + end + smu_id[i].source.levelv = 0 + smu_id[i].measure.nplc = meaSettings[1] + smu_id[i].measure.autorangei = 1 + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + end + + -- Calculate change in voltage for each iteration + local deltaVg = (gSMUsettings[2] - gSMUsettings[1])/ (gSMUsettings[3] - 1) + gateSMU.source.output = 1 + drainSMU.source.output = 1 + -- Set constant drain voltage + drainSMU.source.levelv = dSMUsettings[1] + + print("Vg\t\t Ig\t\t Vd\t\t Id") + for j = 1, gSMUsettings[3] , 1 do + -- Advance gate source level + gateSMU.source.levelv = gSMUsettings[1] + ((j-1) * deltaVg) + delay(meaSettings[2]) + -- Measure i/v for both channels and store in default buffers + gateSMU.measure.iv(gateSMU.defbuffer1, gateSMU.defbuffer2) + drainSMU.measure.iv(drainSMU.defbuffer1, drainSMU.defbuffer2) + -- Display results + print(gateSMU.defbuffer2[j],gateSMU.defbuffer1[j],drainSMU.defbuffer2[j],drainSMU.defbuffer1[j]) + end + + gateSMU.source.output = 0 + drainSMU.source.output = 0 + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + errorQUEUE() +end + +function TransferCurve_VgId_TriggerInOneSlot(infoSMU,dSMUsettings, gSMUsettings, meaSettings,tmName) + -- SMU channel assignment + local gateSMU = slot[infoSMU[1]].smu[infoSMU[2]] + local drainSMU = slot[infoSMU[1]].smu[infoSMU[3]] + local smu_id = {gateSMU,drainSMU} + -- Set both channels for voltage output + for i = 1, 2 do + smu_id[i].reset() + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].sense = smu_id[i].SENSE_2WIRE + if smu_id[i] == gateSMU then + smu_id[i].source.rangev = math.max(math.abs(gSMUsettings[1]), math.abs(gSMUsettings[2])) + smu_id[i].source.limiti = gSMUsettings[4] + + else + smu_id[i].source.rangev = dSMUsettings[1] + smu_id[i].measure.rangev = dSMUsettings[1] + smu_id[i].source.limiti = dSMUsettings[2] + end + smu_id[i].source.levelv = 0 + smu_id[i].measure.nplc = meaSettings[1] + smu_id[i].measure.autorangei = 1 + + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + end + -- Configure both trigger models for voltage sweep + -- Drain does not actually sweep, it remains constant + drainSMU.trigger.source.linearv(dSMUsettings[1], dSMUsettings[1], 2) + gateSMU.trigger.source.linearv(gSMUsettings[1], gSMUsettings[2], gSMUsettings[3]) + -- Set both trigger models to measure i/v and store in default buffer + gateSMU.trigger.measure.iv(gateSMU.defbuffer1,gateSMU.defbuffer2) + drainSMU.trigger.measure.iv(drainSMU.defbuffer1,drainSMU.defbuffer2) + +--############################################## Trigger model ###############################################-- +--############################################################################################################-- + + local triggerModel = slot[infoSMU[1]].trigger.model + triggerModel.create(tmName[1]) + + -- Advance the drain source voltage + triggerModel.addblock.source.action.step(tmName[1], "drainVoltage", infoSMU[3]) + -- Advance the gate source voltage -- Loop 1 + triggerModel.addblock.source.action.step(tmName[1], "gateVoltage", infoSMU[2]) --##-- + triggerModel.addblock.delay.constant(tmName[1],"meaDelay",meaSettings[2]) --##-- + -- Measure i/v on both channels --##-- + triggerModel.addblock.measure(tmName[1], "measure", infoSMU[2], 1) --##-- + triggerModel.addblock.measure(tmName[1], "measure2", infoSMU[3], 1) --##-- + -- Loop through all sweep values --##-- + triggerModel.addblock.branch.counter(tmName[1], "branch-counter", "gateVoltage", gSMUsettings[3]) --##-- + +--############################################################################################################-- + + -- Initiate trigger model and delete when completed + gateSMU.source.output = 1 + gateSMU.source.output = 1 + drainSMU.source.output = 1 + drainSMU.source.output = 1 + triggerModel.initiate(tmName[1]) + waitcomplete() + + -- Display results + print("Vg\t\t Ig\t\t Vd\t\t Id") + for j = 1, gateSMU.defbuffer1.n , 1 do + print(gateSMU.defbuffer2[j],gateSMU.defbuffer1[j],drainSMU.defbuffer2[j],drainSMU.defbuffer1[j]) + end + + gateSMU.source.output = 0 + drainSMU.source.output = 0 + triggerModel.delete(tmName[1]) + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + errorQUEUE() +end + + +function TransferCurve_VgId_TwoTriggers(infoSMU,dSMUsettings, gSMUsettings, meaSettings,tmName) + -- SMU channel assignment + local gateSMU = slot[infoSMU[1]].smu[infoSMU[2]] + local drainSMU = slot[infoSMU[3]].smu[infoSMU[4]] + local smu_id = {gateSMU,drainSMU} + -- Configure both channels for voltage output + for i = 1, 2 do + smu_id[i].reset() + smu_id[i].source.func = smu_id[i].FUNC_DC_VOLTAGE + smu_id[i].sense = smu_id[i].SENSE_2WIRE + + if smu_id[i] == gateSMU then + smu_id[i].source.rangev = math.max(math.abs(gSMUsettings[1]), math.abs(gSMUsettings[2])) + smu_id[i].source.limiti = gSMUsettings[4] + else + smu_id[i].source.rangev = dSMUsettings[1] + smu_id[i].source.limiti = dSMUsettings[2] + end + smu_id[i].source.levelv = 0 + smu_id[i].measure.nplc = meaSettings[1] + smu_id[i].measure.autorangei = 1 + smu_id[i].defbuffer1.clear() + smu_id[i].defbuffer1.appendmode = 1 + smu_id[i].defbuffer2.clear() + smu_id[i].defbuffer2.appendmode = 1 + end + +--############################################ Gate trigger model ############################################-- +--############################################################################################################-- + + -- Configure gate source for linear voltage sweep + gateSMU.trigger.source.linearv(gSMUsettings[1], gSMUsettings[2], gSMUsettings[3]) + -- Set trigger model to measure i/v and store in default buffer + gateSMU.trigger.measure.iv(gateSMU.defbuffer1,gateSMU.defbuffer2) + + local trigger_gate = slot[infoSMU[1]].trigger.model + trigger_gate.create(tmName[1]) + + -- Notify drain that sweep has began + trigger_gate.addblock.notify(tmName[1],"notifyDrainStep",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY1) + + -- Wait for notification that drain source voltage has advanced + trigger_gate.addblock.wait(tmName[1],"waitDrainStep",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY2 ) + -- Advance gate voltage source level -- Loop 1 + trigger_gate.addblock.source.action.step(tmName[1], "gateVoltage", infoSMU[2]) --##-- + -- Notify drain that source voltage has advanced --##-- + trigger_gate.addblock.notify(tmName[1],"notify",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY3) --##-- + -- Measure drain i/v --##-- + trigger_gate.addblock.measure(tmName[1], "measure", infoSMU[2], 1) --##-- + --##-- + -- Wait for measurement completed notification from drain --##-- + trigger_gate.addblock.wait(tmName[1],"wait",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY4 ) --##-- + -- Loop back to advance source voltage --##-- + trigger_gate.addblock.branch.counter(tmName[1], "branch-gate", "gateVoltage", gSMUsettings[3]) --##-- + -- Notify drain that sweep has completed + trigger_gate.addblock.notify(tmName[1],"notifySweepDone",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY5) + trigger_gate.addblock.source.action.bias(tmName[1],"gatezero", infoSMU[2]) + +--########################################### Drain trigger model ############################################-- +--############################################################################################################-- + + -- Configure drain source to maintain constant voltage + drainSMU.trigger.source.linearv(dSMUsettings[1], dSMUsettings[1], 1) + -- Set trigger model to measure i/v and store in default buffers + drainSMU.trigger.measure.iv(drainSMU.defbuffer1,drainSMU.defbuffer2) + + local trigger_drain = slot[infoSMU[3]].trigger.model + trigger_drain.create(tmName[2]) + + -- Wait for notificaiton that sweep has begun + trigger_drain.addblock.wait(tmName[2],"waitStep",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY1 ) + -- Advance source voltage level + trigger_drain.addblock.source.action.step(tmName[2], "drainVoltage", infoSMU[4]) + -- Notify gate that source voltage has advanced + trigger_drain.addblock.notify(tmName[2],"notifyStep",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY2) + + -- Wait for notification that gate voltage source has advanced -- Loop 1 + trigger_drain.addblock.wait(tmName[2],"waitMeas",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY3 ) --##-- + trigger_drain.addblock.delay.constant(tmName[2],"meaDelay",meaSettings[2]) --##-- + -- Measure drain i/v --##-- + trigger_drain.addblock.measure(tmName[2], "measure", infoSMU[4], 1) --##-- + -- Notify gate that measurement has been taken --##-- + trigger_drain.addblock.notify(tmName[2],"notify",slot[infoSMU[3]].trigger.model.EVENT_NOTIFY4) --##-- + -- Loop back to wait block --##-- + trigger_drain.addblock.branch.counter(tmName[2], "branch-drain", "waitMeas", gSMUsettings[3]) --##-- + + -- Wait for notification that sweep has completed + trigger_drain.addblock.wait(tmName[2],"waitSweepDone",slot[infoSMU[1]].trigger.model.EVENT_NOTIFY5 ) + trigger_drain.addblock.source.action.bias(tmName[2], "drainzero", infoSMU[4]) + +--############################################################################################################-- + + -- Initiate trigger models, delete when finished + gateSMU.source.output = 1 + gateSMU.source.output = 1 + + drainSMU.source.output = 1 + drainSMU.source.output = 1 + + trigger_drain.initiate(tmName[2]) + trigger_gate.initiate(tmName[1]) + + waitcomplete() + + -- Display results + print("Vg\t\t Ig\t\t Vd\t\t Id") + for j = 1, gateSMU.defbuffer1.n , 1 do + print(gateSMU.defbuffer2[j],gateSMU.defbuffer1[j],drainSMU.defbuffer2[j],drainSMU.defbuffer1[j]) + end + + gateSMU.source.output = 0 + drainSMU.source.output = 0 + + trigger_drain.delete(tmName[2]) + trigger_gate.delete(tmName[1]) + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end + errorQUEUE() +end + +-- information of SMU +local gate_slot = 1 +local drain_slot = 1 +local common_slot = 1 +local gate_channel = 1 +local drain_channel = 2 +-- Drain Channel Settings +local Vd = 1 +local limitId = 0.1 +-- Gate Channel Settings +local startVg = 0 +local stopVg = 2.2 +local noSwpPoints = 45 +local limitIg = 0.1 +-- Measure Settings for both channels +local nplc = 1 +local mDelay = 0 + +--TransferCurve_VgId_RunTimeEnv({gate_slot,gate_channel,drain_slot,drain_channel},{Vd,limitId},{startVg,stopVg,noSwpPoints,limitIg},{nplc,mDelay}) + +TransferCurve_VgId_TriggerInOneSlot({common_slot,gate_channel,drain_channel},{Vd,limitId},{startVg,stopVg,noSwpPoints,limitIg},{nplc,mDelay},{"tm_vgid"}) + +--TransferCurve_VgId_TwoTriggers({gate_slot,gate_channel,drain_slot,drain_channel},{Vd,limitId},{startVg,stopVg,noSwpPoints,limitIg},{nplc,mDelay},{"tm_gate", "tm_drain"}) + + + + + diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Transfer_Curve/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Transfer_Curve/README.md new file mode 100644 index 0000000..244a70e --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/MOSFET_Transfer_Curve/README.md @@ -0,0 +1,15 @@ +# MOSFET Transfer Curve (Vg-Id) + +Two channels are used to perform a voltage sweep for a transfer curve or Vg-Id. Measurements are recorded throughout the test. + +NOTE: There are two functions provided. “TransferCurve_VgId_TriggerInOneSlot” handles 2 channels on the same module and “TransferCurve_VgId_TwoTriggers” handles using two channels on different slots. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Current_Waveform_Capture/Current_Pulse_Generation.py b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Current_Waveform_Capture/Current_Pulse_Generation.py new file mode 100644 index 0000000..2704333 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Current_Waveform_Capture/Current_Pulse_Generation.py @@ -0,0 +1,123 @@ +import pyvisa + +def plotResults(x, y): + import matplotlib.pyplot as pt + pt.plot(x, y, 'o-') + pt.xlabel("Time (s)") + pt.ylabel("Measured Current (A)") + pt.title("Digital Pulse Generation") + pt.grid(True) + pt.show() + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# SMU Channel Settings +slot_no = 1 +chan_no = 1 +smu = f"slot[{slot_no}].smu[{chan_no}]" +# Source Settings +startI = 1e-3 +stopI = 10e-3 +noPoints = 10 +limitV = 25 +# Measure Settings +measRangeI = 100e-3 +measRangeV = 15 +remoteSense = "false" + +# Pulse Settings +pulsePeriod = 5e-3 # pulsePeriod should be longer than pulseWidth + mDelay + apertureTime +pulseWidth = 3e-3 +mDelay = 1e-3 +apertureTime = 100e-6 + +tm_name = "TM_pulse_digitizer" + +script_buffer = [ + # Channel assignment + f"{smu}.reset()", + # Source settings + f"{smu}.source.func = {smu}.FUNC_DC_CURRENT", + f"{smu}.source.rangei = math.max(math.abs({startI}), math.abs({stopI}))", + f"{smu}.source.limitv = {limitV}", + f"{smu}.source.leveli = 0", + # Measure settings", + f"{smu}.measure.rangei = {measRangeI}", + f"{smu}.measure.rangev = {measRangeV}", + f"{smu}.measure.aperture = {apertureTime}", + f"{smu}.measure.autorangev = 0", + + f"if {remoteSense} then", + f" {smu}.sense = {smu}.SENSE_4WIRE", + f"else", + f" {smu}.sense = {smu}.SENSE_2WIRE", + f"end", + # Buffer clear", + f"{smu}.defbuffer1.clear()", + f"{smu}.defbuffer1.appendmode = 1", + f"{smu}.defbuffer2.clear()", + f"{smu}.defbuffer2.appendmode = 1", + # Calculate number of points + f"nPoints = math.ceil((({pulsePeriod} * {noPoints}) + {pulsePeriod}) / {apertureTime})", + + # Configure trigger model source for linear currrent sweep + f"{smu}.trigger.source.lineari({startI}, {stopI}, {noPoints})", + # Set trigger model to measure i/v and store in default buffers + f"{smu}.trigger.measure.iv({smu}.defbuffer1, {smu}.defbuffer2)", + + # Configure trigger model + f"triggerModel = slot[{slot_no}].trigger.model", + f"triggerModel.create(\"{tm_name}\")", + # Measureoverlapped allows measurements to be taken while trigger model is running in parallel + f"triggerModel.addblock.measureoverlapped(\"{tm_name}\", \"measure_IV\", {chan_no}, nPoints)", + f"triggerModel.addblock.delay.constant(\"{tm_name}\", \"prepulse_delay\", {pulsePeriod}/2)", + # Advance to next current value in current sweep + f"triggerModel.addblock.source.action.step(\"{tm_name}\", \"sweepI_IV\", {chan_no})", + f"triggerModel.addblock.delay.constant(\"{tm_name}\", \"meas_pulse_width\", {pulseWidth}, \"sweepI_IV\")", + # Set source level to 0 + f"triggerModel.addblock.source.action.bias(\"{tm_name}\", \"pulse_bias\", {chan_no})", + f"triggerModel.addblock.delay.constant(\"{tm_name}\", \"meas_pulse_period\", {pulsePeriod}, \"sweepI_IV\")", + # Loop through all current sweep values + f"triggerModel.addblock.branch.counter(\"{tm_name}\", \"branch-counter\", \"sweepI_IV\", {noPoints})", + + # Initiate trigger model, delete when finished + f"{smu}.source.output = 1", + f"triggerModel.initiate(\"{tm_name}\")", + f"waitcomplete()", + f"{smu}.source.output = 0", + f"triggerModel.delete(\"{tm_name}\")" +] +inst.write("loadscript pulse") +for cmd in script_buffer: + inst.write(cmd) +inst.write("endscript") +inst.write("pulse()") + +# Retrieve buffer data +timestamps = inst.query(f"printbuffer(1, {smu}.defbuffer1.n, {smu}.defbuffer1.timestamps)").split(",") +timestamps = [float(x) for x in timestamps] +defbuffer1 = inst.query(f"printbuffer(1, {smu}.defbuffer1.n, {smu}.defbuffer1)").split(",") +defbuffer1 = [float(x) for x in defbuffer1] +defbuffer2 = inst.query(f"printbuffer(1, {smu}.defbuffer2.n, {smu}.defbuffer2)").split(",") +defbuffer2 = [float(x) for x in defbuffer2] + +# Display buffer data +print("Time","V","I",sep="\t\t") +for i in range(len(defbuffer1)): + print(f"{timestamps[i]:.5e}", + f"{defbuffer2[i]:.5e}", + f"{defbuffer1[i]:.5e}", + sep="\t" + ) + +inst.clear() +inst.close() + +plotResults(timestamps, defbuffer1) \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Current_Waveform_Capture/Current_Pulse_Generation.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Current_Waveform_Capture/Current_Pulse_Generation.tsp new file mode 100644 index 0000000..759ad91 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Current_Waveform_Capture/Current_Pulse_Generation.tsp @@ -0,0 +1,91 @@ +-- SMU Channel Settings +local slot_no = 1 +local chan_no = 1 +-- Source Settings +local startI = 1e-3 +local stopI = 10e-3 +local noPoints = 10 +local limitV = 25 +-- Measure Settings +local measRangeI = 100e-3 +local measRangeV = 15 +local remoteSense = false + +-- Pulse Settings +local pulsePeriod = 5e-3 -- pulsePeriod should be longer than pulseWidth + mDelay + apertureTime +local pulseWidth = 3e-3 +local mDelay = 1e-3 +local apertureTime = 100e-6 + +local tm_name = "TM_pulse_digitizer" + +-- Channel assignment +local sweepI_SMU = slot[slot_no].smu[chan_no] +sweepI_SMU.reset() +-- Source settings +sweepI_SMU.source.func = sweepI_SMU.FUNC_DC_CURRENT +sweepI_SMU.source.rangei = math.max(math.abs(startI), math.abs(stopI)) +sweepI_SMU.source.limitv = limitV +sweepI_SMU.source.leveli = 0 +-- Measure settings +sweepI_SMU.measure.rangei = measRangeI +sweepI_SMU.measure.rangev = measRangeV +sweepI_SMU.measure.aperture = apertureTime +sweepI_SMU.measure.autorangev = 0 + +if remoteSense then + sweepI_SMU.sense = sweepI_SMU.SENSE_4WIRE +else + sweepI_SMU.sense = sweepI_SMU.SENSE_2WIRE +end +-- Buffer clear +sweepI_SMU.defbuffer1.clear() +sweepI_SMU.defbuffer1.appendmode = 1 +sweepI_SMU.defbuffer2.clear() +sweepI_SMU.defbuffer2.appendmode = 1 +-- Calculate number of points +local nPoints = math.ceil(((pulsePeriod * noPoints) + pulsePeriod) / apertureTime) + +-- Configure trigger model source for linear currrent sweep +sweepI_SMU.trigger.source.lineari(startI, stopI, noPoints) +-- Set trigger model to measure i/v and store in default buffers +sweepI_SMU.trigger.measure.iv(sweepI_SMU.defbuffer1, sweepI_SMU.defbuffer2) + +-- Configure trigger model +local triggerModel = slot[slot_no].trigger.model +triggerModel.create(tm_name) +-- Measureoverlapped allows measurements to be taken while trigger model is running in parallel +triggerModel.addblock.measureoverlapped(tm_name, "measure_IV", chan_no, nPoints) +triggerModel.addblock.delay.constant(tm_name, "prepulse_delay", pulsePeriod/2) +-- Advance to next current value in current sweep +triggerModel.addblock.source.action.step(tm_name, "sweepI_IV", chan_no) +triggerModel.addblock.delay.constant(tm_name, "meas_pulse_width", pulseWidth, "sweepI_IV") +-- Set source level to 0 +triggerModel.addblock.source.action.bias(tm_name, "pulse_bias", chan_no) +triggerModel.addblock.delay.constant(tm_name, "meas_pulse_period", pulsePeriod, "sweepI_IV") +-- Loop through all current sweep values +triggerModel.addblock.branch.counter(tm_name, "branch-counter", "sweepI_IV", noPoints) + +-- Initiate trigger model, delete when finished +sweepI_SMU.source.output = 1 +triggerModel.initiate(tm_name) +waitcomplete() +sweepI_SMU.source.output = 0 +triggerModel.delete(tm_name) + +-- Retrieve values from buffers and display +print("Time\t\t V\t\t I") +for j = 1, sweepI_SMU.defbuffer1.n, 1 do + print(sweepI_SMU.defbuffer1.timestamps[j], sweepI_SMU.defbuffer2[j], sweepI_SMU.defbuffer1[j]) +end + +-- Check for errors and display +function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end +end \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Current_Waveform_Capture/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Current_Waveform_Capture/README.md new file mode 100644 index 0000000..863d77b --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Current_Waveform_Capture/README.md @@ -0,0 +1,15 @@ +# Pulse Current Sweep Waveform Capture + +Generate a pulsed current sweep while taking continuous measurements. + +Assumes a 50 ohm load. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Current_Sweep/LED_Pulse_Sweep.py b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Current_Sweep/LED_Pulse_Sweep.py new file mode 100644 index 0000000..9f582a9 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Current_Sweep/LED_Pulse_Sweep.py @@ -0,0 +1,243 @@ +################################################################################# +# +# Script File: LED_Pulse_Sweep.py +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# current sweeping. The purpose is show that you can perform a diode device +# characterization with the SMUs. As written, one channel of SMU is +# assigned to the anode for the high and cathode for the low terminal. +# This pulse test should be excuted in trigger model. the first script shows +# how much accurate the delay constance with reference makes pulse. +# The second script shows how to make pulse with trigger timer object. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 1 channel SMU +# 1 small LED +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +################################################################################# + +def plotResults(x, y): + import matplotlib.pyplot as pt + pt.plot(x, y, 'o-') + pt.xlabel("Supplied Current (A)") + pt.ylabel("Measured Voltage (V)") + pt.title("LED Characterization") + pt.grid(True) + pt.show() + +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# information of SMU +slot_no = 1 +IF_channel = 1 +# Source Settings +startIF = 0 +stopIF = 100e-3 +noPoints = 101 +rangeI = 1 +limitV = 6 +# Measure Settings +measRangeV = 6 +remoteSense = False +# Pulse Settings +pulsePeriod = 5e-3 # pulsePeriod should be longer than pulseWidth + mDelay + apertureTime +pulseWidth = 3e-3 +mDelay = 1e-3 +apertureTime = 1e-3 + +tm_name = "TM_diode_pulse" + +smu = f"slot[{slot_no}].smu[{IF_channel}]" +inst.write(f"{smu}.reset()") + +def Pulse_Current_Sweep_Diode_inDelayConstant(): + # Source settings + inst.write(f"{smu}.source.func = {smu}.FUNC_DC_CURRENT") + inst.write(f"{smu}.source.rangei = {rangeI}") + inst.write(f"{smu}.source.leveli = 0") + inst.write(f"{smu}.source.limitv = {limitV}") + # Measure settings + inst.write(f"{smu}.measure.rangev = {measRangeV}") + inst.write(f"{smu}.measure.rangei = {rangeI}") + inst.write(f"{smu}.measure.aperture = {apertureTime}") + inst.write(f"{smu}.measure.autorangei = 0") + if remoteSense: + inst.write(f"{smu}.sense = {smu}.SENSE_4WIRE") + else: + inst.write(f"{smu}.sense = {smu}.SENSE_2WIRE") + # Buffer clear + inst.write(f"{smu}.defbuffer1.clear()") + inst.write(f"{smu}.defbuffer1.appendmode = 1") + inst.write(f"{smu}.defbuffer2.clear()") + inst.write(f"{smu}.defbuffer2.appendmode = 1") + + # Configure trigger model source for a linear current sweep + inst.write(f"{smu}.trigger.source.lineari({startIF}, {stopIF}, {noPoints})") + # Set trigger model measurements to store in default buffers and measure current/voltage + inst.write(f"{smu}.trigger.measure.iv({smu}.defbuffer1, {smu}.defbuffer2)") + + # Configure trigger model + triggerModel = f"slot[{slot_no}].trigger.model" + inst.write(f"{triggerModel}.create(\"{tm_name}\")") + # Step up to next source value + inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name}\", \"IF_sweep\", {IF_channel})") + inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"meas_delay\", {mDelay})") + # Measure iv and store in buffers + inst.write(f"{triggerModel}.addblock.measure(\"{tm_name}\", \"VF_measure\", {IF_channel}, 1)") + inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"delay_pulse_width\", {pulseWidth}, \"IF_sweep\")") + # Reset source to 0 + inst.write(f"{triggerModel}.addblock.source.action.bias(\"{tm_name}\", \"pulse_bias\", {IF_channel})") + inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"delay_pulse_period\", {pulsePeriod}, \"IF_sweep\")") + # Loop through all values in current sweep + inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name}\", \"IF_branch\", \"IF_sweep\", {noPoints})") + # Reset source to 0 at end of cycle + inst.write(f"{triggerModel}.addblock.source.action.bias(\"{tm_name}\", \"IF_bias\", {IF_channel})") + + inst.write(f"{smu}.source.output = 1") + + # Initiate model, delete when completed + inst.write(f"{triggerModel}.initiate(\"{tm_name}\")") + inst.write(f"trigger.generator[1].assert()") + inst.write(f"waitcomplete()") + inst.write(f"{smu}.source.output = 0") + inst.write(f"{triggerModel}.delete(\"{tm_name}\")") + + # Retrieve defbuffers + defbuffer1 = inst.query(f"printbuffer(1, {smu}.defbuffer1.n, {smu}.defbuffer1)").split(",") + defbuffer1 = [float(x) for x in defbuffer1] + defbuffer2 = inst.query(f"printbuffer(1, {smu}.defbuffer2.n, {smu}.defbuffer2)").split(",") + defbuffer2 = [float(x) for x in defbuffer2] + + # Display results + print("IF","VF",sep="\t\t") + for i in range(len(defbuffer1)): + print(f"{defbuffer1[i]:.5e}", + f"{defbuffer2[i]:.5e}", + sep="\t" + ) + inst.clear() + inst.close() + plotResults(defbuffer1, defbuffer2) + +def Pulse_Current_Sweep_Diode_inTriggerTimer(): + # Source settings + inst.write(f"{smu}.source.func = {smu}.FUNC_DC_CURRENT") + inst.write(f"{smu}.source.rangei = {rangeI}") + inst.write(f"{smu}.source.leveli = 0") + inst.write(f"{smu}.source.limitv = {limitV}") + # Measure settings + inst.write(f"{smu}.measure.rangev = {measRangeV}") + inst.write(f"{smu}.measure.rangei = {rangeI}") + inst.write(f"{smu}.measure.aperture = {apertureTime}") + inst.write(f"{smu}.measure.autorangei = 0") + if remoteSense: + inst.write(f"{smu}.sense = {smu}.SENSE_4WIRE") + else: + inst.write(f"{smu}.sense = {smu}.SENSE_2WIRE") + # Buffer clear + inst.write(f"{smu}.defbuffer1.clear()") + inst.write(f"{smu}.defbuffer1.appendmode = 1") + inst.write(f"{smu}.defbuffer2.clear()") + inst.write(f"{smu}.defbuffer2.appendmode = 1") + + # Setup multiple trigger timers to handle timing + # Pulse period timer settings + pulse_period = "trigger.timer[1]" + inst.write(f"{pulse_period}.delay ={pulsePeriod}") + # Passthrough allows script to continue running + inst.write(f"{pulse_period}.passthrough = true") + inst.write(f"{pulse_period}.count = {noPoints}") + inst.write(f"{pulse_period}.stimulus = trigger.generator[1].EVENT_ID") + # Measure delay timer settings + mdelay_on_pulse = "trigger.timer[2]" + inst.write(f"{mdelay_on_pulse}.delay = {mDelay}") + inst.write(f"{mdelay_on_pulse}.passthrough = false") + inst.write(f"{mdelay_on_pulse}.count = 1") + inst.write(f"{mdelay_on_pulse}.stimulus = {pulse_period}.EVENT_ID") + # Pulse width timer settings + pulse_width = "trigger.timer[3]" + inst.write(f"{pulse_width}.delay = {pulseWidth}") + inst.write(f"{pulse_width}.passthrough = false") + inst.write(f"{pulse_width}.count = 1") + inst.write(f"{pulse_width}.stimulus = {pulse_period}.EVENT_ID") + + # Configure trigger model source for a linear current sweep + inst.write(f"{smu}.trigger.source.lineari({startIF}, {stopIF}, {noPoints})") + # Set trigger model measurements to store in default buffers and measure current/voltage + inst.write(f"{smu}.trigger.measure.iv({smu}.defbuffer1, {smu}.defbuffer2)") + + # Configure trigger model + triggerModel = f"slot[{slot_no}].trigger.model" + inst.write(f"{triggerModel}.create(\"{tm_name}\")") + # Wait for event + inst.write(f"{triggerModel}.addblock.wait(\"{tm_name}\", \"wait_period\", {pulse_period}.EVENT_ID)") + # Continue to next source level + inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name}\", \"IF_sweep\", {IF_channel})") + # Wait for measure delay stimulus + inst.write(f"{triggerModel}.addblock.wait(\"{tm_name}\", \"wait_mdelay\", {mdelay_on_pulse}.EVENT_ID)") + # Measure iv and store in default buffers + inst.write(f"{triggerModel}.addblock.measure(\"{tm_name}\", \"VF_measure\", {IF_channel}, 1)") + # Wait for pulse width stimulus + inst.write(f"{triggerModel}.addblock.wait(\"{tm_name}\", \"wait_width\", {pulse_width}.EVENT_ID)") + # Set source to 0 + inst.write(f"{triggerModel}.addblock.source.action.bias(\"{tm_name}\", \"pulse_bias\", {IF_channel})") + # Repeat for each source level + inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name}\", \"IF_branch\", \"wait_period\", {noPoints})") + inst.write(f"{triggerModel}.addblock.source.action.bias(\"{tm_name}\", \"IF_bias\", {IF_channel})") + + inst.write(f"{smu}.source.output = 1") + + # Initiate model, delete when completed + inst.write(f"{triggerModel}.initiate(\"{tm_name}\")") + inst.write(f"trigger.generator[1].assert()") + inst.write(f"waitcomplete()") + inst.write(f"{smu}.source.output = 0") + inst.write(f"{triggerModel}.delete(\"{tm_name}\")") + + # Retrieve defbuffers + defbuffer1 = inst.query(f"printbuffer(1, {smu}.defbuffer1.n, {smu}.defbuffer1)").split(",") + defbuffer1 = [float(x) for x in defbuffer1] + defbuffer2 = inst.query(f"printbuffer(1, {smu}.defbuffer2.n, {smu}.defbuffer2)").split(",") + defbuffer2 = [float(x) for x in defbuffer2] + + # Display results + print("IF","VF",sep="\t\t") + for i in range(len(defbuffer1)): + print(f"{defbuffer1[i]:.5e}", + f"{defbuffer2[i]:.5e}", + sep="\t" + ) + inst.clear() + inst.close() + plotResults(defbuffer1, defbuffer2) + +# Pulse_Current_Sweep_Diode_inDelayConstant() +Pulse_Current_Sweep_Diode_inTriggerTimer() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Current_Sweep/LED_Pulse_Sweep.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Current_Sweep/LED_Pulse_Sweep.tsp new file mode 100644 index 0000000..1c51233 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Current_Sweep/LED_Pulse_Sweep.tsp @@ -0,0 +1,241 @@ +--[[ +################################################################################ + +Script File: LED_Pulse_Sweep.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + current sweeping. The purpose is show that you can perform a diode device + characterization with the SMUs. As written, one channel of SMU is + assigned to the anode for the high and cathode for the low terminal. + This pulse test should be excuted in trigger model. the first script shows + how much accurate the delay constance with reference makes pulse. + The second script shows how to make pulse with trigger timer object. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 1 channel SMU + 1 small LED + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * Pulse_Current_Sweep_Diode_inDelayConstant({slot_no,IF_channel},{startIF,stopIF,noPoints, rangeI, limitV},{measRangeV, remoteSense},{pulsePeriod, pulseWidth, mDelay, apertureTime},tm_name) + * DC_Current_Sweep_Diode_inTriggerModel({slot_no,IF_channel},{startIF,stopIF,noPoints, rangeI, limitV},{measRangeV, remoteSense},{pulsePeriod, pulseWidth, mDelay, apertureTime},tm_name) + +Example Usage: + * Pulse_Current_Sweep_Diode_inDelayConstant({1,1},{0,300e-3,31 , 1000e-3, 6}, {6, false}, {5e-3,3e-3,1e-3,1e-3},"TM_diode_pulse") + * Pulse_Current_Sweep_Diode_inTriggerTimer({1,1},{0,300e-3,31 , 1000e-3, 6}, {6, false}, {5e-3,3e-3,1e-3,1e-3},"TM_diode_pulse") + +################################################################################ +--]] + +function Pulse_Current_Sweep_Diode_inDelayConstant(infoSMU,srcSettings,meaSettings,pulseSettings,triggerName) + -- SMU channel assignment + local IF_SMU = slot[infoSMU[1]].smu[infoSMU[2]] + IF_SMU.reset() + -- Source Settings + IF_SMU.source.func = IF_SMU.FUNC_DC_CURRENT + IF_SMU.source.rangei = srcSettings[4] + IF_SMU.source.leveli = 0 + IF_SMU.source.limitv = srcSettings[5] + -- Measure Settings + IF_SMU.measure.rangev = meaSettings[1] + IF_SMU.measure.rangei = srcSettings[4] + IF_SMU.measure.aperture = pulseSettings[4] + IF_SMU.measure.autorangei = 0 + if meaSettings[2] == true then + IF_SMU.sense = IF_SMU.SENSE_4WIRE + else + IF_SMU.sense = IF_SMU.SENSE_2WIRE + end + -- Buffer clear + IF_SMU.defbuffer1.clear() + IF_SMU.defbuffer1.appendmode = 1 + IF_SMU.defbuffer2.clear() + IF_SMU.defbuffer2.appendmode = 1 + + -- Configure trigger model source for a linear current sweep + IF_SMU.trigger.source.lineari(srcSettings[1], srcSettings[2], srcSettings[3]) + -- Set trigger model measurements to store in default buffers and measure current/voltage + IF_SMU.trigger.measure.iv(IF_SMU.defbuffer1,IF_SMU.defbuffer2) + + -- Configure trigger model + local triggerModel = slot[infoSMU[1]].trigger.model + triggerModel.create(triggerName) + -- Step to next source value + triggerModel.addblock.source.action.step(triggerName, "IF_sweep", infoSMU[2]) + triggerModel.addblock.delay.constant(triggerName, "meas_delay", pulseSettings[3]) + -- Measure iv and store in buffers + triggerModel.addblock.measure(triggerName, "VF_measure", infoSMU[2], 1) + triggerModel.addblock.delay.constant(triggerName, "delay_pulse_width", pulseSettings[2],"IF_sweep") + -- Reset source to 0 + triggerModel.addblock.source.action.bias(triggerName, "pulse_bias", infoSMU[2]) + triggerModel.addblock.delay.constant(triggerName, "delay_pulse_period", pulseSettings[1],"IF_sweep") + -- Loop through all values in current sweep + triggerModel.addblock.branch.counter(triggerName, "IF_branch", "IF_sweep", srcSettings[3]) + -- Reset source to 0 at end of cycle + triggerModel.addblock.source.action.bias(triggerName, "IF_bias", infoSMU[2]) + + IF_SMU.source.output = 1 + IF_SMU.source.output = 1 + + -- Initiate model, delete when completed + triggerModel.initiate(triggerName) + waitcomplete() + IF_SMU.source.output = 0 + triggerModel.delete(triggerName) + + -- Read buffers and display + print("T\t\t IF\t\t VF") + for j = 1, IF_SMU.defbuffer1.n , 1 do + print(IF_SMU.defbuffer1.timestamps[j], IF_SMU.defbuffer1[j],IF_SMU.defbuffer2[j]) + end + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end +end + +function Pulse_Current_Sweep_Diode_inTriggerTimer(infoSMU,srcSettings,meaSettings,pulseSettings,triggerName) + -- SMU channel assignment + local IF_SMU = slot[infoSMU[1]].smu[infoSMU[2]] + IF_SMU.reset() + -- Source Settings + IF_SMU.source.func = IF_SMU.FUNC_DC_CURRENT + IF_SMU.source.rangei = srcSettings[4] + IF_SMU.source.leveli = 0 + IF_SMU.source.limitv = srcSettings[5] + -- Measure Settings + IF_SMU.measure.rangev = meaSettings[1] + IF_SMU.measure.rangei = srcSettings[4] + IF_SMU.measure.aperture = pulseSettings[4] + IF_SMU.measure.autorangei = 0 + if meaSettings[2] == true then + IF_SMU.sense = IF_SMU.SENSE_4WIRE + else + IF_SMU.sense = IF_SMU.SENSE_2WIRE + end + -- Buffer clear + IF_SMU.defbuffer1.clear() + IF_SMU.defbuffer1.appendmode = 1 + IF_SMU.defbuffer2.clear() + IF_SMU.defbuffer2.appendmode = 1 + + -- Setup multiple trigger timers to handle timing + -- Pulse period timer settings + local pulse_period = trigger.timer[1] + pulse_period.delay = pulseSettings[1] + -- Passthrough allows script to continue running + pulse_period.passthrough = true + pulse_period.count = srcSettings[3] + pulse_period.stimulus = trigger.generator[1].EVENT_ID + -- Measure delay timer settings + local mdelay_on_pulse = trigger.timer[2] + mdelay_on_pulse.delay = pulseSettings[3] + mdelay_on_pulse.passthrough = false + mdelay_on_pulse.count = 1 + mdelay_on_pulse.stimulus = pulse_period.EVENT_ID + -- Pulse width timer settings + local pulse_width = trigger.timer[3] + pulse_width.delay = pulseSettings[2] + pulse_width.passthrough = false + pulse_width.count = 1 + pulse_width.stimulus = pulse_period.EVENT_ID + + -- Configure trigger model source for a linear current sweep + IF_SMU.trigger.source.lineari(srcSettings[1], srcSettings[2], srcSettings[3]) + -- Set trigger model measurements to store in default buffers and measure current/voltage + IF_SMU.trigger.measure.iv(IF_SMU.defbuffer1,IF_SMU.defbuffer2) + + -- Configure trigger model + local triggerModel = slot[infoSMU[1]].trigger.model + triggerModel.create(triggerName) + -- Wait for event + triggerModel.addblock.wait(triggerName, "wait_period", pulse_period.EVENT_ID) + -- Continue to next source level + triggerModel.addblock.source.action.step(triggerName, "IF_sweep", infoSMU[2]) + -- Wait for measure delay stimulus + triggerModel.addblock.wait(triggerName, "wait_mdelay", mdelay_on_pulse.EVENT_ID) + -- Measure iv and store in default buffers + triggerModel.addblock.measure(triggerName, "VF_measure", infoSMU[2], 1) + -- Wait for pulse width stimulus + triggerModel.addblock.wait(triggerName, "wait_width", pulse_width.EVENT_ID) + -- Set source to 0 + triggerModel.addblock.source.action.bias(triggerName, "pulse_bias", infoSMU[2]) + -- Repeat for each source level + triggerModel.addblock.branch.counter(triggerName, "IF_branch", "wait_period", srcSettings[3]) + triggerModel.addblock.source.action.bias(triggerName, "IF_bias", infoSMU[2]) + + IF_SMU.source.output = 1 + -- Initiate trigger model, generate trigger event to begin + triggerModel.initiate(triggerName) + trigger.generator[1].assert() + waitcomplete() + IF_SMU.source.output = 0 + triggerModel.delete(triggerName) + + -- Read measurement buffers and display + print("T\t\t IF\t\t VF") + for j = 1, IF_SMU.defbuffer1.n , 1 do + print(IF_SMU.defbuffer1.timestamps[j], IF_SMU.defbuffer1[j],IF_SMU.defbuffer2[j]) + end + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end +end + +-- information of SMU +local slot_no = 1 +local IF_channel = 1 +-- Source Settings +local startIF = 0 +local stopIF = 12e-3 +local noPoints = 101 +local rangeI = 100e-3 +local limitV = 3 +-- Measure Settings +local measRangeV = 6 +local remoteSense = false +-- Pulse Settings +local pulsePeriod = 5e-4 -- pulsePeriod should be longer than pulseWidth + mDelay + apertureTime +local pulseWidth = 3e-4 +local mDelay = 1e-4 +local apertureTime = 1e-4 + +local tm_name = "TM_diode_pulse" + +Pulse_Current_Sweep_Diode_inDelayConstant({slot_no,IF_channel},{startIF,stopIF,noPoints, rangeI, limitV},{measRangeV, remoteSense},{pulsePeriod, pulseWidth, mDelay, apertureTime},tm_name) +--Pulse_Current_Sweep_Diode_inTriggerTimer({slot_no,IF_channel},{startIF,stopIF,noPoints, rangeI, limitV},{measRangeV, remoteSense},{pulsePeriod, pulseWidth, mDelay, apertureTime},tm_name) + +errorQUEUE() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Current_Sweep/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Current_Sweep/README.md new file mode 100644 index 0000000..f80bdb8 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Current_Sweep/README.md @@ -0,0 +1,15 @@ +# Pulsed Linear Current Sweep + +Configures and executes a pulsed current sweep while measuring at the peaks of the pulse. + +NOTE: There are two functions provided that are nearly identical except for how they manage timing. “Pulse_Current_Sweep_Diode_inDelayConstant” uses a delay constant for timing while “Pulse_Current_Sweep_Diode_inTriggerTimer” uses the trigger model. The trigger model is used by default. To change this simply change which function call is commented at the bottom of the script. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Voltage_Sweep/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Voltage_Sweep/README.md new file mode 100644 index 0000000..59f550f --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Voltage_Sweep/README.md @@ -0,0 +1,15 @@ +# Pulsed Linear Voltage Sweep + +Configures and executes a pulsed voltage sweep while measuring at the peaks of the pulse. + +NOTE: There are two functions provided that are nearly identical except for how they manage timing. “Pulse_Voltage_Sweep_Resistor_inDelayConstant” uses a delay constant for timing while “Pulse_Voltage_Sweep_Resistor_inTriggerTimer” uses the trigger model. The trigger timer is used by default. To change this, simply change which function call is commented at the bottom of the script.  + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Voltage_Sweep/Res_Volt_Pulse_Sweep.py b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Voltage_Sweep/Res_Volt_Pulse_Sweep.py new file mode 100644 index 0000000..b020ef3 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Voltage_Sweep/Res_Volt_Pulse_Sweep.py @@ -0,0 +1,236 @@ +################################################################################# +# +# Script File: Res_Volt_Pulse_Sweep.py +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# voltage sweeping. The purpose is show that you can perform a resistor +# device characterization with the SMUs. As written, one channel of SMU is +# assigned to one terminal for the high and the other for the low terminal. +# This pulse test should be excuted in trigger model. the first script shows +# how much accurate the delay constance with reference makes pulse. +# The second script shows how to make pulse with trigger timer object. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 1 channel SMU +# 1 Resistor +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +# Functions created by this script: +# * DC_Voltage_Sweep_Resistor_RunTimeEnv({slot_no,smu_channel},,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense},tm_name) +# * DC_Voltage_Sweep_Resistor_inTriggerModel({slot_no,smu_channel},,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense},tm_name) +# +################################################################################# + +def plotResults(x, y): + import matplotlib.pyplot as pt + pt.plot(x, y, 'o') + pt.ylim(-30000, 70000) + pt.xlabel("Supplied Voltage (V)") + pt.ylabel("Measured Resistance (Ω)") + pt.title("Resistor Characterization") + pt.grid(True) + pt.show() + +import pyvisa + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# information of SMU +slot_no = 1 +sweepV_channel = 1 +# Source Settings +startV = -5 +stopV = 5 +noPoints = 101 +limitI = 1 +# Measure Settings +measRangeV = 6 +measRangeI = 1 +remoteSense = False +# Pulse Settings +pulsePeriod = 5e-3 # pulsePeriod should be longer than pulseWidth + mDelay + apertureTime +pulseWidth = 3e-3 +mDelay = 1e-3 +apertureTime = 1e-3 + +tm_name = "TM_sweepV_pulse" + +smu = f"slot[{slot_no}].smu[{sweepV_channel}]" +inst.write(f"{smu}.reset()") + +def Pulse_Voltage_Sweep_Resistor_inDelayConstant(): + # Source settings + inst.write(f"{smu}.source.func = {smu}.FUNC_DC_VOLTAGE") + inst.write(f"{smu}.source.rangev = {max(abs(startV), abs(stopV))}") + inst.write(f"{smu}.source.limiti = {limitI}") + inst.write(f"{smu}.source.levelv = 0") + # Measure settings + inst.write(f"{smu}.measure.rangev = {measRangeV}") + inst.write(f"{smu}.measure.rangei = {measRangeI}") + inst.write(f"{smu}.measure.aperture = {apertureTime}") + inst.write(f"{smu}.measure.autorangei = 0") + if remoteSense: + inst.write(f"{smu}.sense = {smu}.SENSE_4WIRE") + else: + inst.write(f"{smu}.sense = {smu}.SENSE_2WIRE") + # Buffer clear + inst.write(f"{smu}.defbuffer1.clear()") + inst.write(f"{smu}.defbuffer1.appendmode = 1") + inst.write(f"{smu}.defbuffer2.clear()") + inst.write(f"{smu}.defbuffer2.appendmode = 1") + + inst.write(f"{smu}.trigger.source.linearv({startV}, {stopV}, {noPoints})") + inst.write(f"{smu}.trigger.measure.iv({smu}.defbuffer1, {smu}.defbuffer2)") + + # Configure trigger model + triggerModel = f"slot[{slot_no}].trigger.model" + inst.write(f"{triggerModel}.create(\"{tm_name}\")") + inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name}\", \"sweepV_IV\", {sweepV_channel})") + inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"meas_delay\", {mDelay})") + inst.write(f"{triggerModel}.addblock.measure(\"{tm_name}\", \"measure_IV\", {sweepV_channel}, 1)") + inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"meas_pulse_width\", {pulseWidth}, \"sweepV_IV\")") + inst.write(f"{triggerModel}.addblock.source.action.bias(\"{tm_name}\", \"pulse_bias\", {sweepV_channel})") + inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"meas_pulse_period\", {pulsePeriod}, \"sweepV_IV\")") + inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name}\", \"branch-counter\", \"sweepV_IV\", {noPoints})") + + inst.write(f"{smu}.source.output = 1") + + inst.write(f"{triggerModel}.initiate(\"{tm_name}\")") + inst.write(f"waitcomplete()") + inst.write(f"{smu}.source.output = 0") + inst.write(f"{triggerModel}.delete(\"{tm_name}\")") + + # Retrieve defbuffers + defbuffer1 = inst.query(f"printbuffer(1, {smu}.defbuffer1.n, {smu}.defbuffer1)").split(",") + defbuffer1 = [float(x) for x in defbuffer1] + defbuffer2 = inst.query(f"printbuffer(1, {smu}.defbuffer2.n, {smu}.defbuffer2)").split(",") + defbuffer2 = [float(x) for x in defbuffer2] + resists = [x/y for x, y in zip(defbuffer2, defbuffer1)] + + print("V","I","R",sep="\t\t") + for i in range(len(defbuffer1)): + print(f"{defbuffer2[i]:.5e}", + f"{defbuffer1[i]:.5e}", + f"{resists[i]:.5e}", + sep="\t" + ) + inst.clear() + inst.close() + plotResults(defbuffer2, resists) + +def Pulse_Voltage_Sweep_Resistor_inTriggerTimer(): + # Source settings + inst.write(f"{smu}.source.func = {smu}.FUNC_DC_VOLTAGE") + inst.write(f"{smu}.source.rangev = {max(abs(startV), abs(stopV))}") + inst.write(f"{smu}.source.limiti = {limitI}") + inst.write(f"{smu}.source.levelv = 0") + # Measure settings + inst.write(f"{smu}.measure.rangev = {measRangeV}") + inst.write(f"{smu}.measure.rangei = {measRangeI}") + inst.write(f"{smu}.measure.aperture = {apertureTime}") + inst.write(f"{smu}.measure.autorangei = 0") + if remoteSense: + inst.write(f"{smu}.sense = {smu}.SENSE_4WIRE") + else: + inst.write(f"{smu}.sense = {smu}.SENSE_2WIRE") + # Buffer clear + inst.write(f"{smu}.defbuffer1.clear()") + inst.write(f"{smu}.defbuffer1.appendmode = 1") + inst.write(f"{smu}.defbuffer2.clear()") + inst.write(f"{smu}.defbuffer2.appendmode = 1") + + # Pulse period timer settings + pulse_period = "trigger.timer[1]" + inst.write(f"{pulse_period}.delay = {pulsePeriod}") + # Passthrough allows for script to continue running + inst.write(f"{pulse_period}.passthrough = true") + inst.write(f"{pulse_period}.count = {noPoints}") + inst.write(f"{pulse_period}.stimulus= trigger.generator[1].EVENT_ID") + # Measure delay timer settings + mdelay_on_pulse = "trigger.timer[2]" + inst.write(f"{mdelay_on_pulse}.delay = {mDelay}") + inst.write(f"{mdelay_on_pulse}.passthrough = false") + inst.write(f"{mdelay_on_pulse}.count = 1") + inst.write(f"{mdelay_on_pulse}.stimulus = {pulse_period}.EVENT_ID") + # Pulse width timer settings + pulse_width = "trigger.timer[3]" + inst.write(f"{pulse_width}.delay = {pulseWidth}") + inst.write(f"{pulse_width}.passthrough = false") + inst.write(f"{pulse_width}.count = 1") + inst.write(f"{pulse_width}.stimulus = {pulse_period}.EVENT_ID") + + # Setup trigger model source for linear voltage sweep + inst.write(f"{smu}.trigger.source.linearv({startV}, {stopV}, {noPoints})") + # Setup measurements to store in default buffers and measure current/voltage + inst.write(f"{smu}.trigger.measure.iv({smu}.defbuffer1, {smu}.defbuffer2)") + + # Configure trigger model + triggerModel = f"slot[{slot_no}].trigger.model" + inst.write(f"{triggerModel}.create(\"{tm_name}\")") + # Wait for event from pulse period timer + inst.write(f"{triggerModel}.addblock.wait(\"{tm_name}\", \"wait_period\", {pulse_period}.EVENT_ID)") + # Advance to next source level in sweep + inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name}\", \"sweepV_IV\", {sweepV_channel})") + # Wait for event from mdelay + inst.write(f"{triggerModel}.addblock.wait(\"{tm_name}\", \"wait_mdelay\", {mdelay_on_pulse}.EVENT_ID)") + # Measure current/voltage + inst.write(f"{triggerModel}.addblock.measure(\"{tm_name}\", \"measure_IV\", {sweepV_channel}, 1)") + # Wait for event from pulse_width + inst.write(f"{triggerModel}.addblock.wait(\"{tm_name}\", \"wait_width\", {pulse_width}.EVENT_ID)") + inst.write(f"{triggerModel}.addblock.source.action.bias(\"{tm_name}\", \"pulse_bias\", {sweepV_channel})") + # Loop through all voltage values in sweep + inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name}\", \"branch-counter\", \"sweepV_IV\", {noPoints})") + + # Initiate trigger model, delete when completed + inst.write(f"{smu}.source.output = 1") + inst.write(f"{triggerModel}.initiate(\"{tm_name}\")") + inst.write(f"trigger.generator[1].assert()") + inst.write(f"waitcomplete()") + inst.write(f"{smu}.source.output = 0") + inst.write(f"{triggerModel}.delete(\"{tm_name}\")") + + # Retrieve defbuffers + defbuffer1 = inst.query(f"printbuffer(1, {smu}.defbuffer1.n, {smu}.defbuffer1)").split(",") + defbuffer1 = [float(x) for x in defbuffer1] + defbuffer2 = inst.query(f"printbuffer(1, {smu}.defbuffer2.n, {smu}.defbuffer2)").split(",") + defbuffer2 = [float(x) for x in defbuffer2] + resists = [x/y for x, y in zip(defbuffer2, defbuffer1)] + + print("V","I","R",sep="\t\t") + for i in range(len(defbuffer1)): + print(f"{defbuffer2[i]:.5e}", + f"{defbuffer1[i]:.5e}", + f"{resists[i]:.5e}", + sep="\t" + ) + inst.clear() + inst.close() + plotResults(defbuffer2, resists) + +#Pulse_Voltage_Sweep_Resistor_inDelayConstant() +Pulse_Voltage_Sweep_Resistor_inTriggerTimer() \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Voltage_Sweep/Res_Volt_Pulse_Sweep.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Voltage_Sweep/Res_Volt_Pulse_Sweep.tsp new file mode 100644 index 0000000..1e994b1 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Linear_Voltage_Sweep/Res_Volt_Pulse_Sweep.tsp @@ -0,0 +1,235 @@ +--[[ +################################################################################ + +Script File: Res_Volt_Pulse_Sweep.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + voltage sweeping. The purpose is show that you can perform a resistor + device characterization with the SMUs. As written, one channel of SMU is + assigned to one terminal for the high and the other for the low terminal. + This pulse test should be excuted in trigger model. the first script shows + how much accurate the delay constance with reference makes pulse. + The second script shows how to make pulse with trigger timer object. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 1 channel SMU + 1 Resistor + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * DC_Voltage_Sweep_Resistor_RunTimeEnv({slot_no,smu_channel},,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense},tm_name) + * DC_Voltage_Sweep_Resistor_inTriggerModel({slot_no,smu_channel},,{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, autoRangeI, nplc,mDelay,remoteSense},tm_name) + +Example Usage: + * DC_Voltage_Sweep_Resistor_RunTimeEnv({1,1},-5,5,101,1,6, 1, 1, 1,0,"TM_sweepV_pulse") + * DC_Voltage_Sweep_Resistor_inTriggerModel({1,1},-5,5,101,1,6, 1, 1, 1,0,"TM_sweepV_pulse") + +################################################################################ +--]] + +function Pulse_Voltage_Sweep_Resistor_inDelayConstant(infoSMU,srcSettings,meaSettings,pulseSettings,triggerName) + -- SMU channel assignment + local sweepV_SMU = slot[infoSMU[1]].smu[infoSMU[2]] + sweepV_SMU.reset() + -- Source Settings + sweepV_SMU.source.func = sweepV_SMU.FUNC_DC_VOLTAGE + sweepV_SMU.source.rangev = math.max(math.abs(srcSettings[1]), math.abs(srcSettings[2])) + sweepV_SMU.source.limiti = srcSettings[4] + sweepV_SMU.source.levelv = 0 + -- Measure Settings + sweepV_SMU.measure.rangev = meaSettings[1] + sweepV_SMU.measure.rangei = meaSettings[2] + sweepV_SMU.measure.aperture = pulseSettings[4] + sweepV_SMU.sense = sweepV_SMU.SENSE_2WIRE + sweepV_SMU.measure.autorangei = 0 + if meaSettings[3] == true then + sweepV_SMU.sense = sweepV_SMU.SENSE_4WIRE + else + sweepV_SMU.sense = sweepV_SMU.SENSE_2WIRE + end + -- Buffer clear + sweepV_SMU.defbuffer1.clear() + sweepV_SMU.defbuffer1.appendmode = 1 + sweepV_SMU.defbuffer2.clear() + sweepV_SMU.defbuffer2.appendmode = 1 + + -- Setup trigger model source for linear voltage sweep + sweepV_SMU.trigger.source.linearv(srcSettings[1], srcSettings[2], srcSettings[3]) + -- Set trigger model measurements to store in default buffers and measure voltage/current + sweepV_SMU.trigger.measure.iv(sweepV_SMU.defbuffer1,sweepV_SMU.defbuffer2) + + -- Configure trigger model + local triggerModel = slot[infoSMU[1]].trigger.model + triggerModel.create(triggerName) + -- Step to next voltage value in sweep + triggerModel.addblock.source.action.step(triggerName, "sweepV_IV", infoSMU[2]) + triggerModel.addblock.delay.constant(triggerName, "meas_delay", pulseSettings[3]) + -- Measure iv and store in buffers + triggerModel.addblock.measure(triggerName, "measure_IV", infoSMU[2], 1) + triggerModel.addblock.delay.constant(triggerName, "meas_pulse_width", pulseSettings[2],"sweepV_IV") + -- Set source back to 0 + triggerModel.addblock.source.action.bias(triggerName, "pulse_bias", infoSMU[2]) + triggerModel.addblock.delay.constant(triggerName, "meas_pulse_period", pulseSettings[1],"sweepV_IV") + -- Loop through all sweep values + triggerModel.addblock.branch.counter(triggerName, "branch-counter", "sweepV_IV", srcSettings[3]) + + -- Initiate trigger model and delete when completed + sweepV_SMU.source.output = 1 + triggerModel.initiate(triggerName) + waitcomplete() + sweepV_SMU.source.output = 0 + triggerModel.delete(triggerName) + + -- Display measurement buffer contents + print("V\t\t I\t\t R") + for j = 1, sweepV_SMU.defbuffer1.n , 1 do + print(sweepV_SMU.defbuffer2[j],sweepV_SMU.defbuffer1[j],(sweepV_SMU.defbuffer2[j]/sweepV_SMU.defbuffer1[j])) + end + + -- Display all errors + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end +end + +function Pulse_Voltage_Sweep_Resistor_inTriggerTimer(infoSMU,srcSettings,meaSettings,pulseSettings,triggerName) + -- SMU channel assignment + local sweepV_SMU = slot[infoSMU[1]].smu[infoSMU[2]] + sweepV_SMU.reset() + -- Source Settings + sweepV_SMU.source.func = sweepV_SMU.FUNC_DC_VOLTAGE + sweepV_SMU.source.rangev = math.max(math.abs(srcSettings[1]), math.abs(srcSettings[2])) + sweepV_SMU.source.limiti = srcSettings[4] + sweepV_SMU.source.levelv = 0 + -- Measure Settings + sweepV_SMU.measure.rangev = meaSettings[1] + sweepV_SMU.measure.rangei = meaSettings[2] + sweepV_SMU.measure.aperture = pulseSettings[4] + sweepV_SMU.sense = sweepV_SMU.SENSE_2WIRE + sweepV_SMU.measure.autorangei = 0 + if meaSettings[3] == true then + sweepV_SMU.sense = sweepV_SMU.SENSE_4WIRE + else + sweepV_SMU.sense = sweepV_SMU.SENSE_2WIRE + end + -- Buffer clear + sweepV_SMU.defbuffer1.clear() + sweepV_SMU.defbuffer1.appendmode = 1 + sweepV_SMU.defbuffer2.clear() + sweepV_SMU.defbuffer2.appendmode = 1 + + -- Pulse period timer settings + local pulse_period = trigger.timer[1] + pulse_period.delay = pulseSettings[1] + -- Passthrough allows for script to continue running + pulse_period.passthrough = true + pulse_period.count = srcSettings[3] + pulse_period.stimulus = trigger.generator[1].EVENT_ID + -- Measure delay timer settings + local mdelay_on_pulse = trigger.timer[2] + mdelay_on_pulse.delay = pulseSettings[3] + mdelay_on_pulse.passthrough = false + mdelay_on_pulse.count = 1 + mdelay_on_pulse.stimulus = pulse_period.EVENT_ID + -- Pulse width timer settings + local pulse_width = trigger.timer[3] + pulse_width.delay = pulseSettings[2] + pulse_width.passthrough = false + pulse_width.count = 1 + pulse_width.stimulus = pulse_period.EVENT_ID + + -- Setup trigger model source for linear voltage sweep + sweepV_SMU.trigger.source.linearv(srcSettings[1], srcSettings[2], srcSettings[3]) + -- Setup measurements to store in default buffers and measure current/voltage + sweepV_SMU.trigger.measure.iv(sweepV_SMU.defbuffer1,sweepV_SMU.defbuffer2) + + -- Configure trigger model + local triggerModel = slot[infoSMU[1]].trigger.model + triggerModel.create(triggerName) + -- Wait for event from pulse period timer + triggerModel.addblock.wait(triggerName, "wait_period", pulse_period.EVENT_ID) + -- Advance to next source level in sweep + triggerModel.addblock.source.action.step(triggerName, "sweepV_IV", infoSMU[2]) + -- Wait for event from mdelay + --triggerModel.addblock.delay.constant(triggerName, "delay", pulseSettings[3], "sweepV_IV") + triggerModel.addblock.wait(triggerName, "wait_mdelay", mdelay_on_pulse.EVENT_ID) + -- Measure current/voltage + triggerModel.addblock.measure(triggerName, "measure_IV", infoSMU[2], 1) + -- Wait for event from pulse_width + triggerModel.addblock.wait(triggerName, "wait_width", pulse_width.EVENT_ID) + triggerModel.addblock.source.action.bias(triggerName, "pulse_bias", infoSMU[2]) + -- Loop through all voltage values in sweep + triggerModel.addblock.branch.counter(triggerName, "branch-counter", "wait_period", srcSettings[3]) + + -- Initiate trigger model, delete when finished + sweepV_SMU.source.output = 1 + triggerModel.initiate(triggerName) + trigger.generator[1].assert() + waitcomplete() + sweepV_SMU.source.output = 0 + triggerModel.delete(triggerName) + + -- Read and display measurement buffers + print("V\t\t I\t\t R") + for j = 1, sweepV_SMU.defbuffer1.n , 1 do + print(sweepV_SMU.defbuffer2[j],sweepV_SMU.defbuffer1[j],(sweepV_SMU.defbuffer2[j]/sweepV_SMU.defbuffer1[j])) + end + + -- Display all errors + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end +end + +-- SMU channel Settings +local slot_no = 1 +local sweepV_channel= 1 +-- Source Settings +local startV = -5 +local stopV = 5 +local noPoints = 101 +local limitI = 1 +-- Measure Settings +local measRangeV = 6 +local measRangeI = 1 +local remoteSense = false +-- Pulse Settings +local pulsePeriod = 5e-3 -- pulsePeriod should be longer than pulseWidth + mDelay + apertureTime +local pulseWidth = 3e-3 +local mDelay = 1e-3 +local apertureTime = 1e-3 + +local tm_name = "TM_sweepV_pulse" + +Pulse_Voltage_Sweep_Resistor_inDelayConstant({slot_no, sweepV_channel},{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, remoteSense}, {pulsePeriod, pulseWidth, mDelay, apertureTime},tm_name) +--Pulse_Voltage_Sweep_Resistor_inTriggerTimer({slot_no, sweepV_channel},{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, remoteSense}, {pulsePeriod, pulseWidth, mDelay, apertureTime},tm_name) \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Voltage_Waveform_Capture/Digital_Pulse_Gen.py b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Voltage_Waveform_Capture/Digital_Pulse_Gen.py new file mode 100644 index 0000000..4f8a03f --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Voltage_Waveform_Capture/Digital_Pulse_Gen.py @@ -0,0 +1,153 @@ +################################################################################# +# +# Script File: Pulse_Waveform_Capture_MSMU_MP5000.py +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# voltage sweeping. The purpose is show that you can make digitiazer for pulse train +# As written, one channel of SMU is assigned to the high and the other to the low +# terminal. The script generates voltage pulses measuring voltage and current with +# digitizers at a same time. +# For the same purpose, SMU support is in run time evironment and in trigger model. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 1 channel SMU +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +################################################################################# + +def plotResults(x, y): + import matplotlib.pyplot as pt + pt.plot(x, y, 'o-') + pt.xlabel("Time (s)") + pt.ylabel("Measured Voltage (V)") + pt.title("Digital Pulse Generation") + pt.grid(True) + pt.show() + +import pyvisa +import math + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# SMU channel Settings +slot_no = 1 +sweepV_channel = 1 +# Source Settings +startV = 1 +stopV = 10 +noPoints = 10 +limitI = 1 +# Measure Settings +measRangeV = 10 +measRangeI = 1 +remoteSense = False + +# Pulse Settings +pulsePeriod = 5e-3 # pulsePeriod should be longer than pulseWidth + mDelay + apertureTime +pulseWidth = 3e-3 +mDelay = 1e-3 +apertureTime = 100e-6 + +tm_name = "TM_pulse_digitizer" + +smu = f"slot[{slot_no}].smu[{sweepV_channel}]" +inst.write(f"{smu}.reset()") + +# Source settings +inst.write(f"{smu}.source.func = {smu}.FUNC_DC_VOLTAGE") +inst.write(f"{smu}.source.rangev = {max(abs(startV), abs(stopV))}") +inst.write(f"{smu}.source.limiti = {limitI}") +inst.write(f"{smu}.source.levelv = 0") +# Measure settings +inst.write(f"{smu}.measure.rangev = {measRangeV}") +inst.write(f"{smu}.measure.rangei = {measRangeI}") +inst.write(f"{smu}.measure.aperture = {apertureTime}") +inst.write(f"{smu}.measure.autorangei = 0") + +if remoteSense: + inst.write(f"{smu}.sense = {smu}.SENSE_4WIRE") +else: + inst.write(f"{smu}.sense = {smu}.SENSE_2WIRE") + +# Buffer clear +inst.write(f"{smu}.defbuffer1.clear()") +inst.write(f"{smu}.defbuffer1.appendmode = 1") +inst.write(f"{smu}.defbuffer2.clear()") +inst.write(f"{smu}.defbuffer2.appendmode = 1") +# Calculate number of points +nPoints = math.ceil(((pulsePeriod * noPoints) + pulsePeriod) / apertureTime) + +# Configure trigger model source for linear voltage sweep +inst.write(f"{smu}.trigger.source.linearv({startV}, {stopV}, {noPoints})") +# Set trigger model to measure i/v and store in default buffers +inst.write(f"{smu}.trigger.measure.iv({smu}.defbuffer1, {smu}.defbuffer2)") + +# Configure trigger model +triggerModel = f"slot[{slot_no}].trigger.model" +inst.write(f"{triggerModel}.create(\"{tm_name}\")") +# Measureoverlapped allows measurements to be taken while triggermodel is running in parallel +inst.write(f"{triggerModel}.addblock.measureoverlapped(\"{tm_name}\", \"measure_IV\", {sweepV_channel}, {nPoints})") +inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"prepulse_delay\", {pulsePeriod/2})") +# Advance to next voltage value in voltage sweep +inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name}\", \"sweepV_IV\", {sweepV_channel})") +inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"meas_pulse_width\", {pulseWidth}, \"sweepV_IV\")") +# Set source level to 0 +inst.write(f"{triggerModel}.addblock.source.action.bias(\"{tm_name}\", \"pulse_bias\", {sweepV_channel})") +inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"mean_pulse_period\", {pulsePeriod}, \"sweepV_IV\")") +# Loop through all voltage sweep values +inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name}\", \"branch-counter\", \"sweepV_IV\", {noPoints})") +inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"postpulse_delay\", {pulsePeriod})") + +# Initiate trigger model and delete when finished +inst.write(f"{smu}.source.output = 1") +inst.write(f"{triggerModel}.initiate(\"{tm_name}\")") +inst.write(f"waitcomplete()") +inst.write(f"{smu}.source.output = 0") +inst.write(f"{triggerModel}.delete(\"{tm_name}\")") + +# Retrieve buffer data +timestamps = inst.query(f"printbuffer(1, {smu}.defbuffer1.n, {smu}.defbuffer1.timestamps)").split(",") +timestamps = [float(x) for x in timestamps] +defbuffer1 = inst.query(f"printbuffer(1, {smu}.defbuffer1.n, {smu}.defbuffer1)").split(",") +defbuffer1 = [float(x) for x in defbuffer1] +defbuffer2 = inst.query(f"printbuffer(1, {smu}.defbuffer2.n, {smu}.defbuffer2)").split(",") +defbuffer2 = [float(x) for x in defbuffer2] + +# Display buffer data +print("Time","V","I",sep="\t\t") +for i in range(len(defbuffer1)): + print(f"{timestamps[i]:.5e}", + f"{defbuffer2[i]:.5e}", + f"{defbuffer1[i]:.5e}", + sep="\t" + ) + +inst.clear() +inst.close() + +plotResults(timestamps, defbuffer2) \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Voltage_Waveform_Capture/Digital_Pulse_Gen.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Voltage_Waveform_Capture/Digital_Pulse_Gen.tsp new file mode 100644 index 0000000..6c64891 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Voltage_Waveform_Capture/Digital_Pulse_Gen.tsp @@ -0,0 +1,140 @@ +--[[ +################################################################################ + +Script File: Pulse_Waveform_Capture_MSMU_MP5000.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + voltage sweeping. The purpose is show that you can make digitiazer for pulse train + As written, one channel of SMU is assigned to the high and the other to the low + terminal. The script generates voltage pulses measuring voltage and current with + digitizers at a same time. + For the same purpose, SMU support is in run time evironment and in trigger model. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 1 channel SMU + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * Pulse_Waveform_Capture_MSMU_MP5000({slot_no, sweepV_channel},{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, remoteSense}, {pulsePeriod, pulseWidth, mDelay,apertureTime},tm_name) +Example Usage: + * Pulse_Waveform_Capture_MSMU_MP5000({1, 1},{1,10,10,0.1},{10, 0.1, false}, {5e-3, 3e-3, 1e-3,1e-3},"tm_pulse_digitizer") + +################################################################################ +--]] + +function Pulse_Waveform_Capture_MSMU_MP5000(infoSMU,srcSettings,meaSettings,pulseSettings,triggerName) + -- SMU channel assignment + local sweepV_SMU = slot[infoSMU[1]].smu[infoSMU[2]] + sweepV_SMU.reset() + -- Source Settings + sweepV_SMU.source.func = sweepV_SMU.FUNC_DC_VOLTAGE + sweepV_SMU.source.rangev = math.max(math.abs(srcSettings[1]), math.abs(srcSettings[2])) + sweepV_SMU.source.limiti = srcSettings[4] + sweepV_SMU.source.levelv = 0 + -- Measure Settings + sweepV_SMU.measure.rangev = meaSettings[1] + sweepV_SMU.measure.rangei = meaSettings[2] + sweepV_SMU.measure.aperture = pulseSettings[4] + sweepV_SMU.sense = sweepV_SMU.SENSE_2WIRE + sweepV_SMU.measure.autorangei = 0 + + if meaSettings[3] == true then + sweepV_SMU.sense = sweepV_SMU.SENSE_4WIRE + else + sweepV_SMU.sense = sweepV_SMU.SENSE_2WIRE + end + -- Buffer clear + sweepV_SMU.defbuffer1.clear() + sweepV_SMU.defbuffer1.appendmode = 1 + sweepV_SMU.defbuffer2.clear() + sweepV_SMU.defbuffer2.appendmode = 1 + -- Calculate number of points + local nPoints = math.ceil(((pulseSettings[1] * srcSettings[3]) + pulseSettings[1]) / pulseSettings[4]) + + -- Configure trigger model source for linear voltage sweep + sweepV_SMU.trigger.source.linearv(srcSettings[1], srcSettings[2], srcSettings[3]) + -- Set trigger model to measure i/v and store in default buffers + sweepV_SMU.trigger.measure.iv(sweepV_SMU.defbuffer1,sweepV_SMU.defbuffer2) + + -- Configure trigger model + local triggerModel = slot[infoSMU[1]].trigger.model + triggerModel.create(triggerName) + -- Measureoverlapped allows measurements to be taken while trigger model is running in parallel + triggerModel.addblock.measureoverlapped(triggerName, "measure_IV", infoSMU[2], nPoints) + triggerModel.addblock.delay.constant(triggerName, "prepulse_delay", pulseSettings[1]/2) + -- Advance to next voltage value in voltage sweep + triggerModel.addblock.source.action.step(triggerName, "sweepV_IV", infoSMU[2]) + triggerModel.addblock.delay.constant(triggerName, "meas_pulse_width", pulseSettings[2],"sweepV_IV") + -- Set source level to 0 + triggerModel.addblock.source.action.bias(triggerName, "pulse_bias", infoSMU[2]) + triggerModel.addblock.delay.constant(triggerName, "meas_pulse_period", pulseSettings[1],"sweepV_IV") + -- Loop through all voltage sweep values + triggerModel.addblock.branch.counter(triggerName, "branch-counter", "sweepV_IV", srcSettings[3]) + triggerModel.addblock.delay.constant(triggerName, "postpulse_delay", pulseSettings[1]) + + -- Initiate trigger model, delete when finished + sweepV_SMU.source.output = 1 + triggerModel.initiate(triggerName) + waitcomplete() + sweepV_SMU.source.output = 0 + triggerModel.delete(triggerName) + + -- Retrieve values from buffers and display + print("Time\t\t V\t\t I") + for j = 1, sweepV_SMU.defbuffer1.n , 1 do + print(sweepV_SMU.defbuffer1.timestamps[j],sweepV_SMU.defbuffer2[j],sweepV_SMU.defbuffer1[j]) + end + + -- Check for errors and display + function errorQUEUE() + errorqueue_Count = errorqueue.count + print("Error Queue Count:".. errorqueue_Count) + if errorqueue_Count > 0 then + for i = 1, errorqueue_Count do + print(i, errorqueue.next()) + end + end + end +end + + +-- SMU channel Settings +local slot_no = 1 +local sweepV_channel = 1 +-- Source Settings +local startV = 1 +local stopV = 10 +local noPoints = 10 +local limitI = 1 +-- Measure Settings +local measRangeV = 10 +local measRangeI = 1 +local remoteSense = false + +-- Pulse Settings +local pulsePeriod = 5e-3 -- pulsePeriod should be longer than pulseWidth + mDelay + apertureTime +local pulseWidth = 3e-3 +local mDelay = 1e-3 +local apertureTime = 100e-6 + +local tm_name = "TM_pulse_digitizer" + +Pulse_Waveform_Capture_MSMU_MP5000({slot_no, sweepV_channel},{startV,stopV,noPoints,limitI},{measRangeV, measRangeI, remoteSense}, {pulsePeriod, pulseWidth, mDelay,apertureTime},tm_name) diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Voltage_Waveform_Capture/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Voltage_Waveform_Capture/README.md new file mode 100644 index 0000000..f01bc96 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Pulse_Voltage_Waveform_Capture/README.md @@ -0,0 +1,15 @@ +# Pulse voltage Sweep Waveform Capture + +Generate a pulsed voltage sweep while taking continuous measurements. + +Assumes a 10k ohm load. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/README.md new file mode 100644 index 0000000..d98a282 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/README.md @@ -0,0 +1,52 @@ +# Source Measure Unit (SMU) Module Examples + +The examples in this directory work with the [MP5103 Mainframe and Supported SMU Modules](https://www.tek.com/en/products/mp5000-series-modular-precision-test-system). + +*Note: before running any example - verify slot and channel used.* + +## Directory + +### **[2 Channels In Parallel](./2_Channel_Parallel/)** +Combines channel sourcing in parallel to increase current capabilities. + +### **[2 Channels in Series](./2_Channels_Series/)** +Combines channel sourcing in series to increase voltage capabilities. + +### **[BJT Gummel Characterization](./BJT_Gummel/)** +Two channels are used to perform voltage sweeps for a Gummel characterization of a BJT. Measurements are recorded throughout the test. + +### **[BJT Vce-Ic Characterization](./BJT_Vce-Ic/)** +Two channels are used to perform a voltage and current sweep for a Vce-Ic characterization of a BJT. Measurements are recorded + +### **[DC/Pulse VCSEL LIV Characterization](./DC_Pulse_LIV_VCSEL_Char/)** +Two channels are used to perform a voltage and current sweep for an LIV characterization of a VCSEL. Measurements are recorded throughout the test. + +### **[LED Characterization Config List Example](./LED_ConfigList_Example/)** +Create a large configuration list, iterate through the list and take measurements when the settings are applied. + +### **[DC Linear Current Sweep](./Linear_Current_Sweep/)** +Configures and executes a linear current sweep while measuring voltage. + +### **[DC Linear Voltage Sweep](./Linear_Voltage_Sweep/)** +Configures and executes a linear voltage sweep while measuring current. + +### **[MOSFET Drain Family of Curves](./MOSFET_Drain_Family_Curves/)** +Two channels are used to perform voltage sweeps for a family of curves characterization of a MOSFET. Measurements are recorded throughout the test. + +### **[MOSFET Transfer Curve](./MOSFET_Transfer_Curve/)** +Two channels are used to perform a voltage sweep for a transfer curve or Vg-Id. Measurements are recorded throughout the test. + +### **[Current Pulse Waveform Capture](./Pulse_Current_Waveform_Capture/)** +Generate a pulsed current sweep while taking continuous measurements. + +### **[Pulsed Linear Current Sweep](./Pulse_Linear_Current_Sweep/)** +Configures and executes a pulsed current sweep while measuring at the peaks of the pulse. + +### **[Pulsed Linear Voltage Sweep](./Pulse_Linear_Voltage_Sweep/)** +Configures and executes a pulsed voltage sweep while measuring at the peaks of the pulse. + +### **[Voltage Pulse Waveform Capture](./Pulse_Voltage_Waveform_Capture/)** +Generate a pulsed voltage sweep while taking continuous measurements. + +### **[Sine Wave Generation and Waveform Capture](./Sine_Waveform_Capture/)** +Generates a sinewave using a list sweep and a trigger model. diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Sine_Waveform_Capture/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/Sine_Waveform_Capture/README.md new file mode 100644 index 0000000..28aa897 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Sine_Waveform_Capture/README.md @@ -0,0 +1,15 @@ +# Sine Wave Generation and Waveform Capture + +Generates a sinewave using a list sweep and a trigger model. + +Assumes 10k ohm load. + +## Required Modules +1 x MSMU60-2 + +## Available Languages +* TSP +* Python + +## Instructions +1. Adjust parameters and run code \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Sine_Waveform_Capture/Sinewave_Generation.py b/Examples/Modular_Precision_Test_System/SMU_Examples/Sine_Waveform_Capture/Sinewave_Generation.py new file mode 100644 index 0000000..fe5aa84 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Sine_Waveform_Capture/Sinewave_Generation.py @@ -0,0 +1,140 @@ +################################################################################# +# +# Script File: Sinewave_Generation.py +# +# ************************************************************************ +# *** Copyright Tektronix, Inc. *** +# *** See www.tek.com/sample-license for licensing terms. *** +# ************************************************************************ +# +# Description: +# This script is example code, which creates (and subsequently calls) several +# functions that can be used with the Model MP5000 Based SMUs to perform a +# current sweeping. The purpose is show that you can make a sine waveform in output +# As written, one channel of SMU is assigned to the high and the other to the low +# terminal. The script shows fast changing source bias to be enough to make a sine. +# For the same purpose, SMU support it in run time evironment and in trigger model. +# Upon completion of each test, the data is printed to the TSP Toolkit Console +# in a format that is suitable for copying and pasting into Microsoft Excel for +# graphing and analysis. +# +# Required Equipment: 1 Model MP5000 Mainframe +# 1 channel SMU (MSMU Series) +# 1 Resistor +# +# Note: The functions do not perform any error checking. It is the user's +# responsibility to specify settings that are compatible with the +# instrument model being used, and with its power envelope. +# +# Note: It is the user's responsibility to follow all safety guidelines given in +# the instrument's Reference Manual. This is especially critical if +# voltages in excess of 42VDC will be present in the test circuits. Such +# voltage levels are hazardous. +# +################################################################################# + +def plotResults(x, y): + import matplotlib.pyplot as pt + pt.plot(x, y, 'o-') + pt.xlabel("Time (s)") + pt.ylabel("Measured Voltage (V)") + pt.title("Sine Generation") + pt.grid(True) + pt.show() + +import pyvisa +import math + +# Configure Visa Connection +rm = pyvisa.ResourceManager() +inst = rm.open_resource('TCPIP0::192.168.0.2::5025::SOCKET') +# for using the sockets based implementation +inst.write_termination = "\n" +inst.read_termination = "\n" +inst.send_end = True +inst.timeout = 10000 + +# information of SMU +slot_no = 1 +smu_channel = 1 +# AC input parameters +Vrms = 5 +numCycles = 3 +frequency = 60 +limitI = .100 + +tm_name = "tm_sine" + +smu = f"slot[{slot_no}].smu[{smu_channel}]" +inst.write(f"{smu}.reset()") +# Calculate sinewave values +Vpp = Vrms * math.sqrt(2) +pointsPerCycle = 7200 / frequency +timeInterval = 1 / 7200 +numDataPoints = pointsPerCycle * numCycles +sourceValues = [None] * int(numDataPoints) +# Generate voltage sweep values +inst.write("sourceValues = {}") +for i in range(int(numDataPoints)): + inst.write(f"sourceValues[{i+1}] = {(Vpp * math.sin((i+1) * 2 * math.pi / pointsPerCycle))}") +# Create reading buffers +numReadings = 280 * numCycles +inst.write(f"readingBuffer1 = {smu}.makebuffer(5000)") +inst.write(f"readingBuffer2 = {smu}.makebuffer(5000)") + +# Configure channel settings +inst.write(f"{smu}.source.func = {smu}.FUNC_DC_VOLTAGE") +inst.write(f"{smu}.source.autorangev = {smu}.OFF") +inst.write(f"{smu}.source.rangev = 20") +inst.write(f"{smu}.source.limiti = {limitI}") +inst.write(f"{smu}.source.levelv = 0") +inst.write(f"{smu}.source.output = 1") +inst.write(f"{smu}.measure.aperture = .0001") +inst.write(f"{smu}.measure.rangei = .100") +inst.write(f"{smu}.measure.rangev = 20") + +# Configure sweep voltage list +inst.write(f"{smu}.trigger.source.listv(sourceValues)") +# Set trigger model readings for i/v and store in reading buffers +inst.write(f"{smu}.trigger.measure.iv(readingBuffer1, readingBuffer2)") + +# Configure trigger model +triggerModel = f"slot[{slot_no}].trigger.model" +inst.write(f"{triggerModel}.create(\"{tm_name}\")") +# Measure overlapped allows for readings to hapen while triggermodel is executing in parallel +inst.write(f"{triggerModel}.addblock.measureoverlapped(\"{tm_name}\", \"measure3\", {smu_channel}, {numReadings})") +inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"delay-init\", .002)") +# Advance through voltage sweep +inst.write(f"{triggerModel}.addblock.source.action.step(\"{tm_name}\", \"sweep_step1\", {smu_channel})") +inst.write(f"{triggerModel}.addblock.delay.constant(\"{tm_name}\", \"delay-on2\", {timeInterval}, \"sweep_step1\")") +# Loop through all voltage values +inst.write(f"{triggerModel}.addblock.branch.counter(\"{tm_name}\", \"branch-counter7\", \"sweep_step1\", {numDataPoints})") + +# Execute trigger model and delete when finished +inst.write(f"{smu}.source.output = 1") +inst.write(f"{triggerModel}.initiate(\"{tm_name}\")") +inst.write(f"waitcomplete()") +inst.write(f"{smu}.source.output = 0") +inst.write(f"{triggerModel}.delete(\"{tm_name}\")") + +# Retrieve buffer data +timestamps = inst.query(f"printbuffer(1, readingBuffer1.n, readingBuffer1.timestamps)").split(", ") +timestamps = [float(x) for x in timestamps] +readingBuffer1 = inst.query(f"printbuffer(1, readingBuffer1.n, readingBuffer1)").split(", ") +readingBuffer1 = [float(x) for x in readingBuffer1] +readingBuffer2 = inst.query(f"printbuffer(1, readingBuffer2.n, readingBuffer2)").split(", ") +readingBuffer2 = [float(x) for x in readingBuffer2] + +# Display data +print("Time","V","I",sep="\t\t") +for i in range(len(readingBuffer1)): + print(f"{timestamps[i]:.5e}", + f"{readingBuffer2[i]:.5e}", + f"{readingBuffer1[i]:.5e}", + sep="\t" + ) + +inst.clear() +inst.close() + +plotResults(timestamps, readingBuffer2) \ No newline at end of file diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/Sine_Waveform_Capture/Sinewave_Generation.tsp b/Examples/Modular_Precision_Test_System/SMU_Examples/Sine_Waveform_Capture/Sinewave_Generation.tsp new file mode 100644 index 0000000..92b7fe2 --- /dev/null +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/Sine_Waveform_Capture/Sinewave_Generation.tsp @@ -0,0 +1,119 @@ +--[[ +################################################################################ + +Script File: Sinewave_Generation.tsp + + ************************************************************************ + *** Copyright Tektronix, Inc. *** + *** See www.tek.com/sample-license for licensing terms. *** + ************************************************************************ + +Description: + This script is example code, which creates (and subsequently calls) several + functions that can be used with the Model MP5000 Based SMUs to perform a + current sweeping. The purpose is show that you can make a sine waveform in output + As written, one channel of SMU is assigned to the high and the other to the low + terminal. The script shows fast changing source bias to be enough to make a sine. + For the same purpose, SMU support it in run time evironment and in trigger model. + Upon completion of each test, the data is printed to the TSP Toolkit Console + in a format that is suitable for copying and pasting into Microsoft Excel for + graphing and analysis. + +Required Equipment: 1 Model MP5000 Mainframe + 1 channel SMU (MSMU Series) + 1 Resistor + +Note: The functions do not perform any error checking. It is the user's + responsibility to specify settings that are compatible with the + instrument model being used, and with its power envelope. + +Note: It is the user's responsibility to follow all safety guidelines given in + the instrument's Reference Manual. This is especially critical if + voltages in excess of 42VDC will be present in the test circuits. Such + voltage levels are hazardous. + +Functions created by this script: + * Sine_Waveform_Generate_MSMU_MP5000(noSlot, noCh, Vrms, numCycles, frequency, limitI,triggerName) + +Example Usage: + * Sine_Waveform_Generate_MSMU_MP5000(1, 1, 5, 5, 60, 0.1,"tm_sine") + +################################################################################ +--]] + +function Sine_Waveform_Generate_MSMU_MP5000(noSlot, noCh, Vrms, numCycles, frequency, limitI,triggerName) + -- SMU alias + local smu_ID = slot[noSlot].smu[noCh] + -- Calculate sinewave values + local Vpp = Vrms * math.sqrt(2) + local sourceValues = {} + local pointsPerCycle = 7200 / frequency + local timeInterval = 1/7200 + local numDataPoints = pointsPerCycle * numCycles + local numReadings + -- Generate voltage sweep values + for i=1, numDataPoints do + sourceValues[i] = (Vpp * math.sin(i * 2 * math.pi / pointsPerCycle)) + end + -- Create reading buffers + numReadings = 280 * numCycles + smu_ID.reset() + readingBuffer1 = smu_ID.makebuffer(5000) + readingBuffer2 = smu_ID.makebuffer(5000) + + -- Configure channel settings + smu_ID.source.func = smu_ID.FUNC_DC_VOLTAGE + smu_ID.source.autorangev = smu_ID.OFF + smu_ID.source.rangev = 20 + smu_ID.source.limiti = limitI + smu_ID.source.levelv = 0 + smu_ID.source.output = 1 + smu_ID.measure.aperture = 0.0001 + smu_ID.measure.rangei = 100e-3 + smu_ID.measure.rangev = 20 + + -- Configure sweep voltage list + smu_ID.trigger.source.listv(sourceValues) + -- Set trigger model readings for i/v and store in reading buffers + smu_ID.trigger.measure.iv(readingBuffer1,readingBuffer2) + + -- Configure trigger model + local triggermodel = slot[noSlot].trigger.model + triggermodel.create(triggerName) + -- Measure overlapped allows for readings to happen while triggermodel is excecuting in parallel + triggermodel.addblock.measureoverlapped(triggerName, "measure3", noCh, numReadings) + triggermodel.addblock.delay.constant(triggerName, "delay-init", 2e-3) + -- Advance through voltage sweep + triggermodel.addblock.source.action.step(triggerName, "sweep_step1",noCh) + triggermodel.addblock.delay.constant(triggerName, "delay-on2", timeInterval,"sweep_step1") + -- Loop through all voltage values + triggermodel.addblock.branch.counter(triggerName, "branch-counter7", "sweep_step1", numDataPoints) + + -- Initiate trigger model and delete when finished + smu_ID.source.output = 1 + triggermodel.initiate(triggerName) + waitcomplete() + smu_ID.source.output = 0 + triggermodel.delete(triggerName) + + -- Display results from reading buffers + print("Time\t\t V\t\t I") + for j = 1, readingBuffer1.n , 1 do + print(readingBuffer1.timestamps[j],readingBuffer2[j],readingBuffer1[j]) + end + +end +-- SMU channel Settings +local slot_no = 1 +local smu_channel = 1 +-- AC input parameters +local Vrms = 5 +local numCycles = 3 +local frequency = 60 +local limitI = 100e-3 + +local tm_name = "tm_sine" + +Sine_Waveform_Generate_MSMU_MP5000(slot_no, smu_channel, Vrms, numCycles, frequency, limitI,tm_name) + +print(readingBuffer1) From f8f1d91c7cb37b8c333b3d45a1612e45007f9bea Mon Sep 17 00:00:00 2001 From: Liz Makley <77294716+Little-LIZard@users.noreply.github.com> Date: Tue, 16 Sep 2025 14:54:43 -0400 Subject: [PATCH 2/3] Corrected PSU Readme link --- Examples/Modular_Precision_Test_System/PSU_Examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/Modular_Precision_Test_System/PSU_Examples/README.md b/Examples/Modular_Precision_Test_System/PSU_Examples/README.md index 7bbcb3a..6be4435 100644 --- a/Examples/Modular_Precision_Test_System/PSU_Examples/README.md +++ b/Examples/Modular_Precision_Test_System/PSU_Examples/README.md @@ -9,7 +9,7 @@ The examples in this directory work with the [MP5103 Mainframe and Supported PSU ### **[2 Channels In Parallel](./2_Channel_Parallel/)** Combines channel sourcing in parallel to increase current capabilities. -### **[2 Channels in Series](./2_Channels_Series/)** +### **[2 Channels in Series](./2_Channel_Series/)** Combines channel sourcing in series to increase voltage capabilities. ### **[Bipolar Output](./Bipolar_Output/)** From e7ebac3352b2f4e80fd3df2c5afcbcb00225a1ca Mon Sep 17 00:00:00 2001 From: Liz Makley <77294716+Little-LIZard@users.noreply.github.com> Date: Tue, 16 Sep 2025 14:59:10 -0400 Subject: [PATCH 3/3] Update SMU readme link --- Examples/Modular_Precision_Test_System/SMU_Examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/Modular_Precision_Test_System/SMU_Examples/README.md b/Examples/Modular_Precision_Test_System/SMU_Examples/README.md index d98a282..84647e7 100644 --- a/Examples/Modular_Precision_Test_System/SMU_Examples/README.md +++ b/Examples/Modular_Precision_Test_System/SMU_Examples/README.md @@ -9,7 +9,7 @@ The examples in this directory work with the [MP5103 Mainframe and Supported SMU ### **[2 Channels In Parallel](./2_Channel_Parallel/)** Combines channel sourcing in parallel to increase current capabilities. -### **[2 Channels in Series](./2_Channels_Series/)** +### **[2 Channels in Series](./2_Channel_Series/)** Combines channel sourcing in series to increase voltage capabilities. ### **[BJT Gummel Characterization](./BJT_Gummel/)**