fix(rx): после обрыва кадра не требовать 30 мс тишины перед новым кандидатом преамбулы (releasePreambleGuard)

prevRise (последний декодированный фронт) не сбрасывался при abortFrame, а отброшенные гвардом фронты его не двигают —
валидный кадр, начавшийся через <30 мс после обрыва мусора, проглатывался целиком без счётчика. Стенд 09.09: КУ теряла
пинг машинки после обрывков чужого заднего и всплеска её дальномера за 24 мс до пинга (V-серия 75–83 %, после правки 100 %).
Хост-тест (настоящий IR_DecoderRaw.cpp + шим Arduino): после abort кадр через 5…28 мс — терялся, теперь принимается;
чистый конец кадра не тронут (мин. пауза 26.25 мс), хвост оборванного кадра ложных захватов не даёт, 200/200 валидных
среди мусора, стресс 166→176/200.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeA1K5dBUKrnoXVwyoVjzq
This commit is contained in:
2026-09-09 16:09:55 +03:00
parent 5b220dadd8
commit baac9fbf46
2 changed files with 14 additions and 0 deletions

View File

@ -440,6 +440,19 @@ void IR_DecoderRaw::abortFrame(uint32_t tUs)
isReciveRaw = false; isReciveRaw = false;
msgTypeReceive = 0; msgTypeReceive = 0;
firstRX(); firstRX();
releasePreambleGuard(tUs);
}
// После обрыва кадра «длинная тишина» (IR_timeout × 2 ≈ 30 мс) перед новым кандидатом преамбулы не требуется.
// prevRise — последний ДЕКОДИРОВАННЫЙ фронт; после abort он свежий, а фронты, отброшенные гвардом, его не двигают,
// поэтому валидный кадр, начавшийся через <30 мс после обрыва мусора, проглатывался целиком без счётчика
// (стенд 09.09: КУ теряла пинг машинки после обрывков чужого заднего и всплеска её дальномера за 24 мс до пинга).
// Ложных захватов это не добавляет: хвост оборванного кадра (период фронтов 962 мкс, синхробиты ~1100) не проходит
// грубый фильтр периода преамбулы (2116…3270 мкс), а настоящая преамбула перезапускает кандидата по паузе > IR_timeout.
// После чистого конца кадра гвард остаётся: там он отсекает хвост синхробитов.
void IR_DecoderRaw::releasePreambleGuard(uint32_t tUs)
{
prevRise = tUs - IR_timeout * 2U - 1U; // «тишина уже была»: (front.time - prevRise) > longSilence для следующего фронта
} }
void IR_DecoderRaw::expirePreambleCandidate() void IR_DecoderRaw::expirePreambleCandidate()

View File

@ -186,6 +186,7 @@ private:
RxEndInfo rxEnd; RxEndInfo rxEnd;
void noteRxEnd(RxEndReason reason, uint32_t tUs); // терминал: фиксирует тип/длину/расчётный конец void noteRxEnd(RxEndReason reason, uint32_t tUs); // терминал: фиксирует тип/длину/расчётный конец
void abortFrame(uint32_t tUs); // немедленный сброс битого кадра (sync/длина/overflow) void abortFrame(uint32_t tUs); // немедленный сброс битого кадра (sync/длина/overflow)
void releasePreambleGuard(uint32_t tUs); // после abort: новый кандидат преамбулы без ожидания длинной тишины
void expirePreambleCandidate(); // кандидат без фронтов дольше таймаута → Idle void expirePreambleCandidate(); // кандидат без фронтов дольше таймаута → Idle
uint8_t preambleGoodPeriods = 0; uint8_t preambleGoodPeriods = 0;
uint16_t preambleMeanPeriod = 0; uint16_t preambleMeanPeriod = 0;