From 29170d20c5d32452df292f043c94c59f65a4865e Mon Sep 17 00:00:00 2001 From: Raphael Coeffic Date: Tue, 28 Apr 2026 05:31:23 +0200 Subject: [PATCH] fix(crossfire): handle partial frame from half-duplex IDLE split (#7242) Co-authored-by: Claude Opus 4.6 (1M context) --- radio/src/pulses/crossfire.cpp | 25 ++++++++++++++++++++----- radio/src/pulses/pxx1.cpp | 8 ++++---- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/radio/src/pulses/crossfire.cpp b/radio/src/pulses/crossfire.cpp index 18fc7f612..36cbe64fa 100644 --- a/radio/src/pulses/crossfire.cpp +++ b/radio/src/pulses/crossfire.cpp @@ -286,16 +286,23 @@ static uint8_t* _processFrames(void* ctx, uint8_t* buf, uint8_t& len) static void crossfireProcessFrame(void* ctx, uint8_t* frame, uint8_t frame_len, uint8_t* buf, uint8_t* p_len) { - if (frame_len < MIN_FRAME_LEN) return; - uint8_t& len = *p_len; + if (len == 0) { - // buffer is empty: no re-assembly + if (frame_len == 0) return; + if (!_validHdr(frame)) { TRACE("[XF] invalid frame start"); return; } + if (frame_len < MIN_FRAME_LEN) { + // Too short to process, but valid header: save for reassembly + memcpy(buf, frame, frame_len); + len = frame_len; + return; + } + // process frames directly out of RX buffer uint8_t* p_buf = _processFrames(ctx, frame, frame_len); if (frame_len > 0) { @@ -344,8 +351,16 @@ static void _crsf_extmodule_frame_received() // proxy trigger to avoid calling // FreeRTOS methods from ISR with prio 0 -static void _soft_irq_trigger(void*) +static void _soft_irq_trigger(void* param) { + // detect spurious IDLE IRQ with empty buffer + auto mod_rx = (etx_module_driver_t*)param; + auto drv = modulePortGetSerialDrv(*mod_rx); + auto ctx = modulePortGetCtx(*mod_rx); + + if (!drv || !ctx || !drv->getBufferedBytes) return; + if (drv->getBufferedBytes(ctx) == 0) return; + #if defined(TELEMETRY_USE_CUSTOM_EXTI) stm32_exti_custom_trigger_swi(TELEMETRY_RX_FRAME_EXTI_LINE); #else @@ -396,7 +411,7 @@ static void* crossfireInit(uint8_t module) #if !defined(SIMU) if (drv && ctx && drv->setIdleCb) { - drv->setIdleCb(ctx, _soft_irq_trigger, nullptr); + drv->setIdleCb(ctx, _soft_irq_trigger, &mod_st->rx); #if defined(TELEMETRY_USE_CUSTOM_EXTI) stm32_exti_custom_enable(TELEMETRY_RX_FRAME_EXTI_LINE, 3, _crsf_extmodule_frame_received); diff --git a/radio/src/pulses/pxx1.cpp b/radio/src/pulses/pxx1.cpp index 732cfffd3..14539e6c2 100644 --- a/radio/src/pulses/pxx1.cpp +++ b/radio/src/pulses/pxx1.cpp @@ -262,9 +262,9 @@ static void pxx1SportSensorPolling(void* param) if (outputTelemetryBuffer.destination != TELEMETRY_ENDPOINT_SPORT) return; - auto mod_st = (etx_module_state_t*)param; - auto drv = modulePortGetSerialDrv(mod_st->rx); - auto ctx = modulePortGetCtx(mod_st->rx); + auto mod_rx = (etx_module_driver_t*)param; + auto drv = modulePortGetSerialDrv(*mod_rx); + auto ctx = modulePortGetCtx(*mod_rx); // Match sensor polling from the module // -> detect <0x7E [Physical ID]> as the last sequence @@ -341,7 +341,7 @@ static void* pxx1Init(uint8_t module) auto drv = modulePortGetSerialDrv(mod_st->rx); auto ctx = modulePortGetCtx(mod_st->rx); if (drv && ctx && drv->setIdleCb) { - drv->setIdleCb(ctx, pxx1SportSensorPolling, mod_st); + drv->setIdleCb(ctx, pxx1SportSensorPolling, &mod_st->rx); } }