remove bdshot_timing_analyzer_spec.md

This commit is contained in:
Jacob Dahl 2026-01-13 15:45:35 -09:00
parent 597611e7a5
commit b5e9457ba2

View File

@ -1,367 +0,0 @@
# BDShot Timing Jitter Analyzer - Technical Specification
## Overview
Build a Python command-line tool to analyze BDShot (Bidirectional DShot) signal timing from Saleae Logic Analyzer CSV exports. The tool measures scheduling consistency and jitter in PX4 flight controller DShot output timing.
## Background
### DShot Protocol Basics
DShot is a digital protocol for communication between flight controllers (FC) and ESCs (Electronic Speed Controllers). Each frame consists of 16 bits encoding an 11-bit throttle value, 1 telemetry request bit, and 4-bit CRC.
**DShot Variants and Timing:**
| Variant | Bit Period | Frame Duration (16 bits) |
|-----------|------------|--------------------------|
| DShot150 | 6.67µs | ~107µs |
| DShot300 | 3.33µs | ~53µs |
| DShot600 | 1.67µs | ~27µs |
| DShot1200 | 0.83µs | ~13µs |
Each bit is encoded as a fixed-length pulse where:
- Logic 1: ~75% duty cycle (high for 3/4 of bit period)
- Logic 0: ~37.5% duty cycle (high for 3/8 of bit period)
### BDShot (Bidirectional DShot)
BDShot extends DShot to receive telemetry data (eRPM) back from the ESC on the same signal wire. The ESC responds after each command frame with a 21-bit GCR (Group Code Recording) encoded response.
**BDShot Transaction Structure:**
```
Command Frame (FC→ESC) Response Frame (ESC→FC)
┌─────────────────────┐ ┌──────────────────────────┐
IDLE ────┤ 16 bits DShot ├─────┤ 21 bits GCR telemetry ├──── IDLE
HIGH │ (FC drives line) │ TT │ (ESC drives line) │ HIGH
└─────────────────────┘ └──────────────────────────┘
^
Turnaround time
(line direction switch)
```
**Approximate BDShot300 Timing:**
- Command frame: ~53µs (16 bits × 3.33µs)
- Turnaround gap: ~25-30µs (varies by ESC)
- Response frame: ~50-70µs (21 GCR bits)
- Total transaction: ~130-150µs
The idle state is HIGH (pulled up). Frames begin with a falling edge and end when the line returns to idle HIGH.
## Hardware Setup Context
### PX4 Timer Architecture
The user is running PX4 firmware on a flight controller with the following DShot output configuration:
- **Timer 1:** Drives motor outputs 1-4
- **Timer 2:** Drives motor outputs 5-8
- **DMA Strategy:** Single DMA peripheral handles both timers sequentially
- First: Burst capture for Timer 1 (all 4 channels)
- Then: Burst capture for Timer 2 (all 4 channels)
This sequential triggering means there should be a consistent gap between when Timer 1's transaction completes and Timer 2's transaction begins.
### Capture Setup
- **Saleae Logic Analyzer:** Capturing two BDShot channels
- **Channel A:** Motor output 1 (Timer 1)
- **Channel B:** Motor output 5 (Timer 2)
- **Sample Rate:** Recommend 24+ MS/s for DShot300
- **Capture Duration:** 1-2 minutes typical
- **PX4 Loop Rate:** 800 Hz (1250µs period)
- **DShot Variant:** DShot300
## Analysis Requirements
### Measurements Needed
#### 1. Per-Channel Frame Interval Statistics
For each channel independently, measure the time between consecutive BDShot transactions (command start to next command start).
```
CH1: ──┬═══TRANSACTION═══┬────────────────────────┬═══TRANSACTION═══┬──
│ │ │ │
t1 t2
Frame Interval = t2 - t1 (expected: 1250µs at 800Hz loop rate)
```
**Statistics to compute:**
- Minimum interval
- Maximum interval
- Mean interval
- Standard deviation
- Jitter (deviation from expected interval)
#### 2. Inter-Channel Gap Statistics
Measure the time from Channel A (CH1) transaction END to Channel B (CH5) transaction START. This quantifies the sequential timer scheduling overhead.
```
CH1: ──┬═══TRANSACTION═══┬─────────────────────────────────────────────
│ │
│ └─ CH1 Response End (t_ch1_end)
CH5: ──┴─────────────────────┬═══TRANSACTION═══┬───────────────────────
└─ CH5 Command Start (t_ch5_start)
Inter-Channel Gap = t_ch5_start - t_ch1_end
```
**Statistics to compute:**
- Minimum gap
- Maximum gap
- Mean gap
- Standard deviation
#### 3. Visualization
Generate timing histograms for:
- Channel A frame intervals
- Channel B frame intervals
- Inter-channel gaps
## Input Format
### Saleae Logic 2 CSV Export
The tool should accept CSV exports from Saleae Logic 2's "Export Raw Data" feature. The format for digital channels is edge-based (only transitions are recorded):
**Single Channel Export:**
```csv
Time [s],Channel 0
0.000000000,1
0.000023456,0
0.000025890,1
...
```
**Multi-Channel Export:**
```csv
Time [s],Channel 0,Channel 1
0.000000000,1,1
0.000023456,0,1
0.000025890,1,1
0.000025900,1,0
...
```
**Key characteristics:**
- Timestamps in seconds (floating point, ~9 decimal places)
- Only rows where at least one channel changed state
- Channel values: 1 = HIGH, 0 = LOW
- Idle state for BDShot is HIGH (1)
## Implementation Specification
### Command-Line Interface
```bash
# Basic usage
python bdshot_analyzer.py capture.csv
# With options
python bdshot_analyzer.py capture.csv \
--dshot-rate 300 \
--loop-rate 800 \
--channel-a 0 \
--channel-b 1 \
--output-dir ./results
```
**Arguments:**
| Argument | Default | Description |
|----------|---------|-------------|
| `input_file` | required | Path to Saleae CSV export |
| `--dshot-rate` | 300 | DShot variant (150/300/600/1200) |
| `--loop-rate` | 800 | Expected loop rate in Hz |
| `--channel-a` | 0 | CSV column index for first channel |
| `--channel-b` | 1 | CSV column index for second channel |
| `--output-dir` | `./` | Directory for output files |
| `--no-plots` | false | Disable histogram generation |
### Algorithm
#### Step 1: Parse CSV and Extract Edges
Read the CSV and build edge lists for each channel:
```python
edges_a = [(timestamp, new_state), ...] # Channel A edges
edges_b = [(timestamp, new_state), ...] # Channel B edges
```
#### Step 2: Detect BDShot Transactions
A BDShot transaction consists of a command frame followed by a response frame. We can detect transaction boundaries by looking for idle gaps.
**Transaction Detection Logic:**
1. Find all falling edges (HIGH→LOW transitions) - these are potential frame starts
2. Find all rising edges (LOW→HIGH transitions) - these are potential frame ends
3. Group edges into transactions:
- A transaction STARTS with a falling edge after a long idle period (> threshold)
- A transaction ENDS when the line returns HIGH and stays HIGH for > threshold
**Idle Gap Threshold:**
For DShot300, bit period is 3.33µs. Use a gap threshold of ~20µs to detect transaction boundaries (much longer than any inter-bit gap but shorter than inter-frame idle).
```python
@dataclass
class BDShotTransaction:
command_start: float # Timestamp of first falling edge
command_end: float # Timestamp when command frame ends (estimate)
response_start: float # Timestamp when response frame begins (estimate)
response_end: float # Timestamp of final rising edge before idle
@property
def start(self) -> float:
return self.command_start
@property
def end(self) -> float:
return self.response_end
```
**Simplified approach:** If precise command/response boundary detection is difficult, we can treat each transaction as:
- `start`: First falling edge after idle
- `end`: Last rising edge before next idle
This is sufficient for the timing measurements we need.
#### Step 3: Compute Per-Channel Statistics
For each channel, compute frame intervals:
```python
intervals_a = [t[i+1].start - t[i].start for i in range(len(transactions_a) - 1)]
```
Calculate statistics:
- `min`, `max`, `mean`, `std` using numpy
- Expected interval = 1_000_000 / loop_rate (in µs)
- Jitter = measured - expected
#### Step 4: Compute Inter-Channel Gap Statistics
Match Channel A transactions with the next Channel B transaction:
```python
gaps = []
for tx_a in transactions_a:
# Find first CH_B transaction that starts after this CH_A ends
tx_b = find_next_transaction(transactions_b, after=tx_a.end)
if tx_b and is_same_cycle(tx_a, tx_b): # within same loop iteration
gaps.append(tx_b.start - tx_a.end)
```
**Cycle Matching Logic:**
Since CH1 and CH5 fire sequentially within the same loop iteration, we need to match them correctly. The gap should be small (tens of µs), not a full loop period. If `tx_b.start - tx_a.end` is close to the expected loop period, we've matched wrong transactions.
#### Step 5: Generate Output
**Console Output:**
```
BDShot Timing Analysis
======================
Input: capture.csv
DShot Rate: 300 (bit period: 3.33µs)
Expected Loop Rate: 800 Hz (1250.00µs interval)
Capture Duration: 120.34s
Transactions Detected: CH_A=96268, CH_B=96268
Channel A (Motor 1) Frame Intervals
───────────────────────────────────
Count: 96267
Min: 1247.32 µs (-2.68 µs from nominal)
Max: 1254.21 µs (+4.21 µs from nominal)
Mean: 1250.01 µs (+0.01 µs from nominal)
Std Dev: 1.43 µs
Channel B (Motor 5) Frame Intervals
───────────────────────────────────
Count: 96267
Min: 1248.01 µs (-1.99 µs from nominal)
Max: 1253.87 µs (+3.87 µs from nominal)
Mean: 1250.02 µs (+0.02 µs from nominal)
Std Dev: 1.21 µs
Inter-Channel Gap (CH_A End → CH_B Start)
─────────────────────────────────────────
Count: 96267
Min: 12.34 µs
Max: 18.76 µs
Mean: 15.23 µs
Std Dev: 0.87 µs
Histograms saved to: ./results/
```
**Histogram Plots:**
Generate three histogram plots using matplotlib:
1. `channel_a_intervals.png` - Channel A frame interval distribution
2. `channel_b_intervals.png` - Channel B frame interval distribution
3. `inter_channel_gaps.png` - Inter-channel gap distribution
**Histogram Requirements:**
- Title indicating what's being measured
- X-axis: Time in microseconds
- Y-axis: Count (frequency)
- Vertical line showing expected/mean value
- Statistics annotation (min/max/mean/std) in legend or text box
- Reasonable bin count (~50-100 bins)
- Save as PNG at 150+ DPI
### Error Handling
- Validate CSV format and provide clear error messages
- Handle edge cases:
- Incomplete transactions at start/end of capture
- Corrupted/noisy signals (transactions with wrong number of edges)
- Missing channel data
- Report number of malformed/skipped transactions
### Dependencies
```
numpy
matplotlib
```
Standard library only otherwise (csv, argparse, dataclasses, pathlib, etc.)
## Example Usage Session
```bash
# Capture 2 minutes of BDShot data in Saleae Logic 2
# Export as CSV: File → Export Raw Data → CSV
# Run analysis
$ python bdshot_analyzer.py ~/captures/bdshot_test.csv --loop-rate 800 --dshot-rate 300
BDShot Timing Analysis
======================
Input: /home/user/captures/bdshot_test.csv
...
# Check generated histograms
$ ls *.png
channel_a_intervals.png
channel_b_intervals.png
inter_channel_gaps.png
```
## Deliverables
1. **`bdshot_analyzer.py`** - Main analysis script
2. **`README.md`** - Usage instructions and examples
## Notes for Implementation
- Use microseconds (µs) for all timing displays (convert from seconds in CSV)
- Handle large files efficiently (100k+ transactions for 2-minute capture)
- The inter-channel gap measurement is the most interesting metric - it shows the overhead of sequential DMA scheduling
- Consider adding a `--verbose` flag for debugging transaction detection
- Consider outputting raw statistics to JSON for further processing