diff --git a/Lua_scripts/MultiChan.txt b/Lua_scripts/MultiChan.txt index 4979af07..c4ec7c81 100644 --- a/Lua_scripts/MultiChan.txt +++ b/Lua_scripts/MultiChan.txt @@ -95,7 +95,8 @@ 58,6,FX,A570,1,STOP,Rate,Color,LED,TrimR,TrimA,TrimE 20,0,FY326,FY326,1,Flip,RTH,HLess,Expert,Calib 20,1,FY326,FY319,1,Flip,RTH,HLess,Expert,Calib -23,0,FY326,FY326,1,Flip,RTH,HLess,Expert +23,0,FQ777,124,1,Flip,RTH,HLess,Expert +23,1,FQ777,XBM37,1,Rate,Flip,HLess,LED,Pict,Video,RTH,OK,ETrim,Atrim 47,0,GD00x,V1,1,Trim,LED,Rate 47,1,GD00x,V2,1,Trim,LED,Rate 32,0,GW008,FY326,1,Flip diff --git a/Multiprotocol/FQ777_nrf24l01.ino b/Multiprotocol/FQ777_nrf24l01.ino index 61d0a311..0585689a 100644 --- a/Multiprotocol/FQ777_nrf24l01.ino +++ b/Multiprotocol/FQ777_nrf24l01.ino @@ -31,6 +31,19 @@ enum { FQ777_FLAG_FLIP = 0x80, }; +enum { + XBM37_B5_OK = 0x80, + XBM37_B6_RATE_LOW = 0x00, + XBM37_B6_RATE_MID = 0x01, + XBM37_B6_RATE_HIGH = 0x02, + XBM37_B6_LED_OFF = 0x04, + XBM37_B6_RTH = 0x08, + XBM37_B6_HEADLESS = 0x10, + XBM37_B6_VIDEO = 0x20, + XBM37_B6_PICTURE = 0x40, + XBM37_B6_FLIP = 0x80, +}; + const uint8_t ssv_xor[] = {0x80,0x44,0x64,0x75,0x6C,0x71,0x2A,0x36,0x7C,0xF1,0x6E,0x52,0x9,0x9D,0x1F,0x78,0x3F,0xE1,0xEE,0x16,0x6D,0xE8,0x73,0x9,0x15,0xD7,0x92,0xE7,0x3,0xBA}; uint8_t FQ777_bind_addr [] = {0xe7,0xe7,0xe7,0xe7,0x67}; @@ -87,12 +100,21 @@ static void __attribute__((unused)) FQ777_send_packet() uint8_t packet_ori[8]; if (IS_BIND_IN_PROGRESS) { - // 4,5,6 = address fields - // last field is checksum of address fields + // Byte 0 is always 0x20; bytes 1-3 are sub-protocol specific packet_ori[0] = 0x20; - packet_ori[1] = 0x15; - packet_ori[2] = 0x05; - packet_ori[3] = 0x06; + if (sub_protocol == XBM37) + { + packet_ori[1] = 0x14; + packet_ori[2] = 0x07; + packet_ori[3] = 0x03; + } + else // FQ777 + { + packet_ori[1] = 0x15; + packet_ori[2] = 0x05; + packet_ori[3] = 0x06; + } + // Bytes 4-6: address fields, byte 7: checksum of address fields (shared by both protocols) packet_ori[4] = rx_tx_addr[0]; packet_ori[5] = rx_tx_addr[1]; packet_ori[6] = rx_tx_addr[2]; @@ -100,36 +122,64 @@ static void __attribute__((unused)) FQ777_send_packet() } else { - // throt, yaw, pitch, roll, trims, flags/left button,00,right button - //0-3 0x00-0x64 - //4 roll/pitch/yaw trims. cycles through one trim at a time - 0-40 trim1, 40-80 trim2, 80-C0 trim3 (center: A0 20 60) - //5 flags for throttle button, two buttons above throttle - def: 0x40 - //6 00 ?? - //7 checksum - add values in other fields + if (sub_protocol == XBM37) + { + packet_ori[0] = convert_channel_16b_limit(THROTTLE,0xE1,0); // reverse channel + packet_ori[1] = convert_channel_16b_limit(RUDDER,0,0xE1); + packet_ori[2] = convert_channel_16b_limit(AILERON,0xE1,0); // reverse channel + packet_ori[3] = convert_channel_16b_limit(ELEVATOR,0xE1,0); // reverse channel + packet_ori[4] = (convert_channel_8b(CH13) << 2) + 1; // ele trim (01..20..40) front ^ plus 0x01, back - minus 0x01 per click + packet_ori[5] = 0x3F - (convert_channel_8b(CH14) << 2) // ail trim (40..20..01) left <- plus 0x01, right -> minus 0x01 per click + | GET_FLAG(CH12_SW, XBM37_B5_OK); // bit7 = OK button ...unknown use for this model? - - // Trims are usually done through the radio configuration but leaving the code here just in case... - uint8_t trim_mod = packet_count % 144; - uint8_t trim_val = 0; - if (36 <= trim_mod && trim_mod < 72) // yaw - trim_val = 0x20; // don't modify yaw trim - else - if (108 < trim_mod && trim_mod) // pitch - trim_val = 0xA0; - else // roll - trim_val = 0x60; + uint8_t rate_bits; + if (CH5_SW) + rate_bits = XBM37_B6_RATE_HIGH; // high rate + else if (Channel_data[CH5] < CHANNEL_MIN_COMMAND) + rate_bits = XBM37_B6_RATE_LOW; // low rate + else + rate_bits = XBM37_B6_RATE_MID; // medium rate - packet_ori[0] = convert_channel_16b_limit(THROTTLE,0,0x64); - packet_ori[1] = convert_channel_16b_limit(RUDDER,0,0x64); - packet_ori[2] = convert_channel_16b_limit(ELEVATOR,0,0x64); - packet_ori[3] = convert_channel_16b_limit(AILERON,0,0x64); - packet_ori[4] = trim_val; // calculated above - packet_ori[5] = GET_FLAG(CH5_SW, FQ777_FLAG_FLIP) - | GET_FLAG(CH7_SW, FQ777_FLAG_HEADLESS) - | GET_FLAG(!CH6_SW, FQ777_FLAG_RETURN) - | GET_FLAG(CH8_SW,FQ777_FLAG_EXPERT); - packet_ori[6] = 0x00; - // calculate checksum + packet_ori[6] = rate_bits // bits[1:0] = rate + | GET_FLAG(CH8_SW, XBM37_B6_LED_OFF) // bit2 = LED off + | GET_FLAG(CH11_SW, XBM37_B6_RTH) // bit3 = RTH + | GET_FLAG(CH7_SW, XBM37_B6_HEADLESS) // bit4 = headless + | GET_FLAG(CH10_SW, XBM37_B6_VIDEO) // bit5 = video + | GET_FLAG(CH9_SW, XBM37_B6_PICTURE) // bit6 = picture + | GET_FLAG(CH6_SW, XBM37_B6_FLIP); // bit7 = flip + } + else // FQ777 + { + // throt, yaw, pitch, roll, trims, flags/left button,00,right button + //0-3 0x00-0x64 + //4 roll/pitch/yaw trims. cycles through one trim at a time - 0-40 trim1, 40-80 trim2, 80-C0 trim3 (center: A0 20 60) + //5 flags for throttle button, two buttons above throttle - def: 0x40 + //6 00 ?? + //7 checksum - add values in other fields + + // Trims are usually done through the radio configuration but leaving the code here just in case... + uint8_t trim_mod = packet_count % 144; + uint8_t trim_val = 0; + if (36 <= trim_mod && trim_mod < 72) // yaw + trim_val = 0x20; // don't modify yaw trim + else + if (108 < trim_mod && trim_mod) // pitch + trim_val = 0xA0; + else // roll + trim_val = 0x60; + + packet_ori[0] = convert_channel_16b_limit(THROTTLE,0,0x64); + packet_ori[1] = convert_channel_16b_limit(RUDDER,0,0x64); + packet_ori[2] = convert_channel_16b_limit(ELEVATOR,0,0x64); + packet_ori[3] = convert_channel_16b_limit(AILERON,0,0x64); + packet_ori[4] = trim_val; // calculated above + packet_ori[5] = GET_FLAG(CH5_SW, FQ777_FLAG_FLIP) + | GET_FLAG(CH7_SW, FQ777_FLAG_HEADLESS) + | GET_FLAG(!CH6_SW, FQ777_FLAG_RETURN) + | GET_FLAG(CH8_SW,FQ777_FLAG_EXPERT); + packet_ori[6] = 0x00; + } + // Data packet checksum: sum of bytes 0-6 (not applied to bind packets) uint8_t checksum = 0; for (int i = 0; i < 7; ++i) checksum += packet_ori[i]; @@ -181,12 +231,17 @@ void FQ777_init(void) BIND_IN_PROGRESS; // autobind protocol bind_counter = FQ777_BIND_COUNT; packet_count=0; - hopping_frequency[0] = 0x4D; - hopping_frequency[1] = 0x43; - hopping_frequency[2] = 0x27; + // Sub-protocol hop channel table: first 3 channels differ; channel[3]=0x07 is shared + static const uint8_t hop_ch[2][3] = { + {0x4D, 0x43, 0x27}, // FQ777 + {0x49, 0x34, 0x26}, // XBM-37 + }; + hopping_frequency[0] = hop_ch[sub_protocol][0]; + hopping_frequency[1] = hop_ch[sub_protocol][1]; + hopping_frequency[2] = hop_ch[sub_protocol][2]; hopping_frequency[3] = 0x07; hopping_frequency_no=0; - rx_tx_addr[2] = 0x00; + rx_tx_addr[2] = (sub_protocol == XBM37) ? (RX_num & 0x3F) : 0x00; // XBM-37 uses receiver number (0-63) for model match capability rx_tx_addr[3] = 0xe7; rx_tx_addr[4] = 0x67; FQ777_RF_init(); diff --git a/Multiprotocol/Multi.txt b/Multiprotocol/Multi.txt index 8bc00a8d..a7fb6076 100644 --- a/Multiprotocol/Multi.txt +++ b/Multiprotocol/Multi.txt @@ -20,7 +20,7 @@ 20,FY326,FY326,FY319 21,Futaba,SFHSS 22,J6PRO -23,FQ777 +23,FQ777,124,XBM37 24,ASSAN 25,FrskyV 26,HONTAI,HONTAI,JJRCX1,X5C1,FQ777_951,XKK170 diff --git a/Multiprotocol/Multi_Protos.ino b/Multiprotocol/Multi_Protos.ino index b33dc5a2..b7b3b59c 100644 --- a/Multiprotocol/Multi_Protos.ino +++ b/Multiprotocol/Multi_Protos.ino @@ -147,6 +147,7 @@ const char STR_SUBTYPE_MT99[] = "\x06""MT99\0 ""H7\0 ""YZ\0 ""LS\0 " const char STR_SUBTYPE_MT992[] = "\x04""PA18""SU35"; const char STR_SUBTYPE_MJXQ[] = "\x07""WLH08\0 ""X600\0 ""X800\0 ""H26D\0 ""E010\0 ""H26WH\0 ""Phoenix"; const char STR_SUBTYPE_FY326[] = "\x05""Std\0 ""FY319"; +const char STR_SUBTYPE_FQ777[] = "\x05""124\0 ""XBM37"; const char STR_SUBTYPE_HONTAI[] = "\x06""Std\0 ""JJRCX1""X5C1\0 ""FQ_951""XKK170"; const char STR_SUBTYPE_AFHDS2A[] = "\x08""PWM,IBUS""PPM,IBUS""PWM,SBUS""PPM,SBUS""Gyro_Off""Gyro_On\0""G_On_Rev"; const char STR_SUBTYPE_Q2X2[] = "\x04""Q222""Q242""Q282"; @@ -314,7 +315,7 @@ const mm_protocol_definition multi_protocols[] = { {PROTO_AFHDS2A_RX, STR_AFHDS2A_RX,STR_CPPM, NBR_CPPM, OPTION_NONE, 0, 0, SW_A7105, AFHDS2A_RX_init, AFHDS2A_RX_callback }, #endif #if defined(FQ777_NRF24L01_INO) - {PROTO_FQ777, STR_FQ777, NO_SUBTYPE, 0, OPTION_NONE, 0, 0, SW_NRF, FQ777_init, FQ777_callback }, + {PROTO_FQ777, STR_FQ777, STR_SUBTYPE_FQ777, 2, OPTION_NONE, 0, 0, SW_NRF, FQ777_init, FQ777_callback }, #endif //OpenTX 2.3.x issue: DO NOT CHANGE ORDER below #if defined(FRSKY_RX_CC2500_INO) diff --git a/Multiprotocol/Multiprotocol.h b/Multiprotocol/Multiprotocol.h index c71ce3eb..18832a08 100644 --- a/Multiprotocol/Multiprotocol.h +++ b/Multiprotocol/Multiprotocol.h @@ -19,7 +19,7 @@ #define VERSION_MAJOR 1 #define VERSION_MINOR 3 #define VERSION_REVISION 4 -#define VERSION_PATCH_LEVEL 56 +#define VERSION_PATCH_LEVEL 58 #define MODE_SERIAL 0 @@ -418,6 +418,11 @@ enum ESKY ESKY_STD = 0, ESKY_ET4 = 1, }; +enum FQ777 +{ + FQ777 = 0, + XBM37 = 1, +}; enum FRSKY_RX { FRSKY_RX = 0, diff --git a/Multiprotocol/_Config.h b/Multiprotocol/_Config.h index 17ccfdd3..5e3ba599 100644 --- a/Multiprotocol/_Config.h +++ b/Multiprotocol/_Config.h @@ -654,7 +654,8 @@ const PPM_Parameters PPM_prot[14*NBR_BANKS]= { V912 CX20 PROTO_FQ777 - NONE + FQ777 + XBM37 PROTO_FRSKY_RX FRSKY_RX FRSKY_CLONE diff --git a/Protocols_Details.md b/Protocols_Details.md index 3e427f80..1bd7ce25 100644 --- a/Protocols_Details.md +++ b/Protocols_Details.md @@ -90,7 +90,7 @@ CFlie|AIR|38|CFlie||||||||NRF24L01| [Flysky](Protocols_Details.md#FLYSKY---1)||1|Flysky|V9x9|V6x6|V912|CX20||||A7105| [Flysky AFHDS2A](Protocols_Details.md#FLYSKY-AFHDS2A---28)||28|PWM_IBUS|PPM_IBUS|PWM_SBUS|PPM_SBUS|Gyro_Off|Gyro_On|Gyro_On_Rev||A7105| [Flysky AFHDS2A RX](Protocols_Details.md#FLYSKY-AFHDS2A-RX---56)||56|Multi|CPPM|||||||A7105| -[FQ777](Protocols_Details.md#FQ777---23)||23|||||||||NRF24L01|SSV7241 +[FQ777](Protocols_Details.md#FQ777---23)||23|124|XBM37|||||||NRF24L01|SSV7241 [FrskyD](Protocols_Details.md#FRSKYD---3)||3|D8|Cloned|||||||CC2500| [FrskyL](Protocols_Details.md#FRSKYL---67)||67|LR12|LR12 6CH|||||||CC2500| [FrskyR9](Protocols_Details.md#FRSKYR9---65)||65|FrskyR9|R9_915|R9_868||||||SX1276| @@ -1938,12 +1938,29 @@ A|E|T|R|FLIP|RTH|HEADLESS|EXPERT|CALIBRATE Model: X6 FY319 Quadcopter (Needs Testing) ## FQ777 - *23* +Autobind protocol + +### Sub_protocol 124 - *0* Model: FQ777-124 (with SV7241A) CH1|CH2|CH3|CH4|CH5|CH6|CH7|CH8 ---|---|---|---|---|---|---|--- A|E|T|R|FLIP|RTH|HEADLESS|EXPERT +### Sub_protocol XBM37 - *1* +Model: T-Smart XBM-37 (with SV7241A) + +CH1|CH2|CH3|CH4|CH5|CH6|CH7|CH8|CH9|CH10|CH11|CH12|CH13|CH14 +---|---|---|---|---|---|---|---|---|---|---|---|---|--- +A|E|T|R|RATE|FLIP|HEADLESS|LED|PICTURE|VIDEO|RTH|OK|ETrim|Atrim + + - RATE: -100% Low, 0% Mid, +100% High + - CH6 to CH12 are OFF/-100 and ON/+100 exception LED is OFF/+100 + - Receiver numbers (0-63) available for model match, MPM global ID used for unique module identifier. + - Changing the TX module or RX number will require re-binding the receiver. + +Calibration: Set quad on level surface, pull both sticks down in outer corners until LED's flash (Mode-2 TX) + ## GW008 - *32* Model: Global Drone GW008 from Banggood diff --git a/docs/XBM-37_Analysis.md b/docs/XBM-37_Analysis.md new file mode 100644 index 00000000..e04341ad --- /dev/null +++ b/docs/XBM-37_Analysis.md @@ -0,0 +1,670 @@ +# 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] = → 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.*