Skip to main content
The oscilloscope module provides Python interfaces for waveform capture, triggering, and measurements.

Overview

Use the scope module to control oscilloscopes for analog signal capture, triggering on specific events, and automated measurements.

Instrument support

The same object and the same calls work for a Rigol MSO5000 and for a PicoScope (2000, 2000a, 3000a, 4000a or 5000a). The difference is underneath. The Rigol speaks SCPI over VISA. A PicoScope is a USB device with a vendor SDK, and the oscilloscope daemon on the box drives it. Know these differences before you write a script for a PicoScope:
  • Measurements are computed, not queried. No PicoTech API has an equivalent of :MEAS:VPP?, so the box computes the value from a captured block. One capture yields the whole set, so scope.measurement.all() costs the same as a single voltage_peak_to_peak() and reading five values individually costs five captures.
  • While running, they describe the capture last streamed out. The box computes a measurement or a cursor reading from the block it last sent to subscribers. The numbers therefore agree with the trace on screen. Repeated reads change only as fast as captures arrive: two calls between captures return the same value. Stopped, they describe the last capture, as a stopped bench scope does.
  • display and measurement_cursor do nothing. They put a reading on the instrument’s own screen, and a PicoScope has none. They are accepted and ignored so a script written for a Rigol still runs.
  • Some features raise instead of returning. On-screen cursors, protocol triggers, the logic analyzer, and the measurements listed as Rigol-only below raise UnsupportedScopeFeature naming the gap, rather than returning a plausible-looking zero. Catch it if a script needs to run against both.
Channels are letters on a PicoScope (A-D) and numbers on a Rigol (1-4). Every method accepts either and normalizes, so a script written against one instrument keeps working against the other.

Import

Usage

Methods

Channel Control

enable()

Enable the oscilloscope channel.

disable()

Disable the oscilloscope channel.

Capture Control

start_capture()

Start continuous waveform capture.

start_single_capture()

Start single-shot capture (captures one triggered event).

stop_capture()

Stop waveform capture.

force_trigger()

Trigger now rather than waiting for the configured condition. Use it when a signal does not cross the trigger level and the script needs a capture anyway. The scope must be running: a stopped PicoScope refuses it.

Measurements

Access comprehensive measurements through the measurement attribute.

Availability

Everything below works on a Rigol. On a PicoScope the box computes measurements from the captured samples, and these fourteen are the ones it computes: All other measurements are Rigol only, and raise UnsupportedScopeFeature on a PicoScope. These are the flat-top and threshold voltages, preshoot, period RMS, time-at-extreme, slew rates, edge and pulse counts, area and variance. The delay and phase measurements are Rigol only too, because they compare two channels. The box has no command for that on a PicoScope. period, frequency, the pulse widths, the duty cycles, the rise and fall times and overshoot need a resolvable waveform. On a PicoScope, a period needs two full cycles in the capture. With fewer, the call raises rather than return an imprecise guess. Put at least two cycles on screen before you measure timing.

all()

Every measurement the box computes, from a single capture. On a PicoScope this is the cheap way to read more than one value.

Voltage Measurements

Timing Measurements

Counting Measurements

Area Measurements

Statistical Measurements

Delay and Phase Measurements

Measurement Options

Most measurements accept optional parameters:

Streaming (PicoScope)

For PicoScope devices, streaming capabilities are available:

stream_start(channel, volts_per_div, time_per_div, trigger_level, trigger_slope, capture_mode, coupling)

Start streaming acquisition. Parameters:
  • channel (str): Channel to enable - "A", "B", "1", "2"
  • volts_per_div (float): Vertical scale
  • time_per_div (float): Horizontal scale in seconds
  • trigger_level (float): Trigger level in volts
  • trigger_slope (str): "rising", "falling", "either"
  • capture_mode (str): "auto", "normal", "single"
  • coupling (str): "dc", "ac"
"gnd" is accepted by the API but refused by 2000-series units, which have no ground switch: ps2000_set_channel carries a single flag for coupling. Use "ac" to block the DC component, or unplug the probe to see where zero sits.

stream_stop()

Stop streaming acquisition.

stream_capture(output, duration, samples)

