#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; } 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; } //////////////////////////////////// isr /////////////////////////////////////////// volatile uint32_t time_; void IR_DecoderRaw::isr() { noInterrupts(); time_ = micros(); interrupts(); if (time_ < oldTime) { time_ += 1000; } oldTime = time_; FrontStorage edge; edge.dir = port->IDR & mask; edge.time = time_; #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) { return; } subBuffer.push(edge); } //////////////////////////////////////////////////////////////////////////////////// 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; isWrongPack = false; isPreamb = true; riseSyncTime = bitTime /* 1100 */; #ifdef IRDEBUG wrCounter = 0; #endif memset(dataBuffer, 0x00, dataByteSizeMax); pulseFilterReset(); preambleResetToIdle(); } void IR_DecoderRaw::listenStart() { 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 (micros() - lastEdgeTime > IR_timeout * 2U) { #if defined(IRDEBUG_SERIAL_PACK) packTraceOnTimeoutOrAbort(false); #endif isRecive = false; // приём завершён msgTypeReceive = 0; // firstRX(); // подготовка к новому пакету lastEdgeTime = micros(); // защита от повторного срабатывания } } // ==================================================================== void IR_DecoderRaw::tick() { 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; 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; } // Если данных нет - ничего не делаем 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_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 = 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 дроблёных единиц { 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:; #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 defined(IRDEBUG_SERIAL_PACK) if (packTraceOpen) packTraceEmitErrorFlash(F("ERROR: buffer overflow")); #endif } if (isBufferOverflow || isPreamb || isWrongPack) { isRecive = false; isReciveRaw = false; preambleResetToIdle(); msgTypeReceive = 0; 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 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 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++; 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) { 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