mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-04-14 10:07:39 +08:00
remove bdshot_timing_analyzer_spec.md
This commit is contained in:
parent
597611e7a5
commit
b5e9457ba2
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user