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.
Pattern generation is an SP1000G-series feature (SP1018G, SP1036G, SP1054G). The SE254, SP209 and SP259 families are logic analysers only: they capture, and have no outputs to drive.
The examples on this page use sp1018, the SP1018G demo device
(hw:SP1018G-<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 the exception: it 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.
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;
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(); // 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); }
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);
The preview replaces whatever the workspace was displaying. Save anything you still need first.
resume​
A generator script can pause itself mid-program. resume lets it continue.
- 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({"baud": 9600})
workspace.pattern.remove()
await workspace.pattern.update({ baud: 9600 }); // 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([("baud", 9600i64.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, "{\"baud\": 9600}");
ss_pattern_remove(workspace); /* detach the generator */
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 full working pattern is on Stimulus and capture together.