diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c5a538f8c..3db246a52 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -63,6 +63,7 @@ jobs:
# Set the environment variables
env:
BOARD: ${{ matrix.board }}
+ MULTI_MAX_BIN_SIZE: 119808
steps:
- uses: actions/checkout@v4
@@ -120,6 +121,7 @@ jobs:
echo "CYRF6936_PROTOCOLS=$(echo $CYRF6936_PROTOCOLS)" >> $GITHUB_ENV
echo "NRF24L01_PROTOCOLS=$(echo $NRF24L01_PROTOCOLS)" >> $GITHUB_ENV
echo "SX1276_PROTOCOLS=$(echo $SX1276_PROTOCOLS)" >> $GITHUB_ENV
+ echo "LT8910_PROTOCOLS=$(echo $LT8910_PROTOCOLS)" >> $GITHUB_ENV
echo "CCNRF_INO_PROTOCOLS=$(echo $CCNRF_INO_PROTOCOLS)" >> $GITHUB_ENV
echo "ALL_PROTOCOLS=$(echo $ALL_PROTOCOLS)" >> $GITHUB_ENV
@@ -232,6 +234,9 @@ jobs:
- board: "multi4in1:STM32F1:multistm32f103cb:debug_option=ftdi"
name: "STM32F103 (128KB, Serial Debug)"
release: "stm32f103-128k-serial-debug"
+ - board: "multi4in1:STM32F1:multistm32f103cb:debug_option=none"
+ name: "STM32F103 LT8910 (128KB)"
+ release: "stm32f103-128k-lt8910"
- board: "multi4in1:STM32F1:multistm32f103cb:debug_option=none"
name: "STM32F103 5-in-1 (128KB)"
release: "stm32f103-128k-5in1"
@@ -252,6 +257,7 @@ jobs:
env:
BOARD: ${{ matrix.board }}
RELEASE: ${{ matrix.release }}
+ MULTI_MAX_BIN_SIZE: 119808
steps:
- uses: actions/checkout@v4
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..aa8c1d460
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+Captures_TTX610_Tx/extracted/
+_codeql_detected_source_root
+build/
+*.bak
+bin/
diff --git a/Lua_scripts/MultiChan.txt b/Lua_scripts/MultiChan.txt
index bab4fae0f..13d4fbc88 100644
--- a/Lua_scripts/MultiChan.txt
+++ b/Lua_scripts/MultiChan.txt
@@ -175,6 +175,7 @@
11,4,SLT,MR100,0,Rates,n-a,CH7,CH8,Mode,Flip,Video,Pict
11,5,SLT,V1_4CH,0
11,6,SLT,RF_SIM,0,CH5,CH6,CH7,CH8,CH9,CH10
+11,7,SLT,SLT6_Tx,0,FMODE,PANIC
10,0,Symax,Std,1,Flip,Rates,Pict,Video,HLess
10,1,Symax,X5C,1,Flip,Rates,Pict,Video,HLess
43,0,Traxxas,TQ2,0
@@ -235,3 +236,4 @@
104,0,KAMTOM,Std,0,ST_Tr,TH_Tr,TH_DR
105,0,Shenqi2,Std,1
106,0,WL91x,Std,0
+108,0,Ares,6HPA_Tx,0,CH5,CH6
diff --git a/Multiprotocol/Ares_cc2500.ino b/Multiprotocol/Ares_cc2500.ino
new file mode 100644
index 000000000..750dbdc3d
--- /dev/null
+++ b/Multiprotocol/Ares_cc2500.ino
@@ -0,0 +1,254 @@
+/*
+ This project is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Multiprotocol is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Multiprotocol. If not, see .
+ */
+// Compatible with ARES 6HPA transmitter
+
+#if defined(ARES_CC2500_INO)
+
+#include "iface_cc2500.h"
+
+//#define ARES_FORCE_ID
+
+#define ARES_COARSE 0
+
+#define ARES_PACKET_LEN 17
+#define ARES_NUM_FREQUENCIES 60
+
+enum {
+ ARES_START = 0x00,
+ ARES_CALIB = 0x01,
+ ARES_PREP = 0x02,
+ ARES_DATA = 0x03,
+};
+
+// CC2500 register init values captured from the ARES 6HPA transmitter
+const PROGMEM uint8_t ARES_init_values[] = {
+ /* 00 */ 0x06, 0x2E, 0x2E, 0x07, 0x5A, 0x60, 0x30, 0x04,
+ /* 08 */ 0x05, 0x00, 0x00, 0x06, 0x00, 0x5C, 0xB1, 0x3B + ARES_COARSE,
+ /* 10 */ 0x6A, 0xF8, 0x03, 0x23, 0x7A, 0x44, 0x07, 0x30,
+ /* 18 */ 0x18, 0x16, 0x6C, 0x43, 0x40, 0x91, 0x87, 0x6B,
+ /* 20 */ 0xF8, 0x56, 0x10, 0xA9, 0x0A, 0x00, 0x11
+};
+
+// Fixed hopping sequence captured from the ARES 6HPA transmitter.
+// This is a permutation of 60 channel values spread across the band.
+static const PROGMEM uint8_t ARES_hop[] = {
+ 0xB0, 0x6F, 0x1D, 0xB4, 0x74, 0x20, 0xB8, 0xD8,
+ 0x24, 0xBC, 0xDC, 0x28, 0x48, 0xE0, 0x2C, 0x4C,
+ 0xE4, 0x90, 0x50, 0xE8, 0x94, 0x54, 0xEC, 0x00,
+ 0x98, 0x58, 0x04, 0x9B, 0x5C, 0x08, 0xA0, 0xC0,
+ 0x0C, 0xA4, 0xC3, 0x10, 0x30, 0xC6, 0x14, 0x34,
+ 0xCC, 0x78, 0x38, 0xD0, 0x7C, 0x3C, 0xD4, 0x80,
+ 0x40, 0x60, 0x84, 0x44, 0x64, 0x88, 0xA8, 0x68,
+ 0x8C, 0xAC, 0x6C, 0x18
+};
+
+static void __attribute__((unused)) ARES_CC2500_init()
+{
+ CC2500_Strobe(CC2500_SRES);
+ delayMilliseconds(1);
+ CC2500_Strobe(CC2500_SIDLE);
+
+ for (uint8_t i = 0; i < 39; ++i)
+ CC2500_WriteReg(i, pgm_read_byte_near(&ARES_init_values[i]));
+
+ CC2500_WriteReg(CC2500_0C_FSCTRL0, option);
+ prev_option = option;
+
+ // Write PATABLE to max power (0xFF for all 8 entries) as captured
+ for (uint8_t i = 0; i < 8; i++)
+ CC2500_WriteReg(CC2500_3E_PATABLE, 0xFF);
+
+ CC2500_SetTxRxMode(TX_EN);
+ CC2500_SetPower();
+}
+
+// Load hopping table
+static void __attribute__((unused)) ARES_RF_channels()
+{
+ for (uint8_t i = 0; i < ARES_NUM_FREQUENCIES; i++)
+ hopping_frequency[i] = pgm_read_byte_near(&ARES_hop[i]);
+}
+
+static void __attribute__((unused)) ARES_tune_chan()
+{
+ CC2500_Strobe(CC2500_SIDLE);
+ CC2500_WriteReg(CC2500_0A_CHANNR, hopping_frequency[hopping_frequency_no]);
+ CC2500_Strobe(CC2500_SFTX);
+ CC2500_Strobe(CC2500_SCAL);
+}
+
+static void __attribute__((unused)) ARES_change_chan_fast()
+{
+ CC2500_Strobe(CC2500_SIDLE);
+ CC2500_WriteReg(CC2500_0A_CHANNR, hopping_frequency[hopping_frequency_no]);
+ CC2500_WriteReg(CC2500_25_FSCAL1, calData[hopping_frequency_no]);
+}
+
+// Advance the hop counter: cycles through 0-58 with step, inserting 59 when wrapping through 0
+static uint8_t __attribute__((unused)) ARES_next_counter(uint8_t current, uint8_t step)
+{
+ if (current == 59)
+ return 0;
+ uint8_t next = (current + step) % 59;
+ if (next == 0)
+ return 59;
+ return next;
+}
+
+static void __attribute__((unused)) ARES_build_packet()
+{
+ // Length byte: 16 data bytes follow
+ packet[0] = 0x10;
+
+ // TX ID
+ packet[1] = rx_tx_addr[1];
+ packet[2] = rx_tx_addr[2];
+ packet[3] = rx_tx_addr[3];
+
+ // 6 channels encoded as interleaved 12-bit values in bytes 4-12
+ uint16_t ch[6];
+ for (uint8_t i = 0; i < 6; i++)
+ ch[i] = convert_channel_16b_nolimit(i, 1820, 3300, false);
+
+ packet[4] = ch[0] >> 4;
+ packet[5] = ((ch[0] & 0x0F) << 4) | (ch[1] & 0x0F);
+ packet[6] = ch[1] >> 4;
+ packet[7] = ch[2] >> 4;
+ packet[8] = ((ch[2] & 0x0F) << 4) | (ch[3] & 0x0F);
+ packet[9] = ch[3] >> 4;
+ packet[10] = ch[4] >> 4;
+ packet[11] = ((ch[4] & 0x0F) << 4) | (ch[5] & 0x0F);
+ packet[12] = ch[5] >> 4;
+
+ // Byte 16: counter step size (stored in crc, set to 1-58 in ARES_init)
+ uint8_t step = crc;
+
+ // Bytes 13-15: running counter with rotating bit 7 frame indicator
+ // The counter cycles 0-58 with a step, inserting 59 before wrapping to 0
+ // Each group of 3 packets has 3 consecutive counter values
+ // packet_count holds the current counter value
+ uint8_t c0 = packet_count;
+ uint8_t c1 = ARES_next_counter(c0, step);
+ uint8_t c2 = ARES_next_counter(c1, step);
+
+ // Frame indicator: each data frame is sent 3 times
+ // bind_phase tracks position 0/1/2 within the group of 3
+ packet[13] = c0;
+ packet[14] = c1;
+ packet[15] = c2;
+ packet[16] = step;
+
+ // Set the rotating frame bit (bit 7) on one of bytes 13-15
+ switch (bind_phase)
+ {
+ case 0:
+ packet[13] |= 0x80;
+ break;
+ case 1:
+ packet[14] |= 0x80;
+ break;
+ case 2:
+ packet[15] |= 0x80;
+ break;
+ }
+}
+
+static void __attribute__((unused)) ARES_send_packet()
+{
+ ARES_change_chan_fast();
+ CC2500_SetPower();
+ CC2500_WriteData(packet, ARES_PACKET_LEN);
+}
+
+#define ARES_PACKET_PERIOD 6670 // 6.67ms between packets
+#define ARES_PREP_TIMING 2000
+
+uint16_t ARES_callback()
+{
+ switch(phase)
+ {
+ case ARES_START:
+ ARES_CC2500_init();
+ hopping_frequency_no = 0;
+ bind_phase = 0;
+ ARES_tune_chan();
+ phase = ARES_CALIB;
+ return ARES_PREP_TIMING;
+ case ARES_CALIB:
+ calData[hopping_frequency_no] = CC2500_ReadReg(CC2500_25_FSCAL1);
+ hopping_frequency_no++;
+ if (hopping_frequency_no < ARES_NUM_FREQUENCIES)
+ ARES_tune_chan();
+ else
+ {
+ hopping_frequency_no = 0;
+ phase = ARES_PREP;
+ }
+ return ARES_PREP_TIMING;
+ case ARES_PREP:
+ if (prev_option != option)
+ {
+ phase = ARES_START;
+ return ARES_PREP_TIMING;
+ }
+ #ifdef MULTI_SYNC
+ telemetry_set_input_sync(ARES_PACKET_PERIOD);
+ #endif
+ ARES_build_packet();
+ phase = ARES_DATA;
+ // Fall through
+ case ARES_DATA:
+ ARES_send_packet();
+ hopping_frequency_no++;
+ if (hopping_frequency_no >= ARES_NUM_FREQUENCIES)
+ hopping_frequency_no = 0;
+ bind_phase++;
+ if (bind_phase >= 3)
+ {
+ bind_phase = 0;
+ // Advance counter to start of next group
+ uint8_t step = crc;
+ packet_count = ARES_next_counter(packet_count, step);
+ packet_count = ARES_next_counter(packet_count, step);
+ packet_count = ARES_next_counter(packet_count, step);
+ }
+ phase = ARES_PREP;
+ return ARES_PACKET_PERIOD;
+ }
+ return 0;
+}
+
+void ARES_init()
+{
+ BIND_DONE; // Autobind protocol - no TX-initiated bind phase
+ ARES_RF_channels();
+
+ // rx_tx_addr[1] and [2] are already set from MProtocol_id by the framework
+ // RX_num (0-63) in byte 3 provides model match
+ rx_tx_addr[3] = RX_num;
+
+ // Counter step and start from capture
+ crc = 23;
+ packet_count = 35;
+
+ #ifdef ARES_FORCE_ID
+ rx_tx_addr[1] = 0xDC;
+ rx_tx_addr[2] = 0xCC;
+ rx_tx_addr[3] = 0x00;
+ #endif
+ phase = ARES_START;
+}
+
+#endif
diff --git a/Multiprotocol/CG022_lt8910.ino b/Multiprotocol/CG022_lt8910.ino
new file mode 100644
index 000000000..3b70a22b9
--- /dev/null
+++ b/Multiprotocol/CG022_lt8910.ino
@@ -0,0 +1,404 @@
+/*
+ This project is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Multiprotocol is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Multiprotocol. If not, see .
+ */
+// Ao-Sen-Ma CG022 quad copter protocol via external LT8910 RF board
+// Protocol decoded from stock CG022 TX SPI captures
+//
+// Hardware wiring:
+// LT8910 RF board connects to MPM breakout pins:
+// - MOSI (PB15) → LT8910 MOSI
+// - MISO (PB14) → LT8910 MISO
+// - SCK (PB13) → LT8910 SCK
+// - CS (PA15/SPI_CSN) → LT8910 CS
+// - 3.3V, GND
+// - RESET (PA14/LT8910_RST) → LT8910 RET pin
+//
+// PA14 (SWCLK) is available after afio_cfg_debug_ports(AFIO_DEBUG_NONE).
+
+#if defined(CG022_LT8910_INO)
+
+#include "iface_lt8910.h"
+
+#define CG022_PACKET_PERIOD 2310 // ~2310us per hop (from captures)
+#define CG022_BIND_COUNT 166 // ~0.38s of bind packets (capture 02 with RX present)
+#define CG022_PAYLOAD_SIZE 10 // 10 bytes including length prefix
+#define CG022_NUM_CHANNELS 8
+
+// Channel hopping sequence from stock TX capture
+static const uint8_t CG022_hop_channels[CG022_NUM_CHANNELS] = {
+ 0, 40, 10, 50, 20, 60, 30, 70
+};
+
+// Stock TX register initialization values from capture 02b
+// Format: { register_address (byte), value_high (byte), value_low (byte) }
+static const uint8_t PROGMEM CG022_init_regs[] = {
+ 0x00, 0x6F, 0xE0, // Chip ID (stock TX writes this during init)
+ 0x01, 0x56, 0x81,
+ 0x02, 0x66, 0x17,
+ 0x04, 0x9C, 0xC9, // RSSI
+ 0x05, 0x66, 0x37,
+ 0x07, 0x00, 0x00, // Channel=0, TX off
+ 0x08, 0x6C, 0x90,
+ 0x09, 0x48, 0x00, // PA control
+ 0x0A, 0x7F, 0xFD,
+ 0x0B, 0x00, 0x08,
+ 0x0C, 0x00, 0x00,
+ 0x0D, 0x48, 0xBD,
+ 0x16, 0x00, 0xFF, // RX/PLL
+ 0x17, 0x80, 0x05, // CRC polynomial
+ 0x18, 0x00, 0x67, // Preamble (3 bytes)
+ 0x19, 0x16, 0x59,
+ 0x1A, 0x19, 0xE0,
+ 0x1B, 0x13, 0x00,
+ 0x1C, 0x18, 0x00,
+ 0x20, 0x48, 0x00, // Sync word 1
+ 0x21, 0x3F, 0xC7, // Sync word 2
+ 0x22, 0x20, 0x00, // Sync word 3
+ 0x23, 0x03, 0x00, // Packet config: CRC_INITIAL_DATA_EN + CRC_ON
+ 0x24, 0x22, 0x11, // Sync word 4
+ 0x25, 0x06, 0x8C, // Sync word 5
+ 0x26, 0x5A, 0x5A, // Sync word 6
+ 0x27, 0x00, 0x33, // Sync word 7
+ 0x28, 0x44, 0x02, // CRC seed
+ 0x29, 0xB0, 0x00, // TX power level 11/15
+ 0x2A, 0xFD, 0xB0,
+ 0x2B, 0x00, 0x0F,
+};
+#define CG022_NUM_INIT_REGS (sizeof(CG022_init_regs) / 3)
+
+static bool cg022_first_packet = true;
+static bool cg022_post_bind_sync = false;
+static uint8_t cg022_txid[3] = { 0x00, 0x00, 0x00 };
+
+static void __attribute__((unused)) CG022_restart_bind_sequence()
+{
+ // Restore bind-time sync words and channel before sending bind packets again.
+ LT8910_WriteReg(LT8910_REG_CHANNEL, 0x0000);
+ LT8910_WriteReg(LT8910_REG_SYNCWORD4, (uint16_t)(rx_tx_addr[1] << 8) | rx_tx_addr[0]);
+ LT8910_WriteReg(LT8910_REG_SYNCWORD7, (uint16_t)rx_tx_addr[2]);
+ LT8910_WriteReg(LT8910_REG_FIFO, 0x0000);
+ bind_counter = CG022_BIND_COUNT;
+ cg022_first_packet = true;
+ cg022_post_bind_sync = false;
+ hopping_frequency_no = 0;
+}
+
+static void __attribute__((unused)) CG022_prepare_hardware()
+{
+ // Deselect all 4-in-1 onboard chips before using the external LT8910.
+ // MOSI/MISO/SCK are shared; any chip with CS low would drive the bus.
+ #ifdef CC25_CSN_pin
+ CC25_CSN_on;
+ #endif
+ #ifdef NRF_CSN_pin
+ NRF_CSN_on;
+ #endif
+ #ifdef A7105_CSN_pin
+ A7105_CSN_on;
+ #endif
+ #ifdef CYRF_CSN_pin
+ CYRF_CSN_on;
+ #endif
+
+ // Switch SPI2 to Mode 1 (CPOL=0, CPHA=1) for LT8900/LT8910.
+ // The LT8910 samples MOSI on the falling SCK edge (Mode 1).
+ // initSPI2() sets Mode 0 which shifts every byte by 1 bit.
+ // Set SPI clock to /256 (~140kHz) to match stock TX's ~114kHz bit-bang SPI.
+ // At /256, each 3-byte SPI transaction takes ~196µs (stock TX: ~223µs),
+ // naturally providing the inter-write timing the LT8910 needs without
+ // explicit delays. Faster clocks (e.g. /16 = 2.25MHz) produce 14µs
+ // transactions that are too fast for the LT8910 to process (MISO stays
+ // 0xFF = chip not responding).
+ SPI_DISABLE();
+ SPI2_BASE->CR1 |= SPI_CR1_CPHA;
+ SPI2_BASE->CR1 &= ~SPI_CR1_BR;
+ SPI2_BASE->CR1 |= (7 << 3); // BR=111 = /256 = ~140kHz
+ SPI_ENABLE();
+
+ // Drive MOSI HIGH to match stock TX idle state before first SPI data.
+ // Stock TX (02a) has MOSI HIGH for ~500ms (during hardware RESET)
+ // before writing the first register. STM32 hardware SPI idles MOSI
+ // LOW after mode switch. Clock out 0xFF with CS deselected — the
+ // LT8910 ignores SPI when CS is HIGH, but MOSI stays HIGH (last bit
+ // was 1), matching the stock TX's pre-init MOSI state.
+ SPI_CSN_on;
+ SPI_Write(0xFF);
+ while(SPI2_BASE->SR & SPI_SR_BSY);
+}
+
+static void __attribute__((unused)) CG022_LT8910_init()
+{
+ // Hardware RESET via PA14 (LT8910_RST_pin) — replicates stock TX behavior.
+ // Stock TX (capture 02a) drives RET LOW for 500ms before any SPI:
+ // RET=0 at t=60.275ms, RET=1 at t=561.317ms, first SPI at t=567.180ms.
+ // The hardware RESET initializes the LT8910's SPI interface including the
+ // MISO output driver, which may not be activated by software reset alone.
+ // LT8910_RST_pin (PA14) is a dedicated line wired to STM32 package pin 37.
+ // PKT is the LT8910 packet-status/IRQ output and stays unconnected here,
+ // matching the stock TX hardware described in docs/LT8910_CG022_Implementation.md.
+#ifdef LT8910_RST_HI
+ // Ensure RET is HIGH before starting reset sequence.
+ // By driving HIGH for 50ms, we ensure the chip fully exits any prior
+ // reset state and stabilizes before we start the controlled 500ms LOW
+ // reset pulse.
+ LT8910_RST_HI; // Ensure RET HIGH — chip fully out of reset
+ delayMilliseconds(50); // Extended settle time for rebind (was 10ms)
+ LT8910_RST_LO; // Drive RET LOW — begin hardware reset
+ delayMilliseconds(480); // Hold LOW for 480ms; with the 50ms pre-pulse and 6ms settle this matches the stock reset timing budget.
+ LT8910_RST_HI; // Release RET HIGH — chip exits reset
+ delayMilliseconds(6); // 6ms settling before first SPI (stock TX: 5.9ms)
+#else
+ // Fallback: software reset if LT8910_RST_HI is not defined (e.g. AVR boards).
+ LT8910_WriteReg(LT8910_REG_CHANNEL, LT8910_CHIP_RST);
+ delayMilliseconds(100);
+#endif
+
+ // Write all init registers from stock TX capture.
+ // Stock TX (02b) has ~228µs gaps between every SPI transaction.
+ // At /256, each LT8910_WriteReg() takes ~196µs on the wire, so
+ // back-to-back calls produce ~200µs start-to-start spacing that
+ // closely matches the stock TX's 228µs (slow 8-bit MCU SPI) timing.
+ for(uint8_t i = 0; i < CG022_NUM_INIT_REGS; i++)
+ {
+ uint8_t reg = pgm_read_byte_near(&CG022_init_regs[i * 3]);
+ uint16_t val = ((uint16_t)pgm_read_byte_near(&CG022_init_regs[i * 3 + 1]) << 8)
+ | pgm_read_byte_near(&CG022_init_regs[i * 3 + 2]);
+ LT8910_WriteReg(reg, val);
+ }
+ // Match stock TX post-init sequence (02b transactions [31]-[32]):
+ // Clear FIFO, wait 12ms for PLL settling, then read CRC seed register.
+ // Stock TX 02b has an 11.9ms gap between FIFO clear and CRC read.
+ LT8910_WriteReg(LT8910_REG_FIFO, 0x0000);
+ delayMilliseconds(12);
+ LT8910_ReadReg(LT8910_REG_CRC_SEED);
+}
+
+static void __attribute__((unused)) CG022_reset_lt8910_for_bind()
+{
+ // Reinitialize LT8910 and reset bind state to match power-on autobind.
+ CG022_prepare_hardware();
+ CG022_LT8910_init();
+ if(!LT8910_DetectChip())
+ {
+ SUB_PROTO_INVALID;
+ BIND_DONE;
+ return;
+ }
+ CG022_restart_bind_sequence();
+}
+
+static void __attribute__((unused)) CG022_write_fifo_post_bind(const uint8_t *data, uint8_t length)
+{
+ // Stock TX (02b) clears FIFO, updates sync words 4/7, then loads first data payload.
+ LT8910_WriteReg(LT8910_REG_FIFO, 0x0000);
+ LT8910_WriteReg(LT8910_REG_SYNCWORD4, (uint16_t)(cg022_txid[1] << 8) | cg022_txid[0]);
+ // Syncword7 uses the low byte only; the high byte is unused by the LT8910 for this protocol.
+ LT8910_WriteReg(LT8910_REG_SYNCWORD7, (uint16_t)cg022_txid[2]);
+ LT8910_WriteReg(LT8910_REG_FIFO_CTRL, 0x8080);
+ for(uint8_t i = 0; i < length; i += 2)
+ {
+ uint16_t word = (uint16_t)data[i] << 8;
+ if(i + 1 < length)
+ word |= data[i + 1];
+ LT8910_WriteReg(LT8910_REG_FIFO, word);
+ }
+}
+
+static void __attribute__((unused)) CG022_write_fifo_no_clear(const uint8_t *data, uint8_t length)
+{
+ // Stock TX does not clear FIFO between the CRC seed read and the first bind payload.
+ LT8910_WriteReg(LT8910_REG_FIFO_CTRL, 0x8080);
+ for(uint8_t i = 0; i < length; i += 2)
+ {
+ uint16_t word = (uint16_t)data[i] << 8;
+ if(i + 1 < length)
+ word |= data[i + 1];
+ LT8910_WriteReg(LT8910_REG_FIFO, word);
+ }
+}
+
+static void __attribute__((unused)) CG022_send_bind_packet()
+{
+ // Unique bind payloads from four stock TX captures:
+ // TX1 (02b): 0A 00 11 22 33 06 AB FC AD 00
+ // TX2 (22b): 0A 00 11 22 33 FB E0 FC D7 00
+ // TX3 (32b): 0A 00 11 22 33 F4 B9 FA A7 00
+ // TX4 (42b): 0A 00 11 22 33 14 D2 F9 DF 00
+ // Bind payload from stock TX captures (02b/22b/32b/42b):
+ // 0A 00 11 22 33 xx yy zz cc 00
+ // Byte 0: length (0x0A = 10)
+ // Byte 1: 0x00 (bind indicator)
+ // Bytes 2-4: fixed bind prefix (11 22 33) from bind-time sync words
+ // Bytes 5-7: unique TX ID bytes (match post-bind syncword4/7 writes)
+ // Byte 8: checksum = (byte5 + byte6 + byte7) & 0xFF
+ // Byte 9: 0x00
+ uint8_t buf[CG022_PAYLOAD_SIZE];
+ buf[0] = 0x0A; // Length
+ buf[1] = 0x00; // Bind mode
+ buf[2] = rx_tx_addr[0]; // Bind prefix byte 0 (syncword4 LSB)
+ buf[3] = rx_tx_addr[1]; // Bind prefix byte 1 (syncword4 MSB)
+ buf[4] = rx_tx_addr[2]; // Bind prefix byte 2 (syncword7 LSB)
+ buf[5] = cg022_txid[0]; // TX ID byte 0 (post-bind syncword4 LSB)
+ buf[6] = cg022_txid[1]; // TX ID byte 1 (post-bind syncword4 MSB)
+ buf[7] = cg022_txid[2]; // TX ID byte 2 (post-bind syncword7 LSB)
+ buf[8] = (uint8_t)(cg022_txid[0] + cg022_txid[1] + cg022_txid[2]); // Checksum of bytes 5-7
+ buf[9] = 0x00;
+
+ if(cg022_first_packet)
+ CG022_write_fifo_no_clear(buf, CG022_PAYLOAD_SIZE);
+ else
+ LT8910_WriteFIFO(buf, CG022_PAYLOAD_SIZE);
+}
+
+static void __attribute__((unused)) CG022_send_data_packet()
+{
+ uint8_t buf[CG022_PAYLOAD_SIZE];
+
+ // Channel values: 0x00..0x3F with 0x20 center
+ uint8_t throttle = convert_channel_16b_limit(THROTTLE, 0x00, 0x3F);
+ uint8_t elevator = convert_channel_16b_limit(ELEVATOR, 0x00, 0x3F);
+ uint8_t rudder = convert_channel_16b_limit(RUDDER, 0x00, 0x3F);
+ uint8_t aileron = convert_channel_16b_limit(AILERON, 0x00, 0x3F);
+ rudder = 0x3F - rudder;
+ aileron = 0x3F - aileron;
+
+ // Rate mode (3-position on CH5): 20%/60%/100%
+ // From stock TX captures: 20% = 0x00 (03b_20), 60% = 0x11 (23b_60), 100% = 0x22 (03b)
+ // CH5 > CHANNEL_SWITCH = 100% (high rate), CH5 < CHANNEL_MIN_COMMAND = 20% (low rate)
+ // Otherwise 60% (mid rate)
+ uint8_t rate_byte = 0x11; // Mid rate (60%) - from capture 23b_60
+ if(CH5_SW)
+ rate_byte = 0x22; // High rate (100%) - from capture 03b
+ else if(Channel_data[CH5] < CHANNEL_MIN_COMMAND)
+ rate_byte = 0x00; // Low rate (20%) - from capture 03b_20
+
+ // Flags byte 6: bit7 = LED off (CH8)
+ uint8_t flags6 = 0x20;
+ if(CH8_SW)
+ flags6 |= 0x80; // LED off
+
+ // Flags byte 7: bit7 = headless (CH7), bit6 = flip (CH6)
+ uint8_t flags7 = 0x20;
+ if(CH6_SW)
+ flags7 |= 0x40; // Flip
+ if(CH7_SW)
+ flags7 |= 0x80; // Headless mode
+
+ buf[0] = 0x0A; // Length
+ buf[1] = rate_byte; // Rate mode: 0x00=20%, 0x11=60%, 0x22=100%
+ buf[2] = throttle;
+ buf[3] = elevator;
+ buf[4] = rudder;
+ buf[5] = aileron;
+ buf[6] = flags6;
+ buf[7] = flags7;
+ buf[8] = 0x20; // Fixed from captures
+
+ // Checksum: sum of bytes 2-8
+ uint8_t checksum = 0;
+ for(uint8_t i = 2; i <= 8; i++)
+ checksum += buf[i];
+ buf[9] = checksum;
+
+ if(cg022_post_bind_sync)
+ {
+ CG022_write_fifo_post_bind(buf, CG022_PAYLOAD_SIZE);
+ cg022_post_bind_sync = false;
+ }
+ else
+ LT8910_WriteFIFO(buf, CG022_PAYLOAD_SIZE);
+}
+
+uint16_t CG022_callback()
+{
+ // CRITICAL: Exit immediately if protocol is being changed.
+ // modules_reset() restores SPI to Mode 0, but CG022 uses Mode 1.
+ // Doing ANY SPI operations after SPI settings change would hang/corrupt.
+ if(IS_CHANGE_PROTOCOL_FLAG_on)
+ return CG022_PACKET_PERIOD;
+
+ #ifdef MULTI_SYNC
+ telemetry_set_input_sync(CG022_PACKET_PERIOD);
+ #endif
+
+ // Handle bind counter exhausted during bind - restart bind sequence.
+ // This only triggers the soft reset (sync words, channel) since the
+ // hardware is already initialized.
+ if(IS_BIND_IN_PROGRESS && bind_counter == 0 && !IS_CHANGE_PROTOCOL_FLAG_on)
+ CG022_restart_bind_sequence();
+
+ uint8_t channel = CG022_hop_channels[hopping_frequency_no];
+
+ // Stock TX (02b) cycle: SetChannel → FIFO writes → SetTxOn
+ // Each SPI write takes ~196µs on the wire at /256 clock, closely matching
+ // the stock TX's ~228µs per transaction at ~114kHz bit-bang SPI.
+ // Total SetChannel→SetTxOn ≈ 9 writes × ~200µs ≈ 1800µs (stock TX: 1829µs).
+ // R07 controls both channel and TX_EN; SetChannel must come BEFORE SetTxOn,
+ // and nothing must write R07 AFTER SetTxOn (that would abort the transmission).
+ if(!cg022_first_packet)
+ LT8910_SetChannel(channel);
+
+ // PLL settles concurrently during FIFO write transactions (same as stock TX).
+ if(bind_counter)
+ {
+ CG022_send_bind_packet();
+ bind_counter--;
+ if(bind_counter == 0)
+ {
+ cg022_post_bind_sync = true;
+ BIND_DONE;
+ }
+ }
+ else
+ CG022_send_data_packet();
+
+ // Transmit on current channel (PLL fully settled).
+ // After this, R07 must NOT be written again until the next callback —
+ // the packet needs ~480µs on-air before the next SetChannel.
+ LT8910_SetTxOn(channel);
+
+ // Advance to next hop channel for next callback
+ hopping_frequency_no++;
+ if(hopping_frequency_no >= CG022_NUM_CHANNELS)
+ hopping_frequency_no = 0;
+ cg022_first_packet = false;
+
+ return CG022_PACKET_PERIOD;
+}
+
+static void __attribute__((unused)) CG022_initialize_txid()
+{
+ // Fixed bind prefix bytes from stock capture bind packets (capture 02b)
+ // Stock FIFO word 2 = 0x1122, word 3 = 0x3306
+ // -> bind prefix bytes: 0x11, 0x22, 0x33 (bytes 2-4)
+ // -> TX ID bytes 0-2 derived from MProtocol_id lower 24 bits
+ rx_tx_addr[0] = 0x11;
+ rx_tx_addr[1] = 0x22;
+ rx_tx_addr[2] = 0x33;
+ uint32_t txid = MProtocol_id & 0x00FFFFFF;
+ cg022_txid[0] = (uint8_t)txid;
+ cg022_txid[1] = (uint8_t)(txid >> 8);
+ cg022_txid[2] = (uint8_t)(txid >> 16);
+}
+
+void CG022_init(void)
+{
+ BIND_IN_PROGRESS; // autobind protocol
+ CG022_initialize_txid();
+ // Initial startup uses blocking reset - telemetry not started yet.
+ CG022_reset_lt8910_for_bind();
+ // Init writes R07=0x0000 (channel 0, TX off) as part of register config.
+}
+
+#endif
diff --git a/Multiprotocol/DSM_cyrf6936.ino b/Multiprotocol/DSM_cyrf6936.ino
index 491db09dc..b639670ee 100644
--- a/Multiprotocol/DSM_cyrf6936.ino
+++ b/Multiprotocol/DSM_cyrf6936.ino
@@ -599,6 +599,7 @@ void DSM_init()
uint16_t temp = DSM_CLONE_EEPROM_OFFSET;
for(uint8_t i=0;i<4;i++)
cyrfmfg_id[i] = eeprom_read_byte((EE_ADDR)temp++);
+ cyrfmfg_id[3]^=RX_num; //Model match
#if DEBUG_BIND
debugln("Using cloned ID");
debug("Clone ID=")
@@ -619,8 +620,7 @@ void DSM_init()
{
//SUB_PROTO_VALID;
CYRF_GetMfgData(cyrfmfg_id);
- //Model match
- cyrfmfg_id[3]^=RX_num;
+ cyrfmfg_id[3]^=RX_num; //Model match
}
}
diff --git a/Multiprotocol/LT8910_SPI.ino b/Multiprotocol/LT8910_SPI.ino
new file mode 100644
index 000000000..a14e3d1b4
--- /dev/null
+++ b/Multiprotocol/LT8910_SPI.ino
@@ -0,0 +1,102 @@
+/*
+ This project is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Multiprotocol is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Multiprotocol. If not, see .
+ */
+
+// LT8910/LT8900 SPI driver for external module via PA15 (SPI_CSN)
+// SPI convention: bit7=0 WRITE, bit7=1 READ
+// Each register is 16 bits wide, transferred MSB first
+
+#ifdef LT8910_INSTALLED
+
+#include "iface_lt8910.h"
+
+void LT8910_WriteReg(uint8_t address, uint16_t data)
+{
+ SPI_CSN_off;
+ SPI_Write(address & 0x7F); // bit7=0 for write
+ while(SPI2_BASE->SR & SPI_SR_BSY); // Wait for byte fully clocked out
+ SPI_Write(data >> 8); // MSB first
+ while(SPI2_BASE->SR & SPI_SR_BSY);
+ SPI_Write(data & 0xFF); // LSB
+ while(SPI2_BASE->SR & SPI_SR_BSY); // Ensure last byte done before CS rise
+ SPI_CSN_on;
+}
+
+uint16_t LT8910_ReadReg(uint8_t address)
+{
+ uint16_t result;
+ SPI_CSN_off;
+ SPI_Transfer(address | 0x80); // bit7=1 for read
+ uint8_t msb = SPI_Transfer(0xFF); // Stock TX clocks 0xFF during reads
+ uint8_t lsb = SPI_Transfer(0xFF);
+ while(SPI2_BASE->SR & SPI_SR_BSY); // Ensure last byte done before CS rise
+ SPI_CSN_on;
+ result = ((uint16_t)msb << 8) | lsb;
+ return result;
+}
+
+void LT8910_WriteFIFO(const uint8_t *data, uint8_t length)
+{
+ // Stock TX (02b) has ~228µs between each SPI transaction (slow 8-bit MCU).
+ // At /256 SPI clock (~140kHz), each LT8910_WriteReg() takes ~196µs on the
+ // wire, naturally providing the inter-write timing the LT8910 needs without
+ // explicit delays. Back-to-back calls produce ~200µs start-to-start spacing
+ // that closely matches the stock TX's 228µs gap.
+
+ // Clear the FIFO
+ LT8910_WriteReg(LT8910_REG_FIFO, 0x0000);
+ // Set FIFO control
+ LT8910_WriteReg(LT8910_REG_FIFO_CTRL, 0x8080);
+ // Write data as 16-bit words, each in a separate SPI transaction
+ // (matches stock TX behavior from captures)
+ for(uint8_t i = 0; i < length; i += 2)
+ {
+ uint16_t word = (uint16_t)data[i] << 8;
+ if(i + 1 < length)
+ word |= data[i + 1];
+ LT8910_WriteReg(LT8910_REG_FIFO, word);
+ }
+}
+
+void LT8910_SetChannel(uint8_t channel)
+{
+ LT8910_WriteReg(LT8910_REG_CHANNEL, (uint16_t)channel);
+}
+
+void LT8910_SetTxOn(uint8_t channel)
+{
+ LT8910_WriteReg(LT8910_REG_CHANNEL, LT8910_TX_EN | (uint16_t)channel);
+}
+
+uint8_t LT8910_Reset()
+{
+ // Pull CSN high to deselect
+ SPI_CSN_on;
+ delayMicroseconds(200);
+ return 0;
+}
+
+bool LT8910_DetectChip()
+{
+ uint16_t chip_id = LT8910_ReadReg(LT8910_REG_CHIP_ID);
+ if(chip_id == LT8910_CHIP_ID || chip_id == LT8900_CHIP_ID)
+ {
+ debugln("LT8910 chip ID=0x%04X", chip_id);
+ return true;
+ }
+ debugln("LT8910 not detected! ID=0x%04X", chip_id);
+ return false;
+}
+
+#endif
diff --git a/Multiprotocol/Multi.txt b/Multiprotocol/Multi.txt
index 13983470f..338e8cd16 100644
--- a/Multiprotocol/Multi.txt
+++ b/Multiprotocol/Multi.txt
@@ -8,7 +8,7 @@
8,YD717,YD717,SKYWLKR,SYMAX4,XINXUN,NIHUI
9,KN,WLTOYS,FEILUN
10,SymaX,SYMAX,SYMAX5C
-11,SLT,SLT_V1,SLT_V2,Q100,Q200,MR100,V1_4CH,RF_SIM
+11,SLT,SLT_V1,SLT_V2,Q100,Q200,MR100,V1_4CH,RF_SIM,SLT6_Tx
12,CX10,GREEN,BLUE,DM007,---,J3015_1,J3015_2,MK33041
13,CG023,CG023,YD829
14,Bayang,Bayang,H8S3D,X16_AH,IRDRONE,DHD_D4,QX100
@@ -69,11 +69,11 @@
69,ESKYv2,150V2
70,DSM_RX,Multi,CloneTX,EraseTX,CPPM
71,JJRC345,JJRC345,SkyTmblr
-72,Q90C
-73,Kyosho,FHSS,Hype
-74,RadioLink,Surface,Air,DumboRC,RC4G
-75,---
-76,Realacc
+72,Q90C
+73,Kyosho,FHSS,Hype
+74,RadioLink,Surface,Air,DumboRC,RC4G
+75,---
+76,Realacc
77,OMP
78,M-Link
79,WFLY,RF20x
@@ -99,6 +99,9 @@
100,YuXiang
102,JIABAILE,STD,GYRO
103,H36
-104,KAMTOM
-105,Shenqi2
-106,WL91x
+104,KAMTOM
+105,Shenqi2
+106,WL91x
+107,WPL
+108,Ares,6HPA_Tx
+109,AOSENMA,CG022
diff --git a/Multiprotocol/Multi_Protos.ino b/Multiprotocol/Multi_Protos.ino
index 945f8e05b..6ca8d0fc7 100644
--- a/Multiprotocol/Multi_Protos.ino
+++ b/Multiprotocol/Multi_Protos.ino
@@ -119,6 +119,8 @@ const char STR_JIABAILE[] ="JIABAILE";
const char STR_KAMTOM[] ="KAMTOM";
const char STR_WL91X[] ="WL91x";
const char STR_WPL[] ="WPL";
+const char STR_ARES[] ="Ares";
+const char STR_AOSENMA[] ="AoSenMa";
const char STR_SUBTYPE_FLYSKY[] = "\x04""Std\0""V9x9""V6x6""V912""CX20";
const char STR_SUBTYPE_HUBSAN[] = "\x04""H107""H301""H501";
@@ -139,7 +141,7 @@ const char STR_SUBTYPE_DEVO[] = "\x04""8ch\0""10ch""12ch""6ch\0""7ch\0";
const char STR_SUBTYPE_YD717[] = "\x07""Std\0 ""SkyWlkr""Syma X4""XINXUN\0""NIHUI\0 ";
const char STR_SUBTYPE_KN[] = "\x06""WLtoys""FeiLun";
const char STR_SUBTYPE_SYMAX[] = "\x03""Std""X5C";
-const char STR_SUBTYPE_SLT[] = "\x06""V1_6ch""V2_8ch""Q100\0 ""Q200\0 ""MR100\0""V1_4ch""RF_SIM";
+const char STR_SUBTYPE_SLT[] = "\x07""V1_6ch\0""V2_8ch\0""Q100\0 ""Q200\0 ""MR100\0 ""V1_4ch\0""RF_SIM\0""SLT6_Tx";
const char STR_SUBTYPE_CX10[] = "\x07""Green\0 ""Blue\0 ""DM007\0 ""-\0 ""JC3015a""JC3015b""MK33041";
const char STR_SUBTYPE_CG023[] = "\x05""Std\0 ""YD829";
const char STR_SUBTYPE_BAYANG[] = "\x07""Std\0 ""H8S3D\0 ""X16 AH\0""IRDrone""DHD D4\0""QX100\0 ";
@@ -191,6 +193,8 @@ const char STR_SUBTYPE_E129[] = "\x04""E129""C186";
const char STR_SUBTYPE_FX[] = "\x05""816\0 ""620\0 ""9630\0""Q560\0""QF012";
const char STR_SUBTYPE_SGF22[] = "\x04""F22\0""F22S""J20\0""CX10";
const char STR_SUBTYPE_JIABAILE[] = "\x04""Std\0""Gyro";
+const char STR_SUBTYPE_ARES[] = "\x07""6HPA_Tx";
+const char STR_SUBTYPE_AOSENMA[] = "\x05""CG022";
#define NO_SUBTYPE nullptr
#ifdef SEND_CPPM
@@ -229,6 +233,12 @@ const mm_protocol_definition multi_protocols[] = {
#if defined(MULTI_CONFIG_INO)
{PROTO_CONFIG, STR_CONFIG, NO_SUBTYPE, 0, OPTION_NONE, 0, 0, 0, CONFIG_init, CONFIG_callback },
#endif
+ #if defined(ARES_CC2500_INO)
+ {PROTO_ARES, STR_ARES, STR_SUBTYPE_ARES, 1, OPTION_RFTUNE, 0, 0, SW_CC2500, ARES_init, ARES_callback },
+ #endif
+ #if defined(CG022_LT8910_INO)
+ {PROTO_AOSENMA, STR_AOSENMA, STR_SUBTYPE_AOSENMA, 1, OPTION_NONE, 0, 0, 0, CG022_init, CG022_callback },
+ #endif
#if defined(ASSAN_NRF24L01_INO)
{PROTO_ASSAN, STR_ASSAN, NO_SUBTYPE, 0, OPTION_NONE, 0, 0, SW_NRF, ASSAN_init, ASSAN_callback },
#endif
@@ -486,7 +496,7 @@ const mm_protocol_definition multi_protocols[] = {
{PROTO_SKYARTEC, STR_SKYARTEC, NO_SUBTYPE, 0, OPTION_RFTUNE, 0, 1, SW_CC2500, SKYARTEC_init, SKYARTEC_callback },
#endif
#if defined(SLT_CCNRF_INO)
- {PROTO_SLT, STR_SLT, STR_SUBTYPE_SLT, 7, OPTION_RFTUNE, 0, 1, SW_NRF, SLT_init, SLT_callback },
+ {PROTO_SLT, STR_SLT, STR_SUBTYPE_SLT, 8, OPTION_RFTUNE, 0, 1, SW_NRF, SLT_init, SLT_callback },
#endif
#if defined(SYMAX_NRF24L01_INO)
{PROTO_SYMAX, STR_SYMAX, STR_SUBTYPE_SYMAX, 2, OPTION_NONE, 0, 0, SW_NRF, SYMAX_init, SYMAX_callback },
diff --git a/Multiprotocol/Multiprotocol.h b/Multiprotocol/Multiprotocol.h
index 2cc5972a0..95c04c5c0 100644
--- a/Multiprotocol/Multiprotocol.h
+++ b/Multiprotocol/Multiprotocol.h
@@ -17,9 +17,9 @@
// Version
//******************
#define VERSION_MAJOR 1
-#define VERSION_MINOR 3
+#define VERSION_MINOR 9
#define VERSION_REVISION 4
-#define VERSION_PATCH_LEVEL 52
+#define VERSION_PATCH_LEVEL 57
#define MODE_SERIAL 0
@@ -135,6 +135,8 @@ enum PROTOCOLS
PROTO_SHENQI2 = 105, // =>NRF24L01
PROTO_WL91X = 106, // =>CC2500 & NRF24L01
PROTO_WPL = 107, // =>NRF24L01
+ PROTO_ARES = 108, // =>CC2500
+ PROTO_AOSENMA = 109, // =>LT8910
PROTO_NANORF = 126, // =>NRF24L01
PROTO_TEST = 127, // =>CC2500
@@ -216,6 +218,7 @@ enum SLT
MR100 = 4,
SLT_V1_4 = 5,
RF_SIM = 6,
+ SLT6_Tx = 7,
};
enum CX10
{
@@ -240,6 +243,10 @@ enum CG023
CG023 = 0,
YD829 = 1,
};
+enum AOSENMA
+{
+ CG022 = 0,
+};
enum BAYANG
{
BAYANG = 0,
@@ -350,6 +357,10 @@ enum HITEC
OPT_HUB = 1,
MINIMA = 2,
};
+enum ARES
+{
+ ARES_6HPA_Tx = 0,
+};
enum E01X
{
E012 = 0,
@@ -1135,6 +1146,7 @@ Serial: 100000 Baud 8e2 _ xxxx xxxx p --
MR100 4
SLT_V1_4CH 5
RF_SIM 6
+ SLT6_Tx 7
sub_protocol==E01X
E012 0
E015 1
diff --git a/Multiprotocol/Multiprotocol.ino b/Multiprotocol/Multiprotocol.ino
index fae9502cf..2fbb26e3e 100644
--- a/Multiprotocol/Multiprotocol.ino
+++ b/Multiprotocol/Multiprotocol.ino
@@ -104,7 +104,7 @@ uint16_t packet_period;
uint8_t packet_count;
uint8_t packet_sent;
uint8_t packet_length;
-#if defined(HOTT_CC2500_INO) || defined(ESKY150V2_CC2500_INO) || defined(MLINK_CYRF6936_INO)
+#if defined(HOTT_CC2500_INO) || defined(ESKY150V2_CC2500_INO) || defined(MLINK_CYRF6936_INO) || defined(ARES_CC2500_INO)
uint8_t hopping_frequency[78];
#else
uint8_t hopping_frequency[50];
@@ -130,7 +130,7 @@ uint16_t pps_counter;
#ifdef CC2500_INSTALLED
#ifdef SCANNER_CC2500_INO
uint8_t calData[255];
- #elif defined(HOTT_CC2500_INO) || defined(ESKY150V2_CC2500_INO)
+ #elif defined(HOTT_CC2500_INO) || defined(ESKY150V2_CC2500_INO) || defined(ARES_CC2500_INO)
uint8_t calData[75];
#else
uint8_t calData[50];
@@ -326,6 +326,10 @@ void setup()
pinMode(CYRF_CSN_pin,OUTPUT);
pinMode(SPI_CSN_pin,OUTPUT);
pinMode(CYRF_RST_pin,OUTPUT);
+ #ifdef LT8910_RST_pin
+ pinMode(LT8910_RST_pin,OUTPUT);
+ LT8910_RST_HI; // Start with RESET HIGH (inactive)
+ #endif
pinMode(PE1_pin,OUTPUT);
pinMode(PE2_pin,OUTPUT);
pinMode(TX_INV_pin,OUTPUT);
@@ -629,6 +633,11 @@ void setup()
if (protocol==PROTO_HOTT)
option = FORCE_HOTT_TUNING; // Use config-defined tuning value for HOTT
else
+ #endif
+ #if defined(FORCE_ARES_TUNING) && defined(ARES_CC2500_INO)
+ if (protocol==PROTO_ARES)
+ option = FORCE_ARES_TUNING; // Use config-defined tuning value for ARES
+ else
#endif
option = (uint8_t)PPM_prot_line->option; // Use radio-defined option value
@@ -1178,7 +1187,7 @@ static void protocol_init()
release_trainer_ppm();
#endif
- //Set global ID and rx_tx_addr
+ // Set global ID and rx_tx_addr (RX_num 0-63 drives model match/bind ID)
MProtocol_id = RX_num + MProtocol_id_master;
set_rx_tx_addr(MProtocol_id);
@@ -1252,13 +1261,15 @@ static void protocol_init()
#endif
}
- #if defined(WAIT_FOR_BIND) && defined(ENABLE_BIND_CH)
- if( IS_AUTOBIND_FLAG_on && IS_BIND_CH_PREV_off && (cur_protocol[1]&0x80)==0 && mode_select == MODE_SERIAL)
+#if defined(WAIT_FOR_BIND) && defined(ENABLE_BIND_CH)
+ bool bind_not_requested = (rx_ok_buff[1]&0x80)==0;
+ // Use the current serial frame bind bit here, not cur_protocol, so autobind does not wait on a stale bind flag from the previous selection.
+ if( IS_AUTOBIND_FLAG_on && IS_BIND_CH_PREV_off && bind_not_requested && mode_select == MODE_SERIAL)
{ // Autobind is active but no bind requested by either BIND_CH or BIND. But do not wait if in PPM mode...
WAIT_BIND_on;
return;
}
- #endif
+#endif
WAIT_BIND_off;
CHANGE_PROTOCOL_FLAG_off;
@@ -1371,6 +1382,11 @@ void update_serial_data()
if (protocol==PROTO_HOTT)
option=FORCE_HOTT_TUNING; // Use config-defined tuning value for HOTT
else
+ #endif
+ #if defined(FORCE_ARES_TUNING) && defined(ARES_CC2500_INO)
+ if (protocol==PROTO_ARES)
+ option=FORCE_ARES_TUNING; // Use config-defined tuning value for ARES
+ else
#endif
option=rx_ok_buff[3]; // Use radio-defined option value
@@ -1444,6 +1460,7 @@ void update_serial_data()
if( ((rx_ok_buff[1]&0x80)!=0) && ((cur_protocol[1]&0x80)==0) ) // Bind flag has been set
{ // Restart protocol with bind
CHANGE_PROTOCOL_FLAG_on;
+ WAIT_BIND_off;
BIND_IN_PROGRESS;
}
else
@@ -1604,6 +1621,18 @@ void update_serial_data()
void modules_reset()
{
+ #ifdef LT8910_INSTALLED
+ // CRITICAL: Restore SPI2 to Mode 0 and default speed FIRST, before ANY
+ // chip reset that uses SPI. CG022 sets CPHA=1 (Mode 1) and BR=/256 for
+ // the LT8910; if we call CYRF_Reset() (or any other chip reset) while
+ // SPI is still in Mode 1, those chips won't respond correctly and the
+ // module will lock up. This must happen BEFORE CYRF_Reset().
+ SPI_DISABLE();
+ SPI2_BASE->CR1 &= ~(SPI_CR1_CPOL|SPI_CR1_CPHA); // Mode 0
+ SPI2_BASE->CR1 &= ~SPI_CR1_BR;
+ SPI2_BASE->CR1 |= SPI_CR1_BR_PCLK_DIV_8; // Restore 4.5MHz
+ SPI_ENABLE();
+ #endif
#ifdef CC2500_INSTALLED
CC2500_Reset();
#endif
@@ -1619,6 +1648,9 @@ void modules_reset()
#ifdef SX1276_INSTALLED
SX1276_Reset();
#endif
+ #ifdef LT8910_INSTALLED
+ LT8910_Reset();
+ #endif
//Wait for every component to reset
delayMilliseconds(100);
diff --git a/Multiprotocol/NRF250K_EMU.ino b/Multiprotocol/NRF250K_EMU.ino
index 44da9b8c0..ea31dd2e3 100644
--- a/Multiprotocol/NRF250K_EMU.ino
+++ b/Multiprotocol/NRF250K_EMU.ino
@@ -16,13 +16,25 @@
#include "iface_nrf250k.h"
+#if defined(CC2500_INSTALLED) && defined(NRF24L01_INSTALLED)
+ extern bool xn297_rf;
+#endif
+
uint8_t cc2500_nrf_tx_addr[5], cc2500_nrf_addr_len;
static void __attribute__((unused)) NRF250K_SetTXAddr(uint8_t* addr, uint8_t len)
{
if (len > 5) len = 5;
if (len < 3) len = 3;
- #if defined(CC2500_INSTALLED)
+ #if defined(CC2500_INSTALLED) && defined(NRF24L01_INSTALLED)
+ cc2500_nrf_addr_len = len;
+ memcpy(cc2500_nrf_tx_addr, addr, len);
+ if(xn297_rf == XN297_NRF)
+ {
+ NRF24L01_WriteReg(NRF24L01_03_SETUP_AW, len-2);
+ NRF24L01_WriteRegisterMulti(NRF24L01_10_TX_ADDR, addr, len);
+ }
+ #elif defined(CC2500_INSTALLED)
cc2500_nrf_addr_len = len;
memcpy(cc2500_nrf_tx_addr, addr, len);
#elif defined(NRF24L01_INSTALLED)
@@ -33,6 +45,22 @@ static void __attribute__((unused)) NRF250K_SetTXAddr(uint8_t* addr, uint8_t len
static void __attribute__((unused)) NRF250K_WritePayload(uint8_t* msg, uint8_t len)
{
+ #if defined(NRF24L01_INSTALLED)
+ #if defined(CC2500_INSTALLED)
+ if(xn297_rf == XN297_NRF)
+ #endif
+ {
+ if(len<=32)
+ {
+ NRF24L01_FlushTx();
+ NRF24L01_WriteReg(NRF24L01_07_STATUS, _BV(NRF24L01_07_TX_DS) | _BV(NRF24L01_07_RX_DR) | _BV(NRF24L01_07_MAX_RT));
+ NRF24L01_WritePayload(msg, len);
+ }
+ #if defined(CC2500_INSTALLED)
+ return;
+ #endif
+ }
+ #endif
#if defined(CC2500_INSTALLED)
#if defined(ESKY150V2_CC2500_INO)
uint8_t buf[158];
@@ -106,13 +134,6 @@ static void __attribute__((unused)) NRF250K_WritePayload(uint8_t* msg, uint8_t l
CC2500_WriteRegisterMulti(CC2500_3F_TXFIFO, buff, last);
CC2500_Strobe(CC2500_STX);
}
- #elif defined(NRF24L01_INSTALLED)
- if(len<=32)
- {
- NRF24L01_FlushTx();
- NRF24L01_WriteReg(NRF24L01_07_STATUS, _BV(NRF24L01_07_TX_DS) | _BV(NRF24L01_07_RX_DR) | _BV(NRF24L01_07_MAX_RT));
- NRF24L01_WritePayload(msg, len);
- }
#endif
}
diff --git a/Multiprotocol/Pins.h b/Multiprotocol/Pins.h
index fd1c7cccd..83ad9a691 100644
--- a/Multiprotocol/Pins.h
+++ b/Multiprotocol/Pins.h
@@ -234,6 +234,12 @@
#define A7105_CSN_pin PB9 //A7105
#define CYRF_CSN_pin PB12 //CYRF CSN
#define SPI_CSN_pin PA15
+ //LT8910 RESET pin (external RF board via SPI header)
+ //PA14 (SWCLK) is available after afio_cfg_debug_ports(AFIO_DEBUG_NONE).
+ //Using a dedicated pin avoids conflict with onboard 4in1 RF chips
+ #define LT8910_RST_pin PA14
+ #define LT8910_RST_HI digitalWrite(LT8910_RST_pin,HIGH)
+ #define LT8910_RST_LO digitalWrite(LT8910_RST_pin,LOW)
//SPI pins
#define SCK_pin PB13 //SCK
#define SDO_pin PB14 //MISO
diff --git a/Multiprotocol/SLT_ccnrf.ino b/Multiprotocol/SLT_ccnrf.ino
index 3e1d37ee2..cad2b344d 100644
--- a/Multiprotocol/SLT_ccnrf.ino
+++ b/Multiprotocol/SLT_ccnrf.ino
@@ -25,9 +25,13 @@
#define SLT_PAYLOADSIZE_V1 7
#define SLT_PAYLOADSIZE_V1_4 5
#define SLT_PAYLOADSIZE_V2 11
+#define SLT_PAYLOADSIZE_Q100 19
#define SLT_NFREQCHANNELS 15
#define SLT_TXID_SIZE 4
#define SLT_BIND_CHANNEL 0x50
+#define SLT6_CH_MIN 182 // 10-bit AETR minimum (captures: 180-185, symmetric around 512)
+#define SLT6_CH_MAX 842 // 10-bit AETR maximum (captures: 829-843, symmetric around 512)
+#define SLT6_SW_THRESHOLD 273 // ~33% of half-range (820/3) for 3-position switch zones
enum{
// flags going to packet[6] (Q200)
@@ -47,16 +51,38 @@ enum{
enum {
SLT_BUILD=0,
- SLT_DATA1,
- SLT_DATA2,
- SLT_DATA3,
+ SLT_DATA,
+ SLT_LAST_DATA,
SLT_BIND1,
SLT_BIND2,
};
+// SLT6 sub-cycle states: 3 sub-cycles per triple, each with 2 copies
+enum {
+ SLT6_BUILD_A=0, // Build packet, configure for 7B sub-cycle
+ SLT6_DATA_A1, // Send 7B copy 1
+ SLT6_DATA_A2, // Send 7B copy 2, configure for 6B sub-cycle
+ SLT6_DATA_B1, // Send 6B copy 1
+ SLT6_DATA_B2, // Send 6B copy 2, configure for 5B sub-cycle
+ SLT6_DATA_C1, // Send 5B copy 1
+ SLT6_DATA_C2, // Send 5B copy 2
+ SLT6_BIND, // Send bind packet
+};
+
+// SLT6 address XOR values for the 3 sub-cycles
+#define SLT6_ADDR_XOR_A 0x00 // 7B sub-cycle: base address
+#define SLT6_ADDR_XOR_B 0x06 // 6B sub-cycle: byte[0] XOR 0x06
+#define SLT6_ADDR_XOR_C 0x09 // 5B sub-cycle: byte[0] XOR 0x09
+
+// SLT6 timing (from capture 12b, in microseconds)
+#define SLT6_TIMING_SUBCYCLE 5994 // ~6000us between sub-cycle starts
+#define SLT6_TIMING_PAIR 1633 // ~1633us between the two copies within a sub-cycle
+#define SLT6_TIMING_BUILD 1000 // Build+config time at start of each triple
+#define SLT6_TIMING_TRIPLE (3 * SLT6_TIMING_SUBCYCLE) // ~18ms triple period
+
static void __attribute__((unused)) SLT_RF_init()
{
- NRF250K_Init();
+ XN297_Configure(XN297_CRCEN, XN297_SCRAMBLED, XN297_250K, option == 0); // SLT: option==0 uses NRF24L01, option!=0 uses CC2500 with freq tuning
NRF250K_SetTXAddr(rx_tx_addr, SLT_TXID_SIZE);
}
@@ -74,7 +100,7 @@ static void __attribute__((unused)) SLT_set_freq(void)
}
// Unique freq
- uint8_t max_freq = 0x50; //V1 and V2
+ uint8_t max_freq = 0x50; //V1, V2, and MR100
if(sub_protocol == Q200)
max_freq=45;
for (uint8_t i = 0; i < SLT_NFREQCHANNELS; ++i)
@@ -186,94 +212,236 @@ static void __attribute__((unused)) SLT_build_packet()
}
else
calib_counter = 0;
+
+ // Q100 uses 19-byte packets: bytes[11..18] are zero-padded
+ if(sub_protocol == Q100)
+ for(uint8_t i = 11; i < SLT_PAYLOADSIZE_Q100; i++)
+ packet[i] = 0x00;
+}
+
+// SLT6: build 7-byte data packet from current channel values
+static void __attribute__((unused)) SLT6_build_packet()
+{
+ // aileron, elevator, throttle, rudder (10-bit, limited range)
+ uint8_t e = 0;
+ for (uint8_t i = 0; i < 4; ++i)
+ {
+ uint16_t v = convert_channel_16b_limit(CH_AETR[i], SLT6_CH_MIN, SLT6_CH_MAX);
+ packet[i] = v;
+ e = (e >> 2) | (uint8_t) ((v >> 2) & 0xC0);
+ }
+ packet[4] = e;
+
+ // Flight mode: 3 positions at ~33% each
+ if(Channel_data[CH5] > CHANNEL_MID + SLT6_SW_THRESHOLD)
+ packet[5] = 0xD0;
+ else if(Channel_data[CH5] < CHANNEL_MID - SLT6_SW_THRESHOLD)
+ packet[5] = 0x30;
+ else
+ packet[5] = 0x80;
+
+ // Panic: only active when CH6 is below -33%, center and up = no panic
+ if(Channel_data[CH6] < CHANNEL_MID - SLT6_SW_THRESHOLD)
+ packet[6] = 0x30;
+ else
+ packet[6] = 0xD0;
+}
+
+// SLT6: configure radio for a sub-cycle (set address and channel)
+static void __attribute__((unused)) SLT6_configure_radio(uint8_t addr_xor, uint8_t hop_offset)
+{
+ SLT_wait_radio();
+ // Set TX address with XOR on byte[0]
+ uint8_t addr[SLT_TXID_SIZE];
+ memcpy(addr, rx_tx_addr, SLT_TXID_SIZE);
+ addr[0] ^= addr_xor;
+ NRF250K_SetTXAddr(addr, SLT_TXID_SIZE);
+ // Set RF channel
+ NRF250K_Hopping((hopping_frequency_no + hop_offset) % SLT_NFREQCHANNELS);
}
static void __attribute__((unused)) SLT_send_bind_packet()
{
SLT_wait_radio();
- NRF250K_Hopping(SLT_NFREQCHANNELS); //Bind channel
+ if(phase == SLT_BIND2 || phase == SLT6_BIND)
+ NRF250K_Hopping(SLT_NFREQCHANNELS); //Bind channel for BIND2 and SLT6 BIND
BIND_IN_PROGRESS; //Limit TX power to bind level
NRF250K_SetPower();
BIND_DONE;
NRF250K_SetTXAddr((uint8_t *)"\x7E\xB8\x63\xA9", SLT_TXID_SIZE);
memcpy((void*)packet, (void*)rx_tx_addr, SLT_TXID_SIZE);
- if(phase == SLT_BIND2)
+ if(phase == SLT_BIND2 || phase == SLT6_BIND)
SLT_send_packet(SLT_TXID_SIZE);
else // SLT_BIND1
- SLT_send_packet(SLT_PAYLOADSIZE_V2);
+ SLT_send_packet(packet_length);
}
+#define SLT_FRAME_PERIOD 18000
#define SLT_TIMING_BUILD 1000
-#define SLT_V1_TIMING_PACKET 1000
+#define SLT_V1_TIMING_PACKET 1600
#define SLT_V1_4_TIMING_PACKET 1643
#define SLT_V2_TIMING_PACKET 2042
+#define SLT_Q100_TIMING_PACKET 1897
+#define SLT_MR100_TIMING_PACKET 2007
#define SLT_V1_TIMING_BIND2 1000
#define SLT_V2_TIMING_BIND1 6507
#define SLT_V2_TIMING_BIND2 2112
+#define SLT_Q100_TIMING_BIND1 3652
+#define SLT_Q100_TIMING_BIND2 1217
+#define SLT_MR100_TIMING_BIND2 1008
+
+// SLT6 callback: triple-address state machine
+// Each triple: 3 sub-cycles (7B, 6B, 5B), each sent twice, with different addresses and channels
+static uint16_t __attribute__((unused)) SLT6_callback()
+{
+ switch (phase)
+ {
+ case SLT6_BUILD_A:
+ #ifdef MULTI_SYNC
+ telemetry_set_input_sync(SLT6_TIMING_TRIPLE);
+ #endif
+ SLT6_build_packet();
+ NRF250K_SetPower();
+ SLT6_configure_radio(SLT6_ADDR_XOR_A, 0); // 7B sub-cycle: base address, hop+0
+ phase = SLT6_DATA_A1;
+ return SLT6_TIMING_BUILD;
+ case SLT6_DATA_A1:
+ SLT_send_packet(7);
+ phase = SLT6_DATA_A2;
+ return SLT6_TIMING_PAIR; // 1633us between copies
+ case SLT6_DATA_A2:
+ SLT_send_packet(7);
+ SLT6_configure_radio(SLT6_ADDR_XOR_B, 3); // 6B sub-cycle: XOR 0x06 address, hop+3
+ phase = SLT6_DATA_B1;
+ return SLT6_TIMING_SUBCYCLE - SLT6_TIMING_PAIR; // 4361us to next sub-cycle TX
+ case SLT6_DATA_B1:
+ SLT_send_packet(6);
+ phase = SLT6_DATA_B2;
+ return SLT6_TIMING_PAIR;
+ case SLT6_DATA_B2:
+ SLT_send_packet(6);
+ SLT6_configure_radio(SLT6_ADDR_XOR_C, 6); // 5B sub-cycle: XOR 0x09 address, hop+6
+ phase = SLT6_DATA_C1;
+ return SLT6_TIMING_SUBCYCLE - SLT6_TIMING_PAIR; // 4361us
+ case SLT6_DATA_C1:
+ SLT_send_packet(5);
+ phase = SLT6_DATA_C2;
+ return SLT6_TIMING_PAIR;
+ case SLT6_DATA_C2:
+ SLT_send_packet(5);
+ // Advance hopping for next triple
+ if (++hopping_frequency_no >= SLT_NFREQCHANNELS)
+ hopping_frequency_no = 0;
+ if (++packet_count >= 100)
+ {// Send bind packet periodically
+ packet_count = 0;
+ phase = SLT6_BIND;
+ return SLT_V1_TIMING_BIND2;
+ }
+ phase = SLT6_BUILD_A;
+ return SLT6_TIMING_SUBCYCLE - SLT6_TIMING_PAIR - SLT6_TIMING_BUILD; // Gap before next build
+ case SLT6_BIND:
+ SLT_send_bind_packet();
+ phase = SLT6_BUILD_A;
+ return SLT6_TIMING_SUBCYCLE - SLT6_TIMING_PAIR - SLT6_TIMING_BUILD;
+ }
+ return SLT6_TIMING_TRIPLE;
+}
+
uint16_t SLT_callback()
{
+ // SLT6 has its own state machine
+ if(sub_protocol == SLT6_Tx)
+ return SLT6_callback();
+
switch (phase)
{
case SLT_BUILD:
- //debugln_time("b ");
#ifdef MULTI_SYNC
telemetry_set_input_sync(packet_period);
#endif
SLT_build_packet();
NRF250K_SetPower(); //Change power level
NRF250K_SetTXAddr(rx_tx_addr, SLT_TXID_SIZE);
- phase++;
+ bind_phase = 0; //Reset data packet counter
+ phase = SLT_DATA;
+ if(sub_protocol == MR100)
+ return 0; //MR100: no build delay
return SLT_TIMING_BUILD;
- case SLT_DATA1:
- case SLT_DATA2:
- phase++;
+ case SLT_DATA:
SLT_send_packet(packet_length);
- if(sub_protocol == SLT_V1)
+ bind_phase++;
+ if(bind_phase >= rf_ch_num)
+ {// Last data packet of the frame
+ phase = SLT_LAST_DATA;
+ }
+ if(sub_protocol == SLT_V1 || sub_protocol == SLT_V2 || sub_protocol == RF_SIM)
return SLT_V1_TIMING_PACKET;
if(sub_protocol == SLT_V1_4)
- {
- phase++; //Packets are sent two times only
return SLT_V1_4_TIMING_PACKET;
- }
- //V2
+ if(sub_protocol == Q100)
+ return SLT_Q100_TIMING_PACKET;
+ if(sub_protocol == MR100)
+ return SLT_MR100_TIMING_PACKET;
+ //Q200
return SLT_V2_TIMING_PACKET;
- case SLT_DATA3:
+ case SLT_LAST_DATA:
SLT_send_packet(packet_length);
- if (++packet_count >= 100)
+ if (++packet_count >= num_ch)
{// Send bind packet
packet_count = 0;
- if(sub_protocol == SLT_V1 || sub_protocol == SLT_V1_4)
+ if(sub_protocol == SLT_V1 || sub_protocol == SLT_V1_4 || sub_protocol == SLT_V2 || sub_protocol == RF_SIM)
{
phase = SLT_BIND2;
return SLT_V1_TIMING_BIND2;
}
- //V2
+ if(sub_protocol == MR100)
+ {// MR100: only BIND2 (no BIND1)
+ phase = SLT_BIND2;
+ return SLT_MR100_TIMING_BIND2;
+ }
+ if(sub_protocol == Q100)
+ {// Q100: BIND1+BIND2
+ phase = SLT_BIND1;
+ return SLT_Q100_TIMING_BIND1;
+ }
+ //Q200
phase = SLT_BIND1;
return SLT_V2_TIMING_BIND1;
}
else
{// Continue to send normal packets
phase = SLT_BUILD;
- if(sub_protocol == SLT_V1)
- return 20000 - SLT_TIMING_BUILD;
- if(sub_protocol==SLT_V1_4)
- return 18000 - SLT_TIMING_BUILD - SLT_V1_4_TIMING_PACKET;
- //V2
- return 13730 - SLT_TIMING_BUILD;
+ if(sub_protocol == SLT_V1 || sub_protocol == SLT_V2 || sub_protocol == RF_SIM)
+ return SLT_FRAME_PERIOD - SLT_TIMING_BUILD - SLT_V1_TIMING_PACKET;
+ if(sub_protocol == SLT_V1_4)
+ return SLT_FRAME_PERIOD - SLT_TIMING_BUILD - SLT_V1_4_TIMING_PACKET;
+ if(sub_protocol == Q100)
+ return SLT_FRAME_PERIOD - SLT_TIMING_BUILD - 6*SLT_Q100_TIMING_PACKET;
+ if(sub_protocol == MR100)
+ return SLT_FRAME_PERIOD - 8*SLT_MR100_TIMING_PACKET;
+ //Q200
+ return SLT_FRAME_PERIOD - SLT_TIMING_BUILD - 2*SLT_V2_TIMING_PACKET;
}
case SLT_BIND1:
SLT_send_bind_packet();
- phase++;
+ phase = SLT_BIND2;
+ if(sub_protocol == Q100)
+ return SLT_Q100_TIMING_BIND2;
return SLT_V2_TIMING_BIND2;
case SLT_BIND2:
SLT_send_bind_packet();
phase = SLT_BUILD;
- if(sub_protocol == SLT_V1)
- return 20000 - SLT_TIMING_BUILD - SLT_V1_TIMING_BIND2;
+ if(sub_protocol == SLT_V1 || sub_protocol == SLT_V2 || sub_protocol == RF_SIM)
+ return SLT_FRAME_PERIOD - SLT_TIMING_BUILD - SLT_V1_TIMING_PACKET - SLT_V1_TIMING_BIND2;
if(sub_protocol == SLT_V1_4)
- return 18000 - SLT_TIMING_BUILD - SLT_V1_TIMING_BIND2 - SLT_V1_4_TIMING_PACKET;
- //V2
- return 13730 - SLT_TIMING_BUILD - SLT_V2_TIMING_BIND1 - SLT_V2_TIMING_BIND2;
+ return SLT_FRAME_PERIOD - SLT_TIMING_BUILD - SLT_V1_4_TIMING_PACKET - SLT_V1_TIMING_BIND2;
+ if(sub_protocol == Q100)
+ return SLT_FRAME_PERIOD - SLT_TIMING_BUILD - 6*SLT_Q100_TIMING_PACKET - SLT_Q100_TIMING_BIND1 - SLT_Q100_TIMING_BIND2;
+ if(sub_protocol == MR100)
+ return SLT_FRAME_PERIOD - 8*SLT_MR100_TIMING_PACKET - SLT_MR100_TIMING_BIND2;
+ //Q200
+ return SLT_FRAME_PERIOD - SLT_TIMING_BUILD - 2*SLT_V2_TIMING_PACKET - SLT_V2_TIMING_BIND1 - SLT_V2_TIMING_BIND2;
}
return 19000;
}
@@ -285,18 +453,30 @@ void SLT_init()
packet_sent = 0;
hopping_frequency_no = 0;
- if(sub_protocol == SLT_V1)
+ if(sub_protocol == SLT6_Tx)
+ {
+ hopping_frequency_no = 1; // SLT6 starts hopping at index 1 (verified from captures)
+ // packet_length not used for SLT6 (lengths vary per sub-cycle)
+ #ifdef MULTI_SYNC
+ packet_period = SLT6_TIMING_TRIPLE;
+ #endif
+ }
+ else if(sub_protocol == SLT_V1)
{
packet_length = SLT_PAYLOADSIZE_V1;
+ rf_ch_num = 1; //2 packets per frame
+ num_ch = 88; //Bind every 88 frames
#ifdef MULTI_SYNC
- packet_period = 20000+2*SLT_V1_TIMING_PACKET; //22ms
+ packet_period = SLT_FRAME_PERIOD;
#endif
}
else if(sub_protocol == SLT_V1_4)
{
packet_length = SLT_PAYLOADSIZE_V1_4;
+ rf_ch_num = 1; //2 packets per frame
+ num_ch = 88; //Bind every 88 frames
#ifdef MULTI_SYNC
- packet_period = 18000; //18ms
+ packet_period = SLT_FRAME_PERIOD;
#endif
//Force high part of the ID otherwise the RF frequencies do not match, only tested the 2 last bytes...
rx_tx_addr[0]=0xF4;
@@ -305,16 +485,45 @@ void SLT_init()
memcpy(rx_tx_addr,"\xF4\x71\x8D\x01",SLT_TXID_SIZE);
#endif
}
- else //V2
+ else if(sub_protocol == Q100)
+ {
+ packet_length = SLT_PAYLOADSIZE_Q100;
+ rf_ch_num = 6; //7 packets per frame
+ num_ch = 50; //Bind every 50 frames
+ #ifdef MULTI_SYNC
+ packet_period = SLT_FRAME_PERIOD;
+ #endif
+ }
+ else if(sub_protocol == MR100)
+ {
+ packet_length = SLT_PAYLOADSIZE_V2;
+ rf_ch_num = 8; //9 packets per frame
+ num_ch = 83; //Bind every ~83 frames
+ #ifdef MULTI_SYNC
+ packet_period = SLT_FRAME_PERIOD;
+ #endif
+ }
+ else if(sub_protocol == SLT_V2 || sub_protocol == RF_SIM)
{
packet_length = SLT_PAYLOADSIZE_V2;
+ rf_ch_num = 1; //2 packets per frame (same as V1)
+ num_ch = 88; //Bind every 88 frames (same as V1)
#ifdef MULTI_SYNC
- packet_period = 13730+2*SLT_V2_TIMING_PACKET; //~18ms
+ packet_period = SLT_FRAME_PERIOD;
+ #endif
+ }
+ else //Q200
+ {
+ packet_length = SLT_PAYLOADSIZE_V2;
+ rf_ch_num = 2; //3 packets per frame
+ num_ch = 50; //Bind every 50 frames
+ #ifdef MULTI_SYNC
+ packet_period = SLT_FRAME_PERIOD;
#endif
}
- if(sub_protocol == Q200)
- { //Q200: Force high part of the ID otherwise it won't bind
+ if(sub_protocol == Q200 || sub_protocol == Q100)
+ { //Q200/Q100: Force high part of the ID otherwise it won't bind
rx_tx_addr[0]=0x01;
rx_tx_addr[1]=0x02;
#ifdef SLT_Q200_FORCE_ID // ID taken from TX dumps
@@ -326,7 +535,10 @@ void SLT_init()
SLT_RF_init();
SLT_set_freq();
- phase = SLT_BUILD;
+ if(sub_protocol == SLT6_Tx)
+ phase = SLT6_BUILD_A;
+ else
+ phase = SLT_BUILD;
}
#endif
diff --git a/Multiprotocol/SPI.ino b/Multiprotocol/SPI.ino
index b09712732..a4eeca416 100644
--- a/Multiprotocol/SPI.ino
+++ b/Multiprotocol/SPI.ino
@@ -46,6 +46,16 @@ void SPI_Write(uint8_t command)
command = SPI2_BASE->DR; // ... and read the last received data.
}
+uint8_t SPI_Transfer(uint8_t data)
+{
+ SPI2_BASE->DR = data;
+ #ifdef DEBUG_SPI
+ debug("%02X ",data);
+ #endif
+ while (!(SPI2_BASE->SR & SPI_SR_RXNE));
+ return SPI2_BASE->DR;
+}
+
uint8_t SPI_Read(void)
{
SPI_Write(0x00);
@@ -167,4 +177,4 @@ uint8_t SPI_SDI_Read(void)
}
#endif
-#endif//STM32_BOARD
\ No newline at end of file
+#endif//STM32_BOARD
diff --git a/Multiprotocol/Validate.h b/Multiprotocol/Validate.h
index 62f7c1107..91b6b7e88 100644
--- a/Multiprotocol/Validate.h
+++ b/Multiprotocol/Validate.h
@@ -99,6 +99,11 @@
#error "The HITEC forced frequency tuning value is outside of the range -127..127."
#endif
#endif
+#ifdef FORCE_ARES_TUNING
+ #if ( FORCE_ARES_TUNING < -127 ) || ( FORCE_ARES_TUNING > 127 )
+ #error "The ARES forced frequency tuning value is outside of the range -127..127."
+ #endif
+#endif
#ifdef FORCE_HOTT_TUNING
#if ( FORCE_HOTT_TUNING < -127 ) || ( FORCE_HOTT_TUNING > 127 )
#error "The HOTT forced frequency tuning value is outside of the range -127..127."
@@ -238,6 +243,7 @@
#define SX1276_INSTALLED
#undef ENABLE_PPM
#undef SEND_CPPM
+ #undef RLINK_CC2500_INO // Save flash space...
#endif
//Make sure protocols are selected correctly
@@ -298,6 +304,7 @@
#endif
#undef FUTABA_CC2500_INO
#undef SKYARTEC_CC2500_INO
+ #undef ARES_CC2500_INO
#endif
#if not defined(NRF24L01_INSTALLED) || defined MULTI_EU
@@ -368,6 +375,7 @@
#undef BUMBLEB_CCNRF_INO
#undef Q303_CCNRF_INO
#undef Q90C_CCNRF_INO
+ #undef RLINK_CC2500_INO
#endif
#if not defined(DSM_CYRF6936_INO)
#undef LOSI_CYRF6936_INO
@@ -379,10 +387,17 @@
#endif
#if not defined(STM32_BOARD)
#undef SX1276_INSTALLED
+ #undef LT8910_INSTALLED
#endif
#if not defined(SX1276_INSTALLED) || defined MULTI_EU
#undef FRSKYR9_SX1276_INO
#endif
+#if not defined(LT8910_INSTALLED)
+ #undef CG022_LT8910_INO
+#endif
+#if defined(LT8910_INSTALLED) && defined(SX1276_INSTALLED)
+ #error "LT8910 and SX1276 both use PA15 for chip select and cannot both be installed; comment out LT8910_INSTALLED or SX1276_INSTALLED in _Config.h"
+#endif
#ifdef MULTI_AIR
#undef JOYSWAY_A7105_INO
@@ -404,6 +419,7 @@
//Save flash space...
#undef CABELL_NRF24L01_INO
#undef REDPINE_CC2500_INO
+ #undef NCC1701_NRF24L01_INO
#endif
#ifdef MULTI_SURFACE
@@ -415,6 +431,7 @@
#undef E129_CYRF6936_INO
#undef J6PRO_CYRF6936_INO
#undef SCORPIO_CYRF6936_INO
+ #undef ARES_CC2500_INO
#undef E016HV2_CC2500_INO
#undef ESKY150V2_CC2500_INO
#undef IKEAANSLUTA_CC2500_INO // This is mostly a "for-fun" kind of a thing, not needed for most users
@@ -461,6 +478,7 @@
#undef V911S_CCNRF_INO
#undef SGF22_NRF24L01_INO
#undef YUXIANG_NRF24L01_INO
+ #undef CG022_LT8910_INO
#endif
//OpenTX 2.3.x issue
diff --git a/Multiprotocol/_Config.h b/Multiprotocol/_Config.h
index 0976ed0a2..dca3f25bd 100644
--- a/Multiprotocol/_Config.h
+++ b/Multiprotocol/_Config.h
@@ -79,6 +79,7 @@
#define CC2500_INSTALLED
#define NRF24L01_INSTALLED
//#define SX1276_INSTALLED // only supported on STM32 modules
+//#define LT8910_INSTALLED // only supported on STM32 modules with external LT8910 hardware
//Uncomment (remove //) if you have an internal 5-in-1 Multi module in a Jumper T18 or T-Lite.
//All components are considered to be installed and specifics to that module are automatically configured
@@ -107,6 +108,7 @@
//#define FORCE_REDPINE_TUNING 0
//#define FORCE_FUTABA_TUNING 0
//#define FORCE_SKYARTEC_TUNING 0
+//#define FORCE_ARES_TUNING 0
/** A7105 Fine Frequency Tuning **/
//This is required in rare cases where some A7105 modules and/or RXs have an inaccurate crystal oscillator.
@@ -221,6 +223,7 @@
#define SKYARTEC_CC2500_INO
#define REDPINE_CC2500_INO
#define RLINK_CC2500_INO
+#define ARES_CC2500_INO
//The protocols below need a NRF24L01 to be installed
#define ASSAN_NRF24L01_INO
@@ -285,6 +288,9 @@
//The protocols below need a SX1276 to be installed
#define FRSKYR9_SX1276_INO
+//The protocols below need a LT8910 to be installed
+#define CG022_LT8910_INO
+
/***************************/
/*** PROTOCOLS SETTINGS ***/
/***************************/
@@ -850,6 +856,7 @@ const PPM_Parameters PPM_prot[14*NBR_BANKS]= {
MR100
V1_4CH
RF_SIM
+ SLT6_Tx
PROTO_SYMAX
SYMAX
SYMAX5C
diff --git a/Multiprotocol/iface_lt8910.h b/Multiprotocol/iface_lt8910.h
new file mode 100644
index 000000000..b735c6703
--- /dev/null
+++ b/Multiprotocol/iface_lt8910.h
@@ -0,0 +1,65 @@
+/*
+ This project is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Multiprotocol is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Multiprotocol. If not, see .
+ */
+
+// LT8910/LT8900 register definitions
+// SPI convention: bit7=0 WRITE, bit7=1 READ
+
+#ifndef _IFACE_LT8910_H_
+#define _IFACE_LT8910_H_
+
+// Register addresses
+#define LT8910_REG_CHIP_ID 0x00
+#define LT8910_REG_01 0x01
+#define LT8910_REG_02 0x02
+#define LT8910_REG_RSSI 0x04
+#define LT8910_REG_05 0x05
+#define LT8910_REG_CHANNEL 0x07 // bits[6:0]=channel, bit[8]=TX enable
+#define LT8910_REG_08 0x08
+#define LT8910_REG_CURRENT 0x09
+#define LT8910_REG_0A 0x0A
+#define LT8910_REG_0B 0x0B
+#define LT8910_REG_0C 0x0C
+#define LT8910_REG_0D 0x0D
+#define LT8910_REG_RX_PLL 0x16
+#define LT8910_REG_CRC_POLY 0x17
+#define LT8910_REG_PREAMBLE 0x18
+#define LT8910_REG_19 0x19
+#define LT8910_REG_1A 0x1A
+#define LT8910_REG_1B 0x1B
+#define LT8910_REG_1C 0x1C
+#define LT8910_REG_SYNCWORD1 0x20
+#define LT8910_REG_SYNCWORD2 0x21
+#define LT8910_REG_SYNCWORD3 0x22
+#define LT8910_REG_PACKET_CONFIG 0x23 // bit9=CRC_INITIAL_DATA_EN, bit8=CRC_ON
+#define LT8910_REG_SYNCWORD4 0x24
+#define LT8910_REG_SYNCWORD5 0x25
+#define LT8910_REG_SYNCWORD6 0x26
+#define LT8910_REG_SYNCWORD7 0x27
+#define LT8910_REG_CRC_SEED 0x28
+#define LT8910_REG_TX_POWER 0x29
+#define LT8910_REG_2A 0x2A
+#define LT8910_REG_2B 0x2B
+#define LT8910_REG_FIFO 0x32
+#define LT8910_REG_FIFO_CTRL 0x34
+
+// Channel register bit masks
+#define LT8910_TX_EN 0x0100 // bit 8 in channel register
+#define LT8910_CHIP_RST 0x8000 // bit 15: software reset (auto-clear)
+
+// Chip IDs
+#define LT8900_CHIP_ID 0x6FE0
+#define LT8910_CHIP_ID 0x6FF0
+
+#endif
diff --git a/Protocols_Details.md b/Protocols_Details.md
index 3248ff553..60790a2e0 100644
--- a/Protocols_Details.md
+++ b/Protocols_Details.md
@@ -63,6 +63,8 @@ You've upgraded the module but the radio does not display the name of the protoc
Protocol Name|Build|Protocol Number|Sub_Proto 0|Sub_Proto 1|Sub_Proto 2|Sub_Proto 3|Sub_Proto 4|Sub_Proto 5|Sub_Proto 6|Sub_Proto 7|RF Module|Emulation
---|---|---|---|---|---|---|---|---|---|---|---|---
+[AoSenMa](Protocols_Details.md#AOSENMA---109)|AIR|109|CG022||||||||LT8910|
+[Ares](Protocols_Details.md#ARES---108)|AIR|108|6HPA_Tx||||||||CC2500|
[Assan](Protocols_Details.md#ASSAN---24)|AIR/SFC|24|||||||||NRF24L01|
[Bayang](Protocols_Details.md#BAYANG---14)|AIR/SFC|14|Bayang|H8S3D|X16_AH|IRDRONE|DHD_D4|QX100|||NRF24L01|XN297
[Bayang RX](Protocols_Details.md#BAYANG-RX---59)|AIR/SFC|59|Multi|CPPM|||||||NRF24L01|XN297
@@ -146,7 +148,7 @@ CFlie|AIR|38|CFlie||||||||NRF24L01|
[Shenqi](Protocols_Details.md#Shenqi---19)||19|Shenqi||||||||NRF24L01|LT8900
[Shenqi2](Protocols_Details.md#Shenqi2---105)||105|Shenqi2||||||||NRF24L01|XN297
[Skyartec](Protocols_Details.md#Skyartec---68)||68|||||||||CC2500|CC2500
-[SLT](Protocols_Details.md#SLT---11)||11|SLT_V1|SLT_V2|Q100|Q200|MR100|V1_4CH|RF_SIM||NRF24L01|CC2500
+[SLT](Protocols_Details.md#SLT---11)||11|SLT_V1*|SLT_V2*|Q100*|Q200*|MR100*|V1_4CH*|RF_SIM*|SLT6_Tx*|NRF24L01|CC2500
[SymaX](Protocols_Details.md#Symax---10)||10|SYMAX|SYMAX5C|||||||NRF24L01|
[Traxxas](Protocols_Details.md#Traxxas---43)||43|TQ2|TQ1|||||||CYRF6936|
[V2x2](Protocols_Details.md#V2X2---5)||5|V2x2|JXD506|MR101||||||NRF24L01|
@@ -758,8 +760,55 @@ CH1|CH2|CH3|CH4|CH5|CH6|CH7|CH8|CH9
CH1|CH2|CH3|CH4|CH5|CH6|CH7|CH8|CH9
***
+# LT8910 RF Module
+
+## AOSENMA - *109*
+Requires an external LT8910 RF board connected to the MPM 6-pin SPI header (MOSI, MISO, SCK, CS, 3.3V, GND).
+RET must also be wired to STM32 MPU Pin 37 (PA14), with PKT not connected (floating - same as Original TX).
+Hardware setup: [docs/LT8910_CG022_Implementation.md#1-hardware-setup](docs/LT8910_CG022_Implementation.md#1-hardware-setup).
+
+### Sub_protocol CG022 - *0*
+Autobind protocol
+
+Model: Ao-Sen-Ma CG022 quad copter
+Unique model match is available by changing receiver numbers (0-63); changing the RX number requires re-binding.
+
+CH1|CH2|CH3|CH4|CH5|CH6|CH7|CH8
+---|---|---|---|---|---|---|---
+A|E|T|R|RATE|FLIP|HEADLESS|LED
+
+**Rate control (CH5):** 3-position switch for rate/sensitivity mode:
+- Low position (-100%): 20% rate (gentle)
+- Mid position (0%): 60% rate (normal)
+- High position (+100%): 100% rate (aggressive with flip near endpoints)
+
+**Binding:** Binding can occur in one of these two ways:
+1. Automatic bind on TX power-on: The transmitter automatically enters bind mode when the protocol is selected
+2. Manual rebind via CH16: Enable "Bind on channel" in GUI, then toggle CH16 from low to high to trigger a rebind sequence
+
# CC2500 RF Module
+## ARES - *108*
+Models: ARES RTF Park Flyer Aircraft.
+
+### Sub_protocol 6HPA_Tx - *0*
+Models: ARES Gamma 370, P-51D Mustang 350, RTF models with 6HPA-Tx and AZS12006-Rx (6 channel).
+
+Autobind protocol:
+- to bind, power on the TX first
+- then power on the receiver - LED slow flash
+- press receiver bind button - LED faster flash
+- receiver LED will flash quickly (15 seconds) when bound LED turns solid
+
+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.
+
+Option for this protocol corresponds to fine frequency tuning. This value is different for each Module and **must** be accurate otherwise the link will not be stable.
+Check the [Frequency Tuning page](/docs/Frequency_Tuning.md) to determine it.
+
+CH1|CH2|CH3|CH4|CH5|CH6
+---|---|---|---|---|---
+CH1|CH2|CH3|CH4|CH5|CH6
+
## CORONA - *37*
Models: Corona 2.4GHz FSS and DSSS receivers.
@@ -1407,6 +1456,8 @@ VTX+: -100%->+100% channel+
## SLT - *11*
Autobind protocol
+* All SLT subprotococols are operating at a transmission of 250kbps which will originally default to the NRF24L01 module. This sometimes doesn't work well with every NRF24L01, as there are a few reports of hardware issues with the authenticity and accuracy of components.
+* If option (freq tune) is changed from the default "0", then upon module restart the CC2500 module (if installed) will be used instead. Any value other than "0" will also [freq fine tune](/docs/Frequency_Tuning.md) the CC2500 chip.
### Sub_protocol V1 - *0*
@@ -1502,6 +1553,20 @@ Please save radio-profile with a new name without setting reset-button in RF8. T
Find the [Reset21] section and change Input=INT:-1 to Input=INT:9
+### Sub_protocol SLT6_Tx - *7*
+Models using SLT6 transmitter: Revolution 90 FP heli, and others.
+* The complex triple layer output of this sub protocol requires the use of the NRF24L01 chip. Changing the freq tune to any value other than the default "0", upon module restart, will change to the CC2500 module (if installed) and cause intermittent, jerky servo movements.
+
+CH1|CH2|CH3|CH4|CH5|CH6
+---|---|---|---|---|---
+A|E|T|R|FMODE|PANIC
+
+FMODE: flight mode switch (3-positions available)
+* Revolution 90 FP: (only 2-positions needed) -100% or 0%=stab with alt hold, +100%=stabilized mode
+
+PANIC: panic/recovery button, -100%=down (on), +100%=up (off)
+* Revolution 90 FP: not used for this model
+
## V911S - *46*
diff --git a/buildroot/bin/buildFunctions b/buildroot/bin/buildFunctions
index e395fb42d..413ee63e3 100644
--- a/buildroot/bin/buildFunctions
+++ b/buildroot/bin/buildFunctions
@@ -13,8 +13,10 @@ getAllRFModules() {
ALL_RFMODULES=$(echo CYRF6936_INSTALLED);
elif [[ "$BOARD" =~ ":avr:multiatmega328p:" ]]; then
ALL_RFMODULES=$(echo A7105_INSTALLED CYRF6936_INSTALLED CC2500_INSTALLED NRF24L01_INSTALLED);
- elif [[ "$BOARD" =~ ":STM32F1:" ]]; then
+ elif [[ "$BOARD" =~ ":STM32F1:multi5in1t18int" ]]; then
ALL_RFMODULES=$(echo A7105_INSTALLED CYRF6936_INSTALLED CC2500_INSTALLED NRF24L01_INSTALLED SX1276_INSTALLED);
+ elif [[ "$BOARD" =~ ":STM32F1:" ]]; then
+ ALL_RFMODULES=$(echo A7105_INSTALLED CYRF6936_INSTALLED CC2500_INSTALLED NRF24L01_INSTALLED SX1276_INSTALLED LT8910_INSTALLED);
fi
}
@@ -25,23 +27,63 @@ getAllProtocols() {
NRF24L01_PROTOCOLS=$(sed -n 's/[\/\/]*[[:blank:]]*#define[[:blank:]]*\([[:alnum:]_]*_NRF24L01_INO\)\(.*\)/\1/p' Multiprotocol/_Config.h)
CCNRF_PROTOCOLS=$(sed -n 's/[\/\/]*[[:blank:]]*#define[[:blank:]]*\([[:alnum:]_]*_CCNRF_INO\)\(.*\)/\1/p' Multiprotocol/_Config.h)
SX1276_PROTOCOLS=$(sed -n 's/[\/\/]*[[:blank:]]*#define[[:blank:]]*\([[:alnum:]_]*_SX1276_INO\)\(.*\)/\1/p' Multiprotocol/_Config.h)
+ LT8910_PROTOCOLS=$(sed -n 's/[\/\/]*[[:blank:]]*#define[[:blank:]]*\([[:alnum:]_]*_LT8910_INO\)\(.*\)/\1/p' Multiprotocol/_Config.h)
if [[ "$BOARD" =~ ":avr:multixmega32d4" ]]; then
ALL_PROTOCOLS=$(echo $CYRF6936_PROTOCOLS);
elif [[ "$BOARD" =~ ":avr:multiatmega328p:" ]]; then
ALL_PROTOCOLS=$(echo $A7105_PROTOCOLS $CC2500_PROTOCOLS $CYRF6936_PROTOCOLS $NRF24L01_PROTOCOLS $CCNRF_PROTOCOLS);
- elif [[ "$BOARD" =~ ":STM32F1:" ]]; then
+ elif [[ "$BOARD" =~ ":STM32F1:multi5in1t18int" ]]; then
ALL_PROTOCOLS=$(echo $A7105_PROTOCOLS $CC2500_PROTOCOLS $CYRF6936_PROTOCOLS $NRF24L01_PROTOCOLS $CCNRF_PROTOCOLS $SX1276_PROTOCOLS);
+ elif [[ "$BOARD" =~ ":STM32F1:" ]]; then
+ ALL_PROTOCOLS=$(echo $A7105_PROTOCOLS $CC2500_PROTOCOLS $CYRF6936_PROTOCOLS $NRF24L01_PROTOCOLS $CCNRF_PROTOCOLS $SX1276_PROTOCOLS $LT8910_PROTOCOLS);
+ fi
+}
+
+getMaxBinSize() {
+ if [[ "$BOARD" =~ ":STM32F1:multistm32f103cb" ]] || [[ "$BOARD" =~ ":STM32F1:multi5in1t18int" ]]; then
+ echo "${MULTI_MAX_BIN_SIZE:-119808}"
+ fi
+}
+
+checkBinarySize() {
+ local max_size
+ local bin_path
+ local size
+
+ max_size=$(getMaxBinSize)
+ if [ -z "$max_size" ]; then
+ return 0
fi
+
+ bin_path="${GITHUB_WORKSPACE}/build/Multiprotocol.ino.bin"
+ if [ ! -f "$bin_path" ]; then
+ printf "\e[31;1mBinary size check failed: %s not found.\e[0m\n" "$bin_path"
+ return 1
+ fi
+
+ size=$(wc -c < "$bin_path" | tr -d ' ')
+ printf "\e[33;1mBinary size: %s bytes (limit: %s bytes)\e[0m\n" "$size" "$max_size"
+ if [ "$size" -gt "$max_size" ]; then
+ printf "\e[31;1mBinary exceeds limit by %s bytes.\e[0m\n" "$((size - max_size))"
+ return 1
+ fi
+ return 0
}
buildMulti() {
echo ::group::_Config.h
git diff Multiprotocol/_Config.h
echo ::endgroup::
+ rm -rf "${GITHUB_WORKSPACE}/build"
+ mkdir -p "${GITHUB_WORKSPACE}/build"
BUILDCMD="arduino-cli compile -b $BOARD ${GITHUB_WORKSPACE}/Multiprotocol/Multiprotocol.ino --build-path ${GITHUB_WORKSPACE}/build/";
echo $BUILDCMD;
$BUILDCMD
+ if [ $? -ne 0 ]; then
+ return 1
+ fi
+ checkBinarySize
return $?
}
@@ -49,6 +91,13 @@ buildProtocol() {
exitcode=0;
opt_disable $ALL_PROTOCOLS;
opt_enable $1;
+ if [[ "$1" =~ _SX1276_INO$ ]]; then
+ opt_disable $ALL_RFMODULES;
+ opt_enable SX1276_INSTALLED;
+ elif [[ "$1" =~ _LT8910_INO$ ]]; then
+ opt_disable $ALL_RFMODULES;
+ opt_enable LT8910_INSTALLED;
+ fi
buildMulti;
if [ $? -ne 0 ]; then exitcode=1; fi;
return $exitcode;
@@ -68,6 +117,11 @@ buildRFModule() {
exitcode=0;
opt_disable $ALL_RFMODULES;
opt_enable $1;
+ if [ "$1" = "SX1276_INSTALLED" ]; then
+ opt_disable LT8910_INSTALLED;
+ elif [ "$1" = "LT8910_INSTALLED" ]; then
+ opt_disable SX1276_INSTALLED;
+ fi
buildMulti;
if [ $? -ne 0 ]; then exitcode=1; fi;
return $exitcode;
@@ -98,6 +152,8 @@ buildReleaseFiles(){
build_release_stm32f1_4in1_native_debug;
elif [[ "$RELEASE" == "stm32f103-128k-serial-debug" ]]; then
build_release_stm32f1_4in1_serial_debug;
+ elif [[ "$RELEASE" == "stm32f103-128k-lt8910" ]]; then
+ build_release_stm32f1_lt8910;
elif [[ "$RELEASE" == "stm32f103-cc2500-64k" ]]; then
build_release_stm32f1_cc2500_64k;
elif [[ "$RELEASE" == "stm32f103-cc2500-128k" ]]; then
diff --git a/buildroot/bin/build_release_avr_noboot b/buildroot/bin/build_release_avr_noboot
index 30ea23548..f8a85f131 100644
--- a/buildroot/bin/build_release_avr_noboot
+++ b/buildroot/bin/build_release_avr_noboot
@@ -14,7 +14,7 @@ mv build/Multiprotocol.ino.bin ./binaries/mm-avr-usbasp-aetr-A7105-inv-v$MULTI_V
printf "\e[33;1mBuilding mm-avr-usbasp-aetr-CC2500-inv-v$MULTI_VERSION.bin\e[0m\n";
opt_disable $ALL_PROTOCOLS;
opt_enable $CC2500_PROTOCOLS;
-opt_disable HITEC_CC2500_INO REDPINE_CC2500_INO OMP_CC2500_INO SKYARTEC_CC2500_INO SCANNER_CC2500_INO FRSKYL_CC2500_INO;
+opt_disable HITEC_CC2500_INO REDPINE_CC2500_INO SKYARTEC_CC2500_INO SCANNER_CC2500_INO FRSKYL_CC2500_INO;
buildMulti;
exitcode=$((exitcode+$?));
mv build/Multiprotocol.ino.bin ./binaries/mm-avr-usbasp-aetr-CC2500-inv-v$MULTI_VERSION.bin;
diff --git a/buildroot/bin/build_release_avr_optiboot b/buildroot/bin/build_release_avr_optiboot
index c30804be2..899e7c8d0 100644
--- a/buildroot/bin/build_release_avr_optiboot
+++ b/buildroot/bin/build_release_avr_optiboot
@@ -14,7 +14,7 @@ mv build/Multiprotocol.ino.bin ./binaries/mm-avr-txflash-aetr-A7105-inv-v$MULTI_
printf "\e[33;1mBuilding mm-avr-txflash-aetr-CC2500-inv-v$MULTI_VERSION.bin\e[0m\n";
opt_disable $ALL_PROTOCOLS;
opt_enable $CC2500_PROTOCOLS;
-opt_disable HITEC_CC2500_INO REDPINE_CC2500_INO OMP_CC2500_INO SKYARTEC_CC2500_INO SCANNER_CC2500_INO FRSKYL_CC2500_INO;
+opt_disable HITEC_CC2500_INO REDPINE_CC2500_INO RLINK_CC2500_INO SKYARTEC_CC2500_INO SCANNER_CC2500_INO FRSKYL_CC2500_INO;
buildMulti;
exitcode=$((exitcode+$?));
mv build/Multiprotocol.ino.bin ./binaries/mm-avr-txflash-aetr-CC2500-inv-v$MULTI_VERSION.bin;
diff --git a/buildroot/bin/build_release_stm32f1_lt8910 b/buildroot/bin/build_release_stm32f1_lt8910
new file mode 100644
index 000000000..7de5c2341
--- /dev/null
+++ b/buildroot/bin/build_release_stm32f1_lt8910
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+source ./buildroot/bin/buildFunctions;
+exitcode=0;
+
+# STM32 LT8910 AIR builds
+opt_add MULTI_AIR;
+opt_disable ENABLE_PPM;
+opt_disable CFLIE_NRF24L01_INO
+opt_enable LT8910_INSTALLED;
+
+printf "\e[33;1mBuilding mm-stm-ser-lt8910-aetr-air-v$MULTI_VERSION.bin\e[0m\n";
+buildMulti;
+exitcode=$((exitcode+$?));
+mv build/Multiprotocol.ino.bin ./binaries/mm-stm-ser-lt8910-aetr-air-v$MULTI_VERSION.bin;
+
+printf "\e[33;1mBuilding mm-stm-ser-lt8910-taer-air-v$MULTI_VERSION.bin\e[0m\n";
+opt_replace AETR TAER;
+buildMulti;
+exitcode=$((exitcode+$?));
+mv build/Multiprotocol.ino.bin ./binaries/mm-stm-ser-lt8910-taer-air-v$MULTI_VERSION.bin;
+
+printf "\e[33;1mBuilding mm-stm-ser-lt8910-reta-air-v$MULTI_VERSION.bin\e[0m\n";
+opt_replace TAER RETA;
+buildMulti;
+exitcode=$((exitcode+$?));
+mv build/Multiprotocol.ino.bin ./binaries/mm-stm-ser-lt8910-reta-air-v$MULTI_VERSION.bin;
+
+exit $exitcode;
diff --git a/docs/Compiling_STM32.md b/docs/Compiling_STM32.md
index 7d758328f..09c4be188 100644
--- a/docs/Compiling_STM32.md
+++ b/docs/Compiling_STM32.md
@@ -115,6 +115,8 @@ To check that the program will compile correctly and fit in the STM32 click **Sk
If there are errors, carefully read it, go to the line number indicated and correct your typo. Arduino has different ways to indicate that the firmware is too big so check this by removing a large number of protocols and reenable them as you need.
+**Note for scripted builds:** The `buildroot/bin` build scripts enforce a binary size limit for STM32F103CB (128KB) builds. You can override the limit (in bytes) by setting `MULTI_MAX_BIN_SIZE` when running the scripts or in CI.
+
If there are no errors and you see output like this:
```
Sketch uses 68564 bytes (52%) of program storage space. Maximum is 131072 bytes.
diff --git a/docs/LT8910_CG022_Implementation.md b/docs/LT8910_CG022_Implementation.md
new file mode 100644
index 000000000..ddecdb414
--- /dev/null
+++ b/docs/LT8910_CG022_Implementation.md
@@ -0,0 +1,176 @@
+# CG022 / LT8910 Protocol Implementation
+
+## Executive Summary
+
+This document summarizes the final, working CG022 implementation for the DIY Multiprotocol TX Module using an external LT8910 RF board.
+
+**Status:** ✅ **WORKING**
+
+Validated behavior:
+- successful bind and control with the CG022 receiver
+- LT8910 hardware reset driven from `PA14` (`LT8910_RST_pin`)
+- SPI Mode 1 (`CPOL=0`, `CPHA=1`) at `/256` (~140kHz)
+- 8-channel hop sequence with ~2310µs hop period
+- correct bind/data payload format and checksums
+- rate modes `0x00` / `0x11` / `0x22` for 20% / 60% / 100%
+
+---
+
+## 1. Hardware Setup
+
+### 1.1 Target Hardware
+- **MPM Board:** STM32F103-based Multiprotocol TX Module
+- **RF Module:** external LT8910/LT8900 RF board
+- **Connection:** MPM 6-pin SPI header plus a dedicated reset wire
+
+### 1.2 Wiring to MPM Header
+- `MOSI` `PB15` → LT8910 MOSI
+- `MISO` `PB14` → LT8910 MISO
+- `SCK` `PB13` → LT8910 SCK
+- `CS` `PA15` → LT8910 CS
+- `3.3V` and `GND`
+- Wired reset `PA14` → LT8910 RET / RESET
+
+`PA14` is available after `afio_cfg_debug_ports(AFIO_DEBUG_NONE)` and is used as a dedicated LT8910 reset line to avoid conflicts with the onboard RF chips.
+
+### 1.3 Reference Images
+
+
+*Stock CG022 transmitter*
+
+
+*LT8910 RF board right side*
+
+
+*LT8910 RF board left side*
+
+---
+
+## 2. Firmware Integration
+
+### 2.1 Files
+- `Multiprotocol/CG022_lt8910.ino`
+- `Multiprotocol/LT8910_SPI.ino`
+- `Multiprotocol/iface_lt8910.h`
+- `Multiprotocol/Pins.h`
+- `Multiprotocol/Multi_Protos.ino`
+- `Multiprotocol/Multiprotocol.ino`
+- `Multiprotocol/_Config.h`
+- `Multiprotocol/Validate.h`
+
+### 2.2 Key Implementation Choices
+1. **Dedicated LT8910 reset line on `PA14`**
+2. **SPI Mode 1** for LT8910 access
+3. **Slow SPI clock** (`/256`) to match the stock transmitter timing envelope
+4. **Shared SPI bus protection** by deselecting onboard RF chips before LT8910 use
+5. **SPI restore in `modules_reset()`** before other RF chips are reset, so protocol switching remains safe
+
+### 2.3 Build Configuration
+To compile CG022 support on STM32 targets, enable the LT8910 RF module:
+
+```c
+//#define SX1276_INSTALLED
+#define LT8910_INSTALLED
+```
+
+`CG022_LT8910_INO` is enabled in `_Config.h`, and `Validate.h` automatically removes it when `LT8910_INSTALLED` is not enabled.
+
+LT8910 / CG022 is intended as an **opt-in external-module build**. It is not part of the normal 4-in-1 release binaries. When a prebuilt image is desired, use the dedicated LT8910 air release variant named:
+
+```text
+mm-stm-ser-lt8910-[channel_order]-air-v[version].bin
+```
+
+---
+
+## 3. Protocol Summary
+
+### 3.1 RF Timing
+- **Hop sequence:** `0, 40, 10, 50, 20, 60, 30, 70`
+- **Hop period:** ~`2310µs`
+- **Transmit order per hop:** set channel → write FIFO → TX on
+
+### 3.2 LT8910 Register Configuration
+The LT8910 is initialized from the captured stock register set in `CG022_init_regs[]`, including:
+- preamble configuration
+- sync words
+- CRC polynomial and seed
+- TX power and packet configuration
+
+### 3.3 Bind Packet Format
+Bind packets are 10 bytes:
+
+```text
+0A 00 11 22 33 xx yy zz cc 00
+```
+
+- bytes `2..4` are the fixed bind prefix
+- bytes `5..7` are the transmitter ID derived from `MProtocol_id`
+- byte `8` is `(byte5 + byte6 + byte7) & 0xFF`
+
+### 3.4 Data Packet Format
+Data packets are 10 bytes:
+
+```text
+0A rr tt ee rr aa ff gg 20 cc
+```
+
+Where:
+- byte `1` = rate mode (`0x00`, `0x11`, `0x22`)
+- bytes `2..5` = throttle / elevator / rudder / aileron
+- bytes `6..7` = feature flags
+- byte `8` = fixed `0x20`
+- byte `9` = checksum of bytes `2..8`
+
+### 3.5 Model Match
+Model match is implemented from the low 24 bits of `MProtocol_id`. Changing the RX number changes the generated TX ID and requires re-binding.
+
+---
+
+## 4. Channel Assignment
+
+| CH | Function |
+| --- | --- |
+| 1 | Aileron |
+| 2 | Elevator |
+| 3 | Throttle |
+| 4 | Rudder |
+| 5 | Rate (`-100%=20%`, `0%=60%`, `+100%=100%`) |
+| 6 | Flip |
+| 7 | Headless |
+| 8 | LED |
+| 16 | Rebind |
+
+---
+
+## 5. Validation Summary
+
+### 5.1 Verified Working
+- bind sequence
+- flight control on channels 1-4
+- 3-position rate control on channel 5
+- flip on channel 6
+- headless mode on channel 7
+- LED control on channel 8
+- rebind support through the bind-channel workflow
+- protocol switching after restoring SPI defaults in `modules_reset()`
+
+### 5.2 Final Implementation Details
+- **Reset line:** `PA14`
+- **SPI mode:** Mode 1
+- **SPI speed:** `/256` (~140kHz)
+- **Rate values:** `0x00`, `0x11`, `0x22`
+- **Bind source:** autobind on startup, with manual rebind available through the bind workflow
+
+---
+
+## 6. Notes for Future Maintenance
+
+- LT8910 and SX1276 both use `PA15` for chip select and cannot be enabled together.
+- CG022 depends on the LT8910 support path and should only be built on supported STM32 targets.
+- When changing the LT8910 initialization sequence, keep the captured stock timing assumptions in sync with `CG022_prepare_hardware()` and `CG022_LT8910_init()`.
+
+---
+
+*Document updated: April 29, 2026*
+*Repository: MRC3742/DIY-Multiprotocol-TX-Module*
diff --git a/docs/images/01_TX.jpeg b/docs/images/01_TX.jpeg
new file mode 100644
index 000000000..5905f1195
Binary files /dev/null and b/docs/images/01_TX.jpeg differ
diff --git a/docs/images/02_LT8910_RF_Right.jpeg b/docs/images/02_LT8910_RF_Right.jpeg
new file mode 100644
index 000000000..222ed366e
Binary files /dev/null and b/docs/images/02_LT8910_RF_Right.jpeg differ
diff --git a/docs/images/03_LT8910_RF_Left.jpeg b/docs/images/03_LT8910_RF_Left.jpeg
new file mode 100644
index 000000000..c3b1d6c88
Binary files /dev/null and b/docs/images/03_LT8910_RF_Left.jpeg differ