Keithley SMU automation with Python: IV curves on the 2450 and 2400

By Alex Hernandez · · 14 min read

View Markdown
A bench source-measure instrument drawn in isometric, two test leads running to a single component in a small fixture.
FIG. 1 — SOURCE AND MEASURE, ONE DEVICE

To run an IV curve on a Keithley 2450 from Python, open it with PyVISA, set the current limit, NPLC and sense mode, load a sweep with SOUR:SWE:VOLT:LIN, start it with INIT, and read source and measured values with TRAC:DATA?. Turn the output off in a finally block. The older 2400 uses different commands, also covered here.

Instrument commands come from Keithley's Model 2450 reference manual (Rev. D) and Series 2400 user's manual (Rev. K); the Python uses the current PyVISA API. SCPI instrument automation with Python covers resource strings, error queues and timeouts. This guide covers what is specific to a source-measure unit.

Which command set should a Keithley 2450 use: SCPI, TSP or SCPI 2400?

The 2450 has three remote command sets and runs one at a time; the manual says plainly, "You cannot combine the command sets." Switch with *LANG SCPI, *LANG TSP or *LANG SCPI2400, then reboot. *LANG? reports the active set, and the factory default is SCPI.

Command setSelect withResults come back fromChoose it when
SCPI (default)*LANG SCPI, rebootQueries ending in ?Writing new Python for a 2450
TSP*LANG TSP, rebootprint() and printbuffer()Logic runs on the instrument or across TSP-Link
SCPI 2400*LANG SCPI2400, rebootSeries 2400 queriesOld 2400 code must run unchanged

The 2400 emulation has a cost. In it, the 2450 gives up its 10 nA, 100 nA and 20 mV ranges, the new trigger model and scripting (Appendix D), and Keithley warns that some 2400 code behaves differently. Use it to keep an old program running while you port it.

TSP, Keithley's Test Script Processor, is a scripting language built on Lua with no query commands: you send statements such as smu.source.ilimit.level = 0.01 and read output from print(). Common commands such as *IDN? and *LANG? still work in TSP mode, though not inside scripts.

SCPI is the better default for Python: each step is a short command or query, which is what PyVISA, drivers and instrument profiles expect.

How do I connect to a Keithley 2450 or 2400 from Python?

The 2450 talks GPIB, USB and LAN (Telnet on port 23, VXI-11, raw sockets on port 5025), and the first interface to send a message takes control. The Series 2400 uses GPIB or RS-232.

bench/keithley.py
import re
from contextlib import contextmanager
 
import pyvisa
 
 
def open_smu(rm: pyvisa.ResourceManager, resource: str):
    """Open a Keithley 2450 or Series 2400 SMU in SCPI mode; return (smu, model)."""
    smu = rm.open_resource(
        resource, read_termination="\n", write_termination="\n", timeout=10_000
    )
    idn = smu.query("*IDN?")
    # 2450: KEITHLEY INSTRUMENTS,MODEL 2450,<serial>,<firmware>
    # 2400: KEITHLEY INSTRUMENTS INC., MODEL 2400, <serial>, <firmware>
    match = re.search(r"MODEL (24\d\d)", idn)
    if not match:
        raise ValueError(f"{resource} is not a Keithley 24xx SMU: {idn}")
    model = match.group(1)
    if model == "2450" and (lang := smu.query("*LANG?")) != "SCPI":
        raise RuntimeError(f"2450 is in {lang} mode: send *LANG SCPI, then reboot")
    smu.write("*CLS")
    return smu, model
 
 
def drain_errors(smu, limit: int = 64) -> list[str]:
    """Read SYST:ERR? until it reports code 0 (the 2450 answers 0,"No error;0,0,0")."""
    errors = []
    for _ in range(limit):
        code, _, message = smu.query("SYST:ERR?").partition(",")
        if int(code) == 0:
            return errors
        errors.append(f"{code},{message}")
    raise RuntimeError(f"error log not empty after {limit} reads: {errors}")
 
 
@contextmanager
def io_timeout(smu, seconds: float):
    """Raise the VISA timeout for one slow step, then restore it."""
    previous, smu.timeout = smu.timeout, int(seconds * 1000)
    try:
        yield smu
    finally:
        smu.timeout = previous
 
 
