The SCPI commands you actually use, organised by instrument class, with the universal commands, the syntax rules that trip people up, and where Keysight, Tektronix, and Rigol differ.

SCPI is a text command language for test instruments, standardised at the top of the command tree and vendor-specific below it. That single fact explains why a cheat sheet is useful and why it can never be complete: *IDN? works everywhere, and the command to read a peak-to-peak voltage differs between every oscilloscope vendor.
This reference collects the commands that come up daily, organised by instrument class, with the syntax rules and the specific places vendors diverge.
Short and long form. Documentation writes MEASure:VOLTage:DC?. The uppercase letters are the short form. MEAS:VOLT:DC? and MEASURE:VOLTAGE:DC? are both valid and identical. Mixed case is accepted. Use short form in scripts.
The colon. Separates levels of the tree. A leading colon returns to the root:
VOLT 3.3;:CURR 0.5 correct, CURR resolves from the root
VOLT 3.3;CURR 0.5 may fail, CURR is looked up under VOLTThis is the single most common chaining bug.
The question mark. Makes a command a query, which returns a response. Anything with ? needs a read. Anything without must not be read.
Parameters. Space-separated after the command, comma-separated between them.
CONF:VOLT:DC 10,0.0001Channel lists. (@1) or (@1,3) or (@1:4) for a range.
VOLT 3.3,(@1)
OUTP ON,(@1,2)These work on every compliant instrument.
| Command | Purpose |
|---|---|
*IDN? | Manufacturer, model, serial, firmware |
*RST | Reset to default state |
*CLS | Clear status and error queue |
*OPC? | Returns 1 when pending operations complete, blocks until then |
*OPC | Sets the operation complete bit, non-blocking |
*WAI | Wait for pending operations before continuing |
*ESR? | Read and clear the event status register |
*STB? | Read the status byte |
*TST? | Run self-test, 0 means pass |
*SAV <n> / *RCL <n> | Save and recall instrument state |
SYST:ERR? | Read the next error from the queue |
**Start every script with *RST then *CLS, and check SYST:ERR? after every configuration block.** That is the entire error-handling discipline and it is worth more than the rest of this page.
| Command | Purpose | ||
|---|---|---|---|
CONF:VOLT:DC <range>,<res> | Configure DC voltage | ||
CONF:VOLT:AC <range>,<res> | Configure AC voltage | ||
CONF:CURR:DC <range>,<res> | Configure DC current | ||
CONF:RES <range>,<res> | Configure 2-wire resistance | ||
CONF:FRES <range>,<res> | Configure 4-wire resistance | ||
CONF:FREQ | Configure frequency | ||
CONF:TEMP TC,K | Configure thermocouple, type K | ||
READ? | Trigger and return a reading | ||
INIT then FETC? | Trigger, then fetch without re-triggering | ||
MEAS:VOLT:DC? <range>,<res> | Configure, trigger, and return in one command | ||
VOLT:DC:NPLC <n> | Integration time in power line cycles | ||
| `TRIG:SOUR IMM\ | EXT\ | BUS` | Trigger source |
SAMP:COUN <n> | Samples per trigger |
MEAS vs CONF plus READ. MEAS:VOLT:DC? resets the function and reconfigures every time, which is slow. For a loop, use CONF once outside and READ? inside. This alone can double throughput.
NPLC is the accuracy and speed dial. NPLC 10 is slow and quiet, NPLC 0.02 is fast and noisy. NPLC 1 rejects mains hum well and is a good default.
| Command | Purpose |
|---|---|
VOLT <v> | Set output voltage |
CURR <a> | Set current limit |
VOLT? / CURR? | Read back the setpoints |
OUTP ON / OUTP OFF | Enable and disable output |
OUTP? | Query output state |
MEAS:VOLT? | Measure actual output voltage |
MEAS:CURR? | Measure actual output current |
INST:NSEL <n> | Select channel |
VOLT:PROT <v> | Overvoltage protection level |
CURR:PROT:STAT ON | Enable overcurrent protection |
STAT:QUES:COND? | Query condition register, includes CC/CV state |
Always set limits before enabling the output, and always read setpoints back. Supplies clamp silently to their range and report no error.
This is where vendors diverge most. Both columns shown.
| Purpose | Keysight InfiniiVision | Tektronix |
|---|---|---|
| Autoset | :AUTOSCALE | AUTOSET EXECUTE |
| Timebase | :TIM:SCAL <s> | HORIZONTAL:SCALE <s> |
| Vertical scale | :CHAN1:SCAL <v> | CH1:SCALE <v> |
| Vertical offset | :CHAN1:OFFS <v> | CH1:OFFSET <v> |
| Trigger source | :TRIG:EDGE:SOUR CHAN1 | TRIGGER:A:EDGE:SOURCE CH1 |
| Trigger level | :TRIG:EDGE:LEV <v> | TRIGGER:A:LEVEL:CH1 <v> |
| Run | :RUN | ACQUIRE:STATE RUN |
| Single | :SINGLE | ACQUIRE:STOPAFTER SEQUENCE |
| Measure Vpp | :MEAS:VPP? CHAN1 | MEASUREMENT:IMMED:TYPE PK2PK then ...:VALUE? |
| Measure frequency | :MEAS:FREQ? CHAN1 | MEASUREMENT:IMMED:TYPE FREQUENCY |
| Waveform source | :WAV:SOUR CHAN1 | DATA:SOURCE CH1 |
| Waveform format | :WAV:FORM BYTE | DATA:ENC RIBINARY |
| Get data | :WAV:DATA? | CURVE? |
Rigol scopes largely follow the Keysight-style colon-prefixed tree, which is why Rigol scripts often port from Keysight examples with modest changes. See automating a Rigol oscilloscope and Keysight vs Tektronix.
| Command | Purpose | ||||||
|---|---|---|---|---|---|---|---|
| `FUNC SIN\ | SQU\ | RAMP\ | PULS\ | NOIS\ | DC\ | USER` | Waveform shape |
FREQ <hz> | Frequency | ||||||
VOLT <vpp> | Amplitude, peak to peak by default | ||||||
VOLT:OFFS <v> | DC offset | ||||||
| `VOLT:UNIT VPP\ | VRMS\ | DBM` | Amplitude units | ||||
FUNC:SQU:DCYC <percent> | Square wave duty cycle | ||||||
OUTP ON / OUTP OFF | Enable output | ||||||
| `OUTP:LOAD 50\ | INF` | Expected load impedance | |||||
BURS:STAT ON | Enable burst mode | ||||||
BURS:NCYC <n> | Cycles per burst | ||||||
PHAS <deg> | Phase |
`OUTP:LOAD` catches everyone. Generators default to assuming a 50 ohm load. Drive a high-impedance input with that default and you get exactly twice the amplitude you asked for. Set it to INF for scope inputs and most digital circuits.
| Command | Purpose | |||
|---|---|---|---|---|
| `FUNC CURR\ | VOLT\ | RES\ | POW` | Operating mode: CC, CV, CR, CP |
CURR <a> | Constant current setpoint | |||
VOLT <v> | Constant voltage setpoint | |||
RES <ohm> | Constant resistance setpoint | |||
POW <w> | Constant power setpoint | |||
INP ON / INP OFF | Enable and disable the load | |||
MEAS:VOLT? | Measure voltage at the terminals | |||
MEAS:CURR? | Measure current drawn | |||
TRAN:STAT ON | Enable transient mode | |||
CURR:SLEW <a/s> | Slew rate for current steps |
Note that loads use INP rather than OUTP. Sending OUTP ON to a load is a common and confusing error.
| Command | Purpose |
|---|---|
ROUT:CLOS (@1001) | Close a channel |
ROUT:OPEN (@1001) | Open a channel |
ROUT:OPEN:ALL | Open everything, the safe reset |
ROUT:CLOS? | Query which channels are closed |
ROUT:SCAN (@1001:1010) | Define a scan list |
Channel numbering is typically slot then channel, so 1001 is slot 1 channel 01. Always ROUT:OPEN:ALL before configuring, or you inherit whatever the previous test left closed.
When a command does not work:
Confirm the instrument is there. *IDN?. If this fails, the problem is the connection.
Reset. *RST then *CLS. Removes leftover state.
Send the single command in isolation, then immediately SYST:ERR?. Do not debug inside a loop.
Read the error text. -113,"Undefined header" means the command does not exist on this model. -222,"Data out of range" means the value is wrong, not the command.
Check the programming guide for that exact model. Not the family, not a generic reference.
Step 3 is the one people skip and it is the one that resolves most problems in under a minute.
Responses are strings and they arrive in forms that trip up naive parsing.
| Response form | Example | Parse with |
|---|---|---|
| Real number | +3.30012000E+00 | float(response) |
| Integer | +1 | int(float(response)) |
| Boolean | 1 or 0 | bool(int(response)) |
| Comma-separated | +1.0,+2.0,+3.0 | [float(v) for v in response.split(",")] |
| Quoted string | "No error" | response.strip().strip('"') |
| Error pair | -113,"Undefined header" | code, text = response.split(",", 1) |
| Definite-length block | #800001000<binary> | query_binary_values |
Two habits worth adopting. Always strip() before parsing, because trailing newlines are normal and int("1\n") works while bool("0\n") does not do what you expect. And never compare a boolean response as a string: "0" is truthy in Python, so if inst.query("OUTP?"): is true whether the output is on or off.
Every command is a round trip, so a script sending 5,000 short commands spends real time on latency alone. Semicolons combine them into one transfer.
inst.write("VOLT 3.3;:CURR 0.5;:OUTP ON")The leading colon after each semicolon returns to the root of the tree. Without it, the second command is resolved relative to the first one's node, which either errors or, worse, silently addresses something else.
Within one subsystem you can omit it deliberately:
inst.write("CONF:VOLT:DC 10;:VOLT:DC:NPLC 10") # cross-subsystem, colon needed
inst.write("TRIG:SOUR IMM;COUN 1") # same subsystem, no colonQueries chain too, and the responses come back separated by semicolons:
response = inst.query("VOLT?;:CURR?")
voltage, current = (float(v) for v in response.strip().split(";"))Combining a configuration block into one write typically removes 60 to 80 percent of the setup latency in a loop.
Less standardised than the instrument classes above, because many chambers speak Modbus rather than SCPI. Where SCPI is offered, this is the shape.
| Task | Typical command |
|---|---|
| Set target temperature | :SOUR:TEMP:SPO 85.0 |
| Read current temperature | :MEAS:TEMP? |
| Start the chamber | :OUTP:STAT ON |
| Read stability flag | :STAT:OPER:COND? |
| Set ramp rate | :SOUR:TEMP:SLOP 3.0 |
| Humidity setpoint | :SOUR:HUM:SPO 50.0 |
The one to get right is the stability flag. Reaching a setpoint is not the same as being stable at it, and measuring during the overshoot is a common source of results that will not reproduce. Poll the stability condition and add a dwell on top of it:
def wait_stable(chamber, target_c, tol=1.0, dwell_s=600, timeout_s=7200):
import time
t0 = time.time(); stable_since = None
while time.time() - t0 < timeout_s:
actual = float(chamber.query(":MEAS:TEMP?"))
if abs(actual - target_c) <= tol:
stable_since = stable_since or time.time()
if time.time() - stable_since >= dwell_s:
return actual
else:
stable_since = None
time.sleep(10)
raise TimeoutError(f"chamber never stabilised at {target_c} C")The subsystem most people skip and then need at exactly the wrong moment. These are standard across every SCPI instrument.
| Command | What it does |
|---|---|
*OPC? | Blocks until all pending operations complete, returns 1 |
*OPC | Sets the operation-complete bit, does not block |
*WAI | Instrument waits before processing further commands |
*ESR? | Standard event status register, clears on read |
*STB? | Status byte, does not clear |
*CLS | Clear all status registers and the error queue |
SYST:ERR? | Pop one error off the queue |
The practical pattern for anything slow (an acquisition, a sweep, a calibration) is to issue the command then *OPC?, which blocks until it finishes rather than guessing at a sleep:
scope.write("ACQuire:STATE ON")
scope.query("*OPC?") # returns when the acquisition is genuinely done
value = scope.query("MEASUrement:IMMed:VALue?")Start every session with *CLS so you are not reading errors left over from someone else's session. That single habit removes a category of confusing failures.
* commands and the top of the tree are portable. Measurement commands are vendor territory.*OPC?.** Either too slow or intermittently wrong, and the wrong one is worse.:SOURce:VOLTage:LEVel and :SOUR:VOLT:LEV are the same command. Mixed partial abbreviations are not.CMD1;:CMD2 restarts at the root. CMD1;CMD2 stays in the current subsystem, which is usually not what you meant.*CLS first, every time.*IDN? and *OPT?.A cheat sheet helps with syntax. It does not tell you which commands a particular model needs in which order for a particular measurement, and that is the part that consumes the afternoon.
Connect your instruments. Pick the manufacturer and model, paste the VISA address (USB, LAN, GPIB, or serial), and the agent knows what is on your bench. No bench yet? Use a placeholder address, build the full automation, and swap in the real address when you are in the lab.
Tell the agent what to test, in plain English. For example, "run a VI sweep from 1 to 10 V in 1 V steps at 0.5 A load current," or "suggest the tests for a power-management device."
The agent builds the complete workflow in seconds. Instrument-aware automation appears on the canvas, with the generated scripts visible in a code panel you can inspect and edit.
Run it in your lab. Click Run and the status panel streams results step by step, with measured values inline (VOUT = 3.301 V, asserted 3.2 to 3.4 V, PASS). One click exports a structured PDF report, or the raw results as CSV.


