Skip to main content

Stimulus and capture together

The pattern generator drives the device's outputs from a script, so the instrument stimulates the board as well as watching it. Send a known frame, capture the answer, decode both, all in one workspace.

The generator is a JavaScript file in the server's script library, one implementing on_pattern_generate. Writing one is covered by the pattern generator scripting docs; this page is about driving it from outside.

Only the SP1000G series can generate

Pattern generation is an SP1000G-series feature (SP1018G, SP1036G, SP1054G). The examples on this page use sp1018, the SP1018G demo device; its channels are group-prefixed (G1 PWM, G1 I2C SCL). preview() runs on any device because it is interpreted by the server. See Pattern generator.

Preview first, hardware second​

preview() runs the generator script through an interpreter instead of the device, and the result replaces the workspace data as an acquisition would. The script runs unchanged, so the preview shows the real program. Develop against the preview with no hardware attached, then run the same configuration for real.

from ikalogic_scanastudio import ScanaStudio

GENERATOR = "pwm.js"

with ScanaStudio.connect() as server:
workspace = server.create("sp1018")

# What does this generator take? Ask it.
for option in workspace.pattern.options(GENERATOR):
print(option)

workspace.pattern.add(GENERATOR, {
"channel": workspace.channel("G1 PWM"),
"simple_freq_val": 1_000_000, # 1 MHz
"simple_pwm_val": 25, # 25 % duty
"nb_of_cycles": "100",
})
print(workspace.pattern.current)

# Interpret it, without touching any hardware.
workspace.pattern.preview(timeout=120.0)

# The preview landed in the workspace exactly as a capture would.
rate = workspace.capture.sample_rate
edges = list(workspace.data.transitions(channel=workspace.channel("G1 PWM")))
print(f"preview: {workspace.capture.last_sample} samples "
f"at {rate} S/s, {len(edges)} edges")

workspace.close()

Example output. On the sp1018 demo device:

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'
...
nb_of_cycles: Number of cycles (0 = infinite loop) (text_input) = '1000'

instance_id=1 file_name='pwm.js' name='PWM Builder on CH 8' enabled=True pausable=False paused=False

preview: 6000 samples at 1000000000 S/s, 13 edges

Those 13 edges are the program you asked for: measuring the first full cycle gives 1.000 MHz at 24.8 % duty, against the 1 MHz and 25 % requested.

The generators that ship

A stock library carries pwm.js, frequency_modulation.js, i2c_scanner.js, rgb_led.js, csv_import.js and sp1000-autotest.js. List what your server has with server.scripts.generators() after creating a workspace (see the note on listing scripts).

Preview overwrites the displayed capture

The preview replaces whatever the workspace held. Save anything you still need first.

Stimulate and capture, for real​

On hardware, the generator runs alongside the acquisition. set_generate_on_trigger decides when the pattern plays.

from ikalogic_scanastudio import ScanaStudio, Trigger

with ScanaStudio.connect() as server:
workspace = server.create("hw:SP1018G-000123")

workspace.pattern.add("i2c_scanner.js", {
"ch_scl": workspace.channel("G1 I2C SCL"),
"ch_sda": workspace.channel("G1 I2C SDA"),
})
workspace.pattern.set_enabled(True)

# Play the pattern when the acquisition triggers, not when it starts.
workspace.pattern.set_generate_on_trigger(True)

# Arm on SDA going low — the start condition the scanner will drive.
workspace.capture.run(
samples=2_000_000,
sample_rate=25_000_000,
trigger=Trigger.falling(channel=workspace.channel("G1 I2C SDA"), position=0.05),
timeout=60.0,
)

# Decode what actually went out, and what answered.
i2c = workspace.decoders.add("i2c.js", {
"ch_scl": workspace.channel("G1 I2C SCL"),
"ch_sda": workspace.channel("G1 I2C SDA"),
}, wait=True)

packets = list(i2c.packets())
probed = [p for p in packets if p.title == "Address"]
unanswered = sum(1 for p in packets if p.title == "Addr Nack")
print(f"probed {len(probed)} addresses, {unanswered} went unanswered")
for packet in probed[:3]:
print(" ", packet.content)

workspace.close()

Example output. On the sp1018 demo device, with preview() in place of a real acquisition:

preview: 33861632 samples at 1000000000 S/s

probed 112 addresses, 112 went unanswered
Write to 0x08
Write to 0x09
Write to 0x0a

112 is the whole 7-bit address space minus the reserved ranges, which is what skip_addresses asks for. Every probe goes unanswered because nothing is on the far end of a demo device. On a real bus, the addresses that acknowledge come back without a matching Addr Nack, and those are your devices.

Demo output is deterministic here

Elsewhere a demo device generates pseudo-random signal, so a decoder finds structure by chance. Here the stimulus is a program you wrote, so the decoded result is real protocol traffic: START, Address, Addr Nack, STOP, 112 times over. This is the one setup where you can assert on exact packet content with no hardware attached.

Notes for your own bench​

One generator per workspace. The device has a single pattern generator, so a workspace has one too. pattern.add() replaces whatever was there; there is no list and no instance id, only pattern.current.

Decide when the pattern plays. set_generate_on_trigger(True) starts the stimulus on the acquisition's trigger. False starts it as soon as the acquisition does. For a request/response test the first is usually what you want: arm on something, then speak.

The generator configures its own pins. Pin directions, pulls, voltages and idle states for the generated channels come from the generator. Do not set the same pins in capture.start().

Develop on the preview. It needs no board and runs the real script. Get the program right there, then change the device key.

The acquisition waits for the first chunk

On real hardware 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.