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:
| Extra | Meaning |
|---|---|
position | The share of the acquisition kept before the trigger, 0.0 to 0.9. 0.1 keeps 10 % of history behind the event. |
mode | single 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.
- Python
- NodeJS
- Rust
- C
from ikalogic_scanastudio import Trigger
Trigger.immediate()
import { Trigger } from '@ikalogic/scanastudio';
Trigger.immediate();
use scanastudio_client::Trigger;
Trigger::immediate()
/* Leave `trigger` out of the request and sampling starts at once. */
ss_capture_run(workspace, "{\"samples\": 1000000, \"sample_rate\": 25000000}", 300.0);
/* Or say so explicitly: */
char *trigger = ss_trigger_immediate();
/* ... drop it into the request under "trigger", then ss_string_free(trigger); */
rising and fallingā
An edge on one channel.
- Python
- NodeJS
- Rust
- C
Trigger.rising(channel=0, position=0.1, mode="single")
Trigger.falling(channel=3)
Trigger.rising(0, { position: 0.1, mode: 'single' });
Trigger.falling(3); // a high-to-low edge on channel 3
use scanastudio_client::{Trigger, TriggerExt, Mode};
Trigger::rising(0).at(0.1).mode(Mode::Single)
Trigger::falling(3) // a high-to-low edge on channel 3
/* A falling edge on channel 3, keeping 10 % of the capture before it. */
char *trigger = ss_trigger_edge(3, "falling", 0.1, "single");
char request[256];
snprintf(request, sizeof request,
"{\"samples\": %lld, \"sample_rate\": %llu, \"trigger\": %s}",
(long long)samples, (unsigned long long)rate, trigger);
ss_string_free(trigger);
/* Arm and wait in one call, or ss_capture_start to arm and return. */
ss_capture_run(workspace, request, 300.0);
ss_trigger_* buildersEach one returns the trigger object as JSON, to drop into the request that
ss_capture_run and ss_capture_start take. They are the C equivalent of the
Trigger helpers in the other SDKs. Free the string with ss_string_free once
you have built the request. position is 0.0ā0.9; mode is "single" or
"normal", and NULL means "single".
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.
- Python
- NodeJS
- Rust
- C
Trigger.logic([0, 1, 2, 3], position=0.2)
// Any logic change across these four channels.
Trigger.logic([0, 1, 2, 3], { position: 0.2 });
// Any logic change across these four channels.
Trigger::logic([0u16, 1, 2, 3]).at(0.2)
/* Any logic change across these four channels. */
uint16_t channels[] = { 0, 1, 2, 3 };
char *trigger = ss_trigger_logic(channels, 4, 0.2, NULL);
/* ... build the request, then ss_string_free(trigger); */
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.
- Python
- NodeJS
- Rust
- C
# 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)
// A high pulse (0-1-0) shorter than 50 ns ā a runt.
Trigger.pulse(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(2, { polarity: 'low', min_width: 1e-3 });
// A high pulse (0-1-0) shorter than 50 ns ā a runt.
Trigger::pulse(2, "high", None, Some(50e-9)).at(0.5)
// A low pulse (1-0-1) at least 1 ms long ā a bus held down.
Trigger::pulse(2, "low", Some(1e-3), None)
/* A high pulse (0-1-0) shorter than 50 ns ā a runt.
A negative width leaves that end unconstrained. */
char *runt = ss_trigger_pulse(2, "high", -1.0, 50e-9, 0.5, NULL);
/* A low pulse (1-0-1) at least 1 ms long ā a bus held down. */
char *stuck = ss_trigger_pulse(2, "low", 1e-3, -1.0, 0.0, NULL);
Parameters:
polarityāhighfor a 0ā1ā0 pulse,lowfor 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:
| Cell | Meaning |
|---|---|
x | Ignore this channel. |
0 / 1 | The channel must be at this level. |
rise / fall | The 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").
- Python
- NodeJS
- Rust
- C
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)
import { Trigger, step } from '@ikalogic/scanastudio';
// 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 });
use scanastudio_client::{step, Trigger, TriggerExt};
// A falling edge on ch0 while ch1 is high, then 1-10 us later ch0 rises again.
Trigger::steps(vec![
step(vec!["fall".into(), "1".into()], None, None),
step(vec!["rise".into(), "x".into()], Some(1e-6), Some(10e-6)),
]).at(0.3)
/* A falling edge on ch0 while ch1 is high, then 1-10 us later ch0 rises. */
char *trigger = ss_trigger_steps(
"[{\"cells\": [\"fall\", \"1\"], \"t_min\": null, \"t_max\": null},"
" {\"cells\": [\"rise\", \"x\"], \"t_min\": 1e-6, \"t_max\": 10e-6}]",
0.3, NULL);
A cell is "x" to ignore the channel, "0" or "1" to match a level, and
"rise" or "fall" to match an edge. The timings are in seconds.
externalā
The device's external trigger input, for arming off something outside the signals you are watching.
- Python
- NodeJS
- Rust
- C
Trigger.external(edge="rising", impedance="100k", position=0.1)
// The device's external trigger input, not one of the signal channels.
Trigger.external({ edge: 'rising', impedance: '100k', position: 0.1 });
// The device's external trigger input, not one of the signal channels.
Trigger::external("rising", "100k").at(0.1)
/* The device's external trigger input, not one of the signal channels. */
char *trigger = ss_trigger_external("rising", "100k", 0.1, NULL);
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.
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.
| Order | Fires when |
|---|---|
a_then_b | B, but only after A has happened. |
b_then_a | A, but only after B has happened. |
a_or_b | Either one. |
a_and_b | Both, in any order. |
- Python
- NodeJS
- Rust
- C
# 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,
)
// A chip-select falling, then a clock edge.
Trigger.sequence(
Trigger.falling(0),
Trigger.rising(1),
{ order: 'a_then_b', position: 0.2 },
);
// A chip-select falling, then a clock edge.
Trigger::sequence(Trigger::falling(0), Trigger::rising(1), "a_then_b").at(0.2)
/* A chip-select falling, then a clock edge. The position comes from the first. */
char *cs = ss_trigger_edge(0, "falling", 0.2, NULL);
char *clock = ss_trigger_edge(1, "rising", 0.0, NULL);
char *both = ss_trigger_sequence(cs, clock, "a_then_b");
ss_string_free(cs);
ss_string_free(clock);
/* ... build the request with `both`, then ss_string_free(both); */
order is "a_or_b", "a_then_b", "b_then_a" or "a_and_b".
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.
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.