Keithley SMU automation with Python: IV curves on the 2450 and 2400
By Alex Hernandez · · 14 min read


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 set | Select with | Results come back from | Choose it when |
|---|---|---|---|
| SCPI (default) | *LANG SCPI, reboot | Queries ending in ? | Writing new Python for a 2450 |
| TSP | *LANG TSP, reboot | print() and printbuffer() | Logic runs on the instrument or across TSP-Link |
| SCPI 2400 | *LANG SCPI2400, reboot | Series 2400 queries | Old 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.
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 andKEITHLEY 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, answering0,"No error;0,0,0"when none remain; warnings come fromSYST: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:
| Setting | Series 2400 | Model 2450 |
|---|---|---|
| Current limit while sourcing voltage | SENS:CURR:PROT <amps> | SOUR:VOLT:ILIM <amps> |
| Voltage limit while sourcing current | SENS:VOLT:PROT <volts> | SOUR:CURR:VLIM <volts> |
| Was the current limit reached? | SENS:CURR:PROT:TRIP? | SOUR:VOLT:ILIM:TRIP? |
Voltage limit after *RST | 21 V | 21 V |
Current limit after *RST | 105 µA | 105 µ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.
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:
BESTrange picks one fixed source range that covers every level, which Keithley says avoids overshoots.AUTOmoves to the most sensitive range at each point;FIXEDkeeps the present range and outputs its maximum for any level beyond it.- Fail-abort
OFFcompletes 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 sendsABORbeforeOUTP OFF.TRAC:DATA?interleaves the elements in the order listed, one group per point. With source readback on, the default,SOURis 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 range | Autodelay | With high capacitance on |
|---|---|---|
| 10 nA, 100 nA | 50 ms | 100 ms |
| 1 µA | 3 ms | 20 ms |
| 10 µA | 2 ms | 10 ms |
| 100 µA to 1 mA | 1 ms | 10 ms |
| 10 mA to 1 A | 1 ms | 5 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:
| NPLC | At 60 Hz | At 50 Hz | Where it fits |
|---|---|---|---|
| 0.01 | 0.167 ms | 0.2 ms | Fastest sweeps, most noise |
| 0.1 | 1.67 ms | 2 ms | Currents well above the noise |
| 1 (default) | 16.67 ms | 20 ms | General IV work |
| 10 | 166.7 ms | 200 ms | Low-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 sendingSENS:AZER:ONCEjust 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:
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 SWEfollows start, stop and step, so the sweep is not rebuilt after each command.VOLTis 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 state | What OUTP OFF does | Use it for |
|---|---|---|
NORMal (default) | 0 V source, current limit at 10% of range, 2-wire sense | Most passive DUTs |
HIMPedance | Opens the output relay | Power sources, other SMUs, some capacitors |
ZERO | 0 V on the present range, usable as an ammeter | Current at 0 V bias |
GUARd | 0 A if sourcing current, voltage limit at 10% of range | Guarded 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:
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:
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.
| Step | Code path (this guide) | Galois with Évariste |
|---|---|---|
| Find the SMU | open_smu(): *IDN? and *LANG? checks | "List connected instruments" |
| Driver | bench/keithley.py and raw SCPI strings | Library profile, or one generated from the manual |
| Define the sweep | iv_sweep_2450(smu, 0.0, 0.8, 41, ilimit=0.01) | Plain-English objective; Évariste drafts the sequence |
| Range and protection | BEST, SOUR:VOLT:ILIM, PROT2 | Fixed 2 V range, limit and OVP steps |
| Review | Code review | Draft reviewed, versioned, approved |
| Run | Call the function on the bench PC | Run through galois-edge, watched in Monitor |
| Safe output-off | output_guard: ABOR, then OUTP OFF | Final output-off step; after an early stop, turn the output off yourself (cleanup_commands fire only when the daemon disconnects) |
| Record | Arrays returned to the caller | Per-step current, raw I/O, operator, DUT serial, timestamps |
| Interpret | The limited flags you inspect | Évariste finds points at the limit, compares runs |
| Report | Report code you write | Generated 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 eachwrite,queryandquery_ascii_valuescall 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 typedgaloisSDK 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_commandsrun on disconnect, a natural home forOUTP 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 markedis_dangerousare 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.
Compare
Bring Galois to your bench.
The daemon is Apache-2.0, free forever. Enterprise runs in your cloud or on-prem.