Capture for duration seconds, writing a CSV. Parameters:
  • output (str): Output file path. Omit it to count samples without writing a file
  • duration (float): Capture duration in seconds
  • samples (int): Cap on rows per channel (optional). A cap, not a target: the capture still stops at duration
Returns a summary rather than the samples themselves — captures, samples_per_channel, rows and output. The CSV columns are capture, channel, sample_index, time_ns and voltage, the same layout lager scope stream capture writes. Each capture the scope takes in that time is recorded once. The scope must be running already, from stream_start(), start_capture() or start_single_capture(). A stopped scope raises at once, rather than after duration with nothing recorded. A relative output path is relative to the script’s working directory on the box.

stream_frames(count, timeout)

Yield captures as decoded frames instead of writing a file. This is the zero-copy path: a frame’s counts() is a view over the received buffer, so nothing is converted until asked for. Use it when the samples go into numpy rather than onto disk. Each frame is a new capture, never one it yielded before. When no capture arrives within timeout, it raises ScopeDaemonTimeout. Start the scope first, and in normal mode make sure the signal crosses the trigger level. Parameters:
  • count (int): Number of captures to yield (default 1)
  • timeout (float): Seconds to wait for each capture (default 10)
volts() gives the voltage at the probe tip. With a 10x probe it reports the signal at the tip, not the tenth of it that reaches the BNC. volts_per_div, the trigger level and the measurement functions are also at the tip. Set the ratio with set_channel_probe() before you capture. A 2000-series unit starts at 10x, and the 2000a, 3000a, 4000a and 5000a families start at 1x.

Complete Example

Trace Settings

Configure vertical and horizontal scale through trace_settings:
Time/div is one tenth of the capture, as on a ten-division screen, so set_time_per_div(0.001) asks for a 10 ms window.get_time_per_div() returns the scale in use, which is rarely the one asked for. A scope samples at a fixed ladder of intervals, each twice the last. The box rounds a request to the closest one: 1 ms/div on a 2204A is really 1.024 ms/div. Read it back if you need the exact window. Better still, use get_sample_rate() and the frame’s own time axis, not arithmetic on the value you set.The fastest screen a unit can reach depends on the block depth and the maximum sample rate, because the two multiply out to the window. A 2204A at 100 MS/s over 8000 samples bottoms out at 8 us/div, and asking for anything faster gets you that. Enabling a second channel halves the buffer, which makes the fastest screen faster rather than slower.
set_time_offset() moves the capture window in time relative to the trigger. Use it to look at signal past the left or right edge of one capture. Positive values look forward, at signal later than the trigger; negative values look back before it.On a PicoScope the travel is one window each way. The window is a block, and a block is all the scope holds. So the box clamps the offset where the capture is entirely before or entirely after the trigger. Setting it re-arms the scope, because a window somewhere else must be captured to be seen. The offset then applies to every capture until you change it.

Advanced Trigger Settings

Access advanced trigger configuration through trigger_settings:

Cursor Control

Access cursor functions through the cursor attribute.
scope.cursor.* is Rigol only. These are markers on the instrument’s own display, positioned in screen coordinates and read back over SCPI. A PicoScope has no display, so every method here raises UnsupportedScopeFeature on one.Cursors over the captured samples are a different thing, and a PicoScope has them. You place them with a time and a voltage, not a pixel, so they have their own API. Use lager scope <net> cursor time <t1> <t2>, the same cursor verb in the web UI’s console, or set_cursors() / measure_cursors() on the driver:
The box holds the pair. Cursors placed from a script, the terminal CLI or the web UI’s console are the same two markers, and the web UI draws them.

Supported Hardware

The box detects an attached PicoScope’s channel count, voltage ranges, bandwidth, memory depth and ADC resolution at runtime. Models within a series therefore work without configuration. Read them with scope.capabilities().

Notes

  • Use NetType.Analog for oscilloscope channels (1-4)
  • Use NetType.Logic for digital channels (D0-D15) on MSO scopes
  • Streaming features are only available on PicoScope devices
  • Configure trigger before starting capture for reliable measurements
  • Measurement methods return float on success, or None if the measurement is invalid (e.g., no signal, no trigger, wrong channel). On Rigol hardware, the instrument returns 9.9E+37 for invalid measurements, which is automatically converted to None.
  • For protocol triggering (UART, I2C, SPI, CAN), see the Logic Analyzer documentation