Pattern generator
The pattern generator drives the device's outputs from a script, so the
instrument stimulates the board as well as watching it. Reached through
workspace.pattern.
Like a decoder, a generator is a JavaScript file in the server's script library,
one implementing on_pattern_generate. See
pattern generator scripts for
writing one.
The scripted pattern generator on this page is an SP1000G-series feature
(SP1018G, SP1036G, SP1054G). capabilities(...).pattern_generator says whether
a device has it.
The SQ family (SQ25, SQ50, SQ100, SQ200) also generates signals, through
generation rows and signal builders rather than a generator script. See
generation and the operation
capture option in capture.
capabilities(...).signal_generator says whether a device has it.
The SE254, SP209 and SP259 families capture only.
The examples on this page use sp1018, the SP1018G demo device
(hw:<serial> for the real thing). Its channels are group-prefixed
(G1 PWM, G1 I2C SCL, G2 UART TX), one group per block of 18 channels.
preview() is interpreted by the server rather than sent to hardware, so it
runs on any device. That makes it possible to write a generator without an
SP1000G at hand, but it does not prove that the attached device could play it.
The device has a single pattern generator, so a workspace has one too. Adding a
second replaces the first: there is no list and no instance id, just current.
options and add​
- Python
- NodeJS
- Rust
- C
for option in workspace.pattern.options("pwm.js"):
print(option)
generator = workspace.pattern.add("pwm.js", {
"channel": workspace.channel("G1 PWM"),
"simple_freq_val": 1_000_000, # 1 MHz
"simple_pwm_val": 25, # 25 % duty
"nb_of_cycles": "100",
})
print(generator)
// A generator is a script, so ask it what it takes rather than guessing.
for (const option of await workspace.pattern.options('pwm.js')) {
console.log(option);
}
const generator = await workspace.pattern.add('pwm.js', {
channel: workspace.channel('G1 PWM'),
simple_freq_val: 1_000_000, // 1 MHz
simple_pwm_val: 25, // 25 % duty
nb_of_cycles: '100',
});
console.log(generator);
use scanastudio_client::values;
for option in workspace.pattern().options("pwm.js").await? {
println!("{option}");
}
// Options are keyed by id or caption; anything omitted keeps the default.
let pwm = workspace.channel("G1 PWM")?;
let generator = workspace.pattern().add("pwm.js", &values([
("channel", pwm.into()),
("simple_freq_val", 1_000_000i64.into()),
("simple_pwm_val", 25i64.into()),
("nb_of_cycles", "100".into()),
])).await?;
println!("{generator:?}"); // Option<PatternDesc>, same as pattern().current()
/* What the generator script takes, as a JSON array of its options. */
char *options = ss_pattern_options(workspace, "pwm.js");
if (options) { printf("%s\n", options); ss_string_free(options); }
/* Attaching it returns the generator as JSON. */
char *generator = ss_pattern_add(
workspace, "pwm.js",
"{\"channel\": 0, \"simple_freq_val\": 1000000, \"simple_pwm_val\": 25}");
if (generator) { printf("%s\n", generator); ss_string_free(generator); }
Example output. pwm.js on an SP1018G. Each option with its type, unit, tab
and default:
channel: Target channel (ch_selector) = None
simple_freq_val [Fixed duty cycle]: Frequency (engineering_input) in Hz = nan
simple_pwm_val [Fixed duty cycle]: Duty cycle (engineering_input) in % = 50.0
mod_type [Modulated frequency]: Modulation type (combo) one of ['Sine', 'Triangle', 'SawTooth'] = 'Sine'
f_mod [Modulated frequency]: Modulation frequency (engineering_input) in Hz = nan
ph_mod [Modulated frequency]: Modulation phase (engineering_input) in DEG = 0.0
freq_carrier [Modulated frequency]: Carrier frequency (engineering_input) in Hz = nan
duty_min [Modulated frequency]: Carrier minimum (lower) duty cycle (engineering_input) in % = 10.0
duty_max [Modulated frequency]: Carrier maximum (upper) duty cycle (engineering_input) in % = 90.0
nb_of_cycles: Number of cycles (0 = infinite loop) (text_input) = '1000'
and the generator that add() returns:
instance_id=1 file_name='pwm.js' name='PWM Builder on CH 8' enabled=True pausable=False paused=False
A nan default means the script leaves it unset and expects you to supply it
(simple_freq_val here). The bracketed name is the tab the option sits under in
the ScanaStudio dialog.
add() returns the attached generator (PatternDesc), or None (null in
NodeJS) if the server did not attach one. A script that refuses the values
raises ScriptError.
Options work exactly as they do for decoders: discovered at runtime, keyed by id or caption, anything omitted keeps the script's default.
current​
The generator attached to this workspace, if any.
- Python
- NodeJS
- Rust
- C
generator = workspace.pattern.current # PatternDesc, or None
generator.file_name
generator.name
generator.enabled
generator.pausable # the script can pause mid-generation
generator.paused
workspace.pattern.config() # its current settings, which update() takes
const generator = workspace.pattern.current; // PatternDesc | null
generator.file_name;
generator.enabled;
generator.pausable; // the script can pause mid-generation
generator.paused;
await workspace.pattern.config(); // its current settings, which update() takes
// There is no instance id: a workspace has one generator or none.
let generator = workspace.pattern().current(); // Option<PatternDesc>
let config = workspace.pattern().config().await?; // what update() takes
/* There is no instance id: a workspace has one generator or none. */
char *generator = ss_pattern_current(workspace); /* NULL if there is none */
if (generator) { printf("%s\n", generator); ss_string_free(generator); }
char *config = ss_pattern_config(workspace); /* its current settings */
if (config) { printf("%s\n", config); ss_string_free(config); }
config() returns the attached generator's options with their current values
as defaults, in the same form as options().
preview​
Runs the generator script through an interpreter instead of the device. The result replaces the workspace data exactly as an acquisition would. The script runs unchanged, so the preview shows the real program. This is how you develop and verify a generator with no hardware attached.
- Python
- NodeJS
- Rust
- C
workspace.pattern.preview(timeout=300.0)
# The preview lands in the workspace like a capture would:
for edge in workspace.data.transitions(channel=workspace.channel("G1 PWM")):
print(edge.sample, edge.level)
// Interpreted instead of sent to the device; the result REPLACES the
// displayed data, exactly as an acquisition would.
await workspace.pattern.preview(300_000);
// So the program is readable back as ordinary transitions.
for await (const edge of workspace.data.transitions(0)) {
console.log(edge.sample, edge.level);
}
// Interpreted instead of sent to the device; the result REPLACES the
// displayed data, exactly as an acquisition would.
workspace.pattern().preview(Duration::from_secs(300)).await?;
// So the program is readable back as ordinary transitions.
let edges = workspace.data().transitions(0, 0, None).await?;
/* Builds the waveform into the displayed data, and waits for it. */
ss_pattern_preview(workspace, 300.0);
/* So the program is readable back as ordinary transitions. */
ss_edge edges[4096];
ss_data_transitions(workspace, 0, 0, -1, edges, 4096);
If the script fails, preview raises ScriptError, whose logs hold what
the script printed. In C, ss_pattern_preview returns a failure status and
ss_last_error() holds the message.
The preview replaces whatever the workspace was displaying. Save anything you still need first.
resume​
A generator script can pause its generation with builder_add_pause. resume
continues a generation paused that way. It is the same call in every language.
- Python
- NodeJS
- Rust
- C
if workspace.pattern.current and workspace.pattern.current.paused:
workspace.pattern.resume()
// A generator script can pause itself mid-program.
if (workspace.pattern.current?.paused) {
workspace.pattern.resume();
}
workspace.pattern().resume()?;
ss_pattern_resume(workspace);
set_enabled and set_generate_on_trigger​
set_enabled turns generation off without losing the configuration.
set_generate_on_trigger decides when the pattern plays: on the
acquisition's trigger, or as soon as the acquisition starts.
- Python
- NodeJS
- Rust
- C
workspace.pattern.set_enabled(True)
workspace.pattern.set_generate_on_trigger(True)
workspace.pattern.generate_on_trigger # read it back
workspace.pattern.set_enabled(true); // keeps the config either way
workspace.pattern.set_generate_on_trigger(true); // play on trigger, not on start
workspace.pattern.generate_on_trigger; // read it back
workspace.pattern().set_enabled(true)?; // keeps the config either way
workspace.pattern().set_generate_on_trigger(true)?; // play on trigger, not on start
workspace.pattern().generate_on_trigger(); // read it back
ss_pattern_set_enabled(workspace, true); /* keeps the config either way */
ss_pattern_set_generate_on_trigger(workspace, true); /* play on trigger, not on start */
ss_pattern_generate_on_trigger(workspace); /* read it back */
update and remove​
- Python
- NodeJS
- Rust
- C
workspace.pattern.update({"simple_freq_val": 500_000}) # 500 kHz
workspace.pattern.remove()
await workspace.pattern.update({ simple_freq_val: 500_000 }); // reconfigure in place
workspace.pattern.remove(); // detach the generator
use scanastudio_client::values;
// update() takes values against the ATTACHED generator's config, which
// pattern().config() returns — not against the script file.
workspace.pattern().update(&values([("simple_freq_val", 500_000i64.into())])).await?;
workspace.pattern().remove()?; // detach the generator
/* update takes values against the ATTACHED generator's config, which
ss_pattern_config returns — not against the script file. */
ss_pattern_update(workspace, "{\"simple_freq_val\": 500000}");
ss_pattern_remove(workspace); /* detach the generator */
An option the script does not have is refused before anything is sent:
UnknownOption in Python (a subclass of KeyError), NodeJS and Rust, with the
list of options the script takes.
stop_generation​
Generation does not end with the capture: the device keeps generating after the
acquisition is done. It stops when the pattern ends, when the next capture
starts, or when you call stop_generation.
- Python
- NodeJS
- Rust
- C
workspace.pattern.stop_generation()
workspace.pattern.stop_generation();
workspace.pattern().stop_generation()?;
ss_pattern_stop(workspace);
duration​
How long the attached generator's pattern lasts, loops included. The server
runs the script against a counter, so nothing is generated. Use it to size the
capture's samples so the acquisition covers the whole pattern.
The result has three fields:
| Field | Meaning |
|---|---|
seconds | Duration of every chunk the script starts, loops included. |
endless | A chunk never ends by itself: an endless loop, or a pause. |
over_limit | Counting stopped at limit_seconds: the pattern lasts at least seconds. |
- Python
- NodeJS
- Rust
- C
d = workspace.pattern.duration(limit_seconds=10.0)
# {'seconds': 0.0002, 'endless': False, 'over_limit': False}
if not d["endless"] and not d["over_limit"]:
samples = int(d["seconds"] * sample_rate) + 1
const d = await workspace.pattern.duration(10.0);
// { seconds: 0.0002, endless: false, over_limit: false }
// PatternDuration { seconds, endless, over_limit }
let d = workspace.pattern().duration(Some(10.0)).await?;
/* JSON {"seconds", "endless", "over_limit"}. A negative limit leaves the
server's default. */
char *d = ss_pattern_duration(workspace, 10.0);
if (d) { printf("%s\n", d); ss_string_free(d); }
Without a limit, the server's default applies. A script that cannot be counted
raises ScriptError.
Generating while capturing​
On real hardware the generator runs alongside the acquisition, and a script can produce its pattern in chunks as the capture goes.
The capture is only armed once the first chunk of the pattern has been loaded
and started, so that the stimulus cannot arrive after the trigger. If the script
generates nothing at all, the acquisition is refused with a Refused error.
The generator script's configuration is checked again when the capture starts.
If the script refuses it, the capture is refused with Refused, and the
refusal's subject is pattern_generator.
The full working pattern is on Stimulus and capture together.