mirror of
https://github.com/pascallanger/DIY-Multiprotocol-TX-Module.git
synced 2026-08-01 18:48:59 +00:00
671 lines
28 KiB
Markdown
671 lines
28 KiB
Markdown
|
|
# XBM-37 Quad SPI Capture Analysis
|
|||
|
|
|
|||
|
|
**Manufacturer:** T-Smart
|
|||
|
|
**Model:** XBM-37 (toy quadcopter)
|
|||
|
|
**TX/RX RF Chip:** SV7241A (QFN-20, NRF24L01+ clone / BK2425 derivative)
|
|||
|
|
**Protocol Basis:** Closely related to FQ777 (same RF chip family, same bind address, same ssv ESB air encoding)
|
|||
|
|
**Capture Tool:** Logic analyzer – digital (02a) and SPI-decoded (all "b" files)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Table of Contents
|
|||
|
|
|
|||
|
|
1. [Capture File Inventory](#1-capture-file-inventory)
|
|||
|
|
2. [Key Protocol Parameters](#2-key-protocol-parameters)
|
|||
|
|
3. [SPI Initialization Sequence](#3-spi-initialization-sequence)
|
|||
|
|
4. [Bind Sequence – Deep Analysis (02a + 02b)](#4-bind-sequence--deep-analysis-02a--02b)
|
|||
|
|
5. [Bind vs No-RX Comparison (01b vs 02b)](#5-bind-vs-no-rx-comparison-01b-vs-02b)
|
|||
|
|
6. [Normal Data Packet Format](#6-normal-data-packet-format)
|
|||
|
|
7. [Per-Channel Control Analysis (03b – 24b)](#7-per-channel-control-analysis-03b--24b)
|
|||
|
|
8. [RF Timing Summary](#8-rf-timing-summary)
|
|||
|
|
9. [MPM Implementation Notes](#9-mpm-implementation-notes)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Capture File Inventory
|
|||
|
|
|
|||
|
|
All captures are located in `Captures_XBM-37/`. The **"b" files** are SPI-decoded exports
|
|||
|
|
(columns: `Time [s], Packet ID, MOSI, MISO`). The **"a" file** is the raw digital export
|
|||
|
|
(columns: `Time[s], MOSI, MISO, SCK, CSN, CE, IRQ`).
|
|||
|
|
|
|||
|
|
| File | Type | Description | SPI Transactions | TX Payloads | Duration |
|
|||
|
|
|------|------|-------------|-----------------|-------------|----------|
|
|||
|
|
| `01b-XBM-37_Quad_TX-PowerOn-NoRX.csv` | SPI | TX power-on, **no RX present** | 5,437 | 1,352 | 2.963 s |
|
|||
|
|
| `02a-XBM-37_Quad_TX-PowerOn-withRX-Bind.csv` | **Digital** | TX power-on + bind **with RX** (all 6 lines) | 405,271 samples | 1,345 payloads | 4.380 s |
|
|||
|
|
| `02b-XBM-37_Quad_TX-PowerOn-withRX-Bind.csv` | SPI | TX power-on + bind **with RX** | 5,409 | 1,345 | 2.949 s |
|
|||
|
|
| `03b-XBM-37_Quad_Aileron-Center-Left-Center-Right-Center.csv` | SPI | Aileron stick: center → left → center → right → center | 6,039 | 1,509 | 3.127 s |
|
|||
|
|
| `04b-XBM-37_Quad_Elevator-Center-Back-Center-Forward-Center.csv` | SPI | Elevator stick: center → back → center → fwd → center | 6,035 | 1,509 | 3.123 s |
|
|||
|
|
| `05b-XBM-37_Quad_Throttle-Low-High-Low.csv` | SPI | Throttle: low → high → low | 6,034 | 1,509 | 3.122 s |
|
|||
|
|
| `06b-XBM-37_Quad_Rudder-Center-Left-Center-Right-Center.csv` | SPI | Rudder (yaw): center → left → center → right → center | 6,034 | 1,509 | 3.122 s |
|
|||
|
|
| `07b-XBM-37_Quad_RateModeSwitch-1-2-3.csv` | SPI | Rate/speed mode switch: 1 → 2 → 3 | 6,034 | 1,509 | 3.122 s |
|
|||
|
|
| `08b-XBM-37_Quad_FlipSwitch-PushButton_Off-On.csv` | SPI | Flip trick button: off → on | 6,034 | 1,509 | 3.122 s |
|
|||
|
|
| `09b-XBM-37_Quad_VideoSwitch-Off-On-Off-On.csv` | SPI | Video record toggle: off → on → off → on | 6,023 | 1,506 | 3.116 s |
|
|||
|
|
| `10b-XBM-37_Quad_PictureSwitch-PushButton-3X.csv` | SPI | Photo/picture button: pressed 3 times | 6,019 | 1,505 | 3.113 s |
|
|||
|
|
| `11b-XBM-37_Quad_HeadlessSwitch-PushButton-Off-On.csv` | SPI | Headless mode button: off → on | 6,022 | 1,506 | 3.116 s |
|
|||
|
|
| `12b-XBM-37_Quad_ReturnToHomeSwitch-PushButton-Off-On.csv` | SPI | RTH button: off → on | 6,034 | 1,508 | 3.127 s |
|
|||
|
|
| `13b-XBM-37_Quad_LED-LightsSwitch-PushButton-On-Off.csv` | SPI | LED lights toggle: on → off | 6,019 | 1,505 | 3.113 s |
|
|||
|
|
| `14b-XBM-37_Quad_OK-Switch-PushButton-Off-On.csv` | SPI | OK button: off → on | 6,027 | 1,507 | 3.120 s |
|
|||
|
|
| `20b-XBM-37_Quad_Elevator-Trim-Center-Forward-Max_32-Clicks.csv` | SPI | Elevator trim: center → forward max (32 clicks) | 38,683 | 9,671 | 20.004 s |
|
|||
|
|
| `21b-XBM-37_Quad_Elevator-Trim-Center-Back-Max_31-Clicks.csv` | SPI | Elevator trim: center → back max (31 clicks) | 33,215 | 8,304 | 17.191 s |
|
|||
|
|
| `22b-XBM-37_Quad_Aileron-Trim-Center-Left-Max_32-Clicks.csv` | SPI | Aileron trim: center → left max (32 clicks) | 32,315 | 8,079 | 16.730 s |
|
|||
|
|
| `23b-XBM-37_Quad_Aileron-Trim-Center-Right-Max_31-Clicks.csv` | SPI | Aileron trim: center → right max (31 clicks) | 28,495 | 7,124 | 14.756 s |
|
|||
|
|
| `24b-XBM-37_Quad_Ail-Trim-Forward-Max_Ele-Trim-Right-Max_Push-OK-Button-2X.csv` | SPI | Elevator trim at forward max, aileron trim at right max, OK button pressed 2× | 29,275 | 7,319 | 15.176 s |
|
|||
|
|
|
|||
|
|
### Notes
|
|||
|
|
- The "b" SPI files each contain one SPI byte per row. Multiple rows sharing the same `Packet ID` belong to one CSN-low SPI transaction.
|
|||
|
|
- Files 03b–24b were all captured in **normal (post-bind) flight mode**. The TX had previously
|
|||
|
|
completed a successful bind.
|
|||
|
|
- File 01b and 02b are functionally **identical** captures (confirmed by direct payload comparison)
|
|||
|
|
except for a tiny time offset (~7 µs between corresponding packets). This confirms the TX
|
|||
|
|
transmits identically whether or not an RX is present; see Section 5.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Key Protocol Parameters
|
|||
|
|
|
|||
|
|
| Parameter | Value |
|
|||
|
|
|-----------|-------|
|
|||
|
|
| RF transceiver chip | SV7241A (QFN-20, NRF24L01+ / BK2425 derivative) |
|
|||
|
|
| RF frequency band | 2.4 GHz ISM |
|
|||
|
|
| Air data rate (SV7241A) | 2 Mbps (RF_SETUP = 0x26, bit5 = RF_DR = 1) |
|
|||
|
|
| Air data rate (nRF24L01+ equivalent) | **250 kbps** via `ssv_pack_dpl()` method |
|
|||
|
|
| Packet size (raw, pre-pack) | 8 bytes |
|
|||
|
|
| Packet size (after ssv_pack_dpl) | 12 bytes |
|
|||
|
|
| Enhanced ShockBurst | Yes (dynamic payload length, no auto-ACK) |
|
|||
|
|
| SPI clock frequency | ~143 kHz (7 µs period) |
|
|||
|
|
| Bind packet count | **400** |
|
|||
|
|
| Bind address (broadcast) | `E7 E7 E7 E7 67` |
|
|||
|
|
| Data address format | `[TX_ID₀ TX_ID₁ TX_ID₂ E7 67]` |
|
|||
|
|
| Bind channel (first packet) | 0x00 (universal) |
|
|||
|
|
| Hop channels (bind + data) | **0x49, 0x34, 0x26, 0x07** (4 channels) |
|
|||
|
|
| Packet period (steady state) | **~2,070 µs** (~2.07 ms) |
|
|||
|
|
| CE high pulse width | ~1,172 µs |
|
|||
|
|
| CE low gap (inter-packet) | ~896 µs |
|
|||
|
|
| TX-only mode | Yes — EN_RXADDR = 0x00; TX never listens |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. SPI Initialization Sequence
|
|||
|
|
|
|||
|
|
The sequence below is from `02b` (PIDs 0–24), occurring over the first ~5 ms after the SPI
|
|||
|
|
bus becomes active (~150–335 ms after TX power-on due to startup delays).
|
|||
|
|
|
|||
|
|
### 3.1 SV7241A Private Bank Registers
|
|||
|
|
|
|||
|
|
The SV7241A (like BK2425) has bank-switched extended registers at addresses 0x18–0x1B.
|
|||
|
|
Register 0x1F selects the bank. These registers hold RF/analog calibration constants specific
|
|||
|
|
to the chip and **do not exist on nRF24L01+** — they are ignored when implementing with MPM.
|
|||
|
|
|
|||
|
|
| PID | Register | Data Written | Purpose |
|
|||
|
|
|-----|----------|-------------|---------|
|
|||
|
|
| 0 | 0x1F | `00` | Select bank 0 |
|
|||
|
|
| 1 | 0x1B | `10 E1 D3 3D` | Bank 0 – RF calibration reg 1 |
|
|||
|
|
| 2 | 0x19 | `06 AA A2 DB` | Bank 0 – RF calibration reg 2 |
|
|||
|
|
| 3 | 0x1F | `01` | Select bank 1 |
|
|||
|
|
| 4 | 0x19 | `77 48 9A E8` | Bank 1 – internal reg |
|
|||
|
|
| 5 | 0x1B | `76 87 CA 01` | Bank 1 – internal reg |
|
|||
|
|
| 6 | 0x1F | `02` | Select bank 2 |
|
|||
|
|
| 7 | 0x1B | `A0 00 18 A0` | Bank 2 – internal reg |
|
|||
|
|
| 8 | 0x1F | `04` | Select bank 4 |
|
|||
|
|
| 9 | 0x18 | `01 00 F0 00` | Bank 4 – internal reg |
|
|||
|
|
| 10 | 0x1F | `05` | Select bank 5 |
|
|||
|
|
| 11 | 0x18 | `84 03 2A 03` | Bank 5 – internal reg |
|
|||
|
|
| 12 | 0x19 | `90 BF 00 00` | Bank 5 – internal reg |
|
|||
|
|
| 13 | 0x1A | `A0 0F 00 00` | Bank 5 – internal reg |
|
|||
|
|
|
|||
|
|
### 3.2 Standard NRF24L01-Compatible Registers
|
|||
|
|
|
|||
|
|
Written immediately after the private registers (PIDs 14–24):
|
|||
|
|
|
|||
|
|
| PID | Register | Written Value | Decoded Meaning |
|
|||
|
|
|-----|----------|--------------|-----------------|
|
|||
|
|
| 14 | CONFIG (0x00) | `0x0C` | EN_CRC=1, CRCO=1 (2-byte CRC), PWR_UP=0, PRIM_RX=0 (TX) |
|
|||
|
|
| 15 | CONFIG (0x00) | *read back* `0x0C` | Verify CONFIG |
|
|||
|
|
| 16 | TX_ADDR (0x10) | `E7 E7 E7 E7 67` | Bind broadcast address |
|
|||
|
|
| 17 | RX_ADDR_P0 (0x0A) | `E7 E7 E7 E7 67` | Matches TX_ADDR for bind |
|
|||
|
|
| 18 | EN_AA (0x01) | `0x00` | Auto-ACK **disabled** on all pipes |
|
|||
|
|
| 19 | EN_RXADDR (0x02) | `0x00` | All RX pipes **disabled** – TX only |
|
|||
|
|
| 20 | RF_CH (0x05) | `0x49` | Initial RF channel (73 MHz offset) |
|
|||
|
|
| 21 | FEATURE (0x1D) | `0x04` | EN_DPL = 1 (dynamic payload length) |
|
|||
|
|
| 22 | DYNPD (0x1C) | `0x01` | DPL enabled on pipe 0 |
|
|||
|
|
| 23 | RF_SETUP (0x06) | `0x26` | SV7241A: 2 Mbps, max power |
|
|||
|
|
| 24 | CONFIG (0x00) | `0x0E` | PWR_UP=1 → TX powered up |
|
|||
|
|
|
|||
|
|
**RF_SETUP = 0x26 mapping:**
|
|||
|
|
|
|||
|
|
| Chip | Bit 5 meaning | Value 0x26 decodes as |
|
|||
|
|
|------|--------------|----------------------|
|
|||
|
|
| SV7241A | RF_DR (0=1Mbps, 1=2Mbps) | **2 Mbps**, max power |
|
|||
|
|
| nRF24L01+ | RF_DR_LOW (0=normal, 1=250kbps) | **250 kbps**, max power |
|
|||
|
|
|
|||
|
|
→ On nRF24L01+, writing 0x26 would set **250 kbps** (not 2 Mbps). For MPM emulation the
|
|||
|
|
correct approach (same as FQ777) is `NRF24L01_SetBitrate(NRF24L01_BR_250K)` combined with
|
|||
|
|
`ssv_pack_dpl()` to produce a compatible air packet.
|
|||
|
|
|
|||
|
|
**Startup timing:**
|
|||
|
|
|
|||
|
|
The TX power-on to first SPI transaction takes ~150 ms (CE is held high from
|
|||
|
|
t = –72 ms until t = 0, then the ~150 ms wait elapses before SPI activity at t ≈ +181 ms in the
|
|||
|
|
02b file). The entire init register sequence completes in ~6 ms.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Bind Sequence – Deep Analysis (02a + 02b)
|
|||
|
|
|
|||
|
|
### 4.1 Overview
|
|||
|
|
|
|||
|
|
After the ~150 ms startup wait, the TX sends exactly **400 bind packets** before switching to
|
|||
|
|
normal data mode.
|
|||
|
|
|
|||
|
|
| Property | Value |
|
|||
|
|
|----------|-------|
|
|||
|
|
| Total bind packets | **400** |
|
|||
|
|
| Bind address | `E7 E7 E7 E7 67` (fixed broadcast) |
|
|||
|
|
| First bind packet channel | `0x00` (universal channel) |
|
|||
|
|
| Remaining 399 bind packet channels | Cycling: `0x49 → 0x34 → 0x26 → 0x07 → 0x49 → …` |
|
|||
|
|
| Bind packet payload | `20 14 07 03 TX_ID₀ TX_ID₁ TX_ID₂ CKSUM` |
|
|||
|
|
| Bind packet content | Identical across all 400 transmissions |
|
|||
|
|
|
|||
|
|
The first bind packet is sent on RF channel **0x00**, ensuring a newly powered-on RX (sitting
|
|||
|
|
on its default channel) can hear the bind announcement regardless of any prior state. Subsequent
|
|||
|
|
bind packets cycle through the four data-hopping channels.
|
|||
|
|
|
|||
|
|
### 4.2 Bind Packet Structure
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Byte 0 1 2 3 4 5 6 7
|
|||
|
|
[20 14 07 03 TX_ID₀ TX_ID₁ TX_ID₂ CKSUM]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| Byte | Value (this TX) | Description |
|
|||
|
|
|------|----------------|-------------|
|
|||
|
|
| B0 | `0x20` | Bind identifier byte (constant — marks this as a bind packet) |
|
|||
|
|
| B1 | `0x14` | Protocol variant constant (XBM-37 specific; FQ777 uses `0x15`) |
|
|||
|
|
| B2 | `0x07` | Protocol variant constant (XBM-37 specific; FQ777 uses `0x05`) |
|
|||
|
|
| B3 | `0x03` | Protocol variant constant (XBM-37 specific; FQ777 uses `0x06`) |
|
|||
|
|
| B4 | `0x91` | TX_ID byte 0 — unique per TX unit |
|
|||
|
|
| B5 | `0x05` | TX_ID byte 1 — unique per TX unit |
|
|||
|
|
| B6 | `0x05` | TX_ID byte 2 — unique per TX unit |
|
|||
|
|
| B7 | `0x9B` | Checksum = (TX_ID₀ + TX_ID₁ + TX_ID₂) & 0xFF = (0x91+0x05+0x05) & 0xFF |
|
|||
|
|
|
|||
|
|
**Checksum formula for bind packet:** `B7 = (B4 + B5 + B6) & 0xFF`
|
|||
|
|
(This differs from the data packet checksum which sums B0–B6.)
|
|||
|
|
|
|||
|
|
### 4.3 TX ID
|
|||
|
|
|
|||
|
|
TX ID is **3 bytes** embedded in B4–B6 of the bind packet. This TX unit has:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
TX_ID = [0x91, 0x05, 0x05]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
After bind, the TX_ADDR is set to: `[TX_ID₀ TX_ID₁ TX_ID₂ 0xE7 0x67]`
|
|||
|
|
= `[91 05 05 E7 67]`
|
|||
|
|
|
|||
|
|
The RX extracts TX_ID from the bind packet and uses it to construct the matching address for
|
|||
|
|
normal data reception.
|
|||
|
|
|
|||
|
|
### 4.4 Bind Channel Sequence (from 02b SPI decoded)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Packet #1: ch=0x00 [20 14 07 03 91 05 05 9B] ← universal announce
|
|||
|
|
Packet #2: ch=0x49 [20 14 07 03 91 05 05 9B]
|
|||
|
|
Packet #3: ch=0x34 [20 14 07 03 91 05 05 9B]
|
|||
|
|
Packet #4: ch=0x26 [20 14 07 03 91 05 05 9B]
|
|||
|
|
Packet #5: ch=0x07 [20 14 07 03 91 05 05 9B]
|
|||
|
|
Packet #6: ch=0x49 [20 14 07 03 91 05 05 9B]
|
|||
|
|
...continues cycling 0x49→0x34→0x26→0x07...
|
|||
|
|
Packet #400: ch=0x26 [20 14 07 03 91 05 05 9B] ← last bind
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Channel usage across all 400 bind packets:
|
|||
|
|
|
|||
|
|
| Channel | Count | Notes |
|
|||
|
|
|---------|-------|-------|
|
|||
|
|
| 0x00 | 1 | First packet only |
|
|||
|
|
| 0x49 (73 MHz) | 100 | Regular cycle |
|
|||
|
|
| 0x34 (52 MHz) | 100 | Regular cycle |
|
|||
|
|
| 0x26 (38 MHz) | 100 | Regular cycle (includes last bind packet) |
|
|||
|
|
| 0x07 (7 MHz) | 99 | Regular cycle |
|
|||
|
|
|
|||
|
|
### 4.5 Bind-to-Normal Transition
|
|||
|
|
|
|||
|
|
After the 400th bind packet the TX:
|
|||
|
|
|
|||
|
|
1. Completes the 400th transmission (CE pulse ~1,172 µs, then CE low).
|
|||
|
|
2. Waits **~16.6 ms** (no SPI activity; firmware processing time).
|
|||
|
|
3. Writes the new TX_ADDR: `W_TX_ADDR [91 05 05 E7 67]` (SPI pid=1625 at t=1.175882 s).
|
|||
|
|
4. Immediately clears STATUS and starts normal data mode.
|
|||
|
|
5. First normal packet: ch=**0x07** (continuation of the hop sequence from where bind ended).
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Last bind (400th): t=1.159258 s, ch=0x26
|
|||
|
|
TX_ADDR change: t=1.175882 s (+16.6 ms gap)
|
|||
|
|
First data packet: t=1.176619 s, ch=0x07 [E1 70 70 70 20 20 00 71]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.6 IRQ / STATUS Analysis (from 02a Digital Capture)
|
|||
|
|
|
|||
|
|
The IRQ line (active-low) is asserted by the SV7241A for every successfully transmitted packet
|
|||
|
|
(TX_DS interrupt):
|
|||
|
|
|
|||
|
|
| Observation | Value |
|
|||
|
|
|-------------|-------|
|
|||
|
|
| Total IRQ assertions | 1,340 (matching 1,340 normal CE pulses) |
|
|||
|
|
| IRQ pulse duration (typical) | ~600 µs |
|
|||
|
|
| IRQ source | TX_DS only (TX complete) |
|
|||
|
|
| RX_DR interrupts | **Zero** — no data ever received |
|
|||
|
|
|
|||
|
|
The SPI confirms there are **no `R_RX_PAYLOAD` (0x61) commands** in either 01b or 02b.
|
|||
|
|
Combined with `EN_RXADDR = 0x00` (all RX pipes disabled), it is impossible for the TX to
|
|||
|
|
receive data from the RX. This is confirmed by the STATUS register never showing bit6
|
|||
|
|
(RX_DR) = 1.
|
|||
|
|
|
|||
|
|
One extended IRQ assertion of **15.9 ms** occurs at t=1.160476 s (immediately after the 400th
|
|||
|
|
bind packet). This is not an RX event — it is simply the TX_DS interrupt remaining uncleared
|
|||
|
|
while the firmware processes the bind-completion event and updates TX_ADDR. The IRQ is
|
|||
|
|
cleared when `W_STATUS [70]` is written at t=1.176254 s (pid=1626).
|
|||
|
|
|
|||
|
|
### 4.7 CE Pulse Timing (from 02a)
|
|||
|
|
|
|||
|
|
| Measurement | Value |
|
|||
|
|
|-------------|-------|
|
|||
|
|
| Normal CE pulse duration | min 1,165 µs, max 1,178 µs, **avg 1,172 µs** |
|
|||
|
|
| CE inter-pulse gap (end to start) | **~896 µs** |
|
|||
|
|
| Total cycle (CE high + gap) | **~2,068 µs** |
|
|||
|
|
| Pulse-to-pulse interval (high to high) | min 2.063 ms, max 20.686 ms, **avg 2.084 ms** |
|
|||
|
|
|
|||
|
|
The 20.686 ms outlier corresponds to the bind-to-normal transition pause (~16.6 ms).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Bind vs No-RX Comparison (01b vs 02b)
|
|||
|
|
|
|||
|
|
**Finding: The TX transmits identically whether or not an RX is present.**
|
|||
|
|
|
|||
|
|
| Metric | 01b (No RX) | 02b (With RX) |
|
|||
|
|
|--------|------------|----------------|
|
|||
|
|
| Total TX payloads | 1,352 | 1,345 |
|
|||
|
|
| Bind packets | 400 | 400 |
|
|||
|
|
| Bind packet content | `20 14 07 03 91 05 05 9B` | `20 14 07 03 91 05 05 9B` (identical) |
|
|||
|
|
| TX_ADDR change time | t=1.175899 s | t=1.175882 s |
|
|||
|
|
| TX_ADDR after bind | `[91 05 05 E7 67]` | `[91 05 05 E7 67]` (identical) |
|
|||
|
|
| Normal data payload (idle) | `E1 70 70 70 20 20 00 71` | `E1 70 70 70 20 20 00 71` (identical) |
|
|||
|
|
| Time offset between captures | — | ~7 µs per packet |
|
|||
|
|
|
|||
|
|
The TX performs an automatic timed bind sequence (400 packets) and then switches to data mode
|
|||
|
|
unconditionally, regardless of RX acknowledgment. The TX **never knows** whether the RX
|
|||
|
|
accepted the bind. Bind is one-sided: the RX passively listens for the bind packet on ch=0x00
|
|||
|
|
(or the cycling channels), extracts the TX_ID and hop channels, and then follows the TX's
|
|||
|
|
normal data transmissions.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Normal Data Packet Format
|
|||
|
|
|
|||
|
|
### 6.1 Packet Structure (8 bytes)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Byte 0 1 2 3 4 5 6 7
|
|||
|
|
[Throttle Rudder Aileron Elevator Flags1 Flags2 Flags3 CKSUM]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Checksum (B7):** `B7 = (B0 + B1 + B2 + B3 + B4 + B5 + B6) & 0xFF`
|
|||
|
|
|
|||
|
|
All 8-byte checksum values have been verified against this formula across all capture files.
|
|||
|
|
|
|||
|
|
### 6.2 Stick Channels (B0–B3)
|
|||
|
|
|
|||
|
|
| Byte | Channel | Min | Center | Max | Notes |
|
|||
|
|
|------|---------|-----|--------|-----|-------|
|
|||
|
|
| B0 | Throttle | `0xE1` | `0x70` | `0x00` | Non-return throttle |
|
|||
|
|
| B1 | Rudder (Yaw) | `0x00` | `0x70` | `0xE1` | |
|
|||
|
|
| B2 | Aileron (Roll) | `0xE1` | `0x70` | `0x00` | |
|
|||
|
|
| B3 | Elevator (Pitch) | `0xE1` | `0x70` | `0x00` | |
|
|||
|
|
|
|||
|
|
All analog channels use the same range: **0x00 – 0xE1** (0 – 225 decimal) with center at
|
|||
|
|
**0x70** (112 decimal).
|
|||
|
|
- Throttle, Aileron, and Elevator use reversed state (0xE1...0x70...0x00).
|
|||
|
|
|
|||
|
|
### 6.3 Flags Byte 1 (B4)
|
|||
|
|
|
|||
|
|
| Value | Meaning | Description |
|
|||
|
|
|-------|---------|-------------|
|
|||
|
|
| `0x20` | **Trim Elevator Center** | Resets at TX start |
|
|||
|
|
| `0x21 up to 0x40` | Trim elevator **forward** | 32 Clicks to max |
|
|||
|
|
| `0x1F downto 0x01` | Trim elevator **back** | 31 Clicks to max |
|
|||
|
|
|
|||
|
|
### 6.4 Flags Byte 2 (B5)
|
|||
|
|
|
|||
|
|
| Value | Meaning | Description |
|
|||
|
|
|-----|------|-------------|
|
|||
|
|
| `0x20` | **Trim Aileron Center** | Resets at TX start |
|
|||
|
|
| `0x21 up to 0x40` | Trim aileron **left** | 32 Clicks to max |
|
|||
|
|
| `0x1F down to 0x01` | Trim aileron **right** | 31 Clicks to max |
|
|||
|
|
| `0x80` | OK button | OK pressed (`0x20` → `0xA0`) |
|
|||
|
|
|
|||
|
|
`B5 bit7` is an OR mask on top of trim value (`B5_with_OK = B5_trim | 0x80`), e.g.
|
|||
|
|
center `0x20 -> 0xA0`, right-max `0x01 -> 0x81`.
|
|||
|
|
|
|||
|
|
### 6.5 Flags Byte 3 (B6)
|
|||
|
|
|
|||
|
|
| Bits | Mask | Name | Description |
|
|||
|
|
|------|------|------|-------------|
|
|||
|
|
| [1:0] | `0x03` | Rate mode | `0x00`=rate 1 (slow), `0x01`=rate 2, `0x02`=rate 3 (fast) |
|
|||
|
|
| 2 | `0x04` | LED off | `0`=LED lights ON, `1`=LED lights OFF |
|
|||
|
|
| 3 | `0x08` | RTH | `1`=return-to-home active |
|
|||
|
|
| 4 | `0x10` | Headless | `1`=headless mode active |
|
|||
|
|
| 5 | `0x20` | Video | `1`=video recording active |
|
|||
|
|
| 6 | `0x40` | Picture | `1`=photo capture triggered |
|
|||
|
|
| 7 | `0x80` | Flip | `1`=3D flip command |
|
|||
|
|
|
|||
|
|
Normal state: `B6 = 0x00` (rate 1, LED on, no special modes)
|
|||
|
|
|
|||
|
|
### 6.6 Return-to-Home (RTH)
|
|||
|
|
|
|||
|
|
Updated analysis of **file 12b** (RTH OFF in first half, ON in second half):
|
|||
|
|
|
|||
|
|
- RTH-OFF payload is steady at `7E 70 70 70 20 20 00 0E`.
|
|||
|
|
- After the OFF→ON transition, the payload changes to `7E 70 70 70 20 20 08 16`.
|
|||
|
|
- The persistent functional change in this capture is `B6: 0x00 -> 0x08`
|
|||
|
|
(bit3 asserted when RTH is ON).
|
|||
|
|
|
|||
|
|
In this export, RTH is represented in the steady 8-byte payload by `B6 bit3`.
|
|||
|
|
|
|||
|
|
### 6.7 Example Payloads
|
|||
|
|
|
|||
|
|
| Payload | Meaning |
|
|||
|
|
|---------|---------|
|
|||
|
|
| `E1 70 70 70 20 20 00 71` | Throttle low `0xE1`, all sticks center `0x70`, LED on, no special modes `0x00` |
|
|||
|
|
| `82 70 70 70 20 20 00 12` | Throttle ~mid `0x82`, all sticks center, LED on, no special modes |
|
|||
|
|
| `00 70 70 70 20 20 00 90` | Throttle max `0x00`, all sticks center, LED on, no special modes |
|
|||
|
|
| `E1 70 00 70 20 20 00 01` | Throttle low, aileron full right `0x00`, LED on, no special modes |
|
|||
|
|
| `E1 70 E1 70 20 20 00 E2` | Throttle low, aileron full left `0xE1`, LED on, no special modes |
|
|||
|
|
| `E1 70 70 00 20 20 00 01` | Throttle low, elevator full back `0x00`,LED on, no special modes |
|
|||
|
|
| `82 70 70 70 20 20 80 92` | Throttle ~mid, all sticks center, flip command active `0x80` |
|
|||
|
|
| `82 70 70 70 20 20 10 22` | Throttle ~mid, all sticks center, headless mode active `0x10` |
|
|||
|
|
| `82 70 70 70 20 20 01 13` | Throttle ~mid, all sticks center, rate mode 2 `0x01` |
|
|||
|
|
| `82 70 70 70 20 20 02 14` | Throttle ~mid, all sticks center, rate mode 3 `0x02` |
|
|||
|
|
| `00 70 70 70 20 A0 00 F1` | Throttle max, all sticks center, OK button pressed `0xA0` |
|
|||
|
|
| `00 70 70 70 20 20 04 75` | Throttle max, all sticks center, LED lights OFF `0x04` |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. Per-Channel Control Analysis (03b – 24b)
|
|||
|
|
|
|||
|
|
All control captures were taken in post-bind normal mode. Hopping channels confirmed active:
|
|||
|
|
`0x07, 0x49, 0x34, 0x26` (cyclic). Average packet interval: **~2.07 ms** across all files.
|
|||
|
|
|
|||
|
|
### 03b – Aileron (Roll)
|
|||
|
|
|
|||
|
|
- **Affected byte:** B2
|
|||
|
|
- Range observed: `0xE1` (full left) → `0x70` (center) → `0x00` (full right)
|
|||
|
|
|
|||
|
|
### 04b – Elevator (Pitch)
|
|||
|
|
|
|||
|
|
- **Affected byte:** B3
|
|||
|
|
- Range observed: `0x00` (full back) → `0x70` (center) → `0xE1` (full forward)
|
|||
|
|
|
|||
|
|
### 05b – Throttle
|
|||
|
|
|
|||
|
|
- **Affected byte:** B0
|
|||
|
|
- Range observed: `0xE1` (low minimum) ↔ `0x70` (center) ↔ `0x00` (full maximum)
|
|||
|
|
- Non-return throttle (stick does not spring back to center)
|
|||
|
|
|
|||
|
|
### 06b – Rudder (Yaw)
|
|||
|
|
|
|||
|
|
- **Affected byte:** B1
|
|||
|
|
- Range observed: `0x00` (full left) → `0x70` (center) → `0xE1` (full right)
|
|||
|
|
- Note: B0 (throttle) also varies in this capture because left stick controls both
|
|||
|
|
throttle (up/down) and rudder (left/right) Mode 2 transmitter
|
|||
|
|
|
|||
|
|
### 07b – Rate Mode Switch
|
|||
|
|
|
|||
|
|
- **Affected byte:** B6 bits[1:0]
|
|||
|
|
- `B6 = 0x00`: Rate 1 (slowest/beginner)
|
|||
|
|
- `B6 = 0x01`: Rate 2 (intermediate)
|
|||
|
|
- `B6 = 0x02`: Rate 3 (fastest/expert)
|
|||
|
|
|
|||
|
|
### 08b – Flip Switch
|
|||
|
|
|
|||
|
|
- **Affected byte:** B6 bit7
|
|||
|
|
- `B6 = 0x00`: No flip; `B6 = 0x80`: 3D flip command active
|
|||
|
|
- While flip is held: throttle (B0) increments slightly (`0x82` → `0x83`)
|
|||
|
|
|
|||
|
|
### 09b – Video Switch
|
|||
|
|
|
|||
|
|
- **Affected byte:** B6 bit5
|
|||
|
|
- `B6 = 0x00`: Video off; `B6 = 0x20`: Video recording active
|
|||
|
|
- Toggle on/off: transitions between these two states
|
|||
|
|
|
|||
|
|
### 10b – Picture Switch (3×)
|
|||
|
|
|
|||
|
|
- **Affected byte:** B6 bit6
|
|||
|
|
- `B6 = 0x00`: Idle; `B6 = 0x40`: Photo capture triggered (momentary)
|
|||
|
|
|
|||
|
|
### 11b – Headless Switch
|
|||
|
|
|
|||
|
|
- **Affected byte:** B6 bit4
|
|||
|
|
- `B6 = 0x00`: Headless off; `B6 = 0x10`: Headless mode active
|
|||
|
|
|
|||
|
|
### 12b – Return to Home Switch
|
|||
|
|
|
|||
|
|
- **Capture split tested:** first half RTH OFF, second half RTH ON.
|
|||
|
|
- **RTH OFF payload:** `7E 70 70 70 20 20 00 0E`
|
|||
|
|
- **RTH ON payload:** `7E 70 70 70 20 20 08 16`
|
|||
|
|
- **Primary byte change:** `B6: 0x00 -> 0x08`
|
|||
|
|
- `B6 bit3` is asserted when RTH is active in this capture
|
|||
|
|
- **Minor secondary variation while ON:** `B0` occasionally increments `0x7E -> 0x7F`,
|
|||
|
|
producing checksum `0x16 -> 0x17`
|
|||
|
|
- All other payload bytes remain unchanged across the OFF→ON transition in this file.
|
|||
|
|
|
|||
|
|
### 13b – LED Lights Switch
|
|||
|
|
|
|||
|
|
- **Affected byte:** B6 bit2
|
|||
|
|
- `B6 = 0x00`: LED lights **ON** (default)
|
|||
|
|
- `B6 = 0x04`: LED lights **OFF**
|
|||
|
|
- Note: Inverted logic — bit2=1 means OFF
|
|||
|
|
|
|||
|
|
### 14b – OK Switch
|
|||
|
|
|
|||
|
|
- **Affected byte:** B5 bit7
|
|||
|
|
- `B5 = 0x20`: OK button not pressed
|
|||
|
|
- `B5 = 0xA0`: OK button pressed (0x20 | 0x80)
|
|||
|
|
|
|||
|
|
### 20b – Elevator Trim Center → Forward Max (32 clicks)
|
|||
|
|
|
|||
|
|
- **Affected byte:** B4 (elevator trim)
|
|||
|
|
- Observed progression: `0x20 -> ... -> 0x40`
|
|||
|
|
- Endpoint in this capture: `B4 = 0x40` (forward max)
|
|||
|
|
- `B5` remains `0x20`; `B6` remains `0x00`
|
|||
|
|
|
|||
|
|
### 21b – Elevator Trim Center → Back Max (31 clicks)
|
|||
|
|
|
|||
|
|
- **Affected byte:** B4 (elevator trim)
|
|||
|
|
- Observed progression: `0x20 -> ... -> 0x01`
|
|||
|
|
- Endpoint in this capture: `B4 = 0x01` (back max)
|
|||
|
|
- `B5` remains `0x20`; `B6` remains `0x00`
|
|||
|
|
|
|||
|
|
### 22b – Aileron Trim Center → Left Max (32 clicks)
|
|||
|
|
|
|||
|
|
- **Affected byte:** B5 (aileron trim)
|
|||
|
|
- Observed progression: `0x20 -> ... -> 0x40`
|
|||
|
|
- Endpoint in this capture: `B5 = 0x40` (left max)
|
|||
|
|
- `B4` remains `0x20`; `B6` remains `0x00`
|
|||
|
|
|
|||
|
|
### 23b – Aileron Trim Center → Right Max (31 clicks)
|
|||
|
|
|
|||
|
|
- **Affected byte:** B5 (aileron trim)
|
|||
|
|
- Observed progression: `0x20 -> ... -> 0x01`
|
|||
|
|
- Endpoint in this capture: `B5 = 0x01` (right max)
|
|||
|
|
- `B4` remains `0x20`; `B6` remains `0x00`
|
|||
|
|
|
|||
|
|
### 24b – Trim Endpoints + OK Button (2 presses)
|
|||
|
|
|
|||
|
|
- Fixed trim baseline in this capture:
|
|||
|
|
- `B4 = 0x40` (elevator forward max)
|
|||
|
|
- `B5 trim = 0x01` (aileron right max)
|
|||
|
|
- **OK effect:** `B5 bit7` toggles over trim baseline:
|
|||
|
|
- not pressed: `B5 = 0x01`
|
|||
|
|
- pressed: `B5 = 0x81` (`0x01 | 0x80`)
|
|||
|
|
- `B6` remains `0x00` throughout.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. RF Timing Summary
|
|||
|
|
|
|||
|
|
### 8.1 Per-Packet SPI Cycle (02b decoded)
|
|||
|
|
|
|||
|
|
Each packet transmission consists of 4 SPI transactions:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. W_STATUS [27] = 0x70 → Clear TX_DS / MAX_RT / RX_DR interrupt flags
|
|||
|
|
2. FLUSH_TX [E1] → Empty TX FIFO
|
|||
|
|
3. W_RF_CH [25] = <ch> → Set hop channel
|
|||
|
|
4. W_TX_PAYLOAD [A0] = 8B → Write 8-byte payload to TX FIFO
|
|||
|
|
[CE pulse ~1,172 µs to transmit]
|
|||
|
|
[~896 µs gap before next cycle]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.2 Bind Phase Timing
|
|||
|
|
|
|||
|
|
| Event | Timestamp (02b) |
|
|||
|
|
|-------|----------------|
|
|||
|
|
| Init register writes complete | t = 0.186 s |
|
|||
|
|
| Startup wait completes | t = 0.335 s (~150 ms) |
|
|||
|
|
| First bind packet (ch=0x00) | t = 0.335838 s |
|
|||
|
|
| 400th (last) bind packet (ch=0x26) | t = 1.159258 s |
|
|||
|
|
| TX_ADDR change to data address | t = 1.175882 s (+16.6 ms) |
|
|||
|
|
| First normal data packet | t = 1.176619 s |
|
|||
|
|
|
|||
|
|
Total bind phase duration: **~0.824 s** (150 ms wait + ~674 ms for 400 packets at 2.07 ms each)
|
|||
|
|
|
|||
|
|
### 8.3 Normal Data Phase Timing
|
|||
|
|
|
|||
|
|
| Event | Interval |
|
|||
|
|
|-------|---------|
|
|||
|
|
| Packet 1 → 2 (startup) | 0.918 ms |
|
|||
|
|
| Packet 2 → 3 (startup) | 0.916 ms |
|
|||
|
|
| Packet 3 → 4 (startup) | 1.491 ms |
|
|||
|
|
| Steady state (4+) | **~2.070 ms** per packet |
|
|||
|
|
|
|||
|
|
The first 3 packets after bind-to-data transition have shorter intervals, then settle into the
|
|||
|
|
steady 2.07 ms period.
|
|||
|
|
|
|||
|
|
### 8.4 STATUS Byte Values Observed
|
|||
|
|
|
|||
|
|
| STATUS | Meaning | When seen |
|
|||
|
|
|--------|---------|-----------|
|
|||
|
|
| `0x0E` | TX FIFO not full, RX FIFO empty, all interrupts clear | Normal/idle |
|
|||
|
|
| `0x2E` | TX_DS = 1 (TX complete interrupt), TX FIFO not full | After each TX packet |
|
|||
|
|
|
|||
|
|
RX_DR (bit6) is **never set** in any STATUS byte — confirming no data is ever received.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. MPM Implementation Notes
|
|||
|
|
|
|||
|
|
### 9.1 Comparison with FQ777
|
|||
|
|
|
|||
|
|
The XBM-37 protocol is closely related to FQ777 but has the following differences:
|
|||
|
|
|
|||
|
|
| Property | FQ777 | XBM-37 | Comment|
|
|||
|
|
|----------|-------|---------|---------|
|
|||
|
|
| Bind count | 1,000 | **400** | works with either |
|
|||
|
|
| Bind packet B1 | `0x15` | `0x14` | different |
|
|||
|
|
| Bind packet B2 | `0x05` | `0x07` | different |
|
|||
|
|
| Bind packet B3 | `0x06` | `0x03` | different |
|
|||
|
|
| Hop channels | `4D 43 27 07` | **`49 34 26 07`** | different |
|
|||
|
|
| Data range | 0x00–0x64 | **0x00–0xE1** | different |
|
|||
|
|
| Bind address | `E7 E7 E7 E7 67` | `E7 E7 E7 E7 67` | (same) |
|
|||
|
|
| Checksum method | Sum B0–B6 | Sum B0–B6 | (same) |
|
|||
|
|
| Bind checksum | Sum B4–B6 | Sum B4–B6 | (same) |
|
|||
|
|
| ssv_pack_dpl encoding | Yes | Yes | (same) |
|
|||
|
|
| Air bitrate (nRF24L01+) | 250 kbps | 250 kbps | (same) |
|
|||
|
|
|
|||
|
|
### 9.2 Required Implementation Constants
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
static const uint8_t XBM37_bind_addr[] = {0xE7, 0xE7, 0xE7, 0xE7, 0x67};
|
|||
|
|
static const uint8_t XBM37_hop_channels[] = {0x49, 0x34, 0x26, 0x07};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9.3 Bind Packet Builder
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// Bind packet (bytes 4–6 = TX ID; B7 = checksum of B4+B5+B6)
|
|||
|
|
packet[0] = 0x20;
|
|||
|
|
packet[1] = 0x14;
|
|||
|
|
packet[2] = 0x07;
|
|||
|
|
packet[3] = 0x03;
|
|||
|
|
packet[4] = rx_tx_addr[0]; // TX_ID byte 0
|
|||
|
|
packet[5] = rx_tx_addr[1]; // TX_ID byte 1
|
|||
|
|
packet[6] = rx_tx_addr[2]; // TX_ID byte 2
|
|||
|
|
packet[7] = packet[4] + packet[5] + packet[6];
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9.4 Data Packet Builder
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
packet[0] = convert_channel_16b_limit(THROTTLE, 0xE1, 0x00);
|
|||
|
|
packet[1] = convert_channel_16b_limit(RUDDER, 0x00, 0xE1);
|
|||
|
|
packet[2] = convert_channel_16b_limit(AILERON, 0xE1, 0x00);
|
|||
|
|
packet[3] = convert_channel_16b_limit(ELEVATOR, 0xE1, 0x00);
|
|||
|
|
packet[4] = ((convert_channel_8b(CH13) * 63) / 255) + 1; // ele trim (01..20..40)
|
|||
|
|
packet[5] = 64 - (((uint32_t)convert_channel_8b(CH14) * 63 + 127) / 255); // ail trim (40..20..01)
|
|||
|
|
packet[5] |= GET_FLAG(OK_SW, 0x80); // OK button
|
|||
|
|
packet[6] = (rate_mode & 0x03) // bits[1:0] = rate (0/1/2)
|
|||
|
|
| GET_FLAG(LED_SW, 0x04) // bit2=1 = LED off
|
|||
|
|
| GET_FLAG(RTH_SW, 0x08) // bit3 = RTH
|
|||
|
|
| GET_FLAG(HEADLESS_SW, 0x10) // bit4 = headless
|
|||
|
|
| GET_FLAG(VIDEO_SW, 0x20) // bit5 = video
|
|||
|
|
| GET_FLAG(PHOTO_SW, 0x40) // bit6 = picture
|
|||
|
|
| GET_FLAG(FLIP_SW, 0x80); // bit7 = flip
|
|||
|
|
packet[7] = 0;
|
|||
|
|
for (uint8_t i = 0; i < 7; i++) packet[7] += packet[i]; // checksum
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9.5 Address Management
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// TX init:
|
|||
|
|
rx_tx_addr[2] = 0x00; // original hardcoded value now changes for model match capability (see below)
|
|||
|
|
rx_tx_addr[3] = 0xE7;
|
|||
|
|
rx_tx_addr[4] = 0x67;
|
|||
|
|
// rx_tx_addr[0] and [1] are random/unique to the TX
|
|||
|
|
// rx_tx_addr[2] now varies by changing receiver number (0-63) for model match
|
|||
|
|
|
|||
|
|
// Bind address (used during bind):
|
|||
|
|
NRF24L01_WriteRegisterMulti(NRF24L01_10_TX_ADDR, XBM37_bind_addr, 5);
|
|||
|
|
|
|||
|
|
// After 400th bind packet, switch to data address:
|
|||
|
|
NRF24L01_WriteRegisterMulti(NRF24L01_10_TX_ADDR, rx_tx_addr, 5);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9.6 RF Init
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
void XBM37_RF_init() {
|
|||
|
|
NRF24L01_Initialize();
|
|||
|
|
NRF24L01_WriteRegisterMulti(NRF24L01_10_TX_ADDR, XBM37_bind_addr, 5);
|
|||
|
|
NRF24L01_WriteReg(NRF24L01_01_EN_AA, 0x00); // no auto-ACK
|
|||
|
|
NRF24L01_WriteReg(NRF24L01_02_EN_RXADDR, 0x00); // no RX pipes
|
|||
|
|
NRF24L01_WriteReg(NRF24L01_1D_FEATURE, 0x04); // EN_DPL
|
|||
|
|
NRF24L01_WriteReg(NRF24L01_1C_DYNPD, 0x01); // DPL pipe 0
|
|||
|
|
NRF24L01_SetBitrate(NRF24L01_BR_250K); // 250 kbps on nRF24L01+
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9.7 Air Encoding
|
|||
|
|
|
|||
|
|
Because both the XBM-37 TX and RX use SV7241A (a BK2425 derivative), the air packet format is
|
|||
|
|
the SV7241A Enhanced ShockBurst encoding. When implementing with nRF24L01+, the same
|
|||
|
|
`ssv_pack_dpl()` function used in the FQ777 protocol must be applied to convert the raw 8-byte
|
|||
|
|
payload into the 12-byte packed representation that nRF24L01+ transmits at 250 kbps to produce
|
|||
|
|
a compatible on-air signal.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
*Analysis completed using Python scripts against the raw CSV captures. All packet checksums,
|
|||
|
|
channel sequences, bind counts, and register values verified programmatically.*
|