The step-by-step walkthrough, VISA address formats, and Test Planner prompts are all in the TestFlow product guide.
Standard Commands for Programmable Instruments, a text-based command language for controlling test equipment. It defines a common syntax and a set of required commands, with vendor-specific extensions below the standardised layer.
Only partially. The IEEE 488.2 common commands such as *IDN?, *RST, and *CLS are universal. Measurement subsystems are standardised in structure but differ in detail between vendors and even between models from the same vendor.
It separates levels of the command tree. MEASure:VOLTage:DC? navigates from the MEASure root to VOLTage to DC. A leading colon returns to the root, which matters when chaining commands with semicolons.
MEASure configures and triggers a measurement then returns it, the simplest one-shot form. READ triggers and returns using the existing configuration. FETCh returns the last result without triggering. Use CONFigure plus READ when taking many readings with one configuration.
The uppercase part is the required short form and the full word is the long form. MEASure means you can send either MEAS or MEASURE. Both are valid and identical, and the short form is faster over slow interfaces.
Use the programming guide for that exact model, not a generic SCPI reference. Vendors publish these as PDFs. The instrument's own error queue, read with SYST:ERR?, is the fastest way to confirm whether a command was accepted.
Connect your instruments, describe a test in plain English, and TestFlow builds and runs it in minutes.
A new way for testing, from specs to automated sequences, capture clean data, and accelerate your validation cycle.