diff --git a/Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp b/Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp index 6c22d0b..dd7fb3c 100644 --- a/Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp +++ b/Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp @@ -129,7 +129,7 @@ void IrFoxDecoder::write_to_buffer(bool bit, bool pack_trace_invert_fix, uint64_ if (buf_bit_pos == next_control_bit) { - next_control_bit = static_cast(next_control_bit + (is_data ? irfox::kSyncBits : irfox::kBitPerByte)); + next_control_bit = next_control_bit + (is_data ? irfox::kSyncBits : irfox::kBitPerByte); is_data = !is_data; i_sync_bit = 0; err_sync_bit = 0; diff --git a/Analyzer/raw/IR_Fox/src/IrFoxDecoder.h b/Analyzer/raw/IR_Fox/src/IrFoxDecoder.h index b335f57..c36d6c1 100644 --- a/Analyzer/raw/IR_Fox/src/IrFoxDecoder.h +++ b/Analyzer/raw/IR_Fox/src/IrFoxDecoder.h @@ -127,7 +127,7 @@ private: int16_t buf_bit_pos = 0; bool is_data = true; uint16_t i_data_buffer = 0; - uint8_t next_control_bit = irfox::kBitPerByte; + uint16_t next_control_bit = irfox::kBitPerByte; uint8_t i_sync_bit = 0; uint8_t err_sync_bit = 0; uint16_t error_counter = 0; diff --git a/IR_DecoderRaw.cpp b/IR_DecoderRaw.cpp index b6578c3..4d4ad84 100644 --- a/IR_DecoderRaw.cpp +++ b/IR_DecoderRaw.cpp @@ -26,6 +26,11 @@ static inline uint8_t irPulseFilterHoldbackEdges() 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); @@ -74,6 +79,198 @@ void IR_DecoderRaw::pulseFilterResetStats() 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 /////////////////////////////////////////// volatile uint32_t time_; @@ -99,10 +296,19 @@ void IR_DecoderRaw::isr() if (isPairSending) { +#if IR_RX_BRIEF_LOG + rxBriefNoteMuteBlockedIsr(edge.time); +#endif return; } - subBuffer.push(edge); + if (!subBuffer.push(edge)) + { + isSubBufferOverflow = true; +#if IR_RX_BRIEF_LOG + rxBriefNoteRawOverflowIsr(edge.time); +#endif + } } //////////////////////////////////////////////////////////////////////////////////// @@ -123,6 +329,7 @@ void IR_DecoderRaw::firstRX() i_dataBuffer = 0; nextControlBit = bitPerByte; i_syncBit = 0; + err_syncBit = 0; isWrongPack = false; isPreamb = true; @@ -157,6 +364,10 @@ inline void IR_DecoderRaw::checkTimeout() { #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; @@ -168,6 +379,9 @@ inline void IR_DecoderRaw::checkTimeout() void IR_DecoderRaw::tick() { +#if IR_RX_BRIEF_LOG + rxBriefFlushDeferredIsrLogs(); +#endif FrontStorage currentFront; bool hasCurrentFront = false; FrontStorage rawFront; @@ -250,6 +464,9 @@ void IR_DecoderRaw::tick() 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 @@ -264,6 +481,9 @@ void IR_DecoderRaw::tick() 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 @@ -273,6 +493,9 @@ void IR_DecoderRaw::tick() if (candRp <= riseTimeMax / 4U && !highCount && !lowCount) { errors.other++; +#if IR_RX_BRIEF_LOG + rxBriefLog(RxBriefReason::Timing, irClampU16(candRp), 0, currentFront.time); +#endif goto END; } @@ -336,6 +559,11 @@ void IR_DecoderRaw::tick() 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; } @@ -494,6 +722,9 @@ void IR_DecoderRaw::tick() //////////////////////////////////////////////////////////////////////////////////////////////////////////// END:; +#if IR_RX_BRIEF_LOG + rxBriefFlushDeferredIsrLogs(); +#endif #if defined(IR_EDGE_TRACE) while (edgeTraceFlushChunk(Serial, 48) > 0) {} #endif @@ -507,6 +738,9 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix) 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")); @@ -581,6 +815,9 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix) #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 @@ -674,6 +911,14 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix) 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); @@ -1203,6 +1448,9 @@ 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); @@ -1232,6 +1480,9 @@ void IR_DecoderRaw::pulseFilterFeedOneRaw(const FrontStorage &e) if (pulseFilterHoldCount >= kPulseFilterHoldCap) { pulseFilterDropHoldOverflow++; +#if IR_RX_BRIEF_LOG + rxBriefLog(RxBriefReason::HoldOverflow, irClampU16(pulseFilterDropHoldOverflow), 0, e.time); +#endif pulseFilterEmit(pulseFilterHoldEdges[0]); pulseFilterShiftLeft(1); } @@ -1248,6 +1499,9 @@ void IR_DecoderRaw::pulseFilterFeedOneRaw(const FrontStorage &e) if (dt < minUs) { pulseFilterDropGlitchPairs++; +#if IR_RX_BRIEF_LOG + rxBriefLog(RxBriefReason::Glitch, irClampU16(pulseFilterDropGlitchPairs), 0, e.time); +#endif pulseFilterShiftLeft(2); continue; } @@ -1276,6 +1530,9 @@ void IR_DecoderRaw::pulseFilterFlushTimeout(uint32_t nowUs) if (dt < IR_INPUT_MIN_PULSE_US) { pulseFilterDropGlitchPairs++; +#if IR_RX_BRIEF_LOG + rxBriefLog(RxBriefReason::Glitch, irClampU16(pulseFilterDropGlitchPairs), 0, nowUs); +#endif pulseFilterShiftLeft(2); continue; } @@ -1343,7 +1600,12 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &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) @@ -1362,6 +1624,9 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front) { preambleGoodPeriods = 0; preambleMeanPeriod = 0; +#if IR_RX_BRIEF_LOG + rxBriefLog(RxBriefReason::Preamble, 0, irClampU16(period), front.time); +#endif return true; } @@ -1382,6 +1647,9 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front) } else { +#if IR_RX_BRIEF_LOG + rxBriefLog(RxBriefReason::Preamble, preambleGoodPeriods, irClampU16(period), front.time); +#endif preambleGoodPeriods = 1; preambleMeanPeriod = (uint16_t)period; } diff --git a/IR_DecoderRaw.h b/IR_DecoderRaw.h index 08374a4..d82b129 100644 --- a/IR_DecoderRaw.h +++ b/IR_DecoderRaw.h @@ -72,15 +72,32 @@ public: ////////////////////////////////////////////////////////////////////////// private: + enum class RxBriefReason : uint8_t + { + MuteBegin = 1, + MuteEnd = 2, + RawOverflow = 3, + FilterOverflow = 4, + HoldOverflow = 5, + Glitch = 6, + Timing = 7, + Preamble = 8, + Sync = 9, + BufferOverflow = 10, + Timeout = 11, + Crc = 12, + Ok = 13 + }; + bool isRejectAvailable = false; uint8_t rejectPackSize = 0; uint8_t rejectBuffer[dataByteSizeMax]{}; ErrorsStruct errors; bool isAvailable = false; - uint16_t packSize; - uint16_t crcValue; - volatile uint16_t isPairSending = 0; // Флаг передачи парного передатчика + uint16_t packSize = 0; + uint16_t crcValue = 0; + volatile uint16_t isPairSending = 0; // Число активных TX, временно глушащих этот RX. volatile bool isRecive = false; // Флаг приёма volatile bool isPreamb = false; // флаг начальной последовости volatile bool isSubBufferOverflow = false; @@ -107,6 +124,8 @@ private: RingBuffer subBuffer; /** Очередь фронтов после потокового анти-глитча; tick() читает из неё при включённом фильтре. */ RingBuffer filteredSubBuffer; + IR_Encoder *pairMuteEncoders[IR_PAIR_MUTE_MAX_ENCODERS]{}; + uint8_t pairMuteEncoderCount = 0; static constexpr uint8_t kPulseFilterHoldCap = 6; FrontStorage pulseFilterHoldEdges[kPulseFilterHoldCap]{}; uint8_t pulseFilterHoldCount = 0; @@ -143,6 +162,17 @@ private: volatile bool edgeTrace_overflow = false; #endif +#if IR_RX_BRIEF_LOG + volatile bool rxBriefMuteBeginPending = false; + volatile uint32_t rxBriefMuteBeginUs = 0; + volatile bool rxBriefMuteEndPending = false; + volatile uint32_t rxBriefMuteEndUs = 0; + volatile uint16_t rxBriefMuteEndCount = 0; + volatile uint16_t rxBriefMuteBlockedEdges = 0; + volatile uint16_t rxBriefRawOverflowDrops = 0; + volatile uint32_t rxBriefRawOverflowLastUs = 0; +#endif + //////////////////////////////////////////////////////////////////////// uint8_t dataBuffer[dataByteSizeMax]{0}; // Буффер данных volatile uint32_t prevRise, prevPrevRise, prevFall, prevPrevFall; // Время предыдущих фронтов/спадов @@ -163,7 +193,7 @@ private: int16_t bufBitPos = 0; // Позиция для записи бита в буффер private: -bool isReciveRaw; +bool isReciveRaw = false; void listenStart(); void checkTimeout(); // /** Один сырой фронт из subBuffer -> потоковый holdback-антиглитч. */ @@ -173,6 +203,8 @@ bool isReciveRaw; void pulseFilterShiftLeft(uint8_t n); void pulseFilterReset(); static uint32_t absDiffU32(uint32_t a, uint32_t b); + bool registerPairMuteEncoder(IR_Encoder *enc); + void refreshPairMuteState(); uint32_t preambleJitterTolUs(uint32_t baselineUs) const; bool preambleRisePeriodCoarseOk(uint32_t periodUs) const; void preambleResetToIdle(); @@ -187,10 +219,10 @@ bool isReciveRaw; //////////////////////////////////////////////////////////////////////// bool isData = true; // Флаг относится ли бит к данным, или битам синхронизации - uint16_t i_dataBuffer; // Счётчик буфера данных + uint16_t i_dataBuffer = 0; // Счётчик буфера данных uint16_t nextControlBit = bitPerByte; // Метка для смены флага isData; uint16_t нужен для длинных кадров (>24 байт total) - uint8_t i_syncBit; // Счётчик битов синхронизации - uint8_t err_syncBit; // Счётчик ошибок синхронизации + uint8_t i_syncBit = 0; // Счётчик битов синхронизации + uint8_t err_syncBit = 0; // Счётчик ошибок синхронизации /// @brief Запиь бита в буффер, а так же проверка битов синхранизации и их фильтрация /// @param packTraceInvertFix если true — в IRDEBUG_SERIAL_PACK бит в трассе пишется как `0`/`1` (исправление по фронтам) @@ -205,6 +237,14 @@ bool isReciveRaw; /// @return Результат uint16_t ceil_div(uint16_t val, uint16_t divider); +#if IR_RX_BRIEF_LOG + static const __FlashStringHelper *rxBriefReasonTag(RxBriefReason reason); + void rxBriefLog(RxBriefReason reason, uint16_t a = 0, uint16_t b = 0, uint32_t tUs = 0); + void rxBriefNoteMuteBlockedIsr(uint32_t tUs); + void rxBriefNoteRawOverflowIsr(uint32_t tUs); + void rxBriefFlushDeferredIsrLogs(); +#endif + #ifdef IRDEBUG uint32_t wrCounter; inline void errPulse(uint8_t pin, uint8_t count); diff --git a/IR_Encoder.cpp b/IR_Encoder.cpp index 1d323d2..db08bad 100644 --- a/IR_Encoder.cpp +++ b/IR_Encoder.cpp @@ -15,19 +15,14 @@ IR_Encoder::IR_Encoder(uint8_t pin, uint16_t addr, IR_DecoderRaw *decPair, bool setPin(pin); id = addr; this->decPair = decPair; - signal = noSignal; - isSending = false; -#if disablePairDec if (decPair != nullptr) { - blindDecoders = new IR_DecoderRaw *[1]{decPair}; + singleBlindDecoder = decPair; + blindDecoders = &singleBlindDecoder; decodersCount = 1; - } -#endif - if (decPair != nullptr) - { decPair->encoder = this; } + registerWithBlindDecoders(); if (autoHandle) { @@ -233,7 +228,7 @@ void IR_Encoder::externalFinishSend() } isSending = false; - setDecoder_isSending(); + refreshBlindDecoderMuteState(); } size_t IR_Encoder::buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns) @@ -351,12 +346,16 @@ void IR_Encoder::disable() void IR_Encoder::setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count) { -#if disablePairDec - if (blindDecoders != nullptr) - delete[] blindDecoders; -#endif + if (count > IR_PAIR_MUTE_MAX_ENCODERS) + { + decodersCount = 0; + blindDecoders = nullptr; + return; + } decodersCount = count; blindDecoders = decoders; + registerWithBlindDecoders(); + refreshBlindDecoderMuteState(); } IR_Encoder::~IR_Encoder(){}; @@ -543,18 +542,27 @@ IR_SendResult IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *d return IR_SendResult(true, sendTime); } -void IR_Encoder::setDecoder_isSending() +void IR_Encoder::registerWithBlindDecoders() { - if (decodersCount) + if (!decodersCount || blindDecoders == nullptr) + return; + + for (uint8_t i = 0; i < decodersCount; i++) { - for (uint8_t i = 0; i < decodersCount; i++) - { - blindDecoders[i]->isPairSending ^= id; - // Serial.print("setDecoder_isSending() id = "); - // Serial.print(id); - // Serial.print(" isPairSending = "); - // Serial.println(blindDecoders[i]->isPairSending); - } + if (blindDecoders[i] != nullptr) + blindDecoders[i]->registerPairMuteEncoder(this); + } +} + +void IR_Encoder::refreshBlindDecoderMuteState() +{ + if (!decodersCount || blindDecoders == nullptr) + return; + + for (uint8_t i = 0; i < decodersCount; i++) + { + if (blindDecoders[i] != nullptr) + blindDecoders[i]->refreshPairMuteState(); } } @@ -572,13 +580,13 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) return; } - Serial.print("IR tx hex: "); - for (uint8_t i = 0; i < len; i++) - { - if (ptr[i] < 0x10) Serial.print("0"); - Serial.print(ptr[i], HEX); - } - Serial.println(); + // Serial.print("IR tx hex: "); + // for (uint8_t i = 0; i < len; i++) + // { + // if (ptr[i] < 0x10) Serial.print("0"); + // Serial.print(ptr[i], HEX); + // } + // Serial.println(); if (externalTxStartFn != nullptr) { @@ -587,24 +595,18 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) return; } - // Mark as sending and delegate actual signal output to external backend. - setDecoder_isSending(); sendLen = len; isSending = true; + refreshBlindDecoderMuteState(); const bool ok = externalTxStartFn(externalTxCtx, this, ptr, len); if (!ok) { isSending = false; - setDecoder_isSending(); + refreshBlindDecoderMuteState(); } return; } - IR_Encoder::carrierResume(); - // Serial.println("START"); - setDecoder_isSending(); - - // noInterrupts(); sendLen = len; toggleCounter = preambToggle; // Первая генерация для первого signal @@ -618,8 +620,9 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) signal = preamb; isSending = true; state = HIGH; - currentBitSequence = bitHigh; + refreshBlindDecoderMuteState(); + IR_Encoder::carrierResume(); // interrupts(); } @@ -651,7 +654,7 @@ void IR_Encoder::_isr() if (!active) { isSending = false; - setDecoder_isSending(); + refreshBlindDecoderMuteState(); carrierStopPending = true; } } diff --git a/IR_Encoder.h b/IR_Encoder.h index f3858a9..3c6e5e1 100644 --- a/IR_Encoder.h +++ b/IR_Encoder.h @@ -35,7 +35,8 @@ public: /// @brief Класс передатчика /// @param addr Адрес передатчика /// @param pin Вывод передатчика - /// @param decPair Приёмник, для которого отключается приём в момент передачи передатчиком + /// @param decPair Если задан, конструктор регистрирует этот один приёмник как blind-decoder + /// (аналог setBlindDecoders() для одного RX). IR_Encoder(uint8_t pin, uint16_t addr = 0, IR_DecoderRaw *decPair = nullptr, bool autoHandle = true); static void isr(); static void begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, uint8_t priority, void(*isrCallback)() = nullptr); @@ -58,6 +59,13 @@ public: void disable(); void setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count); + template + void setBlindDecoders(IR_DecoderRaw *(&decoders)[N]) + { + static_assert(N <= IR_PAIR_MUTE_MAX_ENCODERS, + "IR_Encoder::setBlindDecoders: array size exceeds IR_PAIR_MUTE_MAX_ENCODERS"); + setBlindDecoders(decoders, static_cast(N)); + } void rawSend(uint8_t *ptr, uint8_t len); IR_SendResult sendData(uint16_t addrTo, uint8_t dataByte, bool needAccept = false); @@ -99,7 +107,8 @@ private: static void *externalTxCtx; IR_SendResult _sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len); - void setDecoder_isSending(); + void refreshBlindDecoderMuteState(); + void registerWithBlindDecoders(); void sendByte(uint8_t byte, bool *prev, bool LOW_FIRST); void addSync(bool *prev, bool *next); uint32_t calculateSendTime(uint8_t packSize) const; @@ -137,25 +146,26 @@ private: void loadTxFsmFromMembers(TxFsmState &st) const; void storeTxFsmToMembers(const TxFsmState &st); - IR_DecoderRaw *decPair; - IR_DecoderRaw **blindDecoders; - uint8_t decodersCount; + IR_DecoderRaw *decPair = nullptr; + IR_DecoderRaw *singleBlindDecoder = nullptr; + IR_DecoderRaw **blindDecoders = nullptr; + uint8_t decodersCount = 0; - uint8_t sendLen; + uint8_t sendLen = 0; uint8_t sendBuffer[dataByteSizeMax]{0}; /// @brief Буффер данных для отправки - volatile bool isSending; - volatile bool state; /// @brief Текущий уровень генерации + volatile bool isSending = false; + volatile bool state = LOW; /// @brief Текущий уровень генерации - volatile uint8_t dataByteCounter; + volatile uint8_t dataByteCounter = 0; - volatile uint8_t toggleCounter; /// @brief Счётчик переключений - volatile uint8_t dataBitCounter; + volatile uint8_t toggleCounter = 0; /// @brief Счётчик переключений + volatile uint8_t dataBitCounter = 0; - volatile uint8_t preambFrontCounter; - volatile uint8_t dataSequenceCounter; - volatile uint8_t syncSequenceCounter; - volatile bool syncLastBit; + volatile uint8_t preambFrontCounter = 0; + volatile uint8_t dataSequenceCounter = 0; + volatile uint8_t syncSequenceCounter = 0; + volatile bool syncLastBit = false; struct BitSequence { @@ -165,5 +175,5 @@ private: static uint8_t bitHigh[2]; static uint8_t bitLow[2]; uint8_t *currentBitSequence = bitLow; - volatile SignalPart signal; + volatile SignalPart signal = noSignal; }; diff --git a/IR_config.h b/IR_config.h index ad0e77c..aeb39ae 100644 --- a/IR_config.h +++ b/IR_config.h @@ -13,6 +13,16 @@ constexpr size_t kDefaultDmaTxMaxStreams = 4U; // Не обрывать приём сразу при накопленной sync-ошибке — «дописывать» до таймаута (только вместе с IRDEBUG_SERIAL_PACK). // #define IRDEBUG_SERIAL_SOFT_REJECT +// Краткий лог причин, почему физический сигнал не дошёл до распознанного пакета. +// Формат и коды: ref/IR_RX_BRIEF_LOG.md +#ifndef IR_RX_BRIEF_LOG +#define IR_RX_BRIEF_LOG 1 +#endif +// 1: печатать только отклонённые/ошибочные события; успехи и шумовые PREAMB скрыть. +#ifndef IR_RX_BRIEF_LOG_REJECT_ONLY +#define IR_RX_BRIEF_LOG_REJECT_ONLY 1 +#endif + // Журнал фронтов ИК в ISR; сброс строк @IRF1v1: в IR_DecoderRaw::tick(). См. ref/IR_EDGE_TRACE_FORMAT.md // Расход RAM ≈ IR_EDGE_TRACE_CAPACITY * 6 байт на декодер. Выключить — закомментировать: // #define IR_EDGE_TRACE @@ -154,6 +164,11 @@ typedef uint16_t crc_t; #define subBufferSize 250 // Буфер для складирования фронтов, пока их не обработают (передатчик) #endif +/** Максимальное число передатчиков, способных временно заглушить один декодер. */ +#ifndef IR_PAIR_MUTE_MAX_ENCODERS +#define IR_PAIR_MUTE_MAX_ENCODERS 8U +#endif + /** Минимальная длительность удержания уровня (мкс): короче — импульс/пара фронтов выкидывается до tick() * (иголки на плато, дребезг). 0 — фильтр выключен, фронты идут в декодер как с ISR. */ #ifndef IR_INPUT_MIN_PULSE_US @@ -204,8 +219,6 @@ typedef uint16_t crc_t; #define preambPulse 3 -#define disablePairDec false // Отключать парный приёмник, возможны баги, используйте setBlindDecoders() - ///////////////////////////////////////////////////////////////////////////////////// #define bitPerByte 8U // Колличество бит в байте diff --git a/RingBuffer.h b/RingBuffer.h index 4e23bfe..7bd7818 100644 --- a/RingBuffer.h +++ b/RingBuffer.h @@ -13,13 +13,16 @@ public: return start == end; } - void push(T element) { + bool push(T element) { + bool pushed = false; noInterrupts(); if (!isFull()) { data[end] = element; end = (end + 1) % BufferSize; + pushed = true; } interrupts(); + return pushed; } T* pop() { diff --git a/ref/IR_RX_BRIEF_LOG.md b/ref/IR_RX_BRIEF_LOG.md new file mode 100644 index 0000000..d7d86a7 --- /dev/null +++ b/ref/IR_RX_BRIEF_LOG.md @@ -0,0 +1,60 @@ +# IR RX Brief Log + +Краткий лог включается через: + +```cpp +#define IR_RX_BRIEF_LOG 1 +#define IR_RX_BRIEF_LOG_REJECT_ONLY 1 // только отклонённые/ошибочные события +``` + +Лог печатается короткими строками вида: + +```text +IRRX t=1234567 rsn=CRC len=25 err=3 +IRRX t=1234000 rsn=MUTE_BEGIN +IRRX t=1234988 rsn=MUTE_END cnt=42 +``` + +Где: + +- `t` — uptime МК в `micros()` +- `rsn` — краткий код причины +- остальные поля зависят от причины + +## Коды `rsn` + +| Код | Смысл | Типичные поля | +|-----|-------|---------------| +| `MUTE_BEGIN` | Началось окно mute: RX временно игнорирует вход, пока активен связанный TX | - | +| `MUTE_END` | Окно mute завершилось; `cnt` показывает число заблокированных фронтов за всё окно | `cnt` | +| `QRAW` | Потеря фронтов из-за переполнения сырой очереди `subBuffer` | `cnt` | +| `QFLT` | Потеря фронтов из-за переполнения очереди после входного фильтра | `total` | +| `HOLD` | Переполнен holdback фильтра до выпуска фронтов | `total` | +| `GLITCH` | Фронт/пара фронтов отброшены как глитч | `total` | +| `TIME` | Плохой тайминг фронтов/битов, кадр не может нормально разбираться | `rp`, `hp` | +| `PREAMB` | Кандидат преамбулы не залочился или был перезапущен | `good`, `per` | +| `SYNC` | Ошибка sync-бита привела к reject кадра | `err` | +| `BUF` | Переполнен битовый буфер кадра | `bits` | +| `TIMEOUT` | Кадр оборвался по таймауту до завершения | `bits`, `exp` | +| `CRC` | Кадр дошёл до конца по длине, но CRC не сошёлся | `len`, `err` | +| `OK` | Кадр успешно распознан | `len`, `err` | + +## Поля + +- `cnt` — число событий/фронтов, накопленных за окно или пакетную группу +- `total` — накопленный счётчик отбраковок данного типа +- `rp` — `risePeriod` +- `hp` — `highTime` +- `good` — число подряд подходящих периодов преамбулы перед срывом +- `per` — период преамбулы/кандидата +- `err` — суммарные ошибки `lowSignal + highSignal + other` либо счётчик sync-ошибок +- `bits` — сколько data-бит успело накопиться +- `exp` — ожидаемая длина кадра из первого байта, если уже известна +- `len` — полная длина кадра в байтах + +## Когда смотреть подробный debug + +- Если нужен полный поток битов и sync: включать `IRDEBUG_SERIAL_PACK` +- Если нужно понять, какие именно фронты пришли в ISR: включать `IR_EDGE_TRACE` +- `IR_RX_BRIEF_LOG` нужен как короткий always-on-ish индикатор сути проблемы, без длинного дампа +- `IR_RX_BRIEF_LOG_REJECT_ONLY=1` скрывает `OK` и `PREAMB`, оставляя только отклонения/ошибки