From 8361828c44c9b283dc1da470cc672ecc904e1c45 Mon Sep 17 00:00:00 2001 From: DashyFox Date: Tue, 21 Apr 2026 10:00:46 +0300 Subject: [PATCH] tx error tracking and inline glitch filter --- IR_Encoder.cpp | 213 ++++++++++++++++++++++++++++++++++++++++-------- IR_Encoder.h | 43 +++++++--- IrDmaTxStm32.h | 32 +++----- IrTxGateTypes.h | 4 +- 4 files changed, 228 insertions(+), 64 deletions(-) diff --git a/IR_Encoder.cpp b/IR_Encoder.cpp index e116c49..872e47e 100644 --- a/IR_Encoder.cpp +++ b/IR_Encoder.cpp @@ -66,6 +66,41 @@ void *IR_Encoder::externalTxCtx = nullptr; bool IR_Encoder::txIsrLegacyMode_ = true; uint16_t IR_Encoder::s_carrierMultiply = 2; +const char* irSendStatusToString(IR_SendStatus status) +{ + switch (status) + { + case IR_SendStatus::Success: + return "Success"; + case IR_SendStatus::PayloadTooLarge: + return "PayloadTooLarge"; + case IR_SendStatus::EncoderBusy: + return "EncoderBusy"; + case IR_SendStatus::BufferTooLarge: + return "BufferTooLarge"; + case IR_SendStatus::ExternalBackendBusy: + return "ExternalBackendBusy"; + case IR_SendStatus::ExternalStartFailed: + return "ExternalStartFailed"; + case IR_SendStatus::ExternalNoStream: + return "ExternalNoStream"; + case IR_SendStatus::ExternalInvalidConfig: + return "ExternalInvalidConfig"; + case IR_SendStatus::BuildGateRunsFailed: + return "BuildGateRunsFailed"; + case IR_SendStatus::ScaleGateRunsFailed: + return "ScaleGateRunsFailed"; + case IR_SendStatus::DmaStartFailed: + return "DmaStartFailed"; + case IR_SendStatus::EncoderPinUnavailable: + return "EncoderPinUnavailable"; + case IR_SendStatus::BufferedStorageInvalid: + return "BufferedStorageInvalid"; + default: + return "Unknown"; + } +} + void IR_Encoder::setCarrierMultiply(uint16_t multiply) { if (multiply < 2) @@ -465,6 +500,107 @@ size_t IR_Encoder::buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRu return runCount; } +size_t IR_Encoder::buildPhysicalGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns, uint16_t multiply) +{ + if (packet == nullptr || outRuns == nullptr || maxRuns == 0) + { + return 0; + } + if (len == 0 || len > dataByteSizeMax) + { + return 0; + } + if (multiply < 2) + { + multiply = 2; + } + + // Copy into fixed-size buffer to match original encoder behavior (safe reads past sendLen). + uint8_t sendBufferLocal[dataByteSizeMax] = {0}; + memcpy(sendBufferLocal, packet, len); + + TxFsmState st{}; + st.sendLen = len; + st.toggleCounter = preambToggle; + st.dataBitCounter = bitPerByte - 1; + st.dataByteCounter = 0; + st.preambFrontCounter = preambPulse * 2 - 1; + st.dataSequenceCounter = bitPerByte * 2; + st.syncSequenceCounter = syncBits * 2; + st.syncLastBit = false; + st.signal = preamb; + st.state = HIGH; + st.currentBitSequence = bitHigh; + + auto appendPhysicalRun = [&](bool gate, uint32_t logicalLen, size_t& runCount) -> bool { + if (logicalLen == 0) + { + return true; + } + + uint32_t phys = (logicalLen * (uint32_t)multiply) / 2U; + if (logicalLen > 0 && phys == 0) + { + phys = 1; + } + + while (phys > 0) + { + if (runCount >= maxRuns) + { + return false; + } + + const uint32_t chunk = phys > 65535U ? 65535U : phys; + outRuns[runCount].gate = gate; + outRuns[runCount].lenTicks = static_cast(chunk); + runCount++; + phys -= chunk; + } + return true; + }; + + size_t runCount = 0; + bool currentGate = false; + uint32_t currentLogicalLen = 0; + bool havePendingRun = false; + bool isActive = true; + while (isActive) + { + bool gate = false; + isActive = txEmitTick(st, sendBufferLocal, gate); + + if (!havePendingRun) + { + currentGate = gate; + currentLogicalLen = 1U; + havePendingRun = true; + continue; + } + + if (currentGate == gate) + { + currentLogicalLen++; + continue; + } + + if (!appendPhysicalRun(currentGate, currentLogicalLen, runCount)) + { + return 0; + } + + currentGate = gate; + currentLogicalLen = 1U; + } + + if (havePendingRun && !appendPhysicalRun(currentGate, currentLogicalLen, runCount)) + { + return 0; + } + + return runCount; +} + void IR_Encoder::enable() { @@ -555,7 +691,7 @@ IR_SendResult IR_Encoder::sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8 if (len > bytePerPack) { Serial.println("IR Pack to big"); - return IR_SendResult(false, 0); + return IR_SendResult(false, 0, IR_SendStatus::PayloadTooLarge); } constexpr uint8_t dataStart = msgBytes + addrBytes + addrBytes; memset(sendBuffer, 0x00, dataByteSizeMax); @@ -605,11 +741,15 @@ IR_SendResult IR_Encoder::sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8 // } // отправка - rawSend(sendBuffer, packSize); - + const IR_SendStatus status = rawSend(sendBuffer, packSize); + if (status != IR_SendStatus::Success) + { + return IR_SendResult(false, 0, status); + } + // Возвращаем результат отправки uint32_t sendTime = calculateSendTime(packSize); - return IR_SendResult(true, sendTime); + return IR_SendResult(true, sendTime, status); } @@ -633,11 +773,15 @@ IR_SendResult IR_Encoder::sendAccept(uint16_t addrTo, uint8_t customByte) sendBuffer[4] = crc8(sendBuffer, 0, 4, poly1) & 0xFF; sendBuffer[5] = crc8(sendBuffer, 0, 5, poly2) & 0xFF; - rawSend(sendBuffer, packsize); - + const IR_SendStatus status = rawSend(sendBuffer, packsize); + if (status != IR_SendStatus::Success) + { + return IR_SendResult(false, 0, status); + } + // Возвращаем результат отправки uint32_t sendTime = calculateSendTime(packsize); - return IR_SendResult(true, sendTime); + return IR_SendResult(true, sendTime, status); } IR_SendResult IR_Encoder::sendRequest(uint16_t addrTo) @@ -659,11 +803,15 @@ IR_SendResult IR_Encoder::sendRequest(uint16_t addrTo) sendBuffer[5] = crc8(sendBuffer, 0, 5, poly1) & 0xFF; sendBuffer[6] = crc8(sendBuffer, 0, 6, poly2) & 0xFF; - rawSend(sendBuffer, packsize); - + const IR_SendStatus status = rawSend(sendBuffer, packsize); + if (status != IR_SendStatus::Success) + { + return IR_SendResult(false, 0, status); + } + // Возвращаем результат отправки uint32_t sendTime = calculateSendTime(packsize); - return IR_SendResult(true, sendTime); + return IR_SendResult(true, sendTime, status); } IR_SendResult IR_Encoder::sendBack(uint8_t data) @@ -685,7 +833,7 @@ IR_SendResult IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *d { if (len > bytePerPack) { - return IR_SendResult(false, 0); + return IR_SendResult(false, 0, IR_SendStatus::PayloadTooLarge); } memset(sendBuffer, 0x00, dataByteSizeMax); uint8_t dataStart = msgBytes + addrBytes + (isAdressed ? addrBytes : 0); @@ -716,11 +864,15 @@ IR_SendResult IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *d sendBuffer[packSize - crcBytes + 1] = crc8(sendBuffer, 0, packSize - crcBytes + 1, poly2) & 0xFF; // отправка - rawSend(sendBuffer, packSize); - + const IR_SendStatus status = rawSend(sendBuffer, packSize); + if (status != IR_SendStatus::Success) + { + return IR_SendResult(false, 0, status); + } + // Возвращаем результат отправки uint32_t sendTime = calculateSendTime(packSize); - return IR_SendResult(true, sendTime); + return IR_SendResult(true, sendTime, status); } void IR_Encoder::registerWithBlindDecoders() @@ -747,18 +899,18 @@ void IR_Encoder::refreshBlindDecoderMuteState() } } -void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) +IR_SendStatus IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) { if (isSending) { // TODO: Обработка повторной отправки - return; + return IR_SendStatus::EncoderBusy; } // Проверка на переполнение буфера if (len > dataByteSizeMax) { - return; + return IR_SendStatus::BufferTooLarge; } // Serial.print("IR tx hex: "); @@ -773,7 +925,7 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) { if (externalTxBusyFn != nullptr && externalTxBusyFn(externalTxCtx)) { - return; + return IR_SendStatus::ExternalBackendBusy; } sendLen = len; @@ -782,18 +934,18 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) isSending = true; refreshBlindDecoderMuteState(); - const bool ok = externalTxStartFn(externalTxCtx, this, ptr, len); - if (!ok) + const IR_SendStatus status = externalTxStartFn(externalTxCtx, this, ptr, len); + if (status != IR_SendStatus::Success) { isSending = false; refreshBlindDecoderMuteState(); } - return; + return status; } if (port == nullptr || mask == 0) { - return; + return IR_SendStatus::EncoderPinUnavailable; } if (ptr != sendBuffer) @@ -832,7 +984,7 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) isSending = true; refreshBlindDecoderMuteState(); IR_Encoder::carrierResume(); - return; + return IR_SendStatus::Success; } IrTxIsrBufferedStorageBase* buf = txActiveBufferedCtx_; @@ -840,23 +992,18 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) { txUseBufferedIsr_ = false; txActiveBufferedCtx_ = nullptr; - return; + return IR_SendStatus::BufferedStorageInvalid; } buf->resetRuntimeState(); - size_t nRuns = buildGateRuns(sendBuffer, len, buf->gateRuns, buf->maxGateRuns); + txMultiplySnap_ = carrierMultiply(); + size_t nRuns = buildPhysicalGateRuns(sendBuffer, len, buf->gateRuns, buf->maxGateRuns, txMultiplySnap_); if (nRuns == 0U) { txUseBufferedIsr_ = false; txActiveBufferedCtx_ = nullptr; - return; - } - if (!scaleGateRunsToPhysical(buf->gateRuns, &nRuns, buf->maxGateRuns, carrierMultiply())) - { - txUseBufferedIsr_ = false; - txActiveBufferedCtx_ = nullptr; - return; + return IR_SendStatus::BuildGateRunsFailed; } uint32_t total = 0; @@ -868,7 +1015,6 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) const uint32_t setW = (uint32_t)mask; const uint32_t resetW = ((uint32_t)mask) << 16U; - txMultiplySnap_ = carrierMultiply(); { const uint16_t cap = maxPowerNumerator(); txPowerSnap_ = (powerNumerator_ > cap) ? cap : powerNumerator_; @@ -883,6 +1029,7 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) port->BSRR = resetW; } IR_Encoder::carrierResume(); + return IR_SendStatus::Success; } void IR_Encoder::isr() diff --git a/IR_Encoder.h b/IR_Encoder.h index 9123a33..bb21d4b 100644 --- a/IR_Encoder.h +++ b/IR_Encoder.h @@ -4,13 +4,34 @@ // TODO: Отложенная передача после завершения приема +enum class IR_SendStatus : uint8_t { + Success = 0, + PayloadTooLarge, + EncoderBusy, + BufferTooLarge, + ExternalBackendBusy, + ExternalStartFailed, + ExternalNoStream, + ExternalInvalidConfig, + BuildGateRunsFailed, + ScaleGateRunsFailed, + DmaStartFailed, + EncoderPinUnavailable, + BufferedStorageInvalid, +}; + +const char* irSendStatusToString(IR_SendStatus status); + // Структура для возврата результата отправки struct IR_SendResult { - bool success; // Флаг успешности отправки - uint32_t sendTimeMs; // Время отправки пакета в миллисекундах - - IR_SendResult(bool success = false, uint32_t sendTimeMs = 0) - : success(success), sendTimeMs(sendTimeMs) {} + bool success; // Флаг успешности отправки + uint32_t sendTimeMs; // Время отправки пакета в миллисекундах + IR_SendStatus status; // Детализированный статус старта передачи + + IR_SendResult(bool success = false, + uint32_t sendTimeMs = 0, + IR_SendStatus status = IR_SendStatus::ExternalStartFailed) + : success(success), sendTimeMs(sendTimeMs), status(status) {} }; class IR_DecoderRaw; @@ -31,7 +52,7 @@ public: }; using ExternalTxBusyFn = bool (*)(void *ctx); - using ExternalTxStartFn = bool (*)(void *ctx, IR_Encoder *enc, const uint8_t *packet, uint8_t len); + using ExternalTxStartFn = IR_SendStatus (*)(void *ctx, IR_Encoder *enc, const uint8_t *packet, uint8_t len); private: // uint16_t id; /// @brief Адрес передатчика public: @@ -62,7 +83,7 @@ public: /** p∈[0,100] → ближайший допустимый числитель; 100% даёт N = maxPowerNumerator(). */ void setPowerPercent(uint8_t p); - /** После buildGateRuns: lenTicks в тактах 2×Fc → физические тики (carrierFrec×multiply). Может разбить сегменты. */ + /** Legacy helper: lenTicks в тактах 2×Fc → физические тики (carrierFrec×multiply). Может разбить сегменты. */ static bool scaleGateRunsToPhysical(IR_TxGateRun* runs, size_t* ioCount, size_t maxRuns, uint16_t multiply); /** Configure timer frequency for TX clock (carrierFrec × multiply) without attaching ISR. */ @@ -72,7 +93,7 @@ public: static void tick(); /** - * Режим внутреннего TX без DMA: false — BSRR + кольцо (buildGateRuns + scaleGateRunsToPhysical); + * Режим внутреннего TX без DMA: false — BSRR + кольцо (direct physical gate-runs builder); * true — FSM «налету» + скважность несущей как у буферного пути (подшаги multiply/2 на шаг FSM). * По умолчанию включён legacy=true для обратной совместимости. Вызов меняет default и обновляет * все зарегистрированные encoder-объекты. Buffered ISR реально используется только если у encoder @@ -94,8 +115,10 @@ public: /** Called by external TX backend on actual end of transmission. */ void externalFinishSend(); - /** Build RLE runs of carrier gate for a packet (no HW access). */ + /** Build RLE runs of carrier gate for a packet in logical 2×Fc ticks (no HW access). */ static size_t buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns); + /** Build RLE runs directly in physical carrierFrec×multiply ticks (DMA/buffered ISR path). */ + static size_t buildPhysicalGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns, uint16_t multiply); void enable(); void disable(); @@ -108,7 +131,7 @@ public: "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_SendStatus rawSend(uint8_t *ptr, uint8_t len); IR_SendResult sendData(uint16_t addrTo, uint8_t dataByte, bool needAccept = false); IR_SendResult sendData(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false); diff --git a/IrDmaTxStm32.h b/IrDmaTxStm32.h index 9c6699c..8b01eb3 100644 --- a/IrDmaTxStm32.h +++ b/IrDmaTxStm32.h @@ -106,14 +106,14 @@ public: return true; } - bool start(IR_Encoder* enc, const uint8_t* packet, uint8_t len) { - if (enc == nullptr) return false; + IR_SendStatus start(IR_Encoder* enc, const uint8_t* packet, uint8_t len) { + if (enc == nullptr) return IR_SendStatus::ExternalNoStream; for (uint8_t i = 0; i < streamCount_; i++) { if (streams_[i].enc == enc) { return startStream(streams_[i], packet, len); } } - return false; + return IR_SendStatus::ExternalNoStream; } void irqForStream(size_t streamIndex) { @@ -290,28 +290,22 @@ private: return true; } - bool startStream(TxStream& s, const uint8_t* packet, uint8_t len) { - if (s.enc == nullptr || s.port == nullptr || s.mask == 0) return false; - if (s.active) return false; - if (s.dmaBuf == nullptr || s.bufLen < 2 || s.halfLen == 0) return false; - if (s.runs == nullptr || s.maxRuns == 0) return false; + IR_SendStatus startStream(TxStream& s, const uint8_t* packet, uint8_t len) { + if (s.enc == nullptr || s.port == nullptr || s.mask == 0) return IR_SendStatus::ExternalInvalidConfig; + if (s.active) return IR_SendStatus::EncoderBusy; + if (s.dmaBuf == nullptr || s.bufLen < 2 || s.halfLen == 0) return IR_SendStatus::ExternalInvalidConfig; + if (s.runs == nullptr || s.maxRuns == 0) return IR_SendStatus::ExternalInvalidConfig; s.resetWave(); - s.runCount = IR_Encoder::buildGateRuns(packet, len, s.runs, s.maxRuns); - if (s.runCount == 0) return false; - - size_t rc = s.runCount; - if (!IR_Encoder::scaleGateRunsToPhysical(s.runs, &rc, s.maxRuns, IR_Encoder::carrierMultiply())) { - return false; - } - s.runCount = rc; + const uint16_t mult = IR_Encoder::carrierMultiply(); + s.runCount = IR_Encoder::buildPhysicalGateRuns(packet, len, s.runs, s.maxRuns, mult); + if (s.runCount == 0) return IR_SendStatus::BuildGateRunsFailed; uint32_t total = 0; for (size_t i = 0; i < s.runCount; i++) total += s.runs[i].lenTicks; s.totalTicks = total; - const uint16_t mult = IR_Encoder::carrierMultiply(); uint16_t pwr = mult / 2U; if (s.enc != nullptr) { const uint16_t want = s.enc->powerNumerator(); @@ -327,13 +321,13 @@ private: const uint32_t dst = u32ptr(&s.port->BSRR); if (HAL_DMA_Start_IT(&s.hdma, (uint32_t)(uintptr_t)s.dmaBuf, dst, s.bufLen) != HAL_OK) { - return false; + return IR_SendStatus::DmaStartFailed; } s.active = true; activeCount_++; startTimerIfNeeded(); - return true; + return IR_SendStatus::Success; } void stopStream(TxStream& s) { diff --git a/IrTxGateTypes.h b/IrTxGateTypes.h index 78c3682..d5bba86 100644 --- a/IrTxGateTypes.h +++ b/IrTxGateTypes.h @@ -4,8 +4,8 @@ /** * Один RLE-сегмент огибающей несущей. - * В buildGateRuns: lenTicks в тактах логической шкалы 2×carrierFrec (как раньше). - * После IR_Encoder::scaleGateRunsToPhysical — в физических тиках carrierFrec×multiply. + * В legacy buildGateRuns: lenTicks в тактах логической шкалы 2×carrierFrec. + * В современном DMA/buffered ISR пути buildPhysicalGateRuns строит lenTicks сразу в физических тиках carrierFrec×multiply. */ struct IrTxGateRun { uint16_t lenTicks;