@contextmanager
def output_guard(smu, off_state: str = "NORM"):
    """Leave the output off on every exit path: return, exception or Ctrl-C."""
    smu.write(f"OUTP:SMOD {off_state}")  # what "off" means: NORM, HIMP, ZERO or GUAR
    try:
        yield smu
    except BaseException:
        smu.clear()        # device clear: drop any reply still in flight
        smu.write("ABOR")  # stop a running trigger model or sweep
        raise
    finally:
        smu.write("OUTP OFF")
 
 
# smu, model = open_smu(pyvisa.ResourceManager(), "TCPIP0::192.168.1.60::inst0::INSTR")
  • Identity differs by generation. The manuals document KEITHLEY INSTRUMENTS,MODEL 2450,... for the 2450 and KEITHLEY INSTRUMENTS INC., MODEL nnnn, ... for the 2400, so the code matches only the model field.
  • The command set is checked first. A 2450 left in TSP mode rejects every SCPI command in this guide.
  • The 2450 separates errors from warnings. SYST:ERR? returns only errors, answering 0,"No error;0,0,0" when none remain; warnings come from SYST:EVEN:NEXT?.

The guard's except branch runs before its finally, so a sweep interrupted by a timeout or Ctrl-C is aborted before the output is switched off.

How do I set compliance (the current limit) on a Keithley 2450?

Series 2400 manuals call it compliance; the 2450 calls it the source limit. Appendix E of the 2450 manual, on converting 2400 code, maps the commands:

SettingSeries 2400Model 2450
Current limit while sourcing voltageSENS:CURR:PROT <amps>SOUR:VOLT:ILIM <amps>
Voltage limit while sourcing currentSENS:VOLT:PROT <volts>SOUR:CURR:VLIM <volts>
Was the current limit reached?SENS:CURR:PROT:TRIP?SOUR:VOLT:ILIM:TRIP?
Voltage limit after *RST21 V21 V
Current limit after *RST105 µA105 µA

The last row is the common surprise. After *RST, both instruments source voltage with a 105 µA current limit. A diode sweep meant to reach 10 mA flattens at 105 µA instead, because the SMU clamps the source to stay inside the limit, and nothing raises an error. Set the limit in every setup.

Three more rules from the 2450 manual:

  • The measure range constrains the limit. With a fixed measure range, the limit must exceed 0.1% of that range; autorange lifts the constraint.
  • A source range change can move the limit. If the new range cannot support it, the 2450 adjusts the limit and logs a warning, which SYST:ERR? does not return.
  • Check every point. SOUR:VOLT:ILIM:TRIP? reports the present limit state, but the 2450 also stores a source status with each reading, and bit 5 (decimal 32) means the source was limited at that point. On the 2400, bit 3 (decimal 8) of the status word marks a reading taken in compliance.

How do I run an IV sweep on a Keithley 2450 in Python?

SOUR:SWE:VOLT:LIN takes start, stop and points, then optional delay, count, range type, fail-abort, dual and buffer arguments. It replaces the trigger model with the sweep, INIT runs it, and the sweep clears its buffer when it starts. Keithley's sweep section (page 3-61) says to set the limit, readback, protection, sense mode and terminals first.

bench/iv_2450.py
import numpy as np
 
from bench.keithley import drain_errors, io_timeout, output_guard
 
