diff --git a/Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp b/Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp index dd7fb3c..de25571 100644 --- a/Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp +++ b/Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp @@ -77,9 +77,9 @@ void IrFoxDecoder::first_rx() void IrFoxDecoder::listen_start(double t_us) { - (void)t_us; const uint32_t irmax = irfox::irTimeoutUs(rise_sync_time_us); - if (is_recive_raw && (t_us - prev_rise_us) > irmax * 2.0) + // Как IR_DecoderRaw::listenStart: пауза по lastEdgeTime, не по prevRise. + if (is_recive_raw && last_edge_time_us > 0.0 && (t_us - last_edge_time_us) > irmax * 2.0) { is_recive_raw = false; first_rx(); @@ -93,9 +93,12 @@ void IrFoxDecoder::check_timeout(double t_us) const uint32_t irmax = irfox::irTimeoutUs(rise_sync_time_us); if (t_us - last_edge_time_us > irmax * 2.0) { + // Как IR_DecoderRaw::checkTimeout после фикса: полный сброс, иначе залипание FSM. is_recive = false; msg_type_receive = 0; - last_edge_time_us = t_us; + is_recive_raw = false; + first_rx(); + // Не last_edge_time_us = t_us: как IR_DecoderRaw — не расходить с «хвостом» фронтов. } } @@ -121,9 +124,8 @@ void IrFoxDecoder::write_to_buffer(bool bit, bool pack_trace_invert_fix, uint64_ if (is_buffer_overflow || is_preamb || is_wrong_pack) { - is_recive = false; - is_recive_raw = false; - msg_type_receive = 0; + // Как IR_DecoderRaw::writeToBuffer: полный first_rx() вместо только сброса флагов приёма. + first_rx(); return; } @@ -254,7 +256,8 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const listen_start(t_us); - if (have_last_processed && (t_us - last_processed_edge_us) > irmax * 2.0 && is_recive) + // Как IR_DecoderRaw: пауза между фронтами по lastEdgeTime при активном приёме кадра. + if (last_edge_time_us > 0.0 && (t_us - last_edge_time_us) > irmax * 2.0 && is_recive) check_timeout(t_us); last_edge_time_us = t_us; @@ -354,7 +357,7 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const } } - // IR_DecoderRaw::tick (STM32DMA): без firstRX(); старт сырого приёма только этим блоком. + // Как IR_DecoderRaw::tick: после длинной паузы старт сырого приёма (без отдельного firstRX — флаги ниже). if (t_us > prev_rise_us && (t_us - prev_rise_us) > irmax * 2.0 && !is_recive_raw) { preamb_front_counter = static_cast(irfox::kPreambFronts - 1); @@ -583,6 +586,7 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const void IrFoxDecoder::flushEnd(uint64_t last_sample, uint32_t fs, const IrFoxOnBit& on_bit, const IrFoxOnPacket& on_pkt) { const double t_us = sample_to_us(last_sample, fs); + listen_start(t_us); check_timeout(t_us); (void)on_bit; (void)on_pkt; diff --git a/Analyzer/raw/Session Sync anchor.sal b/Analyzer/raw/Session Sync anchor.sal new file mode 100644 index 0000000..6d62fce Binary files /dev/null and b/Analyzer/raw/Session Sync anchor.sal differ diff --git a/IR_DecoderRaw.cpp b/IR_DecoderRaw.cpp index 4d4ad84..81e7463 100644 --- a/IR_DecoderRaw.cpp +++ b/IR_DecoderRaw.cpp @@ -272,22 +272,19 @@ void IR_DecoderRaw::rxBriefFlushDeferredIsrLogs() #endif //////////////////////////////////// isr /////////////////////////////////////////// -volatile uint32_t time_; - void IR_DecoderRaw::isr() { + // Интервалы между соседними фронтами считаются как (uint32_t)(t1 - t0) — корректно при + // паузе < ~35 мин между фронтами; условие «тишина > longSilence» в preambleProcessEdge + // переписано без front.time > prevRise (оно ломается при wrap). + uint32_t t; noInterrupts(); - time_ = micros(); + t = micros(); interrupts(); - if (time_ < oldTime) - { - time_ += 1000; - } - oldTime = time_; FrontStorage edge; edge.dir = port->IDR & mask; - edge.time = time_; + edge.time = t; #if defined(IR_EDGE_TRACE) edgeTracePush(edge.time, edge.dir ? 1u : 0u, @@ -342,8 +339,20 @@ void IR_DecoderRaw::firstRX() preambleResetToIdle(); } +bool IR_DecoderRaw::rxTimeoutPipelineBusy() const +{ + if (pulseFilterHoldCount != 0U) + return true; + noInterrupts(); + const bool busy = !subBuffer.isEmpty() || !filteredSubBuffer.isEmpty(); + interrupts(); + return busy; +} + void IR_DecoderRaw::listenStart() { + if (rxTimeoutPipelineBusy()) + return; if (isReciveRaw && ((micros() - lastEdgeTime) > IR_timeout * 2U)) { #if defined(IRDEBUG_SERIAL_PACK) @@ -359,6 +368,8 @@ void IR_DecoderRaw::listenStart() inline void IR_DecoderRaw::checkTimeout() { if (!isRecive) return; // уже не принимаем – нечего проверять + if (rxTimeoutPipelineBusy()) + return; if (micros() - lastEdgeTime > IR_timeout * 2U) { @@ -371,8 +382,14 @@ inline void IR_DecoderRaw::checkTimeout() #endif isRecive = false; // приём завершён msgTypeReceive = 0; - // firstRX(); // подготовка к новому пакету - lastEdgeTime = micros(); // защита от повторного срабатывания + // Как после listenStart(): без сброса isReciveRaw + firstRX() декодер остаётся + // с «сырым» флагом приёма / Locked и не может заново поймать преамбулу до очень + // длинной тишины (см. preambleProcessEdge Idle и listenStart). + isReciveRaw = false; + firstRX(); + // Не подставлять lastEdgeTime = micros(): при хвосте в subBuffer следующий фронт + // имеет edge.time < micros() — откат lastEdgeTime даёт ложный TIMEOUT на следующем tick(). + // Повторного checkTimeout при тишине нет: isRecive уже false. } } // ==================================================================== @@ -382,6 +399,10 @@ void IR_DecoderRaw::tick() #if IR_RX_BRIEF_LOG rxBriefFlushDeferredIsrLogs(); #endif + // listenStart/checkTimeout: в конце tick (END) и при отсутствии фронта (ниже). + // Не в начале до pop: иначе после checkTimeout lastEdgeTime vs micros() расходятся + // с метками ISR из очереди → ложные TIMEOUT (bits=0) каждый пакет. + FrontStorage currentFront; bool hasCurrentFront = false; FrontStorage rawFront; @@ -420,21 +441,8 @@ void IR_DecoderRaw::tick() if (!hasCurrentFront) { isSubBufferOverflow = false; - bool rawQueueHasPending = false; - bool filteredQueueHasPending = false; - noInterrupts(); - rawQueueHasPending = !subBuffer.isEmpty(); - if (IR_INPUT_MIN_PULSE_US > 0U) - filteredQueueHasPending = !filteredSubBuffer.isEmpty(); - interrupts(); - const bool filterHoldHasPending = (IR_INPUT_MIN_PULSE_US > 0U) && (pulseFilterHoldCount > 0U); - const bool hasPendingEdges = hasRawFront || rawQueueHasPending || filteredQueueHasPending || filterHoldHasPending; - - if (!hasPendingEdges) - { - listenStart(); - checkTimeout(); - } + listenStart(); + checkTimeout(); #if defined(IR_EDGE_TRACE) while (edgeTraceFlushChunk(Serial, 48) > 0) {} #endif @@ -722,6 +730,8 @@ void IR_DecoderRaw::tick() //////////////////////////////////////////////////////////////////////////////////////////////////////////// END:; + listenStart(); + checkTimeout(); #if IR_RX_BRIEF_LOG rxBriefFlushDeferredIsrLogs(); #endif @@ -748,10 +758,12 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix) } if (isBufferOverflow || isPreamb || isWrongPack) { + // Как checkTimeout/listenStart: firstRX() сбрасывает буфер битов, преамбулу и + // pulseFilterReset() — при IR_INPUT_MIN_PULSE_US > 0 иначе остаётся «хвост» в hold/filtered. isRecive = false; isReciveRaw = false; - preambleResetToIdle(); msgTypeReceive = 0; + firstRX(); return; } @@ -1593,7 +1605,12 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front) { if (isReciveRaw) return false; - if (!isReciveRaw && front.dir && front.time > prevRise && (front.time - prevRise) > longSilence) + // Не использовать front.time > prevRise: после переполнения micros (~2^32 µs) новое t + // меньше prevRise в unsigned-смысле — преамбула никогда не стартует («залипание» RX). + // prevRise == 0: как раньше — «длинная тишина» только по абсолютному front.time > longSilence. + if (!isReciveRaw && front.dir && + ((prevRise == 0U && front.time > longSilence) || + (prevRise != 0U && (uint32_t)(front.time - prevRise) > longSilence))) preambleStartCandidate(front); } diff --git a/IR_DecoderRaw.h b/IR_DecoderRaw.h index d82b129..55503a7 100644 --- a/IR_DecoderRaw.h +++ b/IR_DecoderRaw.h @@ -181,7 +181,6 @@ private: volatile uint32_t highTime; volatile uint32_t lowTime; - uint32_t oldTime; uint16_t wrongCounter; int8_t highCount; @@ -196,6 +195,8 @@ private: bool isReciveRaw = false; void listenStart(); void checkTimeout(); // + /** В очередях/hold фильтра ещё есть фронты — не оценивать таймаут по micros()-lastEdgeTime (ложный TIMEOUT). */ + bool rxTimeoutPipelineBusy() const; /** Один сырой фронт из subBuffer -> потоковый holdback-антиглитч. */ void pulseFilterFeedOneRaw(const FrontStorage &e); void pulseFilterFlushTimeout(uint32_t nowUs); diff --git a/ref/IR_RX_BRIEF_LOG.md b/ref/IR_RX_BRIEF_LOG.md index d7d86a7..ffd7414 100644 --- a/ref/IR_RX_BRIEF_LOG.md +++ b/ref/IR_RX_BRIEF_LOG.md @@ -35,7 +35,7 @@ IRRX t=1234988 rsn=MUTE_END cnt=42 | `PREAMB` | Кандидат преамбулы не залочился или был перезапущен | `good`, `per` | | `SYNC` | Ошибка sync-бита привела к reject кадра | `err` | | `BUF` | Переполнен битовый буфер кадра | `bits` | -| `TIMEOUT` | Кадр оборвался по таймауту до завершения | `bits`, `exp` | +| `TIMEOUT` | Кадр оборвался по таймауту до завершения; после записи в лог вызываются `isReciveRaw=false` и `firstRX()` (полный сброс декодера) | `bits`, `exp` | | `CRC` | Кадр дошёл до конца по длине, но CRC не сошёлся | `len`, `err` | | `OK` | Кадр успешно распознан | `len`, `err` | @@ -54,6 +54,8 @@ IRRX t=1234988 rsn=MUTE_END cnt=42 ## Когда смотреть подробный debug +- `listenStart` / `checkTimeout` — в конце обработки фронта (`END:`) и во ветке «нет фронта» в `tick()`; не в начале до `pop`, иначе после таймаута `lastEdgeTime` расходится с метками ISR из очереди → ложные `TIMEOUT` (`bits=0`). +- Пока в `subBuffer` / `filteredSubBuffer` или в hold фильтра есть необработанные фронты, таймаут по `micros() - lastEdgeTime` **не оценивается** (`rxTimeoutPipelineBusy`): иначе при хвосте очереди «тихая пауза» считается слишком длинной и снова ложный `TIMEOUT`. - Если нужен полный поток битов и sync: включать `IRDEBUG_SERIAL_PACK` - Если нужно понять, какие именно фронты пришли в ISR: включать `IR_EDGE_TRACE` - `IR_RX_BRIEF_LOG` нужен как короткий always-on-ish индикатор сути проблемы, без длинного дампа diff --git a/tools/ir_decoder_raw_sim.py b/tools/ir_decoder_raw_sim.py index 825562f..99ddb04 100644 --- a/tools/ir_decoder_raw_sim.py +++ b/tools/ir_decoder_raw_sim.py @@ -161,7 +161,9 @@ class SimState: def first_rx(st: SimState) -> None: - """IR_DecoderRaw::firstRX — сброс буфера; isRecive/isReciveRaw в прошивке здесь не меняются.""" + """Аналог IR_DecoderRaw::firstRX: сброс буфера битов, преамбулы, счётчиков ошибок по битам. + В прошивке isRecive / isReciveRaw сбрасывают вызывающие пути (listenStart, checkTimeout, конец кадра); + затем firstRX() обнуляет буфер и preambleResetToIdle().""" st.is_preamb = True st.is_wrong_pack = False st.is_buffer_overflow = False @@ -188,11 +190,18 @@ def tick( rise_max = rise_sync_time + TOLERANCE_US irmax = IR_TIMEOUT # упрощ.: без подстройки riseSyncTime в timeout - # listenStart: обрыв незавершённого приёма - if st.is_recive_raw and (t_us - st.prev_rise) > irmax * 2: + # listenStart: как IR_DecoderRaw — пауза по lastEdgeTime (между обработанными фронтами), не по prevRise. + if st.last_edge > 0 and st.is_recive_raw and (t_us - st.last_edge) > irmax * 2: st.is_recive_raw = False first_rx(st) + # checkTimeout: как IR_DecoderRaw после фикса — isReciveRaw=0 и firstRX(), иначе залипание FSM. + if st.last_edge > 0 and st.is_recive and (t_us - st.last_edge) > irmax * 2: + st.is_recive = False + st.is_recive_raw = False + first_rx(st) + # Не подставлять last_edge = t_us здесь: как IR_DecoderRaw после фикса. + st.last_edge = t_us skip_rest = False @@ -307,6 +316,7 @@ def tick( if st.is_buffer_overflow or st.is_preamb or st.is_wrong_pack: st.is_recive = False st.is_recive_raw = False + first_rx(st) return if st.buf_bit_pos == st.next_control_bit: st.next_control_bit += SYNC_BITS if st.is_data else BIT_PER_BYTE @@ -345,7 +355,7 @@ def tick( st.packets.append((ok, st.pack_size, bytes(st.data_buffer[: st.pack_size]))) st.is_recive = False st.is_recive_raw = False - # буфер не чистят здесь — как в IR_DecoderRaw; firstRX по listenStart + # Как в IR_DecoderRaw: буфер не чистят на успешном CRC; сброс по listenStart/checkTimeout/firstRX if around_rise_period(st.rise_period, rise_sync_time): if st.high_time > st.low_time: diff --git a/tools/ir_decoder_sim.py b/tools/ir_decoder_sim.py index 95e23dc..35f4e7c 100644 --- a/tools/ir_decoder_sim.py +++ b/tools/ir_decoder_sim.py @@ -16,7 +16,8 @@ WRONG_PACK_SYNC — отдельное событие с причиной и т отброшенных фронтов; без флага эти события только в счётчиках сводки. Не моделирует IRDEBUG_SERIAL_SOFT_REJECT (жёсткий Wrong sync). -Таймауты: между фронтами, если gap > IR_timeout*2 и isRecive — checkTimeout. +Таймауты: как IR_DecoderRaw::tick — listenStart и checkTimeout в начале каждого тика (не только при пустых +очередях), пауза > IR_timeout*2 по lastEdgeTime; при checkTimeout: isReciveRaw=0, firstRX(), lastEdgeTime=now. """ from __future__ import annotations @@ -360,8 +361,8 @@ class DecoderSim: def listen_start(self, now: int) -> None: to = ir_timeout_us(self.rise_sync_time) - if self.is_recive_raw and (now - self.prev_rise) > to * 2: - self.events.append(f"t={now} listenStart abort raw (gap from prev_rise)") + if self.is_recive_raw and self.last_edge_time > 0 and (now - self.last_edge_time) > to * 2: + self.events.append(f"t={now} listenStart abort raw (gap since last edge, как IR_DecoderRaw)") self.is_recive_raw = False self._clear_packet_state() self.first_rx() @@ -371,9 +372,12 @@ class DecoderSim: return to = ir_timeout_us(self.rise_sync_time) if now - self.last_edge_time > to * 2: - self.events.append(f"t={now} checkTimeout (gap since last edge)") + self.events.append(f"t={now} checkTimeout -> isReciveRaw=0, firstRX() (как IR_DecoderRaw)") self.is_recive = False - self.last_edge_time = now + self.is_recive_raw = False + self._clear_packet_state() + self.first_rx() + # Не last_edge_time = now: в прошивке убрано — расхождение с метками фронтов из очереди. def write_to_buffer(self, bit_val: int) -> None: if self.i_data_buffer > DATA_BYTE_SIZE_MAX * 8: @@ -382,6 +386,7 @@ class DecoderSim: if self.is_buffer_overflow or self.is_preamb or self.is_wrong_pack: self.is_recive = False self.is_recive_raw = False + self.first_rx() return if self.buf_bit_pos == self.next_control_bit: @@ -478,6 +483,9 @@ class DecoderSim: def tick_edge(self, t: int, level: int) -> None: """Один фронт: level = состояние линии ПОСЛЕ фронта (как dir в C++).""" self.listen_start(t) + to = ir_timeout_us(self.rise_sync_time) + if self.is_recive and self.last_edge_time > 0 and (t - self.last_edge_time) > to * 2: + self.check_timeout(t) self.last_edge_time = t rising = level == 1 @@ -689,9 +697,6 @@ def main() -> int: dec = DecoderSim(verbose=args.verbose) for e in edges: - to_us = ir_timeout_us(dec.rise_sync_time) - if dec.is_recive and dec.last_edge_time > 0 and (e.t_us - dec.last_edge_time) > to_us * 2: - dec.check_timeout(e.t_us) dec.tick_edge(e.t_us, e.level) print("--- События декодера (первые N), пакеты разделены пустой строкой ---")