diff --git a/Multiprotocol/MC8RE_a7105.ino b/Multiprotocol/MC8RE_a7105.ino new file mode 100644 index 000000000..984fb2a0e --- /dev/null +++ b/Multiprotocol/MC8RE_a7105.ino @@ -0,0 +1,408 @@ +/* + * 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. 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. + * 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; + return 375U + ((uint32_t)input * 1250U + CHANNEL_MAX_125 / 2U) + / CHANNEL_MAX_125; +} + +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..94fdb29cc --- /dev/null +++ b/docs/MC8RE_V2.md @@ -0,0 +1,68 @@ +# 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 linear scaling: EdgeTX + -100%/0%/+100% maps to nominal PWM 1000/1500/2000 us (wire + 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 + 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. + +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. Values above +/-125% do not extend the received serial range. + +## 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.