Skip to main content

Decode a bus and export it

Capture a bus, run a protocol decoder over it, look at what came out, and write it to a CSV file for a spreadsheet, pandas or a report.

The program​

import sys
from collections import Counter

from ikalogic_scanastudio import ScanaStudio, Trigger

SCRIPT = "i2c.js"


def main(device: str = "se254") -> int:
with ScanaStudio.connect() as server:
print(server.info)
for available in server.devices():
print(" ", available)

workspace = server.create(device)
print(f"\nopened {workspace.device_name} "
f"with {len(workspace.channels)} channels")

last = workspace.capture.run(
samples=1_000_000,
sample_rate=25_000_000,
trigger=Trigger.rising(channel=0, position=0.1),
)
seconds = workspace.capture.seconds(last)
print(f"captured {last} samples ({seconds:.3f} s) "
f"at {workspace.capture.sample_rate} S/s")

if server.scripts.find(SCRIPT) is None:
print(f"\n{SCRIPT} is not installed; skipping the decode")
return 0

# What does this script take? Ask it.
print(f"\n{SCRIPT} takes:")
for option in workspace.decoders.options(SCRIPT):
print(" ", option)

# Channels by name, so the same script works on any device that has them.
i2c = workspace.decoders.add(
SCRIPT,
{
"ch_scl": workspace.channel("I2C SCL"),
"ch_sda": workspace.channel("I2C SDA"),
},
wait=True,
)

packets = list(i2c.packets())
print(f"\n{i2c.name} produced {len(packets)} packets:")
for packet in packets[:10]:
at = workspace.capture.seconds(packet.start)
content = f": {packet.content}" if packet.content else ""
print(f" {at:.6f}s {packet.title}{content}")

counts = Counter(p.title for p in packets)
print(" " + ", ".join(f"{t} x{n}" for t, n in counts.most_common()))

# The byte stream behind those packets.
payload = bytes(entry.byte for entry in i2c.hex_bytes())
print(f"\n{len(payload)} bytes decoded; first 16: {payload[:16].hex(' ')}")

workspace.export_csv(
"packets.csv", source="packets", instances=[i2c.instance_id])
print("\nwrote packets.csv (on the server's machine)")

workspace.close()
return 0


if __name__ == "__main__":
raise SystemExit(main(*sys.argv[1:]))

What it prints​

Against the se254 demo device:

ScanaStudio 6.0.13 (1a909ee), protocol 8
se254 — SE254 (demo) (demo)
sp209 — SP209 (demo) (demo)
...

opened SE254 (demo) with 4 channels
captured 1000000 samples (0.040 s) at 25000000 S/s

i2c.js takes:
ch_sda: SDA Channel (ch_selector) = None
ch_scl: SCL Channel (ch_selector) = None
AUTO0: Advanced options (tab) = False
address_opt [Advanced options]: Address convention (combo) one of ['7 bit address', '8 bit address (inlcuding R/W flag)'] = '7 bit address'
...

I2C produced 133 packets:
0.000006s I2C: CH4
0.000006s START
0.000020s RE-START
0.000032s STOP
I2C x38, START x38, STOP x38, RE-START x19

0 bytes decoded; first 16:

wrote packets.csv (on the server's machine)

0 bytes decoded is the demo device showing through: the decoder found framing in the noise but no data bytes, so the hex view is empty. On real bus traffic that line reports the payload.

And packets.csv on the server looks like:

Time,I2C,DATA
0.000005600,I2C,CH4
0.000005600,START,
0.000020400,RE-START,

Notes for your own bench​

Ask the script what it takes. decoders.options(script) prints every setting with its default. Do not guess option names from another decoder: a UART script wants baud, an SPI script wants a chip-select channel, and the names are the script's own.

Pick the right export source. Four are available, and they answer different questions:

sourceUse it for
packetsThe protocol view: addresses, commands, ACKs. Best for a report.
hexThe payload as bytes. Best for firmware and CRC work.
rawEvery element a decoder produced, with its exact span. Best for timing.
samplesThe logic levels themselves. Big, and the only one that needs no decoder.

Remember where the file lands. The server writes it. Pointing a local script at a remote server puts the CSV on the remote machine, not yours.

Filter on the server, not in your loop. packets(filter=...) narrows the read before the data is sent. See Reading captured data.

The workspace freezes while it exports

A CSV export freezes the workspace: every other command, from every other client, is refused with Busy until it finishes. Export at the end of a run, not in the middle of one.

Do not chain exports back to back

A second export_csv issued immediately after the first is currently refused, because the workspace is still frozen, and the Python and NodeJS SDKs return without raising. No file is written and nothing tells you. If you need several files from one capture, pause between the calls and check that each file exists. See Saving and exporting.

Chain decoders

A high-level decoder can sit on top of a low-level one (a sensor protocol over I²C, say). Add both; each one is an instance with its own packets, and packets() without a filter merges them in time order.