OVP_LEVELS = (2, 5, 10, 20, 40, 60, 80, 100, 120, 140, 160, 180)  # PROT<n> volts
LIMITED, FOUR_WIRE = 32, 64  # source status bits 5 and 6
 
 
def iv_sweep_2450(smu, start: float, stop: float, points: int, ilimit: float,
                  nplc: float = 1.0, delay: float = 0.005, four_wire: bool = True):
    """Linear voltage sweep measuring current. Returns volts, amps and limited flags."""
    v_max = max(abs(start), abs(stop))
    ovp = next((f"PROT{p}" for p in OVP_LEVELS if p >= 1.2 * v_max), "NONE")
    line_hz = float(smu.query("SYST:LFR?"))  # the 2450 detects line frequency itself
 
    for command in (
        "*RST",                                    # the current limit is back to 105 uA
        "SOUR:FUNC VOLT",
        f"SOUR:VOLT:ILIM {ilimit}",                # compliance, before the sweep command
        f"SOUR:VOLT:PROT {ovp}",                   # caps the output if a sense lead opens
        f"SOUR:VOLT:DEL {delay}",                  # fixed source delay; autodelay off
        'SENS:FUNC "CURR"',
        "SENS:CURR:RANG:AUTO ON",
        f"SENS:CURR:NPLC {nplc}",
        f"SENS:CURR:RSEN {'ON' if four_wire else 'OFF'}",
        # start, stop, points, sweep delay, count, range type, fail-abort
        f"SOUR:SWE:VOLT:LIN {start}, {stop}, {points}, 0, 1, BEST, OFF",
    ):
        smu.write(command)
    if errors := drain_errors(smu):
        raise RuntimeError(f"setup rejected: {errors}")
 
    budget_s = points * (delay + 4 * nplc / line_hz) + 5  # headroom: autozero, readback
    with output_guard(smu), io_timeout(smu, budget_s):
        smu.write("INIT")   # runs the trigger model; the sweep switches the output on
        smu.query("*OPC?")  # answers once the trigger model is idle again
        n = int(smu.query('TRAC:ACT? "defbuffer1"'))
        raw = smu.query_ascii_values(
            f'TRAC:DATA? 1, {n}, "defbuffer1", SOUR, READ, SOURSTAT'
        )
 
    data = np.array(raw).reshape(-1, 3)  # one row per point: source, reading, status
    status = data[:, 2].astype(int)
    if four_wire and not np.all(status & FOUR_WIRE):
        raise RuntimeError("some points were measured without 4-wire sense")
    return data[:, 0], data[:, 1], (status & LIMITED) != 0
 
 
# volts, amps, limited = iv_sweep_2450(smu, 0.0, 0.8, 41, ilimit=0.01)

What each choice does:

  • BEST range picks one fixed source range that covers every level, which Keithley says avoids overshoots. AUTO moves to the most sensitive range at each point; FIXED keeps the present range and outputs its maximum for any level beyond it.
  • Fail-abort OFF completes the sweep at the limit, and the status column records where. The default, ON, aborts. A diode reaching the limit at high forward bias is expected; in a leakage test it may be the failure you want to stop on.
  • *OPC? waits for the trigger model to go idle, so the I/O timeout covers the whole sweep. While the trigger model runs, the 2450 does not execute most commands, which is why the guard sends ABOR before OUTP OFF.
  • TRAC:DATA? interleaves the elements in the order listed, one group per point. With source readback on, the default, SOUR is the measured source voltage, not the programmed one.
  • The four-wire check reads bit 6 (decimal 64), which records whether four-wire sense was used for each reading.

How do source delay and NPLC trade speed for accuracy?

Source delay. The 2450 applies it at the first source output and whenever the source magnitude changes, so at every step of a staircase. Autodelay, the default, depends on the current range. The manual's values for a voltage source:

Current rangeAutodelayWith high capacitance on
10 nA, 100 nA50 ms100 ms
1 µA3 ms20 ms
10 µA2 ms10 ms
100 µA to 1 mA1 ms10 ms
10 mA to 1 A1 ms5 ms

