diff --git a/IR_DecoderRaw.cpp b/IR_DecoderRaw.cpp index 9ad793d..b6578c3 100644 --- a/IR_DecoderRaw.cpp +++ b/IR_DecoderRaw.cpp @@ -3,6 +3,29 @@ #include #include +#if IR_GLITCH_REJECT_PHASE_NUDGE +/** Подтяжка опоры фазы после отброса шипа (как irfoxGlitchPhaseNudgeUs в плагине). */ +static inline void irGlitchPhaseNudge(uint32_t edgeTime, uint16_t riseSync, volatile uint32_t &prevRise) +{ + if (edgeTime > riseSync) + { + const uint32_t nudged = edgeTime - riseSync; + if (nudged > prevRise && nudged < edgeTime) + prevRise = nudged; + } +} +#endif + +static inline uint8_t irPulseFilterHoldbackEdges() +{ + uint8_t hb = (uint8_t)IR_INPUT_FILTER_HOLDBACK_EDGES; + if (hb < 2U) + hb = 2U; + if (hb > 4U) + hb = 4U; + return hb; +} + IR_DecoderRaw::IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPair) : encoder(encPair) { setPin(pin); @@ -44,6 +67,13 @@ bool IR_DecoderRaw::availableRaw() } }; +void IR_DecoderRaw::pulseFilterResetStats() +{ + pulseFilterDropFilteredOverflow = 0; + pulseFilterDropHoldOverflow = 0; + pulseFilterDropGlitchPairs = 0; +} + //////////////////////////////////// isr /////////////////////////////////////////// volatile uint32_t time_; @@ -101,11 +131,13 @@ void IR_DecoderRaw::firstRX() wrCounter = 0; #endif memset(dataBuffer, 0x00, dataByteSizeMax); + pulseFilterReset(); + preambleResetToIdle(); } void IR_DecoderRaw::listenStart() { - if (isReciveRaw && ((micros() - prevRise) > IR_timeout * 2)) + if (isReciveRaw && ((micros() - lastEdgeTime) > IR_timeout * 2U)) { #if defined(IRDEBUG_SERIAL_PACK) packTraceOnTimeoutOrAbort(true); @@ -136,49 +168,119 @@ inline void IR_DecoderRaw::checkTimeout() void IR_DecoderRaw::tick() { - // FrontStorage *currentFrontPtr; - // noInterrupts(); - // currentFrontPtr = subBuffer.pop(); - // interrupts(); - FrontStorage currentFront; + bool hasCurrentFront = false; + FrontStorage rawFront; + bool hasRawFront = false; noInterrupts(); - listenStart(); - FrontStorage *currentFrontPtr; - currentFrontPtr = subBuffer.pop(); - if (currentFrontPtr == nullptr) + FrontStorage *rawPtr = subBuffer.pop(); + if (rawPtr != nullptr) + { + rawFront = *rawPtr; + hasRawFront = true; + } + interrupts(); + + if (IR_INPUT_MIN_PULSE_US > 0U) + { + if (hasRawFront) + pulseFilterFeedOneRaw(rawFront); + else + pulseFilterFlushTimeout(micros()); + + noInterrupts(); + FrontStorage *flt = filteredSubBuffer.pop(); + if (flt != nullptr) + { + currentFront = *flt; + hasCurrentFront = true; + } + interrupts(); + } + else if (hasRawFront) + { + currentFront = rawFront; + hasCurrentFront = true; + } + + if (!hasCurrentFront) { isSubBufferOverflow = false; - checkTimeout(); // <--- новое место проверки + 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(); + } #if defined(IR_EDGE_TRACE) while (edgeTraceFlushChunk(Serial, 48) > 0) {} #endif return; } // Если данных нет - ничего не делаем - currentFront = *currentFrontPtr; - interrupts(); - // ---------- буфер пуст: фронтов нет, проверяем тайм-аут ---------- - // if (currentFrontPtr == nullptr) - // { - // isSubBufferOverflow = false; - // return; - // } + if (preambleProcessEdge(currentFront)) + { + lastEdgeTime = currentFront.time; + goto END; + } - // // ---------- есть фронт: продолжаем обработку ---------- - // FrontStorage currentFront = *currentFrontPtr; lastEdgeTime = currentFront.time; // запоминаем любой фронт //////////////////////////////////////////////////////////////////////////////////////////////////////////// if (currentFront.dir) { // Если __/``` ↑ - if (currentFront.time - prevRise > riseTimeMax / 4 || highCount || lowCount) + const uint32_t candRp = currentFront.time - prevRise; + const uint32_t candHt = currentFront.time - prevFall; + const uint32_t candLt = prevFall - prevRise; + +#if IR_SHORT_LOW_GLITCH_REJECT + const bool short_low_glitch = + isRecive && !isPreamb && candHt < (riseTimeMin / 8U) && candLt >= riseTimeMin && + candRp >= riseTimeMin && candRp <= IR_timeout; + if (short_low_glitch) + { + errors.other++; +#if IR_GLITCH_REJECT_PHASE_NUDGE + irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise); +#endif + goto END; + } +#endif +#if IR_MICRO_GAP_RISE_REJECT + const bool micro_gap_cand_lt_ok = + (candLt >= riseTimeMin) || (candLt >= (riseTimeMin / 4U) && candLt < riseTimeMin); + const bool micro_gap_rise = isRecive && !isPreamb && candHt < (riseTimeMin / 8U) && micro_gap_cand_lt_ok && + candRp >= (riseTimeMin / 4U) && candRp < riseTimeMin && candRp <= IR_timeout; + if (micro_gap_rise) + { + errors.other++; +#if IR_GLITCH_REJECT_PHASE_NUDGE + irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise); +#endif + goto END; + } +#endif + if (candRp <= riseTimeMax / 4U && !highCount && !lowCount) + { + errors.other++; + goto END; + } + + if (candRp > riseTimeMax / 4 || highCount || lowCount) { // комплексный фикс рваной единицы - risePeriod = currentFront.time - prevRise; - highTime = currentFront.time - prevFall; - lowTime = prevFall - prevRise; + risePeriod = candRp; + highTime = candHt; + lowTime = candLt; prevRise = currentFront.time; if ( @@ -231,79 +333,6 @@ void IR_DecoderRaw::tick() digitalWrite(errOut, currentFront.dir); #endif - if (currentFront.time > prevRise && currentFront.time - prevRise > IR_timeout * 2 && !isReciveRaw) - { // первый -#ifdef IRDEBUG - errPulse(up, 50); - errPulse(down, 50); - errPulse(up, 150); - errPulse(down, 150); -#endif - preambFrontCounter = preambFronts - 1U; - isPreamb = true; - - isRecive = true; - isReciveRaw = true; - isWrongPack = false; -#if defined(IRDEBUG_SERIAL_PACK) - packTraceResetFrame(); - packTraceOpen = true; -#endif - } - - //------------------------------------------------------------------------------------------------------- - - if (preambFrontCounter) - { // в преамбуле -#ifdef IRDEBUG - // Serial.print("risePeriod: "); - // Serial.println(risePeriod); -#endif - if (currentFront.dir && risePeriod < IR_timeout) - { // __/``` ↑ и мы в внутри пакета - - if (risePeriod < riseTimeMin / 2) - { // fix рваной единицы - preambFrontCounter += 2; - errors.other++; -#ifdef IRDEBUG - errPulse(down, 350); -#endif - } - else - { - if (freeFrec) - { - riseSyncTime = (riseSyncTime + risePeriod / 2) / 2; - } // tuner - } - } - else - { /* riseSyncTime = bitTime; */ - } // сброс тюнера - preambFrontCounter--; - // Serial.print("preambFrontCounter: "); Serial.println(preambFrontCounter); - } - else - { - if (isPreamb) - { // первый фронт после - // gotTune.set(riseSyncTime); - isPreamb = false; -#ifdef IRDEBUG - errPulse(up, 50); - errPulse(down, 50); -#endif - prevRise += risePeriod / 2; - // prevRise = currentFront.time + riseTime; - goto END; - } - } - - if (isPreamb) - { - goto END; - } if (risePeriod > IR_timeout || isBufferOverflow || risePeriod < riseTimeMin || isWrongPack) // ~Мы в пределах таймаута и буффер не переполнен и fix дроблёных единиц { @@ -487,6 +516,7 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix) { isRecive = false; isReciveRaw = false; + preambleResetToIdle(); msgTypeReceive = 0; return; } @@ -598,6 +628,7 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix) isRecive = false; isReciveRaw = false; + preambleResetToIdle(); msgTypeReceive = 0; isAvailable = crcCheck(packSize - crcBytes, crcValue); @@ -1147,6 +1178,265 @@ __attribute__((weak)) void irPackTracePrintOkCommand(const uint8_t *buf, uint8_t #endif // IRDEBUG_SERIAL_PACK +uint32_t IR_DecoderRaw::absDiffU32(uint32_t a, uint32_t b) +{ + return (a > b) ? (a - b) : (b - a); +} + +void IR_DecoderRaw::pulseFilterShiftLeft(uint8_t n) +{ + if (n == 0 || pulseFilterHoldCount == 0) + return; + if (n >= pulseFilterHoldCount) + { + pulseFilterHoldCount = 0; + return; + } + const uint8_t newCount = static_cast(pulseFilterHoldCount - n); + for (uint8_t i = 0; i < newCount; ++i) + pulseFilterHoldEdges[i] = pulseFilterHoldEdges[static_cast(i + n)]; + pulseFilterHoldCount = newCount; +} + +bool IR_DecoderRaw::pulseFilterEmit(const FrontStorage &e) +{ + if (filteredSubBuffer.isFull()) + { + pulseFilterDropFilteredOverflow++; + return false; + } + filteredSubBuffer.push(e); + return true; +} + +void IR_DecoderRaw::pulseFilterReset() +{ + pulseFilterHoldCount = 0; + pulseFilterLastRawValid = false; + pulseFilterLastRawTime = 0; + while (filteredSubBuffer.pop() != nullptr) {} +} + +void IR_DecoderRaw::pulseFilterFeedOneRaw(const FrontStorage &e) +{ + const uint32_t minUs = IR_INPUT_MIN_PULSE_US; + if (minUs == 0U) + { + pulseFilterEmit(e); + return; + } + + pulseFilterLastRawTime = e.time; + pulseFilterLastRawValid = true; + + if (pulseFilterHoldCount >= kPulseFilterHoldCap) + { + pulseFilterDropHoldOverflow++; + pulseFilterEmit(pulseFilterHoldEdges[0]); + pulseFilterShiftLeft(1); + } + + pulseFilterHoldEdges[pulseFilterHoldCount++] = e; + const uint8_t holdback = irPulseFilterHoldbackEdges(); + + for (;;) + { + if (pulseFilterHoldCount < 2) + return; + + const uint32_t dt = pulseFilterHoldEdges[1].time - pulseFilterHoldEdges[0].time; + if (dt < minUs) + { + pulseFilterDropGlitchPairs++; + pulseFilterShiftLeft(2); + continue; + } + + if (pulseFilterHoldCount <= holdback) + return; + + pulseFilterEmit(pulseFilterHoldEdges[0]); + pulseFilterShiftLeft(1); + } +} + +void IR_DecoderRaw::pulseFilterFlushTimeout(uint32_t nowUs) +{ + if (IR_INPUT_MIN_PULSE_US == 0U || !pulseFilterLastRawValid || pulseFilterHoldCount == 0) + return; + const uint32_t waitUs = IR_INPUT_MIN_PULSE_US * (uint32_t)IR_INPUT_FILTER_TIMEOUT_MULT; + if ((uint32_t)(nowUs - pulseFilterLastRawTime) < waitUs) + return; + + while (pulseFilterHoldCount > 0) + { + if (pulseFilterHoldCount >= 2) + { + const uint32_t dt = pulseFilterHoldEdges[1].time - pulseFilterHoldEdges[0].time; + if (dt < IR_INPUT_MIN_PULSE_US) + { + pulseFilterDropGlitchPairs++; + pulseFilterShiftLeft(2); + continue; + } + } + pulseFilterEmit(pulseFilterHoldEdges[0]); + pulseFilterShiftLeft(1); + } +} + +uint32_t IR_DecoderRaw::preambleJitterTolUs(uint32_t baselineUs) const +{ + const uint32_t pct = (baselineUs * (uint32_t)IR_PREAMBLE_JITTER_PCT) / 100U; + return (pct > (uint32_t)IR_PREAMBLE_JITTER_US_MIN) ? pct : (uint32_t)IR_PREAMBLE_JITTER_US_MIN; +} + +bool IR_DecoderRaw::preambleRisePeriodCoarseOk(uint32_t periodUs) const +{ + const uint32_t base = (uint32_t)bitTime; + const uint32_t minP = (base * (uint32_t)IR_PREAMBLE_PERIOD_MIN_FACTOR_PCT) / 100U; + const uint32_t maxP = (base * (uint32_t)IR_PREAMBLE_PERIOD_MAX_FACTOR_PCT) / 100U; + return periodUs >= minP && periodUs <= maxP; +} + +void IR_DecoderRaw::preambleResetToIdle() +{ + preambleState = PreambleState::Idle; + preambleGoodPeriods = 0; + preambleMeanPeriod = 0; + preambleCandidateLastEdgeTime = 0; + preambleCandidateFirstRiseTime = 0; + preambleCandidateFirstRiseValid = false; + preambFrontCounter = 0; + isPreamb = false; + isWrongPack = false; + isBufferOverflow = false; +} + +void IR_DecoderRaw::preambleStartCandidate(const FrontStorage &front) +{ + preambleState = PreambleState::Candidate; + preambleGoodPeriods = 0; + preambleMeanPeriod = 0; + preambleCandidateLastEdgeTime = front.time; + preambleCandidateFirstRiseTime = front.time; + preambleCandidateFirstRiseValid = front.dir; + preambFrontCounter = 0; + isPreamb = true; + isRecive = false; + isReciveRaw = false; +} + +bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front) +{ + const uint32_t longSilence = IR_timeout * 2U; + const uint32_t candTimeout = IR_timeout * (uint32_t)IR_PREAMBLE_CANDIDATE_TIMEOUT_MULT; + + if (preambleState == PreambleState::Idle) + { + if (isReciveRaw) + return false; + if (!isReciveRaw && front.dir && front.time > prevRise && (front.time - prevRise) > longSilence) + preambleStartCandidate(front); + } + + if (preambleState == PreambleState::Candidate) + { + if ((uint32_t)(front.time - preambleCandidateLastEdgeTime) > candTimeout) + preambleStartCandidate(front); + + preambleCandidateLastEdgeTime = front.time; + if (!front.dir) + return true; + + if (!preambleCandidateFirstRiseValid) + { + preambleCandidateFirstRiseValid = true; + preambleCandidateFirstRiseTime = front.time; + return true; + } + + const uint32_t period = front.time - preambleCandidateFirstRiseTime; + preambleCandidateFirstRiseTime = front.time; + if (!preambleRisePeriodCoarseOk(period)) + { + preambleGoodPeriods = 0; + preambleMeanPeriod = 0; + return true; + } + + if (preambleGoodPeriods == 0) + { + preambleGoodPeriods = 1; + preambleMeanPeriod = (uint16_t)period; + } + else + { + const uint32_t tol = preambleJitterTolUs(preambleMeanPeriod); + if (absDiffU32(period, preambleMeanPeriod) <= tol) + { + if (preambleGoodPeriods < 255U) + ++preambleGoodPeriods; + preambleMeanPeriod = + (uint16_t)(((uint32_t)preambleMeanPeriod * 3U + period) / 4U); + } + else + { + preambleGoodPeriods = 1; + preambleMeanPeriod = (uint16_t)period; + } + } + + if (freeFrec) + riseSyncTime = (riseSyncTime + period / 2U) / 2U; + + if (preambleGoodPeriods >= kPreambleLockNeed) + { + // Новый кадр обязан стартовать с чистого state, иначе возможны CRC/sync срывы + // при lock без промежуточного listenStart()->firstRX(). + errors.reset(); + packSize = 0; + isBufferOverflow = false; + isAvailable = false; + bufBitPos = 0; + isData = true; + i_dataBuffer = 0; + nextControlBit = bitPerByte; + i_syncBit = 0; + err_syncBit = 0; + isWrongPack = false; + msgTypeReceive = 0; + memset(dataBuffer, 0x00, dataByteSizeMax); + + preambleState = PreambleState::Locked; + isPreamb = false; + isRecive = true; + isReciveRaw = true; + risePeriod = preambleMeanPeriod; +#if defined(IRDEBUG_SERIAL_PACK) + packTraceResetFrame(); + packTraceOpen = true; +#endif +#ifdef IRDEBUG + errPulse(up, 50); + errPulse(down, 50); +#endif + prevRise = front.time + preambleMeanPeriod / 2U; + return true; + } + return true; + } + + if (preambleState == PreambleState::Locked) + { + if (!isReciveRaw) + preambleResetToIdle(); + return false; + } + + return !isReciveRaw; +} + // IRDEBUG FUNC #ifdef IRDEBUG inline void IR_DecoderRaw::errPulse(uint8_t pin, uint8_t count) diff --git a/IR_DecoderRaw.h b/IR_DecoderRaw.h index 25c86f5..e634e23 100644 --- a/IR_DecoderRaw.h +++ b/IR_DecoderRaw.h @@ -52,6 +52,10 @@ public: inline bool isOverflow() { return isBufferOverflow; }; // Буффер переполнился bool isSubOverflow(); volatile inline bool isReciving() { return isRecive; }; // Возвращает true, если происходит приём пакета + uint32_t pulseFilterDroppedByFilteredOverflow() const { return pulseFilterDropFilteredOverflow; } + uint32_t pulseFilterDroppedByHoldOverflow() const { return pulseFilterDropHoldOverflow; } + uint32_t pulseFilterDroppedGlitchPairs() const { return pulseFilterDropGlitchPairs; } + void pulseFilterResetStats(); #if defined(IR_EDGE_TRACE) void edgeTraceClear(); @@ -101,6 +105,29 @@ private: // volatile FrontStorage subBuffer[subBufferSize]; // вспомогательный буфер для хранения необработанных фронтов/спадов RingBuffer subBuffer; + /** Очередь фронтов после потокового анти-глитча; tick() читает из неё при включённом фильтре. */ + RingBuffer filteredSubBuffer; + static constexpr uint8_t kPulseFilterHoldCap = 6; + FrontStorage pulseFilterHoldEdges[kPulseFilterHoldCap]{}; + uint8_t pulseFilterHoldCount = 0; + bool pulseFilterLastRawValid = false; + uint32_t pulseFilterLastRawTime = 0; + uint32_t pulseFilterDropFilteredOverflow = 0; + uint32_t pulseFilterDropHoldOverflow = 0; + uint32_t pulseFilterDropGlitchPairs = 0; + static constexpr uint8_t kPreambleLockNeed = (uint8_t)IR_PREAMBLE_LOCK_RISE_PERIODS; + enum class PreambleState : uint8_t + { + Idle = 0, + Candidate = 1, + Locked = 2 + }; + PreambleState preambleState = PreambleState::Idle; + uint8_t preambleGoodPeriods = 0; + uint16_t preambleMeanPeriod = 0; + uint32_t preambleCandidateLastEdgeTime = 0; + uint32_t preambleCandidateFirstRiseTime = 0; + bool preambleCandidateFirstRiseValid = false; #if defined(IR_EDGE_TRACE) struct IrEdgeTraceRec @@ -139,6 +166,18 @@ private: bool isReciveRaw; void listenStart(); void checkTimeout(); // + /** Один сырой фронт из subBuffer -> потоковый holdback-антиглитч. */ + void pulseFilterFeedOneRaw(const FrontStorage &e); + void pulseFilterFlushTimeout(uint32_t nowUs); + bool pulseFilterEmit(const FrontStorage &e); + void pulseFilterShiftLeft(uint8_t n); + void pulseFilterReset(); + static uint32_t absDiffU32(uint32_t a, uint32_t b); + uint32_t preambleJitterTolUs(uint32_t baselineUs) const; + bool preambleRisePeriodCoarseOk(uint32_t periodUs) const; + void preambleResetToIdle(); + void preambleStartCandidate(const FrontStorage &front); + bool preambleProcessEdge(const FrontStorage &front); /// @brief Проверка CRC. Проверяет len байт со значением crc, пришедшим в пакете /// @param len Длина в байтах проверяемых данных diff --git a/IR_config.h b/IR_config.h index 698823a..ad0e77c 100644 --- a/IR_config.h +++ b/IR_config.h @@ -154,6 +154,54 @@ typedef uint16_t crc_t; #define subBufferSize 250 // Буфер для складирования фронтов, пока их не обработают (передатчик) #endif +/** Минимальная длительность удержания уровня (мкс): короче — импульс/пара фронтов выкидывается до tick() + * (иголки на плато, дребезг). 0 — фильтр выключен, фронты идут в декодер как с ISR. */ +#ifndef IR_INPUT_MIN_PULSE_US +#define IR_INPUT_MIN_PULSE_US 0 +#endif +/** Сколько подтверждённых фронтов держать перед выпуском в декодер (потоковая задержка). */ +#ifndef IR_INPUT_FILTER_HOLDBACK_EDGES +#define IR_INPUT_FILTER_HOLDBACK_EDGES 3U +#endif +/** Если новых фронтов нет, через minPulse*mult держатель принудительно сбрасывается в декодер. */ +#ifndef IR_INPUT_FILTER_TIMEOUT_MULT +#define IR_INPUT_FILTER_TIMEOUT_MULT 5U +#endif + +/** Синхронно с IrFoxProtocolConstants.h / IrFoxDecoder (плагин Saleae). */ +#ifndef IR_SHORT_LOW_GLITCH_REJECT +#define IR_SHORT_LOW_GLITCH_REJECT 1 +#endif +#ifndef IR_GLITCH_REJECT_PHASE_NUDGE +#define IR_GLITCH_REJECT_PHASE_NUDGE 1 +#endif +#ifndef IR_MICRO_GAP_RISE_REJECT +#define IR_MICRO_GAP_RISE_REJECT 1 +#endif +/** Лок преамбулы: сколько одинаковых подряд периодов подъёма нужно для старта кадра. */ +#ifndef IR_PREAMBLE_LOCK_RISE_PERIODS +#define IR_PREAMBLE_LOCK_RISE_PERIODS 2U +#endif +/** Допуск одинаковости периода преамбулы (проценты) + минимальная абсолютная полка в мкс. */ +#ifndef IR_PREAMBLE_JITTER_PCT +#define IR_PREAMBLE_JITTER_PCT 18U +#endif +#ifndef IR_PREAMBLE_JITTER_US_MIN +#define IR_PREAMBLE_JITTER_US_MIN 80U +#endif +/** Грубое окно валидности периода преамбулы RISE->RISE (в процентах от bitTime). + * Для текущего протокола преамбула заметно длиннее обычного битового периода. */ +#ifndef IR_PREAMBLE_PERIOD_MIN_FACTOR_PCT +#define IR_PREAMBLE_PERIOD_MIN_FACTOR_PCT 220U +#endif +#ifndef IR_PREAMBLE_PERIOD_MAX_FACTOR_PCT +#define IR_PREAMBLE_PERIOD_MAX_FACTOR_PCT 340U +#endif +/** Таймаут окна кандидата преамбулы: IR_timeout * mult. */ +#ifndef IR_PREAMBLE_CANDIDATE_TIMEOUT_MULT +#define IR_PREAMBLE_CANDIDATE_TIMEOUT_MULT 3U +#endif + #define preambPulse 3 #define disablePairDec false // Отключать парный приёмник, возможны баги, используйте setBlindDecoders()