From c29fe2cf7cb815117ec82d96cc29489efc1b946a Mon Sep 17 00:00:00 2001 From: DashyFox Date: Tue, 7 Apr 2026 15:08:02 +0300 Subject: [PATCH] Analyzer --- Analyzer/raw/IR_Fox/src/IrFoxAnalyzer.cpp | 82 ++++++++++++++++++- Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp | 82 ++++++++----------- .../raw/IR_Fox/src/IrFoxProtocolConstants.h | 46 ++++------- 3 files changed, 129 insertions(+), 81 deletions(-) diff --git a/Analyzer/raw/IR_Fox/src/IrFoxAnalyzer.cpp b/Analyzer/raw/IR_Fox/src/IrFoxAnalyzer.cpp index 91b4991..e4cc182 100644 --- a/Analyzer/raw/IR_Fox/src/IrFoxAnalyzer.cpp +++ b/Analyzer/raw/IR_Fox/src/IrFoxAnalyzer.cpp @@ -3,9 +3,11 @@ #include "IrFoxDecoder.h" #include #include +#include #include #include #include +#include IrFoxAnalyzer::IrFoxAnalyzer() : Analyzer2(), @@ -72,6 +74,45 @@ void IrFoxAnalyzer::WorkerThread() IrFoxDecoder decoder; decoder.reset(); + /** Потоковый фильтр: убирает импульсы короче kMinFilteredPulseUs (иголки/дребезг в сэмплах). */ + const U64 min_seg_samples = + std::max(1ULL, static_cast((static_cast(irfox::kMinFilteredPulseUs) * 1e-6) * static_cast(fs) + 0.5)); + struct RawEdge + { + U64 sample; + bool rising; + }; + std::vector pending; + U64 last_dec_edge_sample = 0; + bool last_dec_edge_valid = false; + + auto collapse_short_pairs = [&]() { + for (size_t i = 0; i + 1 < pending.size();) + { + if (pending[i + 1].sample - pending[i].sample < min_seg_samples) + { + pending.erase(pending.begin() + static_cast(i), + pending.begin() + static_cast(i + 2)); + if (i > 0) + --i; + } + else + ++i; + } + }; + + auto strip_vs_last_decoder = [&]() { + for (;;) + { + collapse_short_pairs(); + if (!last_dec_edge_valid || pending.empty()) + return; + if (pending[0].sample - last_dec_edge_sample >= min_seg_samples) + return; + pending.erase(pending.begin()); + } + }; + U32 frames_since_commit = 0; const U32 kCommitBatch = 256; @@ -130,6 +171,43 @@ void IrFoxAnalyzer::WorkerThread() } }; + auto emit_confirmed_edges = [&]() { + for (;;) + { + collapse_short_pairs(); + strip_vs_last_decoder(); + if (pending.size() < 2) + return; + if (pending[1].sample - pending[0].sample < min_seg_samples) + continue; + decoder.processEdge(pending[0].sample, pending[0].rising, fs, on_bit, on_pkt); + last_dec_edge_sample = pending[0].sample; + last_dec_edge_valid = true; + pending.erase(pending.begin()); + } + }; + + auto flush_pending_tail = [&]() { + collapse_short_pairs(); + strip_vs_last_decoder(); + while (pending.size() >= 2 && pending[1].sample - pending[0].sample >= min_seg_samples) + { + decoder.processEdge(pending[0].sample, pending[0].rising, fs, on_bit, on_pkt); + last_dec_edge_sample = pending[0].sample; + last_dec_edge_valid = true; + pending.erase(pending.begin()); + collapse_short_pairs(); + strip_vs_last_decoder(); + } + if (pending.size() == 1) + { + decoder.processEdge(pending[0].sample, pending[0].rising, fs, on_bit, on_pkt); + last_dec_edge_sample = pending[0].sample; + last_dec_edge_valid = true; + pending.clear(); + } + }; + for (;;) { CheckIfThreadShouldExit(); @@ -146,10 +224,12 @@ void IrFoxAnalyzer::WorkerThread() const BitState new_level = mIr->GetBitState(); const bool rising = (new_level == BIT_HIGH); - decoder.processEdge(edge_sample, rising, fs, on_bit, on_pkt); + pending.push_back(RawEdge{edge_sample, rising}); + emit_confirmed_edges(); ReportProgress(edge_sample); } + flush_pending_tail(); decoder.flushEnd(mIr->GetSampleNumber(), fs, on_bit, on_pkt); if (frames_since_commit != 0) diff --git a/Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp b/Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp index 8f946e1..6c22d0b 100644 --- a/Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp +++ b/Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp @@ -262,7 +262,7 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const const uint32_t rise_max_us = rise_sync_time_us + irfox::kToleranceUs; - /** Начало PRE-бабла: при первом подъёме пузыря метка ИК (активный низ) начинается со спада — берём prev_fall, если он близок. */ + /** Визуализация: начало PRE с ближайшего спада в пределах ~3 битовых периодов (ИК-метка). */ auto new_bubble_preamble_start = [&](uint64_t edge_s, bool is_rising) -> uint64_t { if (!is_rising) return edge_s; @@ -278,9 +278,11 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const if (rising) { - const uint32_t cand_rp = static_cast(t_us - prev_rise_us); + const double delta_rp = t_us - prev_rise_us; + const uint32_t cand_rp = static_cast(delta_rp); const uint32_t cand_ht = static_cast(t_us - prev_fall_us); const uint32_t cand_lt = static_cast(prev_fall_us - prev_rise_us); + #if IRFOX_SHORT_LOW_GLITCH_REJECT const bool short_low_glitch = is_recive && !is_preamb && cand_ht < (rise_min_us / 8U) && cand_lt >= rise_min_us && @@ -288,9 +290,7 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const if (short_low_glitch) { err_other++; -#if IRFOX_GLITCH_REJECT_PHASE_NUDGE irfox::irfoxGlitchPhaseNudgeUs(t_us, rise_sync_time_us, prev_rise_us); -#endif last_processed_edge_us = t_us; have_last_processed = true; return; @@ -304,9 +304,7 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const if (micro_gap_rise) { err_other++; -#if IRFOX_GLITCH_REJECT_PHASE_NUDGE irfox::irfoxGlitchPhaseNudgeUs(t_us, rise_sync_time_us, prev_rise_us); -#endif last_processed_edge_us = t_us; have_last_processed = true; return; @@ -319,31 +317,32 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const have_last_processed = true; return; } - // Как IR_DecoderRaw::tick: до prev_rise_us = t_us, иначе ниже (t_us - prev_rise_us) на подъёме == 0. + + // Визуализация PRE: длинная пауза, первый подъём — якорь от спада метки (декод как STM32DMA). if (cand_rp > irmax * 2U && !is_recive_raw) { - first_rx(); - preamb_front_counter = static_cast(irfox::kPreambFronts - 1U); - is_preamb = true; - is_recive = true; - is_recive_raw = true; - is_wrong_pack = false; preamble_bubble_start_sample_ = new_bubble_preamble_start(sample, true); preamble_bubble_start_valid_ = true; } - rise_period_anchor_sample_ = prev_rise_sample; - rise_period_us = cand_rp; - high_time_us = cand_ht; - low_time_us = cand_lt; - prev_rise_us = t_us; - prev_rise_sample = sample; + + const bool accept_rise_timing = + (delta_rp > static_cast(rise_max_us) / 4.0) || high_count != 0 || low_count != 0; + if (accept_rise_timing) + { + rise_period_anchor_sample_ = prev_rise_sample; + rise_period_us = cand_rp; + high_time_us = cand_ht; + low_time_us = cand_lt; + prev_rise_us = t_us; + prev_rise_sample = sample; + } + else + { + err_other++; + } } else { -#if IRFOX_IN_MARK_DOUBLE_FALL_IGNORE - const double hi_since_rise = t_us - prev_rise_us; - const uint32_t mark_end_min_us = (rise_min_us * irfox::kBitActiveTakts) / irfox::kBitTakts; -#endif if (t_us - prev_fall_us > rise_min_us / 4.0) { prev_fall_us = t_us; @@ -351,26 +350,23 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const } else { -#if IRFOX_IN_MARK_DOUBLE_FALL_IGNORE - if (!(is_recive && !is_preamb && hi_since_rise < static_cast(mark_end_min_us))) -#endif - { - err_other++; - } + err_other++; } } - // Первый фронт после длинной тишины на спаде; на подъёме — cand_rp выше (до обновления prev_rise_us). + // IR_DecoderRaw::tick (STM32DMA): без firstRX(); старт сырого приёма только этим блоком. if (t_us > prev_rise_us && (t_us - prev_rise_us) > irmax * 2.0 && !is_recive_raw) { - first_rx(); - preamb_front_counter = static_cast(irfox::kPreambFronts - 1U); + preamb_front_counter = static_cast(irfox::kPreambFronts - 1); is_preamb = true; is_recive = true; is_recive_raw = true; is_wrong_pack = false; - preamble_bubble_start_sample_ = new_bubble_preamble_start(sample, rising); - preamble_bubble_start_valid_ = true; + if (!preamble_bubble_start_valid_) + { + preamble_bubble_start_sample_ = new_bubble_preamble_start(sample, rising); + preamble_bubble_start_valid_ = true; + } } if (preamb_front_counter) @@ -390,10 +386,10 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const if (is_preamb) { is_preamb = false; - // IR_DecoderRaw: prevRise += risePeriod / 2 — prevRise на этом тике ещё время последнего подъёма - // (на спаде не обновлялся). В сэмплах: якорь = последний подъём + полпериода, не «спад + полпериод». - const uint64_t preamble_bubble_end_sample = - rising ? rise_period_anchor_sample_ : prev_rise_sample; + // IR_DecoderRaw: prevRise += risePeriod / 2 — фаза как в прошивке. + // Бабл PRE: до текущего фронта (sample−1), чтобы охватить все kPreambPulse периодов (3 импульса), + // а не только до предыдущего подъёма (~2 периода). + const uint64_t preamble_bubble_end_sample = sample > 0 ? sample - 1 : sample; prev_rise_us += rise_period_us / 2.0; { const double half_us = 0.5 * static_cast(rise_period_us); @@ -460,16 +456,6 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const else write_to_buffer(false, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::WithBubble); } -#if IRFOX_RISE_GRAY_SINGLE_BIT_FALLBACK - else if (irfox::riseGraySingleBitFallback(rise_period_us, rise_sync_time_us)) - { - err_other++; - if (high_time_us > low_time_us) - write_to_buffer(true, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::WithBubble); - else - write_to_buffer(false, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::WithBubble); - } -#endif else { uint16_t hc = ceil_div_u16(static_cast(high_time_us > 0xFFFF ? 0xFFFF : high_time_us), diff --git a/Analyzer/raw/IR_Fox/src/IrFoxProtocolConstants.h b/Analyzer/raw/IR_Fox/src/IrFoxProtocolConstants.h index 24cc137..1811471 100644 --- a/Analyzer/raw/IR_Fox/src/IrFoxProtocolConstants.h +++ b/Analyzer/raw/IR_Fox/src/IrFoxProtocolConstants.h @@ -11,25 +11,8 @@ constexpr uint32_t kBitTakts = kBitActiveTakts + kBitPauseTakts; constexpr uint32_t kBitTimeUs = kBitTakts * kCarrierPeriodUs; constexpr uint32_t kToleranceUs = 300U; -/** Синхронно с IR_config.h: IR_SHORT_LOW_GLITCH_REJECT (1 = включено, как на железе). */ -#ifndef IRFOX_SHORT_LOW_GLITCH_REJECT -#define IRFOX_SHORT_LOW_GLITCH_REJECT 1 -#endif -#ifndef IRFOX_RISE_INCLUSIVE_AROUND -#define IRFOX_RISE_INCLUSIVE_AROUND 1 -#endif -#ifndef IRFOX_RISE_GRAY_SINGLE_BIT_FALLBACK -#define IRFOX_RISE_GRAY_SINGLE_BIT_FALLBACK 0 -#endif -#ifndef IRFOX_GLITCH_REJECT_PHASE_NUDGE -#define IRFOX_GLITCH_REJECT_PHASE_NUDGE 1 -#endif -#ifndef IRFOX_MICRO_GAP_RISE_REJECT -#define IRFOX_MICRO_GAP_RISE_REJECT 1 -#endif -#ifndef IRFOX_IN_MARK_DOUBLE_FALL_IGNORE -#define IRFOX_IN_MARK_DOUBLE_FALL_IGNORE 0 -#endif +/** Мин. длительность плато (мкс) для потокового анти-глитча в анализаторе; согласовано с IR_INPUT_MIN_PULSE_US. */ +constexpr uint32_t kMinFilteredPulseUs = 10U; constexpr uint8_t kBitPerByte = 8U; constexpr uint8_t kMsgBytes = 1; @@ -45,32 +28,31 @@ constexpr uint8_t kDataByteSizeMax = constexpr uint8_t kPreambPulse = 3; constexpr uint8_t kPreambFronts = kPreambPulse * 2U; +/** Отброс ложного подъёма после микро-LOW в паузе (как расширенный IR_DecoderRaw::tick). */ +#ifndef IRFOX_SHORT_LOW_GLITCH_REJECT +#define IRFOX_SHORT_LOW_GLITCH_REJECT 1 +#endif +#ifndef IRFOX_GLITCH_REJECT_PHASE_NUDGE +#define IRFOX_GLITCH_REJECT_PHASE_NUDGE 1 +#endif +#ifndef IRFOX_MICRO_GAP_RISE_REJECT +#define IRFOX_MICRO_GAP_RISE_REJECT 1 +#endif + inline uint32_t irTimeoutUs(uint32_t riseSyncTimeUs) { const uint32_t riseMax = riseSyncTimeUs + kToleranceUs; return riseMax * (8U + kSyncBits + 1U); } +/** Как IR_DecoderRaw.h: aroundRise(t) → riseTimeMin < t && t < riseTimeMax (ветка STM32DMA). */ inline bool aroundRisePeriod(uint32_t periodUs, uint32_t riseSyncTimeUs) { const uint32_t lo = riseSyncTimeUs > kToleranceUs ? riseSyncTimeUs - kToleranceUs : 0U; const uint32_t hi = riseSyncTimeUs + kToleranceUs; -#if IRFOX_RISE_INCLUSIVE_AROUND - return lo <= periodUs && periodUs <= hi; -#else return lo < periodUs && periodUs < hi; -#endif } -/** Синхронно с IR_config.h: IR_RISE_GRAY_SINGLE_BIT_FALLBACK. */ -inline bool riseGraySingleBitFallback(uint32_t periodUs, uint32_t riseSyncTimeUs) -{ - const uint32_t hi = riseSyncTimeUs + kToleranceUs; - const uint32_t twice = riseSyncTimeUs * 2U; - return periodUs > hi && periodUs <= twice; -} - -/** Синхронно с IR_config.h: IR_GLITCH_REJECT_PHASE_NUDGE. */ inline void irfoxGlitchPhaseNudgeUs(double edge_us, uint32_t rise_sync_us, double& prev_rise_us) { #if IRFOX_GLITCH_REJECT_PHASE_NUDGE