Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit 12b1f2d

Browse files
committed
sigrok: improve documentation
1 parent c8647c4 commit 12b1f2d

4 files changed

Lines changed: 214 additions & 56 deletions

File tree

Lines changed: 163 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
# Sigrok Driver
22

3-
`jumpstarter-driver-sigrok` wraps `sigrok-cli` to provide logic analyzer and oscilloscope capture from Jumpstarter exporters. It supports:
4-
- **Logic analyzers** (digital channels) - with protocol decoding (SPI, I2C, UART, etc.)
3+
`jumpstarter-driver-sigrok` wraps [sigrok-cli](https://sigrok.org/wiki/Sigrok-cli) to provide logic analyzer and oscilloscope capture from Jumpstarter exporters. It supports:
4+
- **Logic analyzers** (digital channels)
55
- **Oscilloscopes** (analog channels) - voltage waveform capture
66
- One-shot and streaming capture
7-
- Decoder-friendly channel mappings
8-
- Real-time protocol decoding
7+
- Multiple output formats with parsing (VCD, CSV, Bits, ASCII)
98

109
## Installation
1110

@@ -19,44 +18,34 @@ pip3 install --extra-index-url https://pkg.jumpstarter.dev/simple/ jumpstarter-d
1918
export:
2019
sigrok:
2120
type: jumpstarter_driver_sigrok.driver.Sigrok
22-
driver: demo # sigrok driver (demo, fx2lafw, etc.)
23-
conn: null # optional: USB VID.PID or serial path
24-
executable: null # optional: path to sigrok-cli (auto-detected)
25-
channels: # channel mappings (device_name: semantic_name)
26-
D0: vcc
27-
D1: cs
21+
driver: fx2lafw # sigrok driver (demo, fx2lafw, rigol-ds, etc.)
22+
conn: null # optional: USB VID.PID, serial path, or null for auto
23+
channels: # optional: map device channels to friendly names
24+
D0: clk
25+
D1: mosi
2826
D2: miso
29-
D3: mosi
30-
D4: clk
31-
D5: sda
32-
D6: scl
27+
D3: cs
3328
```
3429
35-
## CaptureConfig (client-side)
30+
### Configuration Parameters
3631
37-
```python
38-
from jumpstarter_driver_sigrok.common import CaptureConfig, DecoderConfig
32+
| Parameter | Description | Type | Required | Default |
33+
|-----------|-------------|------|----------|---------|
34+
| `driver` | Sigrok driver name (e.g., `demo`, `fx2lafw`, `rigol-ds`) | str | yes | - |
35+
| `conn` | Connection string (USB VID.PID, serial path, or `null` for auto-detect) | str \| None | no | None |
36+
| `executable` | Path to `sigrok-cli` executable | str | no | Auto-detected from PATH |
37+
| `channels` | Channel mapping from device names (D0, A0) to semantic names (clk, voltage) | dict[str, str] | no | {} (empty) |
3938

40-
config = CaptureConfig(
41-
sample_rate="8MHz",
42-
samples=20000,
43-
pretrigger=5000,
44-
triggers={"cs": "falling"},
45-
decoders=[
46-
DecoderConfig(
47-
name="spi",
48-
channels={"clk": "clk", "mosi": "mosi", "miso": "miso", "cs": "cs"},
49-
annotations=["mosi-data"],
50-
)
51-
],
52-
)
53-
```
39+
## CaptureConfig Parameters (client-side)
5440

55-
This maps to:
56-
```bash
57-
sigrok-cli -d fx2lafw -c samplerate=8MHz,samples=20000,pretrigger=5000 --triggers D1=falling \
58-
-P spi:clk=D4:mosi=D3:miso=D2:cs=D1 -A spi=mosi-data
59-
```
41+
| Parameter | Description | Type | Required | Default |
42+
|-----------|-------------|------|----------|---------|
43+
| `sample_rate` | Sampling rate (e.g., `"1M"`, `"8MHz"`, `"24000000"`) | str | no | "1M" |
44+
| `samples` | Number of samples to capture (`None` for continuous) | int \| None | no | None |
45+
| `pretrigger` | Number of samples to capture before trigger | int \| None | no | None |
46+
| `triggers` | Trigger conditions by channel name (e.g., `{"cs": "falling"}`) | dict[str, str] \| None | no | None |
47+
| `channels` | List of channel names to capture (overrides defaults) | list[str] \| None | no | None |
48+
| `output_format` | Output format (vcd, csv, bits, ascii, srzip, binary) | str | no | "vcd" |
6049

6150
## Client API
6251

@@ -67,23 +56,107 @@ sigrok-cli -d fx2lafw -c samplerate=8MHz,samples=20000,pretrigger=5000 --trigger
6756
- `get_channel_map()` — device-to-semantic name mappings
6857
- `list_output_formats()` — supported formats (csv, srzip, vcd, binary, bits, ascii)
6958

59+
## Output Formats
60+
61+
The driver supports multiple output formats. **VCD (Value Change Dump) is the default** because:
62+
- ✅ **Efficient**: Only records signal changes (not every sample)
63+
- ✅ **Precise timing**: Includes exact timestamps in nanoseconds
64+
- ✅ **Widely supported**: Standard format for signal analysis tools
65+
- ✅ **Mixed signals**: Handles both digital and analog data
66+
67+
### Available Formats
68+
69+
| Format | Use Case | Decoded By |
70+
|--------|----------|------------|
71+
| `vcd` (default) | Change-based signals with timing | `result.decode()` → `list[Sample]` |
72+
| `csv` | All samples with timing | `result.decode()` → `list[Sample]` |
73+
| `bits` | Bit sequences by channel | `result.decode()` → `dict[str, list[int]]` |
74+
| `ascii` | ASCII art visualization | `result.decode()` → `str` |
75+
| `srzip` | Raw sigrok session (for PulseView) | `result.data` (raw bytes) |
76+
| `binary` | Raw binary data | `result.data` (raw bytes) |
77+
78+
### Output Format Constants
79+
80+
```python
81+
from jumpstarter_driver_sigrok.common import OutputFormat
82+
83+
config = CaptureConfig(
84+
sample_rate="1MHz",
85+
samples=1000,
86+
output_format=OutputFormat.VCD, # or CSV, BITS, ASCII, SRZIP, BINARY
87+
)
88+
```
89+
7090
## Examples
7191

72-
### Logic Analyzer (Digital Channels)
92+
### Example 1: Simple Capture (VCD format - default)
93+
94+
**Python client code:**
95+
```python
96+
from jumpstarter_driver_sigrok.common import CaptureConfig
97+
98+
# Capture with default VCD format (efficient, change-based with timing)
99+
config = CaptureConfig(
100+
sample_rate="1MHz",
101+
samples=1000,
102+
channels=["D0", "D1", "D2"], # Use device channel names or mapped names
103+
)
104+
result = client.capture(config)
73105
74-
One-shot with trigger:
106+
# Decode VCD to get samples with timing
107+
samples = result.decode() # list[Sample]
108+
for sample in samples[:5]:
109+
print(f"Time: {sample.time_ns}ns, Values: {sample.values}")
110+
```
111+
112+
**Equivalent sigrok-cli command:**
75113
```bash
76-
sigrok-cli -d fx2lafw -c samplerate=8MHz,samples=20000,pretrigger=5000 --triggers D0=rising -o out.sr
114+
sigrok-cli -d fx2lafw -C D0,D1,D2 \
115+
-c samplerate=1MHz --samples 1000 \
116+
-O vcd -o /tmp/capture.vcd
77117
```
78118

79-
Real-time decode (SPI):
119+
---
120+
121+
### Example 2: Triggered Capture with Pretrigger
122+
123+
**Python client code:**
124+
```python
125+
from jumpstarter_driver_sigrok.common import CaptureConfig
126+
127+
# Capture with trigger and pretrigger buffer (VCD format - default)
128+
config = CaptureConfig(
129+
sample_rate="8MHz",
130+
samples=20000,
131+
pretrigger=5000, # Capture 5000 samples before trigger
132+
triggers={"D0": "rising"}, # Trigger on D0 rising edge
133+
channels=["D0", "D1", "D2", "D3"],
134+
# output_format defaults to VCD (efficient change-based format)
135+
)
136+
result = client.capture(config)
137+
138+
# Decode to analyze signal changes with precise timing
139+
samples = result.decode() # list[Sample] - only changes recorded
140+
print(f"Captured {len(samples)} signal changes")
141+
142+
# Access timing and values
143+
for sample in samples[:3]:
144+
print(f"Time: {sample.time_ns}ns, Changed: {sample.values}")
145+
```
146+
147+
**Equivalent sigrok-cli command:**
80148
```bash
81-
sigrok-cli -d fx2lafw -c samplerate=1M --continuous \
82-
-P spi:clk=D4:mosi=D3:miso=D2:cs=D1 -A spi=mosi-data
149+
sigrok-cli -d fx2lafw -C D0,D1,D2,D3 \
150+
-c samplerate=8MHz,samples=20000,pretrigger=5000 \
151+
--triggers D0=rising \
152+
-O vcd -o /tmp/capture.vcd
83153
```
84154

85-
### Oscilloscope (Analog Channels)
155+
---
86156

157+
### Example 3: Oscilloscope (Analog Channels)
158+
159+
**Exporter configuration:**
87160
```yaml
88161
export:
89162
oscilloscope:
@@ -95,16 +168,60 @@ export:
95168
A1: CH2
96169
```
97170

171+
**Python client code:**
98172
```python
99-
from jumpstarter_driver_sigrok.common import CaptureConfig
173+
from jumpstarter_driver_sigrok.common import CaptureConfig, OutputFormat
100174
101175
# Capture analog waveforms
102176
config = CaptureConfig(
103177
sample_rate="1MHz",
104178
samples=10000,
105179
channels=["CH1", "CH2"], # Analog channels
106-
output_format="csv", # or "vcd" for waveform viewers
180+
output_format=OutputFormat.CSV, # CSV for voltage values
107181
)
108182
result = client.capture(config)
109-
waveform_data = result.data # bytes with voltage measurements
183+
184+
# Parse voltage data
185+
samples = result.decode() # list[Sample]
186+
for sample in samples[:5]:
187+
print(f"Time: {sample.time_ns}ns")
188+
print(f" CH1: {sample.values.get('A0', 'N/A')}V")
189+
print(f" CH2: {sample.values.get('A1', 'N/A')}V")
190+
```
191+
192+
**Equivalent sigrok-cli command:**
193+
```bash
194+
sigrok-cli -d rigol-ds:conn=usb -C A0=CH1,A1=CH2 \
195+
-c samplerate=1MHz --samples 10000 \
196+
-O csv -o /tmp/capture.csv
197+
```
198+
199+
---
200+
201+
### Example 4: Bits Format (Simple Bit Sequences)
202+
203+
**Python client code:**
204+
```python
205+
from jumpstarter_driver_sigrok.common import CaptureConfig, OutputFormat
206+
207+
# Capture in bits format (useful for visual inspection)
208+
config = CaptureConfig(
209+
sample_rate="100kHz",
210+
samples=100,
211+
channels=["D0", "D1", "D2"],
212+
output_format=OutputFormat.BITS,
213+
)
214+
result = client.capture(config)
215+
216+
# Get bit sequences per channel
217+
bits_by_channel = result.decode() # dict[str, list[int]]
218+
for channel, bits in bits_by_channel.items():
219+
print(f"{channel}: {''.join(map(str, bits[:20]))}") # First 20 bits
220+
```
221+
222+
**Equivalent sigrok-cli command:**
223+
```bash
224+
sigrok-cli -d demo -C D0,D1,D2 \
225+
-c samplerate=100kHz --samples 100 \
226+
-O bits -o /tmp/capture.bits
110227
```

packages/jumpstarter-driver-sigrok/jumpstarter_driver_sigrok/common.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ class CaptureConfig(BaseModel):
4343
triggers: dict[str, str] | None = Field(default=None, description="e.g., {'D0': 'rising'}")
4444
channels: list[str] | None = Field(default=None, description="override default channels by name")
4545
output_format: str = Field(
46-
default="srzip",
47-
description="csv, srzip, vcd, binary, bits, ascii",
46+
default=OutputFormat.VCD,
47+
description="Output format (default: vcd - efficient change-based format with timing). "
48+
"Options: vcd, csv, srzip, binary, bits, ascii",
4849
)
4950
decoders: list[DecoderConfig] | None = Field(default=None, description="real-time protocol decoding")
5051

packages/jumpstarter-driver-sigrok/jumpstarter_driver_sigrok/driver.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,14 @@ def find_sigrok_cli() -> str:
1919
return executable
2020

2121

22-
def _default_channel_map() -> dict[str, str]:
23-
# Decoder-friendly default names for demo driver
24-
# Maps device channel name -> semantic name
25-
return {"D0": "vcc", "D1": "cs", "D2": "miso", "D3": "mosi", "D4": "clk", "D5": "sda", "D6": "scl"}
26-
27-
2822
@dataclass(kw_only=True)
2923
class Sigrok(Driver):
3024
"""Sigrok driver wrapping sigrok-cli for logic analyzer and oscilloscope support."""
3125

3226
driver: str = "demo"
3327
conn: str | None = None
3428
executable: str = field(default_factory=find_sigrok_cli)
35-
channels: dict[str, str] = field(default_factory=_default_channel_map)
29+
channels: dict[str, str] = field(default_factory=dict)
3630

3731
def __post_init__(self):
3832
if hasattr(super(), "__post_init__"):

packages/jumpstarter-driver-sigrok/jumpstarter_driver_sigrok/driver_test.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from .common import CaptureConfig, CaptureResult
5+
from .common import CaptureConfig, CaptureResult, OutputFormat
66
from .driver import Sigrok
77
from jumpstarter.common.utils import serve
88

@@ -68,6 +68,43 @@ def test_capture_with_demo_driver(demo_client):
6868
assert len(result.channel_map) > 0
6969

7070

71+
@pytest.mark.skipif(which("sigrok-cli") is None, reason="sigrok-cli not installed")
72+
def test_capture_default_format(demo_client):
73+
"""Test capture with default output format (VCD).
74+
75+
VCD is the default because it's the most efficient format:
76+
- Only records changes (not every sample)
77+
- Includes precise timing information
78+
- Widely supported by signal analysis tools
79+
"""
80+
# Don't specify output_format - should default to VCD
81+
cfg = CaptureConfig(
82+
sample_rate="100kHz",
83+
samples=50,
84+
channels=["D0", "D1", "D2"],
85+
)
86+
87+
result = demo_client.capture(cfg)
88+
89+
# Verify we got VCD format by default
90+
assert isinstance(result, CaptureResult)
91+
assert result.output_format == OutputFormat.VCD
92+
assert isinstance(result.data, bytes)
93+
assert len(result.data) > 0
94+
95+
# Verify VCD data can be decoded
96+
samples = result.decode()
97+
assert isinstance(samples, list)
98+
assert len(samples) > 0
99+
100+
# Verify samples have timing information (VCD feature)
101+
for sample in samples:
102+
assert hasattr(sample, "time_ns")
103+
assert isinstance(sample.time_ns, int)
104+
assert hasattr(sample, "values")
105+
assert isinstance(sample.values, dict)
106+
107+
71108
@pytest.mark.skipif(which("sigrok-cli") is None, reason="sigrok-cli not installed")
72109
def test_capture_csv_format(demo_client):
73110
"""Test capture with CSV output format via client."""
@@ -297,6 +334,7 @@ def test_decode_bits_format(demo_client):
297334
Verifies:
298335
- Bits format decoding works
299336
- Returns dict with bit sequences
337+
- Channel names are mapped from device names (D0) to user-friendly names (vcc)
300338
"""
301339
from .common import OutputFormat
302340

@@ -314,11 +352,19 @@ def test_decode_bits_format(demo_client):
314352
assert isinstance(decoded, dict)
315353
assert len(decoded) > 0
316354

355+
# Should have user-friendly channel names (vcc, cs, miso) from channel_map
356+
# Not generic names like CH0, CH1
357+
assert "vcc" in decoded or "D0" in decoded
358+
assert "cs" in decoded or "D1" in decoded
359+
assert "miso" in decoded or "D2" in decoded
360+
317361
# Each channel should have a list of bits
318362
for channel, bits in decoded.items():
319363
assert isinstance(channel, str)
320364
assert isinstance(bits, list)
321365
assert all(b in [0, 1] for b in bits)
366+
# Should have bits (at least some, exact count may vary with demo driver timing)
367+
assert len(bits) > 0
322368

323369

324370
@pytest.mark.skipif(which("sigrok-cli") is None, reason="sigrok-cli not installed")

0 commit comments

Comments
 (0)