Saleae Logic MSO Analysis
This skill enables analysis of captured signals from Saleae Logic MSO devices using the saleae-mso-api Python library. It supports loading binary exports, analyzing signal transitions, and decoding common protocols.
Prerequisites
- •
saleae-mso-apiPython package (pip install saleae-mso-api) - •Binary export files from Saleae Logic software (
.binformat)
Quick Reference
Loading Binary Files
python
from saleae.mso_api.binary_files import read_file
from pathlib import Path
file_path = Path("capture.bin")
saleae_file = read_file(file_path)
# Access metadata
print(f"Version: {saleae_file.version}")
print(f"Type: {saleae_file.type}")
# Access data
contents = saleae_file.contents
Digital Capture Structure
Digital exports contain DigitalExport_V1 with chunks:
python
chunk = saleae_file.contents.chunks[0] # Key attributes: chunk.initial_state # Starting logic level (0 or 1) chunk.transition_times # numpy array of transition timestamps (seconds) chunk.sample_rate # Capture rate in Hz chunk.begin_time # Capture start time chunk.end_time # Capture end time
Calculating Pulse Durations
python
import numpy as np times = np.array(chunk.transition_times) durations_ms = np.diff(times) * 1000 # Convert to milliseconds # If initial_state is 0 (LOW): # - Even indices (0, 2, 4...) = HIGH pulse durations # - Odd indices (1, 3, 5...) = LOW gap durations # If initial_state is 1 (HIGH): # - Even indices = LOW gap durations # - Odd indices = HIGH pulse durations
Helper Scripts
This skill includes helper scripts for common analysis tasks:
Protocol Analyzer
bash
# Analyze signal characteristics python3 skills/logicmso/analyze_protocol.py capture.bin # Show detailed timing histogram python3 skills/logicmso/analyze_protocol.py capture.bin --histogram # Show detected timing clusters python3 skills/logicmso/analyze_protocol.py capture.bin --clusters # Export transitions to CSV python3 skills/logicmso/analyze_protocol.py capture.bin --export transitions.csv # Show raw transition values python3 skills/logicmso/analyze_protocol.py capture.bin --raw -n 50
Common Protocol Patterns
UART (Asynchronous Serial)
- •Idle state: HIGH
- •Start bit: LOW (1 bit period)
- •Data bits: 8 bits, LSB first
- •Stop bit: HIGH (1-2 bit periods)
- •Common baud rates: 9600, 19200, 38400, 57600, 115200
- •Bit period calculation:
1/baud_rateseconds - •Identifying features: Consistent bit periods, durations are multiples of base period
SPI (Serial Peripheral Interface)
- •4 signals: SCLK (clock), MOSI (master out), MISO (master in), CS (chip select)
- •Clock polarity (CPOL): Idle clock state (0=LOW, 1=HIGH)
- •Clock phase (CPHA): Sample edge (0=leading, 1=trailing)
- •Data: Sampled on clock edges, typically 8 bits per transaction
- •Identifying features: Regular clock signal, CS goes LOW during transaction
I2C (Inter-Integrated Circuit)
- •2 signals: SDA (data), SCL (clock)
- •Idle state: Both HIGH (pulled up)
- •Start condition: SDA falls while SCL is HIGH
- •Stop condition: SDA rises while SCL is HIGH
- •Data: 8 bits + ACK/NACK, MSB first
- •Address: 7-bit (first byte after START)
- •Identifying features: START/STOP conditions, 9 clock pulses per byte (8 data + ACK)
1-Wire
- •Single signal: DQ (data/power)
- •Idle state: HIGH (pulled up)
- •Reset pulse: Master pulls LOW for 480us minimum
- •Presence pulse: Slave responds LOW for 60-240us
- •Write 0: LOW for 60-120us
- •Write 1: LOW for 1-15us, then release
- •Read: Master samples 15us after pulling LOW
Analysis Workflow
Step 1: Initial Exploration
python
from saleae.mso_api.binary_files import read_file
import numpy as np
f = read_file("capture.bin")
chunk = f.contents.chunks[0]
print(f"Sample rate: {chunk.sample_rate/1e6:.1f} MHz")
print(f"Duration: {chunk.end_time - chunk.begin_time:.3f}s")
print(f"Initial state: {'HIGH' if chunk.initial_state else 'LOW'}")
print(f"Transitions: {len(chunk.transition_times)}")
Step 2: Analyze Timing Patterns
python
times = np.array(chunk.transition_times)
durations_us = np.diff(times) * 1e6 # microseconds
# Separate HIGH and LOW durations
high_idx = 0 if chunk.initial_state == 0 else 1
high_durations = durations_us[high_idx::2]
low_durations = durations_us[(1-high_idx)::2]
print(f"HIGH pulses: min={min(high_durations):.1f}us, max={max(high_durations):.1f}us")
print(f"LOW gaps: min={min(low_durations):.1f}us, max={max(low_durations):.1f}us")
# Find unique timing values (cluster detection)
unique_high = sorted(set(round(d, -1) for d in high_durations)) # Round to 10us
unique_low = sorted(set(round(d, -1) for d in low_durations))
print(f"HIGH clusters: {unique_high}")
print(f"LOW clusters: {unique_low}")
Step 3: Identify Protocol
Based on timing patterns:
- •UART: Consistent bit periods, durations are multiples of base period, idles HIGH
- •SPI/I2C: us-scale timing, needs clock signal analysis, look for regular patterns
- •1-Wire: Reset pulses ~480us, data pulses 1-120us
Step 4: Decode
Once protocol is identified, decode based on protocol rules. For unknown/custom protocols, analyze the timing clusters and bit patterns to determine encoding scheme.
UART Decoding Example
python
from saleae.mso_api.binary_files import read_file
import numpy as np
f = read_file("uart_capture.bin")
chunk = f.contents.chunks[0]
times = np.array(chunk.transition_times)
BAUD = 115200
BIT_PERIOD = 1 / BAUD
def decode_uart_byte(start_time, times, bit_period):
"""Decode a single UART byte starting at start_time."""
byte_val = 0
for bit_num in range(8):
# Sample at center of each bit (1.5, 2.5, 3.5... bit periods from start)
sample_time = start_time + (1.5 + bit_num) * bit_period
# Find state at sample_time
idx = np.searchsorted(times, sample_time)
state = (chunk.initial_state + idx) % 2
if state:
byte_val |= (1 << bit_num) # LSB first
return byte_val
# Find start bits (falling edges when idle HIGH)
decoded_bytes = []
i = 0
while i < len(times) - 1:
# Look for falling edge (start bit)
if chunk.initial_state == 1 or i > 0:
byte_val = decode_uart_byte(times[i], times, BIT_PERIOD)
decoded_bytes.append(byte_val)
# Skip to next potential start bit (after stop bit)
i += 1
while i < len(times) and times[i] < times[i-1] + 10 * BIT_PERIOD:
i += 1
else:
i += 1
print("Decoded:", bytes(decoded_bytes))
CTF Tips
- •Unknown protocol: Start with
analyze_protocol.py --clustersto see timing distribution - •Multiple channels: Export each channel separately, identify clock vs data lines
- •Inverted signals: Some captures have inverted logic levels
- •Timing variations: Real hardware has jitter, use threshold-based detection
- •Partial captures: Check if capture starts mid-transmission
- •Custom protocols: Look for repeating patterns, identify sync/framing bytes
Troubleshooting
"No module named 'saleae.mso_api'"
bash
pip install saleae-mso-api
Empty or corrupt file
Check file size and try re-exporting from Saleae Logic software.
No transitions detected
- •Signal may be constant (stuck high/low)
- •Check if correct channel was exported
- •Verify trigger settings in original capture
Timing seems wrong
- •Check sample rate matches original capture settings
- •Verify time units (seconds vs milliseconds vs microseconds)