#include "IR_DecoderRaw.h" #include "IR_Encoder.h" #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; } static inline uint16_t irClampU16(uint32_t v) { return (v > 0xFFFFU) ? 0xFFFFU : (uint16_t)v; } IR_DecoderRaw::IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPair) : encoder(encPair) { setPin(pin); id = addr; prevRise = prevFall = prevPrevFall = prevPrevRise = 0; if (encPair != nullptr) { encPair->decPair = this; } #ifdef IRDEBUG pinMode(wrHigh, OUTPUT); pinMode(wrLow, OUTPUT); pinMode(writeOp, OUTPUT); pinMode(errOut, OUTPUT); pinMode(up, OUTPUT); pinMode(down, OUTPUT); #endif } bool IR_DecoderRaw::isSubOverflow() { noInterrupts(); volatile bool ret = isSubBufferOverflow; interrupts(); return ret; } bool IR_DecoderRaw::availableRaw() { if (isAvailable) { isAvailable = false; return true; } else { return false; } }; void IR_DecoderRaw::pulseFilterResetStats() { pulseFilterDropFilteredOverflow = 0; pulseFilterDropHoldOverflow = 0; pulseFilterDropGlitchPairs = 0; } bool IR_DecoderRaw::registerPairMuteEncoder(IR_Encoder *enc) { if (enc == nullptr) return false; for (uint8_t i = 0; i < pairMuteEncoderCount; ++i) { if (pairMuteEncoders[i] == enc) return true; } if (pairMuteEncoderCount >= IR_PAIR_MUTE_MAX_ENCODERS) return false; pairMuteEncoders[pairMuteEncoderCount++] = enc; return true; } void IR_DecoderRaw::refreshPairMuteState() { uint16_t active = 0; for (uint8_t i = 0; i < pairMuteEncoderCount; ++i) { IR_Encoder *enc = pairMuteEncoders[i]; if (enc != nullptr && enc->isBusy()) ++active; } const uint32_t nowUs = micros(); noInterrupts(); const bool wasActive = (isPairSending != 0); isPairSending = active; #if IR_RX_BRIEF_LOG if (!wasActive && active != 0) { rxBriefMuteBlockedEdges = 0; rxBriefMuteBeginPending = true; rxBriefMuteBeginUs = nowUs; } else if (wasActive && active == 0) { rxBriefMuteEndPending = true; rxBriefMuteEndUs = nowUs; rxBriefMuteEndCount = rxBriefMuteBlockedEdges; rxBriefMuteBlockedEdges = 0; } #endif interrupts(); } #if IR_RX_BRIEF_LOG const __FlashStringHelper *IR_DecoderRaw::rxBriefReasonTag(RxBriefReason reason) { switch (reason) { case RxBriefReason::MuteBegin: return F("MUTE_BEGIN"); case RxBriefReason::MuteEnd: return F("MUTE_END"); case RxBriefReason::RawOverflow: return F("QRAW"); case RxBriefReason::FilterOverflow: return F("QFLT"); case RxBriefReason::HoldOverflow: return F("HOLD"); case RxBriefReason::Glitch: return F("GLITCH"); case RxBriefReason::Timing: return F("TIME"); case RxBriefReason::Preamble: return F("PREAMB"); case RxBriefReason::Sync: return F("SYNC"); case RxBriefReason::BufferOverflow: return F("BUF"); case RxBriefReason::Timeout: return F("TIMEOUT"); case RxBriefReason::Crc: return F("CRC"); case RxBriefReason::Ok: return F("OK"); default: return F("UNK"); } } void IR_DecoderRaw::rxBriefLog(RxBriefReason reason, uint16_t a, uint16_t b, uint32_t tUs) { #if IR_RX_BRIEF_LOG_REJECT_ONLY if (reason == RxBriefReason::Ok || reason == RxBriefReason::Preamble) return; #endif if (tUs == 0U) tUs = micros(); Serial.print(F("IRRX t=")); Serial.print((unsigned long)tUs); Serial.print(F(" rsn=")); Serial.print(rxBriefReasonTag(reason)); switch (reason) { case RxBriefReason::MuteBegin: break; case RxBriefReason::MuteEnd: case RxBriefReason::RawOverflow: Serial.print(F(" cnt=")); Serial.print(a); break; case RxBriefReason::FilterOverflow: case RxBriefReason::HoldOverflow: case RxBriefReason::Glitch: Serial.print(F(" total=")); Serial.print(a); break; case RxBriefReason::Timing: Serial.print(F(" rp=")); Serial.print(a); if (b) { Serial.print(F(" hp=")); Serial.print(b); } break; case RxBriefReason::Preamble: Serial.print(F(" good=")); Serial.print(a); if (b) { Serial.print(F(" per=")); Serial.print(b); } break; case RxBriefReason::Sync: Serial.print(F(" err=")); Serial.print(a); break; case RxBriefReason::BufferOverflow: Serial.print(F(" bits=")); Serial.print(a); break; case RxBriefReason::Timeout: Serial.print(F(" bits=")); Serial.print(a); if (b) { Serial.print(F(" exp=")); Serial.print(b); } break; case RxBriefReason::Crc: case RxBriefReason::Ok: Serial.print(F(" len=")); Serial.print(a); if (b) { Serial.print(F(" err=")); Serial.print(b); } break; } Serial.println(); } void IR_DecoderRaw::rxBriefNoteMuteBlockedIsr(uint32_t tUs) { (void)tUs; if (rxBriefMuteBlockedEdges != UINT16_MAX) ++rxBriefMuteBlockedEdges; } void IR_DecoderRaw::rxBriefNoteRawOverflowIsr(uint32_t tUs) { if (rxBriefRawOverflowDrops != UINT16_MAX) ++rxBriefRawOverflowDrops; rxBriefRawOverflowLastUs = tUs; } void IR_DecoderRaw::rxBriefFlushDeferredIsrLogs() { bool muteBeginPending = false; uint32_t muteBeginUs = 0; bool muteEndPending = false; uint32_t muteEndUs = 0; uint16_t muteEndCnt = 0; uint16_t rawCnt = 0; uint32_t rawLastUs = 0; noInterrupts(); muteBeginPending = rxBriefMuteBeginPending; muteBeginUs = rxBriefMuteBeginUs; rxBriefMuteBeginPending = false; rxBriefMuteBeginUs = 0; muteEndPending = rxBriefMuteEndPending; muteEndUs = rxBriefMuteEndUs; muteEndCnt = rxBriefMuteEndCount; rxBriefMuteEndPending = false; rxBriefMuteEndUs = 0; rxBriefMuteEndCount = 0; rawCnt = rxBriefRawOverflowDrops; rawLastUs = rxBriefRawOverflowLastUs; rxBriefRawOverflowDrops = 0; rxBriefRawOverflowLastUs = 0; interrupts(); if (muteBeginPending) rxBriefLog(RxBriefReason::MuteBegin, 0, 0, muteBeginUs); if (muteEndPending) rxBriefLog(RxBriefReason::MuteEnd, muteEndCnt, 0, muteEndUs); if (rawCnt) rxBriefLog(RxBriefReason::RawOverflow, rawCnt, 0, rawLastUs); } #endif //////////////////////////////////// isr /////////////////////////////////////////// void IR_DecoderRaw::isr() { // Интервалы между соседними фронтами считаются как (uint32_t)(t1 - t0) — корректно при // паузе < ~35 мин между фронтами; условие «тишина > longSilence» в preambleProcessEdge // переписано без front.time > prevRise (оно ломается при wrap). uint32_t t; noInterrupts(); t = micros(); interrupts(); FrontStorage edge; edge.dir = port->IDR & mask; edge.time = t; #if defined(IR_EDGE_TRACE) edgeTracePush(edge.time, edge.dir ? 1u : 0u, isPairSending ? (uint8_t)IR_EDGE_TRACE_F_SKIP_DECODE : 0u); #endif if (isPairSending) { #if IR_RX_BRIEF_LOG rxBriefNoteMuteBlockedIsr(edge.time); #endif return; } if (!subBuffer.push(edge)) { isSubBufferOverflow = true; #if IR_RX_BRIEF_LOG rxBriefNoteRawOverflowIsr(edge.time); #endif } } //////////////////////////////////////////////////////////////////////////////////// void IR_DecoderRaw::firstRX() { #if defined(IRDEBUG_SERIAL_PACK) packTraceResetFrame(); #endif 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; isPreamb = true; riseSyncTime = bitTime /* 1100 */; #ifdef IRDEBUG wrCounter = 0; #endif memset(dataBuffer, 0x00, dataByteSizeMax); pulseFilterReset(); 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) packTraceOnTimeoutOrAbort(true); #endif isReciveRaw = false; firstRX(); } } // ---- быстрая проверка конца пакета --------------------------------- inline void IR_DecoderRaw::checkTimeout() { if (!isRecive) return; // уже не принимаем – нечего проверять if (rxTimeoutPipelineBusy()) return; if (micros() - lastEdgeTime > IR_timeout * 2U) { #if defined(IRDEBUG_SERIAL_PACK) packTraceOnTimeoutOrAbort(false); #endif #if IR_RX_BRIEF_LOG const uint16_t expected = (i_dataBuffer >= 8U) ? uint16_t(dataBuffer[0] & IR_MASK_MSG_INFO) : 0U; rxBriefLog(RxBriefReason::Timeout, i_dataBuffer, expected, micros()); #endif isRecive = false; // приём завершён msgTypeReceive = 0; // Как после listenStart(): без сброса isReciveRaw + firstRX() декодер остаётся // с «сырым» флагом приёма / Locked и не может заново поймать преамбулу до очень // длинной тишины (см. preambleProcessEdge Idle и listenStart). isReciveRaw = false; firstRX(); // Не подставлять lastEdgeTime = micros(): при хвосте в subBuffer следующий фронт // имеет edge.time < micros() — откат lastEdgeTime даёт ложный TIMEOUT на следующем tick(). // Повторного checkTimeout при тишине нет: isRecive уже false. } } // ==================================================================== 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; bool hasRawFront = false; noInterrupts(); 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; listenStart(); checkTimeout(); #if defined(IR_EDGE_TRACE) while (edgeTraceFlushChunk(Serial, 48) > 0) {} #endif return; } // Если данных нет - ничего не делаем if (preambleProcessEdge(currentFront)) { lastEdgeTime = currentFront.time; goto END; } lastEdgeTime = currentFront.time; // запоминаем любой фронт //////////////////////////////////////////////////////////////////////////////////////////////////////////// if (currentFront.dir) { // Если __/``` ↑ 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_RX_BRIEF_LOG rxBriefLog(RxBriefReason::Glitch, 1, 0, currentFront.time); #endif #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_RX_BRIEF_LOG rxBriefLog(RxBriefReason::Glitch, 1, 0, currentFront.time); #endif #if IR_GLITCH_REJECT_PHASE_NUDGE irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise); #endif goto END; } #endif if (candRp <= riseTimeMax / 4U && !highCount && !lowCount) { errors.other++; #if IR_RX_BRIEF_LOG rxBriefLog(RxBriefReason::Timing, irClampU16(candRp), 0, currentFront.time); #endif goto END; } if (candRp > riseTimeMax / 4 || highCount || lowCount) { // комплексный фикс рваной единицы risePeriod = candRp; highTime = candHt; lowTime = candLt; prevRise = currentFront.time; if ( risePeriod > UINT32_MAX - IR_timeout * 10 || highTime > UINT32_MAX - IR_timeout * 10 || lowTime > UINT32_MAX - IR_timeout * 10 || prevRise > UINT32_MAX - IR_timeout * 10) { #ifdef IRDEBUG errPulse(down, 50); // Serial.print("\n"); // Serial.print("risePeriod: "); // Serial.println(risePeriod); // Serial.print("highTime: "); // Serial.println(highTime); // Serial.print("lowTime: "); // Serial.println(lowTime); // Serial.print("prevRise: "); // Serial.println(prevRise); #endif } } else { errors.other++; } } else { // Если ```\__ ↓ if (currentFront.time - prevFall > riseTimeMin / 4) { prevFall = currentFront.time; } else { errors.other++; } } #ifdef IRDEBUG // goto END; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #endif //---------------------------------------------------------------------------------- #ifdef IRDEBUG digitalWrite(errOut, currentFront.dir); #endif if (risePeriod > IR_timeout || isBufferOverflow || risePeriod < riseTimeMin || isWrongPack) // ~Мы в пределах таймаута и буффер не переполнен и fix дроблёных единиц { #if IR_RX_BRIEF_LOG if (!isBufferOverflow && !isWrongPack) rxBriefLog(RxBriefReason::Timing, irClampU16((uint32_t)risePeriod), irClampU16((uint32_t)highTime), currentFront.time); #endif goto END; } // определить направление фронта if (currentFront.dir) { // Если __/``` ↑ highCount = 0; lowCount = 0; allCount = 0; bool invertErr = false; // #ifdef IRDEBUG // Serial.print("\n"); // Serial.print("wrCounter: "); // Serial.println(wrCounter++); // Serial.print("risePeriod: "); // Serial.println(risePeriod); // Serial.print("highTime: "); // Serial.println(highTime); // Serial.print("lowTime: "); // Serial.println(lowTime); // #endif if (aroundRise(risePeriod)) { // тактирование есть, сигнал хороший - без ошибок(?) if (highTime > lowTime) { // 1 #ifdef IRDEBUG errPulse(wrHigh, 1); #endif writeToBuffer(HIGH); } else { // 0 #ifdef IRDEBUG errPulse(wrLow, 1); #endif writeToBuffer(LOW); } } else { // пропущены такты! сигнал средний // ошибка пропуска highCount = ceil_div(highTime, riseTime); // предполагаемое колличество HIGH битов lowCount = ceil_div(lowTime, riseTime); // предполагаемое колличество LOW битов allCount = ceil_div(risePeriod, riseTime); // предполагаемое колличество всего битов if (highCount == 0 && highTime > riseTime / 3) { // fix короткой единицы (?)после пропуска нулей(?) highCount++; errors.other++; #ifdef IRDEBUG errPulse(up, 50); #endif } if (lowCount + highCount > allCount) { // fix ошибочных сдвигов if (lowCount > highCount) { // Лишние нули lowCount = allCount - highCount; errors.lowSignal += lowCount; #ifdef IRDEBUG // errPulse(errOut, 3); errPulse(down, 40); errPulse(up, 10); errPulse(down, 40); #endif } else if (lowCount < highCount) { // Лишние единицы highCount = allCount - lowCount; errors.highSignal += highCount; #ifdef IRDEBUG errPulse(down, 10); errPulse(up, 40); errPulse(down, 10); // errPulse(errOut, 4); #endif // неизвестный случай Инверсит след бит или соседние // Очень редко // TODO: Отловить проверить } else if (lowCount == highCount) { #ifdef IRDEBUG errPulse(down, 40); errPulse(up, 40); errPulse(down, 40); #endif invertErr = true; // Serial.print("..."); errors.other += allCount; } // errorCounter += allCount; } // errorCounter += allCount; // errors.other+=allCount; if (lowCount < highCount) { errors.highSignal += highCount; } else { errors.lowSignal += lowCount; } // errPulse(errOut, 1); for (int8_t i = 0; i < lowCount && 8 - i; i++) { // отправка LOW битов, если есть if (i == lowCount - 1 && invertErr) { invertErr = false; writeToBuffer(HIGH, true); #ifdef IRDEBUG errPulse(wrHigh, 1); #endif } else { writeToBuffer(LOW); #ifdef IRDEBUG errPulse(wrLow, 1); #endif } } for (int8_t i = 0; i < highCount && 8 - i; i++) { // отправка HIGH битов, если есть if (i == highCount - 1 && invertErr) { invertErr = false; writeToBuffer(LOW, true); #ifdef IRDEBUG errPulse(wrLow, 1); #endif } else { writeToBuffer(HIGH); #ifdef IRDEBUG errPulse(wrHigh, 1); #endif } } } } else { // Если ```\__ ↓ } //////////////////////////////////////////////////////////////////////////////////////////////////////////// END:; listenStart(); checkTimeout(); #if IR_RX_BRIEF_LOG rxBriefFlushDeferredIsrLogs(); #endif #if defined(IR_EDGE_TRACE) while (edgeTraceFlushChunk(Serial, 48) > 0) {} #endif } void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix) { #if !defined(IRDEBUG_SERIAL_PACK) (void)packTraceInvertFix; #endif if (i_dataBuffer > dataByteSizeMax * 8) { // проверка переполнения isBufferOverflow = true; #if IR_RX_BRIEF_LOG rxBriefLog(RxBriefReason::BufferOverflow, i_dataBuffer, 0, micros()); #endif #if defined(IRDEBUG_SERIAL_PACK) if (packTraceOpen) packTraceEmitErrorFlash(F("ERROR: buffer overflow")); #endif } if (isBufferOverflow || isPreamb || isWrongPack) { // Как checkTimeout/listenStart: firstRX() сбрасывает буфер битов, преамбулу и // pulseFilterReset() — при IR_INPUT_MIN_PULSE_US > 0 иначе остаётся «хвост» в hold/filtered. isRecive = false; isReciveRaw = false; msgTypeReceive = 0; firstRX(); return; } // Переключение флага, data или syncBit if (bufBitPos == nextControlBit) { nextControlBit += (isData ? syncBits : bitPerByte); // маркер следующего переключения isData = !isData; i_syncBit = 0; // сброс счетчика битов синхронизации err_syncBit = 0; // сброс счетчика ошибок синхронизации } if (isData) { // Запись битов в dataBuffer dataBuffer[(i_dataBuffer / 8)] |= bit << (7 - i_dataBuffer % 8); // Запись в буффер i_dataBuffer++; bufBitPos++; #if defined(IRDEBUG_SERIAL_PACK) if (packTraceInvertFix) { packTracePushChar('`'); packTracePushChar(bit ? '1' : '0'); packTracePushChar('`'); } else packTracePushBit(bit); #endif } else { //********************************* Проверка контрольных sync битов *******************************// ////////////////////// Исправление лишнего нуля /////////////////////// if (i_syncBit == 0) { // Первый бит синхронизации if (bit != (dataBuffer[((i_dataBuffer - 1) / 8)] >> (7 - (i_dataBuffer - 1) % 8) & 1)) { bufBitPos++; i_syncBit++; #if defined(IRDEBUG_SERIAL_PACK) packTracePushBit(bit); #endif } else { i_syncBit = 0; errors.other++; err_syncBit++; const bool fatalSync = (err_syncBit >= syncBits); if (fatalSync) { #if defined(IRDEBUG_SERIAL_PACK) && defined(IRDEBUG_SERIAL_SOFT_REJECT) if (packTraceSoftReject()) { packTraceHadWrongSync = true; packTraceForceEndSyncPhase(); err_syncBit = 0; i_syncBit = 0; isWrongPack = false; } else #endif { isWrongPack = true; #if IR_RX_BRIEF_LOG rxBriefLog(RxBriefReason::Sync, err_syncBit, 0, micros()); #endif #if defined(IRDEBUG_SERIAL_PACK) packTraceEmitErrorFlash(F("ERROR: Wrong sync bit")); #endif } } } } else { // Последующие биты синхронизации bufBitPos++; i_syncBit++; #if defined(IRDEBUG_SERIAL_PACK) packTracePushBit(bit); #endif } isWrongPack = (err_syncBit >= syncBits); } //**************************************************************************************************// #ifdef IRDEBUG bit ? infoPulse(writeOp, 2) : infoPulse(writeOp, 1); #endif if (!isAvailable && isData && !isWrongPack) { if (i_dataBuffer == 8 * msgBytes) { // Ппервый байт packSize = dataBuffer[0] & IR_MASK_MSG_INFO; } // Тип приёма (для isReceive): выставляем сразу после первого байта, ДО проверки «Конец». // Иначе при packSize==1 один и тот же шаг i_dataBuffer==8 одновременно «закрывает» кадр (msgTypeReceive=0) // и снова выставляет msgTypeReceive ниже — флаг залипает, пока не придёт ошибка/другой кадр. if (packSize && (i_dataBuffer == 8)) { msgTypeReceive = (dataBuffer[0] >> 5) | 0b11111000; } if (packSize && (i_dataBuffer == packSize * bitPerByte)) { // Конец packInfo.buffer = dataBuffer; packInfo.crc = crcValue; packInfo.err = errors; packInfo.packSize = packSize; packInfo.rTime = riseSyncTime; isRecive = false; isReciveRaw = false; preambleResetToIdle(); msgTypeReceive = 0; isAvailable = crcCheck(packSize - crcBytes, crcValue); #ifdef BRUTEFORCE_CHECK { uint16_t packTraceBfByte = 0; uint8_t packTraceBfBit = 0; bool packTraceBfMark = false; if (!isAvailable) // Исправление первого бита // Очень большая затычка... for (size_t i = 0; i < min(uint16_t(packSize - crcBytes * 2U), uint16_t(dataByteSizeMax)); ++i) { for (int j = 0; j < 8; ++j) { // инвертируем бит dataBuffer[i] ^= 1 << j; isAvailable = crcCheck(min(uint16_t(packSize - crcBytes), uint16_t(dataByteSizeMax - 1U)), crcValue); // обратно инвертируем бит в исходное состояние if (isAvailable) { packTraceBfByte = static_cast(i); packTraceBfBit = static_cast(j); packTraceBfMark = true; goto OUT_BRUTEFORCE; } else { dataBuffer[i] ^= 1 << j; } } } OUT_BRUTEFORCE: #if defined(IRDEBUG_SERIAL_PACK) if (packTraceBfMark) packTraceWrapDataBitInBackticks(packTraceBfByte, packTraceBfBit); #endif } #endif #if defined(IRDEBUG_SERIAL_PACK) if (isAvailable) packTraceEmitEndOk(static_cast(packSize)); else packTraceEmitEndBadCrc(static_cast(packSize)); #endif const uint16_t errSum = uint16_t(errors.lowSignal) + uint16_t(errors.highSignal) + uint16_t(errors.other); #if IR_RX_BRIEF_LOG if (isAvailable) rxBriefLog(RxBriefReason::Ok, packSize, errSum, micros()); else rxBriefLog(RxBriefReason::Crc, packSize, errSum, micros()); #endif if (!isAvailable && packSize > 0 && packSize <= dataByteSizeMax) { memcpy(rejectBuffer, dataBuffer, packSize); rejectPackSize = static_cast(packSize); isRejectAvailable = true; } } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } bool IR_DecoderRaw::crcCheck(uint8_t len, crc_t &crc) { bool crcOK = false; crc = 0; crc = (crc8(dataBuffer, 0, len, poly1) << 8) & ~((crc_t)0xFF); crc |= crc8(dataBuffer, 0, len + 1, poly2) & (crc_t)0xFF; if (dataBuffer[len] == (crc >> 8) & 0xFF && dataBuffer[len + 1] == (crc & 0xFF)) { crcOK = true; } else { crcOK = false; } return crcOK; } bool IR_DecoderRaw::availableReject() { if (!isRejectAvailable) return false; isRejectAvailable = false; return true; } uint16_t IR_DecoderRaw::ceil_div(uint16_t val, uint16_t divider) { int ret = val / divider; if ((val << 4) / divider - (ret << 4) >= 8) ret++; return ret; } #if defined(IR_EDGE_TRACE) void IR_DecoderRaw::edgeTracePush(uint32_t t_us, uint8_t level, uint8_t flags) { const uint16_t cap = static_cast(IR_EDGE_TRACE_CAPACITY); noInterrupts(); const uint16_t w = edgeTrace_w; const uint16_t r = edgeTrace_r; const uint16_t next = static_cast((w + 1u) % cap); if (next == r) { edgeTrace_overflow = true; interrupts(); return; } edgeTrace_buf[w].t_us = t_us; edgeTrace_buf[w].level = level ? 1u : 0u; edgeTrace_buf[w].flags = flags; edgeTrace_w = next; interrupts(); } void IR_DecoderRaw::edgeTraceClear() { noInterrupts(); edgeTrace_w = 0; edgeTrace_r = 0; edgeTrace_overflow = false; interrupts(); } uint16_t IR_DecoderRaw::edgeTracePendingCount() const { noInterrupts(); const uint16_t w = edgeTrace_w; const uint16_t r = edgeTrace_r; interrupts(); const uint16_t cap = static_cast(IR_EDGE_TRACE_CAPACITY); if (w >= r) return static_cast(w - r); return static_cast(cap - r + w); } uint16_t IR_DecoderRaw::edgeTraceFlushChunk(Print &out, uint16_t maxRec) { if (maxRec == 0) maxRec = 48; constexpr uint16_t kStackCap = 64; if (maxRec > kStackCap) maxRec = kStackCap; const uint16_t cap = static_cast(IR_EDGE_TRACE_CAPACITY); noInterrupts(); const uint16_t w = edgeTrace_w; const uint16_t r = edgeTrace_r; uint16_t avail = (w >= r) ? static_cast(w - r) : static_cast(cap - r + w); uint16_t toCopy = (avail > maxRec) ? maxRec : avail; const bool truncated = (avail > toCopy); if (toCopy == 0) { interrupts(); return 0; } uint8_t tmp[kStackCap * 6]; for (uint16_t i = 0; i < toCopy; ++i) { const uint16_t idx = static_cast((r + i) % cap); memcpy(tmp + i * 6u, &edgeTrace_buf[idx], 6u); } edgeTrace_r = static_cast((r + toCopy) % cap); const bool ovf = edgeTrace_overflow; interrupts(); uint8_t meta = 0; if (ovf) meta |= 0x01u; if (truncated) meta |= 0x02u; uint8_t line[3 + kStackCap * 6]; line[0] = meta; line[1] = static_cast(toCopy & 0xFFu); line[2] = static_cast((toCopy >> 8) & 0xFFu); memcpy(line + 3, tmp, toCopy * 6u); out.print(F("\n@IRF1v1:")); static const char hd[] = "0123456789abcdef"; const uint16_t lineLen = static_cast(3u + toCopy * 6u); for (uint16_t i = 0; i < lineLen; ++i) { const uint8_t b = line[i]; out.write(hd[b >> 4]); out.write(hd[b & 0x0Fu]); } out.write('\n'); return toCopy; } #endif // IR_EDGE_TRACE #if defined(IRDEBUG_SERIAL_PACK) struct IrPackTraceSeg { uint8_t isSync; uint8_t nbits; // число логических бит (данные) или символов синхры uint8_t nchars; // сырых символов в b (данные: 8…24 из‑за `0`/`1`) char b[24]; }; namespace { void ptPrintHexU8(uint8_t v) { static const char hd[] = "0123456789ABCDEF"; Serial.print(hd[v >> 4]); Serial.print(hd[v & 0x0Fu]); } /** Тройной пробел перед началом блока: msg→from, from→to, to→data, data→CRC. */ static bool ptRawLeadTriple(uint8_t byteIndex, uint8_t framePs) { if (byteIndex == 1 || byteIndex == 3) return true; if (framePs > 7 && byteIndex == 5) return true; if (framePs >= 7 && byteIndex == static_cast(framePs - 2)) return true; return false; } /** В IR raw: только 0/1 из flex-сегмента (без `), takeBits логических бит после skipLogical. */ static void ptEmitRawFlexSliceSerial(const char *s, uint8_t nbytes, uint8_t skipLogical, uint8_t takeBits) { size_t i = 0; uint8_t logical = 0; uint8_t emitted = 0; while (i < nbytes && emitted < takeBits) { if (s[i] == '`' && i + 2u < nbytes && (s[i + 1] == '0' || s[i + 1] == '1') && s[i + 2] == '`') { if (logical >= skipLogical) { Serial.print(s[i + 1]); ++emitted; } i += 3; ++logical; } else if (s[i] == '0' || s[i] == '1') { if (logical >= skipLogical) { Serial.print(s[i]); ++emitted; } ++i; ++logical; } else break; } } static bool ptTryConsumeFlexDataBit(const char *buf, uint16_t len, uint16_t &pos, IrPackTraceSeg &d) { if (pos + 2 < len && buf[pos] == '`' && (buf[pos + 1] == '0' || buf[pos + 1] == '1') && buf[pos + 2] == '`') { if (d.nchars + 3u > sizeof(d.b)) return false; d.b[d.nchars++] = '`'; d.b[d.nchars++] = buf[pos + 1]; d.b[d.nchars++] = '`'; pos = static_cast(pos + 3u); return true; } if (pos < len && (buf[pos] == '0' || buf[pos] == '1')) { if (d.nchars + 1u > sizeof(d.b)) return false; d.b[d.nchars++] = buf[pos++]; return true; } return false; } } // namespace void IR_DecoderRaw::packTraceResetFrame() { packTraceOpen = false; packTraceHadWrongSync = false; packTraceLen = 0; packTraceBitBuf[0] = '\0'; } void IR_DecoderRaw::packTracePushChar(char c) { if (packTraceLen + 1u < kPackTraceBufCap) { packTraceBitBuf[packTraceLen++] = c; packTraceBitBuf[packTraceLen] = '\0'; } } void IR_DecoderRaw::packTracePushBit(bool bit) { packTracePushChar(bit ? '1' : '0'); } void IR_DecoderRaw::packTraceWrapDataBitInBackticks(uint16_t byteIndex, uint8_t bitInByte) { const uint32_t target = uint32_t(byteIndex) * 8u + bitInByte; uint32_t dbit = 0; uint16_t pos = 0; bool inData = true; uint16_t len = packTraceLen; if (len >= kPackTraceBufCap) len = kPackTraceBufCap - 1u; while (pos < len) { if (inData) { uint8_t bitLen = 0; if (pos + 2 < len && packTraceBitBuf[pos] == '`' && packTraceBitBuf[pos + 2] == '`' && (packTraceBitBuf[pos + 1] == '0' || packTraceBitBuf[pos + 1] == '1')) bitLen = 3; else if (packTraceBitBuf[pos] == '0' || packTraceBitBuf[pos] == '1') bitLen = 1; else return; if (dbit == target) { if (bitLen == 3) return; if (packTraceLen + 2u >= kPackTraceBufCap) return; const uint8_t finalBit = static_cast((dataBuffer[byteIndex] >> (7u - bitInByte)) & 1u); const char ch = finalBit ? '1' : '0'; memmove(packTraceBitBuf + pos + 2, packTraceBitBuf + pos, packTraceLen - pos); packTraceBitBuf[pos] = '`'; packTraceBitBuf[pos + 1] = ch; packTraceBitBuf[pos + 2] = '`'; packTraceLen += 2; if (packTraceLen < kPackTraceBufCap) packTraceBitBuf[packTraceLen] = '\0'; return; } ++dbit; pos = static_cast(pos + bitLen); if ((dbit % 8u) == 0u) inData = false; } else { uint8_t sc = 0; while (pos < len && sc < syncBits) { const char c = packTraceBitBuf[pos]; if (c == '0' || c == '1' || c == '?') { ++pos; ++sc; } else break; } inData = true; } } } bool IR_DecoderRaw::packTraceSoftReject() const { #if defined(IRDEBUG_SERIAL_SOFT_REJECT) return true; #else return false; #endif } void IR_DecoderRaw::packTraceForceEndSyncPhase() { for (uint8_t i = 0; i < syncBits; i++) packTracePushChar('?'); const uint8_t cycLen = bitPerByte + syncBits; const uint16_t cyc = uint16_t(bufBitPos / cycLen); bufBitPos = uint16_t((cyc + 1u) * cycLen); if (bufBitPos == nextControlBit) { nextControlBit += (isData ? syncBits : bitPerByte); isData = !isData; i_syncBit = 0; err_syncBit = 0; } } void IR_DecoderRaw::packTraceEmitHex(uint8_t byteCount) const { Serial.print(F("IR hex:")); for (uint8_t i = 0; i < byteCount && i < dataByteSizeMax; i++) { Serial.print(' '); ptPrintHexU8(dataBuffer[i]); } Serial.println(); } void IR_DecoderRaw::packTraceEmitRawBitsLine(bool endWithNewline) const { Serial.print(F("IR raw: ")); uint16_t len = packTraceLen; if (len >= kPackTraceBufCap) len = kPackTraceBufCap - 1u; const char *buf = packTraceBitBuf; uint16_t pos = 0; uint8_t byteIndex = 0; bool firstSeg = true; const uint8_t framePs = (i_dataBuffer >= 8) ? static_cast(dataBuffer[0] & IR_MASK_MSG_INFO) : 0; while (pos < len) { IrPackTraceSeg d{}; d.isSync = 0; while (pos < len && d.nbits < 8) { const uint16_t posBefore = pos; if (!ptTryConsumeFlexDataBit(buf, len, pos, d)) break; if (pos == posBefore) break; ++d.nbits; } if (!d.nbits) break; if (!firstSeg) { if (ptRawLeadTriple(byteIndex, framePs)) Serial.print(F(" ")); else Serial.print(' '); } firstSeg = false; if (d.nbits < 8) { ptEmitRawFlexSliceSerial(d.b, d.nchars, 0, d.nbits); break; } if (byteIndex == 0u) { ptEmitRawFlexSliceSerial(d.b, d.nchars, 0, 3); Serial.print(' '); ptEmitRawFlexSliceSerial(d.b, d.nchars, 3, 5); } else ptEmitRawFlexSliceSerial(d.b, d.nchars, 0, 8); ++byteIndex; if (pos >= len) break; IrPackTraceSeg s{}; s.isSync = 1; while (pos < len && s.nbits < syncBits) { const char c = buf[pos]; if (c != '0' && c != '1' && c != '?') break; if (s.nchars >= sizeof(s.b)) break; s.b[s.nchars++] = c; ++s.nbits; ++pos; } if (s.nbits) { Serial.print(' '); for (uint8_t i = 0; i < s.nbits; ++i) Serial.print(s.b[i]); } } if (endWithNewline) Serial.println(); } void IR_DecoderRaw::packTraceEmitErrorFlash(const __FlashStringHelper *msg) { Serial.println(); packTraceEmitRawBitsLine(false); Serial.print(F(" => ")); Serial.println(msg); { uint16_t nb = i_dataBuffer / 8u; if (nb > dataByteSizeMax) nb = dataByteSizeMax; packTraceEmitHex(static_cast(nb)); } packTraceResetFrame(); } void IR_DecoderRaw::packTraceEmitEndOk(uint8_t packSize) { Serial.println(); packTraceEmitRawBitsLine(false); Serial.print(F(" => OK: ")); irPackTracePrintOkCommand(dataBuffer, packSize); Serial.println(); packTraceEmitHex(packSize); packTraceResetFrame(); } void IR_DecoderRaw::packTraceEmitEndBadCrc(uint8_t packSize) { Serial.println(); packTraceEmitRawBitsLine(false); Serial.println(F(" => ERROR: Wrong CRC")); packTraceEmitHex(packSize); packTraceResetFrame(); } void IR_DecoderRaw::packTraceOnTimeoutOrAbort(bool fromListenStart) { (void)fromListenStart; if (!packTraceOpen) return; const uint16_t expected = (i_dataBuffer >= 8) ? uint16_t(dataBuffer[0] & IR_MASK_MSG_INFO) : 0; uint16_t gotBytes = i_dataBuffer / 8; if (gotBytes > dataByteSizeMax) gotBytes = dataByteSizeMax; Serial.println(); packTraceEmitRawBitsLine(false); Serial.print(F(" => ERROR: TIMEOUT, rx_data_size = ")); Serial.print(expected); Serial.print(F(", but only ")); Serial.print(gotBytes); Serial.println(F(" bytes received")); packTraceEmitHex(static_cast(gotBytes)); packTraceResetFrame(); } __attribute__((weak)) void irPackTracePrintOkCommand(const uint8_t *buf, uint8_t packSize) { (void)buf; (void)packSize; } #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++; #if IR_RX_BRIEF_LOG rxBriefLog(RxBriefReason::FilterOverflow, irClampU16(pulseFilterDropFilteredOverflow), 0, e.time); #endif 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++; #if IR_RX_BRIEF_LOG rxBriefLog(RxBriefReason::HoldOverflow, irClampU16(pulseFilterDropHoldOverflow), 0, e.time); #endif 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++; #if IR_RX_BRIEF_LOG rxBriefLog(RxBriefReason::Glitch, irClampU16(pulseFilterDropGlitchPairs), 0, e.time); #endif 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++; #if IR_RX_BRIEF_LOG rxBriefLog(RxBriefReason::Glitch, irClampU16(pulseFilterDropGlitchPairs), 0, nowUs); #endif 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; // Не использовать 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); } if (preambleState == PreambleState::Candidate) { if ((uint32_t)(front.time - preambleCandidateLastEdgeTime) > candTimeout) { #if IR_RX_BRIEF_LOG rxBriefLog(RxBriefReason::Preamble, preambleGoodPeriods, 0, front.time); #endif 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; #if IR_RX_BRIEF_LOG rxBriefLog(RxBriefReason::Preamble, 0, irClampU16(period), front.time); #endif 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 { #if IR_RX_BRIEF_LOG rxBriefLog(RxBriefReason::Preamble, preambleGoodPeriods, irClampU16(period), front.time); #endif 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) { for (size_t i = 0; i < count; i++) { digitalWrite(pin, 1); digitalWrite(pin, 0); } digitalWrite(pin, 0); } inline void IR_DecoderRaw::infoPulse(uint8_t pin, uint8_t count) { for (size_t i = 0; i < count; i++) { digitalWrite(pin, 1); digitalWrite(pin, 0); } } #endif