Setting SOUR:VOLT:DEL (0 to 4 seconds) turns autodelay off, so every point waits the same time; choose a delay longer than your DUT's settling time. The sweep's own delay argument adds to the source delay rather than replacing it (Keithley's example: 10 ms plus 25 ms gives 35 ms), so the code passes 0 there.

NPLC. Integration time is set in power-line cycles, from 0.01 to 10, default 1:

NPLCAt 60 HzAt 50 HzWhere it fits
0.010.167 ms0.2 msFastest sweeps, most noise
0.11.67 ms2 msCurrents well above the noise
1 (default)16.67 ms20 msGeneral IV work
10166.7 ms200 msLow-current points

Per the manual, shorter times read faster with more noise and fewer usable digits; longer times read slower with less noise.

Two defaults add time on top:

  • Autozero (SENS:CURR:AZER) re-measures the internal ground and reference with each reading. Keithley suggests turning it off when sweep timing is critical and sending SENS:AZER:ONCE just before the sweep to limit drift.
  • Source readback (SOUR:VOLT:READ:BACK) measures the actual source value before each DUT reading: more accurate, slower.

For the example call, 41 points at 1 NPLC on 60 Hz spend 683 ms integrating and 205 ms in delays; the timeout budget leaves room for autozero and readback.

When do I need 4-wire (remote) sensing on an SMU?

Two-wire sensing measures voltage at the output terminals, so the reading includes the lead drop. Four-wire sensing measures at the DUT through separate sense leads, and a voltage source raises its output until the DUT sees the programmed value. Keithley guarantees the 2450's specified accuracies only with 4-wire remote sensing.

The lead error is Ohm's law. If leads and contacts add 0.2 Ω, then 100 mA drops 20 mV, 2.5% of a 0.8 V diode point; at 10 µA the drop is 2 µV. Four-wire sensing matters most for low impedances and high currents.

The 2450 sets sense per measure function, so a current measurement uses SENS:CURR:RSEN ON; the 2400 uses :SYST:RSEN ON. On the 2450, changing the sense setting turns the output off, and with the output off it uses 2-wire sensing whatever the setting.

The hazard is an open sense lead. It reads 0 V, so a voltage source raises its output to compensate. Both manuals recommend overvoltage protection: SOUR:VOLT:PROT PROT2 through PROT180 on the 2450, a voltage such as :SOUR:VOLT:PROT 20 on the 2400, both off by default. Each sweep function picks the smallest level at or above 1.2 times its largest voltage.

How do I run the same IV sweep on a Keithley 2400?

The Series 2400 builds the sweep from separate commands, adapted from the diode example in its user's manual, which sources current instead:

bench/iv_2400.py
import numpy as np
 
from bench.keithley import drain_errors, io_timeout, output_guard
 
COMPLIANCE = 8  # status word bit 3: reading taken in real compliance
OVP_LEVELS = (20, 40, 60, 80, 100, 120, 160)  # Model 2400 levels; other 24xx differ
 
 
def iv_sweep_2400(smu, start: float, stop: float, step: float, ilimit: float,
                  nplc: float = 1.0, delay: float = 0.005, four_wire: bool = True):
    """Linear voltage sweep measuring current. Returns volts, amps and compliance flags."""
    v_max = max(abs(start), abs(stop))
    ovp = next((p for p in OVP_LEVELS if p >= 1.2 * v_max), 210)  # 210 = NONE
    for command in (
        "*RST",                                    # compliance is back to 105 uA
        ":SENS:FUNC:CONC OFF",
        ":SOUR:FUNC VOLT",
        ':SENS:FUNC "CURR"',
        f":SENS:CURR:PROT {ilimit}",               # current compliance
        f":SOUR:VOLT:PROT {ovp}",                  # caps the output if a sense lead opens
        f":SENS:CURR:NPLC {nplc}",
        f":SYST:RSEN {'ON' if four_wire else 'OFF'}",
        f":SOUR:VOLT:START {start}",
        f":SOUR:VOLT:STOP {stop}",
        f":SOUR:VOLT:STEP {step}",
        ":SOUR:VOLT:MODE SWE",                     # after START, STOP and STEP
        ":SOUR:SWE:RANG BEST",
        ":SOUR:SWE:SPAC LIN",
        f":SOUR:DEL {delay}",                      # manual delay; disables auto delay
        ":FORM:ELEM VOLT,CURR,STAT",
    ):
        smu.write(command)
    points = int(float(smu.query(":SOUR:SWE:POIN?")))
    if points > 2500:
        raise ValueError(f"{points} points: the 2400 buffer holds 2,500 readings")
    smu.write(f":TRIG:COUN {points}")              # one trigger per sweep point
    if errors := drain_errors(smu):
        raise RuntimeError(f"setup rejected: {errors}")
 
    line_hz = float(smu.query(":SYST:LFR?"))
    budget_s = points * (delay + 4 * nplc / line_hz) + 5
    with output_guard(smu), io_timeout(smu, budget_s):
        smu.write(":OUTP ON")                      # READ? needs the output on
        raw = smu.query_ascii_values(":READ?")     # runs the sweep, returns every point
 
    data = np.array(raw).reshape(-1, 3)  # one row per point: volts, amps, status
    return data[:, 0], data[:, 1], (data[:, 2].astype(int) & COMPLIANCE) != 0
  • The trigger count equals the points for a single sweep: (stop − start) / step + 1, which SOUR:SWE:POIN? reads back. Arm count times trigger count cannot exceed the 2,500-reading buffer.
  • SOUR:VOLT:MODE SWE follows start, stop and step, so the sweep is not rebuilt after each command.
  • VOLT is the programmed level here, because only current is measured.
  • The output stays on. Auto output-off is off after a reset, so the output must be on for READ? and stays on afterward, which the manual warns can leave hazardous voltage on the terminals. The guard turns it off.

How do I turn a Keithley SMU output off safely?

OUTP OFF in a finally block is the minimum, and the guard adds ABOR for a sweep still running. What "off" means is a separate setting, the output-off state, set with OUTP:SMOD on both instruments. The 2450 manual describes four:

Output-off stateWhat OUTP OFF doesUse it for
NORMal (default)0 V source, current limit at 10% of range, 2-wire senseMost passive DUTs
HIMPedanceOpens the output relayPower sources, other SMUs, some capacitors
ZERO0 V on the present range, usable as an ammeterCurrent at 0 V bias
GUARd0 A if sourcing current, voltage limit at 10% of rangeGuarded setups

In NORMal, off is still a 0 V source, so a battery or charged capacitor on the terminals still sees the SMU. Keithley recommends HIMPedance when the instrument is connected to a power source or another SMU, but not for tests that switch the output often, because each cycle wears the relay.

Two more checks:

  • The interlock gates the 200 V range. Without it, the 2450's nominal output stays below ±42 V. OUTP:INT:TRIP? returns 1 when the interlock is asserted; check it before a high-voltage sweep.
  • Protection is not a safe-to-touch level. Keithley says to assume hazardous voltage (more than 30 V rms) whenever the output is on.

When is TSP the better choice?

TSP pays off when logic should run on the instrument. A script loaded with loadscript and endscript runs on the 2450 without a round trip per command, and TSP-Link synchronizes several instruments. This is the same sweep in TSP, adapted from Keithley's example, for a 2450 already switched with *LANG TSP:

iv_tsp.py
from bench.keithley import io_timeout
 
SWEEP = """\
reset()
smu.measure.func = smu.FUNC_DC_CURRENT
smu.measure.autorange = smu.ON
smu.measure.nplc = 1
smu.measure.sense = smu.SENSE_4WIRE
smu.source.func = smu.FUNC_DC_VOLTAGE
smu.source.ilimit.level = 0.01
smu.source.protect.level = smu.PROTECT_2V
smu.source.delay = 0.005
smu.source.sweeplinear("IV", 0, 0.8, 41, 0, 1, smu.RANGE_BEST, smu.OFF)
trigger.model.initiate()
waitcomplete()"""
 
# smu = rm.open_resource(...)  # open_smu() expects SCPI mode
try:
    for statement in SWEEP.splitlines():
        smu.write(statement)  # TSP has no queries; print() is the only way back
    with io_timeout(smu, 30):
        raw = smu.query_ascii_values(
            "printbuffer(1, defbuffer1.n, defbuffer1.sourcevalues, defbuffer1.readings)"
        )
except BaseException:
    smu.clear()
    smu.write("trigger.model.abort()")
    raise
finally:
    smu.write("smu.source.output = smu.OFF")

printbuffer() returns one comma-separated message, which query_ascii_values parses. The cost is that instrument logic becomes Lua inside Python strings: typos surface on the instrument, not in your editor, and the commands no longer map onto a driver class or an instrument profile. For step-by-step bench code, SCPI stays simpler.

How do I run this IV sweep in Galois with Évariste?

Évariste, the agent in the Galois platform, does the work of bench/keithley.py and iv_sweep_2450() from a plain-English objective. Open it from the app sidebar (Ctrl+Shift+E) beside a project and state the sweep with your datasheet's limits:

Create an IV sweep sequence for the Keithley 2450: source 0 V to 0.8 V in 41 points with a 10 mA current limit, measure current at 1 NPLC with 4-wire sensing, a 5 ms source delay and overvoltage protection at 2 V, record every point, and leave the output off at the end.

Évariste lists the instruments on your team's edges and reads the 2450's profile commands. If the sweep needs a command the profile does not define, upload the 2450 reference manual: Évariste generates a profile from it and, after your review, deploys it to an edge and binds it to the SMU. It then drafts a sequence of named profile commands, a source step and a measure step per point. An excerpt:

diode_iv_2450.yaml (excerpt)
name: "Diode IV, 0 to 0.8 V, 41 points, 10 mA limit"
steps:
  # setup elided: reset, source function VOLT, sense function CURR with autorange,
  # NPLC 1 and 4-wire sense on the current function, OVP PROT2, 5 ms source delay
  - name: "Source range 2 V, fixed for the sweep"
    type: action
    config:
      instrument_id: "smu"
      command_name: "source_voltage_range"
      parameters: { value: "2" }
 
  - name: "Current limit 10 mA"
    type: action
    config:
      instrument_id: "smu"
      command_name: "current_compliance"
      parameters: { value: "0.01" }
 
  - name: "Source 0.00 V"
    type: action
    config:
      instrument_id: "smu"
      command_name: "source_voltage"
      parameters: { value: "0" }
 
  - name: "Output on"
    type: action
    config:
      instrument_id: "smu"
      command_name: "output_state"
      parameters: { state: "ON" }
 
  - name: "Measure current at 0.00 V"
    type: measure
    config:
      instrument_id: "smu"
      command_name: "measure_current"
      unit: "A"
 
  # 40 more source and measure pairs, 0.02 V apart, to 0.80 V
 
  - name: "Output off"
    type: action
    config:
      instrument_id: "smu"
      command_name: "output_state"
      parameters: { state: "OFF" }

The fixed 2 V source range does the job of BEST: one range covers every level. measure steps record each current without a verdict. A point held at the limit reads about 10 mA; iv_sweep_2450() flags the same points from status bit 5.

The sequence lands as a draft, and a draft does not run until an engineer approves it. Check the draft and each command's profile entry against this guide: the limit must send SOUR:VOLT:ILIM, not the 2400's SENS:CURR:PROT; NPLC and sense must target the current function; overvoltage protection must be on; the 41 source levels must run from 0 V to 0.8 V; the last step must turn the output off. How to review an AI-generated test plan lists what else to check. Ask Évariste for changes in conversation or edit in the sequence builder; every change is a new version with history and diffs.

Start the run; galois-edge executes it on the bench and Monitor shows the channels live. Stopping a run partway skips the final output-off step, so turn the output off at the 2450 or from the conversation before anyone touches the leads. Commands you send the SMU from the conversation ask for confirmation when the profile flags them as dangerous.

Each measure step records the current, the raw command and response, instrument, operator, DUT serial and timestamps. Ask Évariste which points sit at the 10 mA limit or how this diode compares with the last one; answers cite the runs they draw on. "Generate a test report from the last run" produces a PDF or HTML report from a LaTeX template; add the diode's part number and the force and sense wiring in the report editor, and results can go to Slack.

Your job is the objective, the datasheet limits, the review, the approval and the bench: force and sense wiring, a 2450 in SCPI mode, and checking that the output is off before anyone touches the leads. The helper module, sweep function, timeout budget, and the code that stores each sweep and builds its report are no longer yours to maintain.

StepCode path (this guide)Galois with Évariste
Find the SMUopen_smu(): *IDN? and *LANG? checks"List connected instruments"
Driverbench/keithley.py and raw SCPI stringsLibrary profile, or one generated from the manual
Define the sweepiv_sweep_2450(smu, 0.0, 0.8, 41, ilimit=0.01)Plain-English objective; Évariste drafts the sequence
Range and protectionBEST, SOUR:VOLT:ILIM, PROT2Fixed 2 V range, limit and OVP steps
ReviewCode reviewDraft reviewed, versioned, approved
RunCall the function on the bench PCRun through galois-edge, watched in Monitor
Safe output-offoutput_guard: ABOR, then OUTP OFFFinal output-off step; after an early stop, turn the output off yourself (cleanup_commands fire only when the daemon disconnects)
RecordArrays returned to the callerPer-step current, raw I/O, operator, DUT serial, timestamps
InterpretThe limited flags you inspectÉvariste finds points at the limit, compares runs
ReportReport code you writeGenerated PDF or HTML, shared to Slack

How do I run Keithley SMU sweeps on other benches or from an agent?

The Python in this guide runs on the PC the SMU is cabled to. With several benches, the questions become where each SMU lives, who may switch it on, and what record each sweep leaves.

Galois is agent-driven test engineering for hardware teams: agents generate tests and instrument drivers, run them on real benches through the open-source galois-edge daemon, and turn the results into reports and a shared engineering record.

  • Remote PyVISA. pyvisa.ResourceManager("@galois") sends each write, query and query_ascii_values call over HTTPS to Galois Cloud, which relays it to the galois-edge daemon on the bench PC, with no NI-VISA or local drivers on your machine. The PyVISA backend docs list which operations carry over, and the typed galois SDK covers notebooks.
  • Typed profiles. Galois ships 573 instrument profiles across 135 manufacturers in its instrument library. A matched instrument gains keyword-only methods named after the profile's commands, so a misspelled parameter name fails fast instead of mapping to the wrong argument, and a profile's cleanup_commands run on disconnect, a natural home for OUTP OFF. Declarative instrument drivers explains why commands live in data.
  • Agents with limits. Each profile command becomes a typed MCP tool, such as keithley_2400__source_voltage. Out-of-range values are rejected before any SCPI reaches the instrument, and commands marked is_dangerous are flagged so clients can ask for confirmation. MCP for lab instruments explains the direct and relay access paths, and Can an LLM safely drive lab instruments? covers the other layers.
  • A record per step. Galois sequences store the SCPI sent, the raw response, the measured value and its limits for every step.

If one engineer owns one SMU, the functions in this guide are enough; the Galois and PyVISA comparison shows where that line falls. AI test automation for hardware benches covers what changes when an agent writes the sweep. For SMU sweeps in a university lab, see lab automation for university research labs; for voltage and temperature corners on first silicon, see post-silicon validation bench automation.

Frequently asked questions

Can I run a Keithley 2450 IV sweep without writing Python?
Yes. In Galois, describe the sweep to Évariste in plain English, with its voltage range, points, current limit, NPLC and 4-wire sensing. Évariste reads the 2450's profile, or generates one from the reference manual you upload, and drafts a sequence of source and measure steps that ends with the output off. The draft does not run until an engineer approves it. Then galois-edge runs it on the bench, and each point's current is recorded with the raw command and response, instrument, operator, DUT serial and timestamps. Évariste can compare runs and generate a PDF or HTML report.
Why does my Keithley 2450 IV curve flatten at 105 µA?
Because *RST sets the current limit to 105 µA when the 2450 sources voltage, and the SMU clamps the source to stay inside the limit without raising an error. Send SOUR:VOLT:ILIM with the limit you need before the sweep command, and check each point's source status: bit 5 (decimal 32) means the source was limited at that point.
Can a Keithley 2450 run Keithley 2400 code?
Yes. Send *LANG SCPI2400 and reboot, and the 2450 accepts most Series 2400 SCPI commands. In that mode it gives up its 10 nA, 100 nA and 20 mV ranges, the new trigger model and scripting, and Keithley notes that some 2400 code behaves differently. Send *LANG SCPI and reboot to return to the native command set.
What is the difference between SCPI and TSP on a Keithley SMU?
SCPI mode takes text commands and answers queries that end in a question mark. TSP, Keithley's Test Script Processor, is a scripting language based on Lua: you send statements such as smu.source.ilimit.level = 0.01, get output only from print() or printbuffer(), and can store scripts on the instrument. The 2450 runs one command set at a time, chosen with *LANG and a reboot.
What NPLC should I use for an IV curve?
Start at the default of 1 power-line cycle, which is 16.67 ms per reading at 60 Hz and 20 ms at 50 Hz. Drop to 0.1 or lower for fast sweeps where noise is acceptable, and go up to 10 for low-current points where noise dominates. Every step also pays the source delay, plus autozero and source readback time while those are on.

Related

Bring Galois to your bench.

The daemon is Apache-2.0, free forever. Enterprise runs in your cloud or on-prem.