Skip to main content

Triggers

A trigger says what arms the capture. Build one with the Trigger helpers and hand it to capture.start() or capture.run().

Every builder takes the same two extras:

ExtraMeaning
positionThe share of the acquisition kept before the trigger, 0.0 to 0.9. 0.1 keeps 10 % of history behind the event.
modesingle captures once and stops. normal re-arms after every trigger until you stop the capture.

The Python and NodeJS builders check their arguments before anything is sent. A position outside 0.0–0.9, or a mode, polarity or order that is not one of the listed values, raises ValueError in Python and RangeError in NodeJS. These are plain language errors, not ScanaStudioError. Rust and C clamp position into range instead.

immediate​

No trigger: sampling starts as soon as the device is armed.

from ikalogic_scanastudio import Trigger

Trigger.immediate()

rising and falling​

An edge on one channel.

Trigger.rising(channel=0, position=0.1, mode="single")
Trigger.falling(channel=3)
Rust uses a trait

position and mode are not arguments in Rust: they are .at() and .mode() from the TriggerExt trait, which must be in scope. .at() clamps to 0.0–0.9, as C does; Python and NodeJS raise instead.

logic​

Fires on any logic change across a set of channels: a bus transaction starting, rather than one wire moving.

Trigger.logic([0, 1, 2, 3], position=0.2)

pulse​

A pulse on one channel, optionally bounded in width. This is how you catch a glitch: ask for a pulse shorter than anything legitimate.

# A high pulse (0-1-0) shorter than 50 ns — a runt.
Trigger.pulse(channel=2, polarity="high", max_width=50e-9, position=0.5)

# A low pulse (1-0-1) at least 1 ms long — a bus held down.
Trigger.pulse(channel=2, polarity="low", min_width=1e-3)

Parameters:

  • polarity — high for a 0‑1‑0 pulse, low for 1‑0‑1.
  • min_width, max_width — in seconds. Leaving one out leaves that side unconstrained.

steps​

A multi-step pattern: a sequence of per-channel cells, each with its own timing window. This is the most selective trigger the hardware offers.

Each step is a list of cells, one per channel:

CellMeaning
xIgnore this channel.
0 / 1The channel must be at this level.
rise / fallThe channel must show this edge.

The server refuses any other cell: the capture is not started, and start() or run() raises Refused with the reason (unknown trigger step cell "r").

from ikalogic_scanastudio import Trigger, step

# A falling edge on ch0 while ch1 is high, then 1-10 us later ch0 rises again.
Trigger.steps([
step(["fall", "1"]),
step(["rise", "x"], t_min=1e-6, t_max=10e-6),
], position=0.3)

external​

The device's external trigger input, for arming off something outside the signals you are watching.

Trigger.external(edge="rising", impedance="100k", position=0.1)

Parameters: edge is rising or falling; impedance is 100k for the high-impedance input or 50ohm for the terminated one. Python and NodeJS default them to rising and 100k; Rust and C take both as required arguments.

The server reads impedance as terminated or not: 50ohm selects the 50 Ω input, and any other value selects 100 kΩ without an error.

SP1000G: external trigger with a second condition

On the SP1000G series, the external trigger combined with a second condition through sequence() (external with an edge, a pulse or a logic change, in either order) makes the device send no data. After 15 s the capture ends in error with capture stream stalled, raised as CaptureFailed. Use the external trigger on its own on these devices until this is fixed.

sequence​

Combines two triggers into one condition. The default order is a_then_b.

OrderFires when
a_then_bB, but only after A has happened.
b_then_aA, but only after B has happened.
a_or_bEither one.
a_and_bBoth, in any order.
# A chip-select falling, then a clock edge.
Trigger.sequence(
Trigger.falling(channel=0),
Trigger.rising(channel=1),
order="a_then_b",
position=0.2,
)
Position and mode belong to the sequence

sequence() keeps only the condition of each argument (its trig_a). The position and mode given to the inner triggers are dropped. In Python and NodeJS, pass them to sequence() itself. In Rust, call .at() and .mode() on the result. In C, the position comes from first and the mode is single; for a sequence in normal mode, set "mode": "normal" in the trigger JSON before you build the request.

protocol​

A trigger built by a decoder: an I²C address, a UART byte or string, anything the decoder's script can describe as edges and timing windows. It is not a Trigger builder; ask the decoder instance for it, with the same position and mode extras:

i2c = workspace.decoders.add("i2c.js", {"ch_scl": 0, "ch_sda": 1})
print(i2c.trigger_options()) # what it takes, as the script describes it
trigger = i2c.trigger({"trig_addr": "0x41"}, position=0.5)
workspace.capture.run(samples=2_000_000, sample_rate=25_000_000, trigger=trigger)

NodeJS decoder.trigger(values, { position }), Rust decoders().trigger(instance_id, &values) then .at(), C ss_decoder_trigger(). The script turns it into steps at every capture start, from the decoder's configuration of the moment; a removed decoder's trigger is refused. decoder.can_trigger says whether a script builds one. The whole walk-through, in every language: Trigger on a protocol frame.

What the device supports

Not every device offers every trigger. A definition the hardware cannot arm is refused rather than silently downgraded: you get a Refused error with the reason.