From fd43fdea30b6732f68070efbf3237a3577ffb87a Mon Sep 17 00:00:00 2001 From: dududu009 <277218636+dududu009@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:37:41 +0800 Subject: [PATCH 1/3] Add fixed-ID MC8RE V2 A7105 protocol --- Multiprotocol/MC8RE_a7105.ino | 407 +++++++++++++++++++++++++++++++++ Multiprotocol/Multi_Protos.ino | 6 +- Multiprotocol/Multiprotocol.h | 6 + Multiprotocol/Telemetry.ino | 12 + Multiprotocol/Validate.h | 2 + Multiprotocol/_Config.h | 1 + Protocols_Details.md | 15 ++ docs/MC8RE_V2.md | 35 +++ 8 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 Multiprotocol/MC8RE_a7105.ino create mode 100644 docs/MC8RE_V2.md diff --git a/Multiprotocol/MC8RE_a7105.ino b/Multiprotocol/MC8RE_a7105.ino new file mode 100644 index 000000000..0fd8285d4 --- /dev/null +++ b/Multiprotocol/MC8RE_a7105.ino @@ -0,0 +1,407 @@ +/* + * MC8RE V2 A7105 protocol. This is the fixed-ID V59 implementation. + * + * The first 61 normal packets use the captured header-84 startup cadence: + * two slots are RX/idle. At packet 61, the capture switches to header 04 + * and resumes the ordinary 16-channel sequence, retaining only the RX slot + * following channel 02. + */ +#if defined(MC8RE_A7105_INO) + +#include "iface_a7105.h" + +#define MC8V43_SLOT_US 1970U +#define MC8V43_RX_START_US 1025U +#define MC8V43_RX_HOLD_US 2915U +#define MC8V43_BIND_PACKETS 6U +#define MC8V43_PACKET_SIZE 13U +#define MC8V43_STARTUP_PACKETS 61U +/* Session 1 captured this normal-mode A7105 ID. A later attempt to derive + * it from EdgeTX receiver number bound briefly but did not maintain control; + * this implementation deliberately keeps the capture-proven fixed ID. */ +#define MC8RE_LINK_ID 0x51D567EBUL + +static const uint8_t PROGMEM mc8v43_first_tx[9] = { + 0x7A, 0x2A, 0x70, 0x20, 0x66, 0x5C, 0x0C, 0x52, 0x02 +}; + +static const uint8_t PROGMEM mc8v43_startup_tx[15] = { + 0x98, 0x98, 0x8E, 0x3E, 0x84, 0x34, 0x7A, 0x2A, + 0x70, 0x20, 0x66, 0x5C, 0x0C, 0x52, 0x02 +}; + +static const uint8_t PROGMEM mc8v43_normal_hop[16] = { + 0x7A, 0x2A, 0x70, 0x20, 0x66, 0x16, 0x5C, 0x0C, + 0x52, 0x02, 0x98, 0x48, 0x8E, 0x3E, 0x84, 0x34 +}; + +/* The captured header-04 transition retains 0x98 for one extra slot. + * Subsequent completed normal cycles use mc8v43_normal_hop above. */ +static const uint8_t PROGMEM mc8v43_first_steady_hop[16] = { + 0x7A, 0x2A, 0x70, 0x20, 0x66, 0x16, 0x5C, 0x0C, + 0x52, 0x02, 0x98, 0x98, 0x8E, 0x3E, 0x84, 0x34 +}; + +static const uint8_t PROGMEM mc8v43_rx[16] = { + 0x98, 0x48, 0x8E, 0x3E, 0x84, 0x34, 0x7A, 0x2A, + 0x70, 0x20, 0x66, 0x16, 0x5C, 0x0C, 0x52, 0x02 +}; + +static const uint8_t PROGMEM mc8v43_startup_packet[13] = { + 0x84, 0x00, 0xE9, 0x4B, 0x5F, 0x64, 0xD2, + 0x57, 0x9F, 0xFA, 0xD4, 0xA7, 0x3E +}; + +static const uint8_t PROGMEM mc8v43_steady_packet[13] = { + 0x04, 0x04, 0xEC, 0x23, 0x5F, 0x77, 0xC7, + 0xD7, 0xDD, 0xF4, 0x4D, 0x0A, 0x60 +}; + +static const uint8_t PROGMEM mc8v43_regs[0x32] = { + 0xFF, 0x42, 0xFF, 0x0C, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x01, 0x50, + 0x9E, 0x4B, 0x00, 0x02, 0x16, 0x2B, 0x12, 0x00, + 0x62, 0x80, 0x80, 0x00, 0x0A, 0x32, 0xC3, 0x0F, + 0x13, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x3B, 0x00, + 0x17, 0x47, 0x80, 0x03, 0x01, 0x45, 0x18, 0x00, + 0x01, 0x0F +}; + +enum MC8V43_State : uint8_t { + MC8V43_WAIT_BIND, + MC8V43_BIND, + MC8V43_TX, + MC8V43_IDLE_AFTER_66, + MC8V43_RX_START, + MC8V43_RX_FINISH +}; + +static MC8V43_State mc8v43_state; +static uint8_t mc8v43_bind_sent; +static uint8_t mc8v43_tx_index; +static uint8_t mc8v43_rx_index; +static bool mc8v43_first_fragment; +static bool mc8v43_startup; +static bool mc8v43_first_steady; +static uint32_t mc8v43_packets; +static uint32_t mc8v43_frame; +static uint32_t mc8v43_link_id; + +/* P1 is a rolling frame counter. P2..P12 are 88 bits containing eight + * consecutive little-endian 11-bit channel values. Captured hardware tests + * established 358/1024/1689 as the wire values for 1000/1500/2000 us. */ +static uint16_t mc8v53_standard_channel(uint8_t source) +{ + const uint16_t input = limit_channel_100(CH_AETR[source]); + const uint16_t low = 358U; + const uint16_t high = 1689U; + if(input <= CHANNEL_MID) + return 1024U - ((uint32_t)(CHANNEL_MID - input) * (1024U - low) + + (CHANNEL_MID - CHANNEL_MIN_100) / 2U) + / (CHANNEL_MID - CHANNEL_MIN_100); + return 1024U + ((uint32_t)(input - CHANNEL_MID) * (high - 1024U) + + (CHANNEL_MAX_100 - CHANNEL_MID) / 2U) + / (CHANNEL_MAX_100 - CHANNEL_MID); +} + +static void mc8v53_put_u11(uint8_t slot, uint16_t value) +{ + const uint8_t first_bit = slot * 11U; + value &= 0x07FF; + for(uint8_t bit = 0; bit < 11U; bit++) + { + const uint8_t packed_bit = first_bit + bit; + const uint8_t offset = 2U + (packed_bit >> 3); + const uint8_t mask = 1U << (packed_bit & 7U); + if(value & (1U << bit)) + packet[offset] |= mask; + else + packet[offset] &= ~mask; + } +} + +static void mc8v53_apply_packed_channels() +{ + for(uint8_t source = 0; source < 8U; source++) + mc8v53_put_u11(source, mc8v53_standard_channel(source)); +} + +#if defined(TELEMETRY) +/* + * Session 1 was captured with the receiver's external voltage input at 0 V. + * Its replies were 01 0D 00 31 ... (149 times) and 01 0D 00 30 ... (once). + * V54 originally used byte 2 as a tentative external-voltage value. V57 + * raw telemetry instead measured byte 3 = 0x32 for the 5.0 V BEC and byte 4 + * = 0xA0..0xA3 with a known 16.3 V external pack. V58/V59 therefore use + * byte 4 for A2 external voltage, with 0.1 V/count. They send the unscaled + * decivolt value so that the EdgeTX model can set A2's ratio to 25.5 V for + * 4S range. + */ +static uint8_t mc8v54_frsky_adc(uint8_t decivolts) +{ + const uint16_t adc = ((uint16_t)decivolts * 255U + 66U) / 132U; + return adc > 255U ? 255U : adc; +} + +/* Register 1D is the A7105 RSSI ADC while 1E is configured for continuous + * RSSI sampling by the captured register table. Its useful reference-code + * span is about 8 (very strong) to 160 (very weak). This is intentionally a + * relative 0..100 indication, not a calibrated dBm conversion. */ +static uint8_t mc8v59_relative_rssi(uint8_t raw) +{ + if(raw <= 8U) + return 100U; + if(raw >= 160U) + return 0U; + return ((uint16_t)(160U - raw) * 100U + 76U) / 152U; +} + +static uint8_t mc8v59_rssi; +static bool mc8v59_rssi_valid; + +static void mc8v54_process_reply() +{ + if(packet[0] != 0x01 || packet[1] != 0x0D) + return; + + const uint8_t external_raw = packet[4]; + const uint8_t link_raw = packet[2]; + const uint8_t receiver_raw = packet[3]; + v_lipo1 = mc8v54_frsky_adc(receiver_raw); + /* The default FrSky-D A2 ratio is only 13.2 V. Preserve the original + * 0.1 V/count receiver value for an EdgeTX A2 ratio of 25.5 V. */ + v_lipo2 = external_raw; + { + const uint8_t sample = mc8v59_relative_rssi(A7105_ReadReg(A7105_1D_RSSI_THOLD)); + if(!mc8v59_rssi_valid) + { + mc8v59_rssi = sample; + mc8v59_rssi_valid = true; + } + else + mc8v59_rssi = ((uint16_t)mc8v59_rssi * 3U + sample + 2U) / 4U; + RX_RSSI = mc8v59_rssi; + } + TX_RSSI = receiver_raw <= 127U ? receiver_raw << 1 : 0xFF; + RX_LQI = link_raw; + TX_LQI = receiver_raw; + telemetry_counter++; + telemetry_lost = 0; + telemetry_link = 1; +} +#endif + +static void mc8v43_radio_init() +{ + A7105_Reset(); + A7105_WriteID(0x5475C52A); + for(uint8_t reg = 0; reg < 0x32; reg++) + { + const uint8_t value = pgm_read_byte_near(&mc8v43_regs[reg]); + if(value != 0xFF) + A7105_WriteReg(reg, value); + } + A7105_Strobe(A7105_STANDBY); + A7105_WriteReg(A7105_02_CALC, 0x01); + while(A7105_ReadReg(A7105_02_CALC)); + A7105_WriteReg(A7105_24_VCO_CURCAL, 0x13); + A7105_WriteReg(A7105_26_VCO_SBCAL_II, 0x3B); + A7105_WriteReg(A7105_0F_CHANNEL, 0x00); + A7105_WriteReg(A7105_02_CALC, 0x02); + while(A7105_ReadReg(A7105_02_CALC)); + A7105_WriteReg(A7105_0F_CHANNEL, 0xA0); + A7105_WriteReg(A7105_02_CALC, 0x02); + while(A7105_ReadReg(A7105_02_CALC)); + A7105_WriteReg(A7105_25_VCO_SBCAL_I, 0x0A); + A7105_SetTxRxMode(TX_EN); + /* The register table has already written the captured init value 0x17. + * send_bind() changes it to 0x0D immediately before the first FIFO load. */ + prev_power = 0x17; + A7105_Strobe(A7105_STANDBY); +} + +static void mc8v43_build_bind() +{ + memset(packet, 0, MC8V43_PACKET_SIZE); + packet[0] = 0xAA; + packet[1] = 0x11; + packet[2] = (uint8_t)(mc8v43_link_id >> 16); + packet[3] = (uint8_t)(mc8v43_link_id >> 8); + packet[4] = (uint8_t)mc8v43_link_id; +} + +static void mc8v43_build_normal() +{ + const uint8_t *source = mc8v43_startup + ? mc8v43_startup_packet : mc8v43_steady_packet; + for(uint8_t i = 0; i < MC8V43_PACKET_SIZE; i++) + packet[i] = pgm_read_byte_near(source + i); + /* P1 is the rolling counter; P2..P12 contain eight packed 11-bit values. */ + packet[1] = mc8v43_frame & 0x0F; + if(!mc8v43_startup) + mc8v53_apply_packed_channels(); +} + +static void mc8v43_send_bind() +{ + mc8v43_build_bind(); + A7105_WriteReg(A7105_28_TX_TEST, 0x0D); + prev_power = 0x0D; + A7105_WriteReg(A7105_27_BATTERY_DET, 0x0F); + A7105_Strobe(A7105_STANDBY); + A7105_WriteData(MC8V43_PACKET_SIZE, 0x01); +} + +static void mc8v43_enter_normal() +{ + A7105_Strobe(A7105_STANDBY); + A7105_WriteID(mc8v43_link_id); + A7105_WriteReg(A7105_28_TX_TEST, 0x1F); + prev_power = 0x1F; + mc8v43_state = MC8V43_TX; + mc8v43_tx_index = 0; + mc8v43_rx_index = 0; + mc8v43_first_fragment = true; + mc8v43_startup = true; + mc8v43_first_steady = false; + mc8v43_packets = 0; + mc8v43_frame = 0; + if(IS_BIND_IN_PROGRESS) + BIND_DONE; +} + +void MC8V43_init() +{ + mc8v43_link_id = MC8RE_LINK_ID; + mc8v43_radio_init(); + /* Do not radiate until the Multi protocol's Bind flag is present. + * This also handles the path where WAIT_FOR_BIND has already initialized + * the radio and the subsequent EdgeTX Bind request only releases it. */ + mc8v43_state = IS_BIND_IN_PROGRESS ? MC8V43_BIND : MC8V43_WAIT_BIND; + mc8v43_bind_sent = 0; + mc8v43_tx_index = 0; + mc8v43_rx_index = 0; + mc8v43_first_fragment = true; + mc8v43_startup = true; + mc8v43_first_steady = false; + mc8v43_packets = 0; + mc8v43_frame = 0; + #if defined(TELEMETRY) + mc8v59_rssi = 0; + mc8v59_rssi_valid = false; + #endif +} + +uint16_t MC8V43_callback() +{ + if(mc8v43_state == MC8V43_WAIT_BIND) + { + if(!IS_BIND_IN_PROGRESS) + return MC8V43_SLOT_US; + mc8v43_state = MC8V43_BIND; + } + + if(mc8v43_state == MC8V43_BIND) + { + if(mc8v43_bind_sent < MC8V43_BIND_PACKETS) + { + mc8v43_send_bind(); + mc8v43_bind_sent++; + return MC8V43_SLOT_US; + } + mc8v43_enter_normal(); + } + + if(mc8v43_state == MC8V43_IDLE_AFTER_66) + { + mc8v43_tx_index++; + mc8v43_state = MC8V43_TX; + return MC8V43_SLOT_US; + } + + if(mc8v43_state == MC8V43_RX_START) + { + A7105_Strobe(A7105_STANDBY); + A7105_SetTxRxMode(RX_EN); + A7105_WriteReg(A7105_0F_CHANNEL, + pgm_read_byte_near(&mc8v43_rx[mc8v43_rx_index])); + A7105_Strobe(A7105_RST_RDPTR); + A7105_Strobe(A7105_RX); + mc8v43_rx_index = (mc8v43_rx_index + 1) & 0x0F; + mc8v43_state = MC8V43_RX_FINISH; + return MC8V43_RX_HOLD_US; + } + + if(mc8v43_state == MC8V43_RX_FINISH) + { + if((A7105_ReadReg(A7105_00_MODE) & 0x01) == 0) + { + A7105_ReadData(MC8V43_PACKET_SIZE); + #if defined(TELEMETRY) + mc8v54_process_reply(); + #endif + } + mc8v43_frame++; + if(mc8v43_startup) + { + mc8v43_tx_index = 0; + mc8v43_first_fragment = false; + } + mc8v43_state = MC8V43_TX; + } + + mc8v43_build_normal(); + const uint8_t channel = mc8v43_startup + ? (mc8v43_first_fragment + ? pgm_read_byte_near(&mc8v43_first_tx[mc8v43_tx_index]) + : pgm_read_byte_near(&mc8v43_startup_tx[mc8v43_tx_index])) + : pgm_read_byte_near(mc8v43_first_steady + ? &mc8v43_first_steady_hop[mc8v43_tx_index] + : &mc8v43_normal_hop[mc8v43_tx_index]); + A7105_WriteReg(A7105_27_BATTERY_DET, 0x0F); + A7105_Strobe(A7105_STANDBY); + A7105_WriteData(MC8V43_PACKET_SIZE, channel); + mc8v43_packets++; + + if(mc8v43_startup && mc8v43_packets == MC8V43_STARTUP_PACKETS) + { + /* The observed header-04 transition is at 2A, normal_hop[1]. */ + mc8v43_startup = false; + mc8v43_first_steady = true; + mc8v43_tx_index = 1; + return MC8V43_SLOT_US; + } + if(mc8v43_startup && channel == 0x66) + { + mc8v43_state = MC8V43_IDLE_AFTER_66; + return MC8V43_SLOT_US; + } + if(channel == 0x02) + { + if(!mc8v43_startup) + mc8v43_tx_index = (mc8v43_tx_index + 1) & 0x0F; + mc8v43_state = MC8V43_RX_START; + return MC8V43_RX_START_US; + } + /* Complete the captured first-steady table, then use the normal table. */ + if(!mc8v43_startup && mc8v43_tx_index == 0x0F) + { + mc8v43_tx_index = 0; + mc8v43_first_steady = false; + } + else + mc8v43_tx_index++; + return MC8V43_SLOT_US; +} + +void MC8RE_init() +{ + MC8V43_init(); +} + +uint16_t MC8RE_callback() +{ + return MC8V43_callback(); +} + +#endif diff --git a/Multiprotocol/Multi_Protos.ino b/Multiprotocol/Multi_Protos.ino index c2cf4f0cb..9e5a4505f 100644 --- a/Multiprotocol/Multi_Protos.ino +++ b/Multiprotocol/Multi_Protos.ino @@ -96,6 +96,7 @@ const char STR_RLINK[] ="RadLink"; const char STR_REALACC[] ="Realacc"; const char STR_OMP[] ="OMP"; const char STR_MLINK[] ="M-Link"; +const char STR_MC8RE[] ="MC8RE V2"; const char STR_TEST[] ="Test"; const char STR_NANORF[] ="NanoRF"; const char STR_E016HV2[] ="E016Hv2"; @@ -433,7 +434,10 @@ const mm_protocol_definition multi_protocols[] = { {PROTO_MLINK, STR_MLINK, NO_SUBTYPE, 0, OPTION_NONE, 1, 0, SW_CYRF, MLINK_init, MLINK_callback }, #endif #if defined(XK_CCNRF_INO) - {PROTO_MOFLY, STR_MOFLY, NO_SUBTYPE, 0, OPTION_RFTUNE, 0, 0, SW_NRF, XK_init, XK_callback }, + {PROTO_MOFLY, STR_MOFLY, NO_SUBTYPE, 0, OPTION_RFTUNE, 0, 0, SW_NRF, XK_init, XK_callback }, + #endif + #if defined(MC8RE_A7105_INO) + {PROTO_MC8RE, STR_MC8RE, NO_SUBTYPE, 0, OPTION_NONE, 0, 0, SW_A7105, MC8RE_init, MC8RE_callback }, #endif #if defined(MOULDKG_NRF24L01_INO) {PROTO_MOULDKG, STR_MOULDKG, STR_SUBTYPE_MOULDKG, 3, OPTION_OPTION, 0, 0, SW_NRF, MOULDKG_init, MOULDKG_callback }, diff --git a/Multiprotocol/Multiprotocol.h b/Multiprotocol/Multiprotocol.h index 6e73255fa..36221003a 100644 --- a/Multiprotocol/Multiprotocol.h +++ b/Multiprotocol/Multiprotocol.h @@ -137,6 +137,7 @@ enum PROTOCOLS PROTO_WPL = 107, // =>NRF24L01 PROTO_ARES = 108, // =>CC2500 PROTO_MOFLY = 109, // =>CC2500 & NRF24L01 + PROTO_MC8RE = 110, // =>A7105 PROTO_NANORF = 126, // =>NRF24L01 PROTO_TEST = 127, // =>CC2500 @@ -597,6 +598,11 @@ enum MultiPacketTypes MULTI_TELEMETRY_RLINK = 18, }; +#if defined(MC8RE_A7105_INO) +void MC8RE_init(); +uint16_t MC8RE_callback(); +#endif + // Macros #define NOP() __asm__ __volatile__("nop") diff --git a/Multiprotocol/Telemetry.ino b/Multiprotocol/Telemetry.ino index b6554d57e..133bf3c3f 100644 --- a/Multiprotocol/Telemetry.ino +++ b/Multiprotocol/Telemetry.ino @@ -326,6 +326,18 @@ static void multi_send_status() static void multi_send_frskyhub() { + #if defined(MC8RE_A7105_INO) + /* MC8RE V2 provides only A1/A2 plus the FrSky-D link marker. A short + * frame intentionally prevents EdgeTX from creating synthetic link + * quality telemetry fields that this receiver does not transmit. */ + if(protocol == PROTO_MC8RE && frame[0] == 0xFE) + { + multi_send_header(MULTI_TELEMETRY_HUB, 4); + for(uint8_t i = 0; i < 4; i++) + Serial_write(frame[i]); + return; + } + #endif multi_send_header(MULTI_TELEMETRY_HUB, 9); for (uint8_t i = 0; i < 9; i++) Serial_write(frame[i]); diff --git a/Multiprotocol/Validate.h b/Multiprotocol/Validate.h index 739e6455b..84286f0c5 100644 --- a/Multiprotocol/Validate.h +++ b/Multiprotocol/Validate.h @@ -257,6 +257,7 @@ #undef HUBSAN_A7105_INO #undef JOYSWAY_A7105_INO #undef KYOSHO_A7105_INO + #undef MC8RE_A7105_INO #undef PELIKAN_A7105_INO #undef WFLY2_A7105_INO #endif @@ -418,6 +419,7 @@ #undef BUGS_A7105_INO #undef HEIGHT_A7105_INO #undef HUBSAN_A7105_INO + #undef MC8RE_A7105_INO #undef E010R5_CYRF6936_INO #undef E01X_CYRF6936_INO #undef E129_CYRF6936_INO diff --git a/Multiprotocol/_Config.h b/Multiprotocol/_Config.h index 63f705962..b708cc765 100644 --- a/Multiprotocol/_Config.h +++ b/Multiprotocol/_Config.h @@ -186,6 +186,7 @@ #define HUBSAN_A7105_INO #define JOYSWAY_A7105_INO #define KYOSHO_A7105_INO +#define MC8RE_A7105_INO #define PELIKAN_A7105_INO #define WFLY2_A7105_INO diff --git a/Protocols_Details.md b/Protocols_Details.md index dcc869d47..a4a51ea2a 100644 --- a/Protocols_Details.md +++ b/Protocols_Details.md @@ -127,6 +127,7 @@ CFlie|AIR|38|CFlie||||||||NRF24L01| [MJXq](Protocols_Details.md#MJXQ---18)||18|WLH08|X600|X800|H26D|E010*|H26WH|PHOENIX*||NRF24L01|XN297 [MLINK](Protocols_Details.md#MLINK---78)||78|||||||||CYRF6936| [MoFly](Protocols_Details.md#MoFly---109)||109|||||||||NRF24L01&CC2500|XN297 +[MC8RE V2](Protocols_Details.md#MC8RE-V2---110)|AIR|110|||||||||A7105| [MouldKg](Protocols_Details.md#mouldkg---90)||90|A4444|D4444|A664||||||NRF24L01|XN297 [MT99xx](Protocols_Details.md#MT99XX---17)||17|MT|H7|YZ|LS|FY805|A180|DRAGON|F949G|NRF24L01|XN297 [MT99xx2](Protocols_Details.md#MT99XX2---92)||92|PA18|SU35|||||||NRF24L01|XN297 @@ -381,6 +382,20 @@ A|E|T|R|CH5|CH6 RX output will match the Hype standard AETR independently of the input configuration AETR, RETA... unless on OpenTX 2.3.3+ you use the "Disable channel mapping" feature on the GUI. +## MC8RE V2 - *110* + +MC8RE V2 receiver family. Bind from the transmitter GUI, then set A1 to the +receiver/BEC voltage and A2 to the external battery voltage. Set A2's ratio +to 25.5 V for a 4S voltage range. + +This protocol is fixed to the capture-proven transmitter identity and hopping +sequence. It is not yet a universal implementation for transmitters with a +different original identity; see [MC8RE_V2.md](docs/MC8RE_V2.md). + +CH1|CH2|CH3|CH4|CH5|CH6|CH7|CH8 +---|---|---|---|---|---|---|--- +A|E|T|R|CH5|CH6|CH7|CH8 + ## Pelikan - *60* Extended limits supported diff --git a/docs/MC8RE_V2.md b/docs/MC8RE_V2.md new file mode 100644 index 000000000..54c7beec3 --- /dev/null +++ b/docs/MC8RE_V2.md @@ -0,0 +1,35 @@ +# MC8RE V2 (A7105) + +This protocol supports the MC8RE V2 receiver family using an A7105 RF chip. +It was reconstructed from a wired A7105 SPI capture of the original +transmitter and then verified on two MC8RE V2 receivers. + +## Implemented behaviour + +- bind sequence: six packets on A7105 ID `5475C52A`, channel + `01`, then normal operation on ID `51D567EB`; +- 16-channel hopping and the original startup/RX slot timing captured from + the transmitter; +- eight 11-bit packed output channels, with EdgeTX +/-100% mapped to + 1000/1500/2000 us; +- telemetry A1 (receiver/BEC voltage) and A2 (external battery voltage, + 0.1 V per count; set EdgeTX A2 ratio to 25.5 V for a 4S range); +- a relative local A7105 RSSI indication. It is not a calibrated received + signal-strength measurement supplied by the receiver. + +## Known limitation + +The normal link ID and hopping sequence are intentionally fixed to the +capture-proven transmitter identity `51D567EB`. Another original +transmitter has been observed to use a different ID and hopping sequence. +Deriving only the low 24 bits from the EdgeTX receiver number bound briefly +but did not maintain control, so it is deliberately not exposed as an option. +This is why this change is suitable for a **Draft PR / request for more +captures**, rather than a claim of universal MC8RE compatibility. + +## Reproduction evidence + +The contributor can provide the original Saleae `.sal` wired capture on +request. Its SPI settings are CPOL=0, CPHA=0, MSB first; A7105 CS, SCK, +MOSI and MISO were captured. No receiver serial output or third-party RF +packet decoder was used to infer the normal-channel format. From 704238c68a60b2de93b4b31203c84ce27506c656 Mon Sep 17 00:00:00 2001 From: dududu009 <277218636+dududu009@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:05:43 +0800 Subject: [PATCH 2/3] Fix MC8RE PWM midpoint and standard/extended channel ranges --- Multiprotocol/MC8RE_a7105.ino | 30 +++++++++++++++++++++++------- docs/MC8RE_V2.md | 31 +++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Multiprotocol/MC8RE_a7105.ino b/Multiprotocol/MC8RE_a7105.ino index 0fd8285d4..04f7c2bba 100644 --- a/Multiprotocol/MC8RE_a7105.ino +++ b/Multiprotocol/MC8RE_a7105.ino @@ -88,18 +88,34 @@ static uint32_t mc8v43_frame; static uint32_t mc8v43_link_id; /* P1 is a rolling frame counter. P2..P12 are 88 bits containing eight - * consecutive little-endian 11-bit channel values. Captured hardware tests - * established 358/1024/1689 as the wire values for 1000/1500/2000 us. */ + * consecutive little-endian 11-bit channel values. Session 3 receiver CH1 + * PWM measurements with v1.3.4.73 gave about 858/1523/2187 us at minimum, + * neutral and maximum. These support PWM_us ~= wire_value + 500. + * Session 4 confirmed nominal 1000/1500/2000 us with wire 500/1000/1500. + * Preserve nominal 1000/1500/2000 us at -100/0/+100%. + * Only the extended input segments -125..-100% and +100..+125% stretch + * to nominal 500 and 2500 us. Receiver CH1 extended-range measurements + * with v1.3.4.76 gave approximately 499/1500/2499 us. */ static uint16_t mc8v53_standard_channel(uint8_t source) { - const uint16_t input = limit_channel_100(CH_AETR[source]); - const uint16_t low = 358U; - const uint16_t high = 1689U; + const uint16_t channel = Channel_data[CH_AETR[source]]; + const uint16_t input = channel > CHANNEL_MAX_125 ? CHANNEL_MAX_125 : channel; + const uint16_t low = 500U; + const uint16_t middle = 1000U; + const uint16_t high = 1500U; + if(input < CHANNEL_MIN_100) + return ((uint32_t)(input - CHANNEL_MIN_125) * low + + (CHANNEL_MIN_100 - CHANNEL_MIN_125) / 2U) + / (CHANNEL_MIN_100 - CHANNEL_MIN_125); + if(input > CHANNEL_MAX_100) + return high + ((uint32_t)(input - CHANNEL_MAX_100) * 500U + + (CHANNEL_MAX_125 - CHANNEL_MAX_100) / 2U) + / (CHANNEL_MAX_125 - CHANNEL_MAX_100); if(input <= CHANNEL_MID) - return 1024U - ((uint32_t)(CHANNEL_MID - input) * (1024U - low) + return middle - ((uint32_t)(CHANNEL_MID - input) * (middle - low) + (CHANNEL_MID - CHANNEL_MIN_100) / 2U) / (CHANNEL_MID - CHANNEL_MIN_100); - return 1024U + ((uint32_t)(input - CHANNEL_MID) * (high - 1024U) + return middle + ((uint32_t)(input - CHANNEL_MID) * (high - middle) + (CHANNEL_MAX_100 - CHANNEL_MID) / 2U) / (CHANNEL_MAX_100 - CHANNEL_MID); } diff --git a/docs/MC8RE_V2.md b/docs/MC8RE_V2.md index 54c7beec3..4b3d1af0c 100644 --- a/docs/MC8RE_V2.md +++ b/docs/MC8RE_V2.md @@ -10,13 +10,40 @@ transmitter and then verified on two MC8RE V2 receivers. `01`, then normal operation on ID `51D567EB`; - 16-channel hopping and the original startup/RX slot timing captured from the transmitter; -- eight 11-bit packed output channels, with EdgeTX +/-100% mapped to - 1000/1500/2000 us; +- eight 11-bit packed output channels with piecewise scaling: EdgeTX + -100%/0%/+100% maps to nominal PWM 1000/1500/2000 us (wire + 500/1000/1500). Extended -125%/+125% reaches nominal 500/2500 us + (wire 0/2000); - telemetry A1 (receiver/BEC voltage) and A2 (external battery voltage, 0.1 V per count; set EdgeTX A2 ratio to 25.5 V for a 4S range); - a relative local A7105 RSSI indication. It is not a calibrated received signal-strength measurement supplied by the receiver. +## PWM mapping and receiver measurements + +The v1.3.4.74 PWM mapping is based on Session 3 receiver CH1 measurements +with v1.3.4.73 (approximately 858/1523/2187 us at minimum/neutral/maximum). +Session 4 verified v1.3.4.74 CH1 at approximately 1000/1499–1500/1998 us. +The v1.3.4.76 extended-range test preserves the standard mapping within ++/-100% and stretches only the outer 100–125% segments to nominal +500–2500 us. A subsequent +/-125% capture measured receiver CH1 at +499.042/1500.125/2499.208 us (minimum/neutral/maximum). Median PWM period +was 14.001167 ms. This capture contained 538 complete pulses, including +58 stable minimum pulses and 44 stable maximum pulses. Logic D7 was +connected directly to receiver CH1; sampling rate was 24 MHz. + +These are receiver CH1 PWM measurements. All eight channel fields use the +same encoder, but other receiver channels have not been measured individually. +The extended-range capture did not separately hold +/-100% positions; +those were measured on the preceding standard-range build. No simultaneous +SPI traffic was recorded in these PWM captures. + +For the full range, enable EdgeTX Extended Limits, keep mixer weight at +100%, and set output limits to -125%/+125% with zero trim/subtrim/offset. +Confirm the channel monitor reaches +/-125%. MULTI serial input saturates +at that range. The outer 100–125% segments intentionally have a steeper +slope than the standard range. + ## Known limitation The normal link ID and hopping sequence are intentionally fixed to the From 3316534dc265024cfe2baf2df36ce5f94a7e9c8a Mon Sep 17 00:00:00 2001 From: dududu009 <277218636+dududu009@users.noreply.github.com> Date: Fri, 9 Oct 2026 10:15:10 +0800 Subject: [PATCH 3/3] Use linear MC8RE channel scaling through extended 125 percent range --- Multiprotocol/MC8RE_a7105.ino | 29 +++++++---------------------- docs/MC8RE_V2.md | 16 +++++++++++----- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/Multiprotocol/MC8RE_a7105.ino b/Multiprotocol/MC8RE_a7105.ino index 04f7c2bba..984fb2a0e 100644 --- a/Multiprotocol/MC8RE_a7105.ino +++ b/Multiprotocol/MC8RE_a7105.ino @@ -92,32 +92,17 @@ static uint32_t mc8v43_link_id; * PWM measurements with v1.3.4.73 gave about 858/1523/2187 us at minimum, * neutral and maximum. These support PWM_us ~= wire_value + 500. * Session 4 confirmed nominal 1000/1500/2000 us with wire 500/1000/1500. - * Preserve nominal 1000/1500/2000 us at -100/0/+100%. - * Only the extended input segments -125..-100% and +100..+125% stretch - * to nominal 500 and 2500 us. Receiver CH1 extended-range measurements - * with v1.3.4.76 gave approximately 499/1500/2499 us. */ + * v1.3.4.76 confirmed wire 0/1000/2000 near 499/1500/2499 us on CH1. + * Linear mapping: serial 0..2047 (-125..+125%) -> wire 375..1625, + * nominal PWM 875..2125 us. EdgeTX -100/0/+100% serial 205/1024/1843 + * maps to wire 500/1000/1500 (nominal PWM 1000/1500/2000 us). + * Use one slope across the full range; round to the nearest wire count. */ static uint16_t mc8v53_standard_channel(uint8_t source) { const uint16_t channel = Channel_data[CH_AETR[source]]; const uint16_t input = channel > CHANNEL_MAX_125 ? CHANNEL_MAX_125 : channel; - const uint16_t low = 500U; - const uint16_t middle = 1000U; - const uint16_t high = 1500U; - if(input < CHANNEL_MIN_100) - return ((uint32_t)(input - CHANNEL_MIN_125) * low - + (CHANNEL_MIN_100 - CHANNEL_MIN_125) / 2U) - / (CHANNEL_MIN_100 - CHANNEL_MIN_125); - if(input > CHANNEL_MAX_100) - return high + ((uint32_t)(input - CHANNEL_MAX_100) * 500U - + (CHANNEL_MAX_125 - CHANNEL_MAX_100) / 2U) - / (CHANNEL_MAX_125 - CHANNEL_MAX_100); - if(input <= CHANNEL_MID) - return middle - ((uint32_t)(CHANNEL_MID - input) * (middle - low) - + (CHANNEL_MID - CHANNEL_MIN_100) / 2U) - / (CHANNEL_MID - CHANNEL_MIN_100); - return middle + ((uint32_t)(input - CHANNEL_MID) * (high - middle) - + (CHANNEL_MAX_100 - CHANNEL_MID) / 2U) - / (CHANNEL_MAX_100 - CHANNEL_MID); + return 375U + ((uint32_t)input * 1250U + CHANNEL_MAX_125 / 2U) + / CHANNEL_MAX_125; } static void mc8v53_put_u11(uint8_t slot, uint16_t value) diff --git a/docs/MC8RE_V2.md b/docs/MC8RE_V2.md index 4b3d1af0c..94fdb29cc 100644 --- a/docs/MC8RE_V2.md +++ b/docs/MC8RE_V2.md @@ -10,10 +10,10 @@ transmitter and then verified on two MC8RE V2 receivers. `01`, then normal operation on ID `51D567EB`; - 16-channel hopping and the original startup/RX slot timing captured from the transmitter; -- eight 11-bit packed output channels with piecewise scaling: EdgeTX +- eight 11-bit packed output channels with linear scaling: EdgeTX -100%/0%/+100% maps to nominal PWM 1000/1500/2000 us (wire - 500/1000/1500). Extended -125%/+125% reaches nominal 500/2500 us - (wire 0/2000); + 500/1000/1500). Extended -125%/+125% reaches nominal 875/2125 us + (wire 375/1625). v1.3.4.77 uses a single slope throughout; - telemetry A1 (receiver/BEC voltage) and A2 (external battery voltage, 0.1 V per count; set EdgeTX A2 ratio to 25.5 V for a 4S range); - a relative local A7105 RSSI indication. It is not a calibrated received @@ -38,11 +38,17 @@ The extended-range capture did not separately hold +/-100% positions; those were measured on the preceding standard-range build. No simultaneous SPI traffic was recorded in these PWM captures. +The current v1.3.4.77 mapping replaces the earlier piecewise expansion with +one linear mapping: serial 0..2047 -> wire 375..1625, rounded to the +nearest count. EdgeTX serial values 205/1024/1843 at -100%/0%/+100% +map to wire 500/1000/1500. The nominal extended PWM range is now +875–2125 us. New hardware measurements are pending; the measurements +above describe the preceding builds, not the new extended endpoint values. + For the full range, enable EdgeTX Extended Limits, keep mixer weight at 100%, and set output limits to -125%/+125% with zero trim/subtrim/offset. Confirm the channel monitor reaches +/-125%. MULTI serial input saturates -at that range. The outer 100–125% segments intentionally have a steeper -slope than the standard range. +at that range. Values above +/-125% do not extend the received serial range. ## Known limitation