mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-04-28 03:08:08 +00:00
Compare commits
16 Commits
01a34ed3f7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9935ac192a | |||
| 8361828c44 | |||
| 38d93edd1b | |||
| 2f5b57680b | |||
| 31ac7a3625 | |||
| b4f644c258 | |||
| a75fd0c564 | |||
| 4061a4ed10 | |||
| fc93ea41db | |||
| 0620d98e35 | |||
| 4caed06218 | |||
| 37522f974f | |||
| 6375c4eed5 | |||
| c4000d6b75 | |||
| fc02c79135 | |||
| d068a576f7 |
@ -74,7 +74,6 @@ bool IR_DecoderRaw::availableRaw()
|
|||||||
|
|
||||||
void IR_DecoderRaw::pulseFilterResetStats()
|
void IR_DecoderRaw::pulseFilterResetStats()
|
||||||
{
|
{
|
||||||
pulseFilterDropFilteredOverflow = 0;
|
|
||||||
pulseFilterDropHoldOverflow = 0;
|
pulseFilterDropHoldOverflow = 0;
|
||||||
pulseFilterDropGlitchPairs = 0;
|
pulseFilterDropGlitchPairs = 0;
|
||||||
}
|
}
|
||||||
@ -344,7 +343,7 @@ bool IR_DecoderRaw::rxTimeoutPipelineBusy() const
|
|||||||
if (pulseFilterHoldCount != 0U)
|
if (pulseFilterHoldCount != 0U)
|
||||||
return true;
|
return true;
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
const bool busy = !subBuffer.isEmpty() || !filteredSubBuffer.isEmpty();
|
const bool busy = !subBuffer.isEmpty();
|
||||||
interrupts();
|
interrupts();
|
||||||
return busy;
|
return busy;
|
||||||
}
|
}
|
||||||
@ -403,10 +402,9 @@ void IR_DecoderRaw::tick()
|
|||||||
// Не в начале до pop: иначе после checkTimeout lastEdgeTime vs micros() расходятся
|
// Не в начале до pop: иначе после checkTimeout lastEdgeTime vs micros() расходятся
|
||||||
// с метками ISR из очереди → ложные TIMEOUT (bits=0) каждый пакет.
|
// с метками ISR из очереди → ложные TIMEOUT (bits=0) каждый пакет.
|
||||||
|
|
||||||
FrontStorage currentFront;
|
|
||||||
bool hasCurrentFront = false;
|
|
||||||
FrontStorage rawFront;
|
FrontStorage rawFront;
|
||||||
bool hasRawFront = false;
|
bool hasRawFront = false;
|
||||||
|
bool processedFront = false;
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
FrontStorage *rawPtr = subBuffer.pop();
|
FrontStorage *rawPtr = subBuffer.pop();
|
||||||
if (rawPtr != nullptr)
|
if (rawPtr != nullptr)
|
||||||
@ -419,26 +417,33 @@ void IR_DecoderRaw::tick()
|
|||||||
if (IR_INPUT_MIN_PULSE_US > 0U)
|
if (IR_INPUT_MIN_PULSE_US > 0U)
|
||||||
{
|
{
|
||||||
if (hasRawFront)
|
if (hasRawFront)
|
||||||
pulseFilterFeedOneRaw(rawFront);
|
|
||||||
else
|
|
||||||
pulseFilterFlushTimeout(micros());
|
|
||||||
|
|
||||||
noInterrupts();
|
|
||||||
FrontStorage *flt = filteredSubBuffer.pop();
|
|
||||||
if (flt != nullptr)
|
|
||||||
{
|
{
|
||||||
currentFront = *flt;
|
pulseFilterPushRaw(rawFront);
|
||||||
hasCurrentFront = true;
|
FrontStorage confirmedFront;
|
||||||
|
while (pulseFilterTryTakeConfirmed(confirmedFront))
|
||||||
|
{
|
||||||
|
processDecodedFront(confirmedFront);
|
||||||
|
processedFront = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const uint32_t nowUs = micros();
|
||||||
|
FrontStorage confirmedFront;
|
||||||
|
while (pulseFilterTryFlushOne(nowUs, confirmedFront))
|
||||||
|
{
|
||||||
|
processDecodedFront(confirmedFront);
|
||||||
|
processedFront = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
interrupts();
|
|
||||||
}
|
}
|
||||||
else if (hasRawFront)
|
else if (hasRawFront)
|
||||||
{
|
{
|
||||||
currentFront = rawFront;
|
processDecodedFront(rawFront);
|
||||||
hasCurrentFront = true;
|
processedFront = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasCurrentFront)
|
if (!processedFront)
|
||||||
{
|
{
|
||||||
isSubBufferOverflow = false;
|
isSubBufferOverflow = false;
|
||||||
listenStart();
|
listenStart();
|
||||||
@ -448,11 +453,22 @@ void IR_DecoderRaw::tick()
|
|||||||
#endif
|
#endif
|
||||||
return;
|
return;
|
||||||
} // Если данных нет - ничего не делаем
|
} // Если данных нет - ничего не делаем
|
||||||
|
listenStart();
|
||||||
|
checkTimeout();
|
||||||
|
#if IR_RX_BRIEF_LOG
|
||||||
|
rxBriefFlushDeferredIsrLogs();
|
||||||
|
#endif
|
||||||
|
#if defined(IR_EDGE_TRACE)
|
||||||
|
while (edgeTraceFlushChunk(Serial, 48) > 0) {}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void IR_DecoderRaw::processDecodedFront(const FrontStorage ¤tFront)
|
||||||
|
{
|
||||||
if (preambleProcessEdge(currentFront))
|
if (preambleProcessEdge(currentFront))
|
||||||
{
|
{
|
||||||
lastEdgeTime = currentFront.time;
|
lastEdgeTime = currentFront.time;
|
||||||
goto END;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastEdgeTime = currentFront.time; // запоминаем любой фронт
|
lastEdgeTime = currentFront.time; // запоминаем любой фронт
|
||||||
@ -478,7 +494,7 @@ void IR_DecoderRaw::tick()
|
|||||||
#if IR_GLITCH_REJECT_PHASE_NUDGE
|
#if IR_GLITCH_REJECT_PHASE_NUDGE
|
||||||
irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise);
|
irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise);
|
||||||
#endif
|
#endif
|
||||||
goto END;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if IR_MICRO_GAP_RISE_REJECT
|
#if IR_MICRO_GAP_RISE_REJECT
|
||||||
@ -495,7 +511,7 @@ void IR_DecoderRaw::tick()
|
|||||||
#if IR_GLITCH_REJECT_PHASE_NUDGE
|
#if IR_GLITCH_REJECT_PHASE_NUDGE
|
||||||
irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise);
|
irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise);
|
||||||
#endif
|
#endif
|
||||||
goto END;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (candRp <= riseTimeMax / 4U && !highCount && !lowCount)
|
if (candRp <= riseTimeMax / 4U && !highCount && !lowCount)
|
||||||
@ -504,7 +520,7 @@ void IR_DecoderRaw::tick()
|
|||||||
#if IR_RX_BRIEF_LOG
|
#if IR_RX_BRIEF_LOG
|
||||||
rxBriefLog(RxBriefReason::Timing, irClampU16(candRp), 0, currentFront.time);
|
rxBriefLog(RxBriefReason::Timing, irClampU16(candRp), 0, currentFront.time);
|
||||||
#endif
|
#endif
|
||||||
goto END;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (candRp > riseTimeMax / 4 || highCount || lowCount)
|
if (candRp > riseTimeMax / 4 || highCount || lowCount)
|
||||||
@ -557,7 +573,7 @@ void IR_DecoderRaw::tick()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef IRDEBUG
|
#ifdef IRDEBUG
|
||||||
// goto END; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// return; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
#endif
|
#endif
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
#ifdef IRDEBUG
|
#ifdef IRDEBUG
|
||||||
@ -572,7 +588,7 @@ void IR_DecoderRaw::tick()
|
|||||||
rxBriefLog(RxBriefReason::Timing, irClampU16((uint32_t)risePeriod),
|
rxBriefLog(RxBriefReason::Timing, irClampU16((uint32_t)risePeriod),
|
||||||
irClampU16((uint32_t)highTime), currentFront.time);
|
irClampU16((uint32_t)highTime), currentFront.time);
|
||||||
#endif
|
#endif
|
||||||
goto END;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// определить направление фронта
|
// определить направление фронта
|
||||||
@ -727,17 +743,6 @@ void IR_DecoderRaw::tick()
|
|||||||
else
|
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)
|
void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
|
||||||
@ -1455,34 +1460,18 @@ void IR_DecoderRaw::pulseFilterShiftLeft(uint8_t n)
|
|||||||
pulseFilterHoldCount = newCount;
|
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()
|
void IR_DecoderRaw::pulseFilterReset()
|
||||||
{
|
{
|
||||||
pulseFilterHoldCount = 0;
|
pulseFilterHoldCount = 0;
|
||||||
pulseFilterLastRawValid = false;
|
pulseFilterLastRawValid = false;
|
||||||
pulseFilterLastRawTime = 0;
|
pulseFilterLastRawTime = 0;
|
||||||
while (filteredSubBuffer.pop() != nullptr) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IR_DecoderRaw::pulseFilterFeedOneRaw(const FrontStorage &e)
|
void IR_DecoderRaw::pulseFilterPushRaw(const FrontStorage &e)
|
||||||
{
|
{
|
||||||
const uint32_t minUs = IR_INPUT_MIN_PULSE_US;
|
const uint32_t minUs = IR_INPUT_MIN_PULSE_US;
|
||||||
if (minUs == 0U)
|
if (minUs == 0U)
|
||||||
{
|
{
|
||||||
pulseFilterEmit(e);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1495,44 +1484,53 @@ void IR_DecoderRaw::pulseFilterFeedOneRaw(const FrontStorage &e)
|
|||||||
#if IR_RX_BRIEF_LOG
|
#if IR_RX_BRIEF_LOG
|
||||||
rxBriefLog(RxBriefReason::HoldOverflow, irClampU16(pulseFilterDropHoldOverflow), 0, e.time);
|
rxBriefLog(RxBriefReason::HoldOverflow, irClampU16(pulseFilterDropHoldOverflow), 0, e.time);
|
||||||
#endif
|
#endif
|
||||||
pulseFilterEmit(pulseFilterHoldEdges[0]);
|
|
||||||
pulseFilterShiftLeft(1);
|
pulseFilterShiftLeft(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pulseFilterHoldEdges[pulseFilterHoldCount++] = e;
|
pulseFilterHoldEdges[pulseFilterHoldCount++] = e;
|
||||||
const uint8_t holdback = irPulseFilterHoldbackEdges();
|
}
|
||||||
|
|
||||||
|
bool IR_DecoderRaw::pulseFilterTryTakeConfirmed(FrontStorage &out, uint32_t logTime)
|
||||||
|
{
|
||||||
|
const uint32_t minUs = IR_INPUT_MIN_PULSE_US;
|
||||||
|
if (minUs == 0U)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const uint8_t holdback = irPulseFilterHoldbackEdges();
|
||||||
|
if (logTime == 0U)
|
||||||
|
logTime = pulseFilterLastRawTime;
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (pulseFilterHoldCount < 2)
|
if (pulseFilterHoldCount < 2)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
const uint32_t dt = pulseFilterHoldEdges[1].time - pulseFilterHoldEdges[0].time;
|
const uint32_t dt = pulseFilterHoldEdges[1].time - pulseFilterHoldEdges[0].time;
|
||||||
if (dt < minUs)
|
if (dt < minUs)
|
||||||
{
|
{
|
||||||
pulseFilterDropGlitchPairs++;
|
pulseFilterDropGlitchPairs++;
|
||||||
#if IR_RX_BRIEF_LOG
|
#if IR_RX_BRIEF_LOG
|
||||||
rxBriefLog(RxBriefReason::Glitch, irClampU16(pulseFilterDropGlitchPairs), 0, e.time);
|
rxBriefLog(RxBriefReason::Glitch, irClampU16(pulseFilterDropGlitchPairs), 0, logTime);
|
||||||
#endif
|
#endif
|
||||||
pulseFilterShiftLeft(2);
|
pulseFilterShiftLeft(2);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pulseFilterHoldCount <= holdback)
|
if (pulseFilterHoldCount <= holdback)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
pulseFilterEmit(pulseFilterHoldEdges[0]);
|
out = pulseFilterHoldEdges[0];
|
||||||
pulseFilterShiftLeft(1);
|
pulseFilterShiftLeft(1);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IR_DecoderRaw::pulseFilterFlushTimeout(uint32_t nowUs)
|
bool IR_DecoderRaw::pulseFilterTryFlushOne(uint32_t nowUs, FrontStorage &out)
|
||||||
{
|
{
|
||||||
if (IR_INPUT_MIN_PULSE_US == 0U || !pulseFilterLastRawValid || pulseFilterHoldCount == 0)
|
if (IR_INPUT_MIN_PULSE_US == 0U || !pulseFilterLastRawValid || pulseFilterHoldCount == 0)
|
||||||
return;
|
return false;
|
||||||
const uint32_t waitUs = IR_INPUT_MIN_PULSE_US * (uint32_t)IR_INPUT_FILTER_TIMEOUT_MULT;
|
const uint32_t waitUs = IR_INPUT_MIN_PULSE_US * (uint32_t)IR_INPUT_FILTER_TIMEOUT_MULT;
|
||||||
if ((uint32_t)(nowUs - pulseFilterLastRawTime) < waitUs)
|
if ((uint32_t)(nowUs - pulseFilterLastRawTime) < waitUs)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
while (pulseFilterHoldCount > 0)
|
while (pulseFilterHoldCount > 0)
|
||||||
{
|
{
|
||||||
@ -1549,9 +1547,11 @@ void IR_DecoderRaw::pulseFilterFlushTimeout(uint32_t nowUs)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pulseFilterEmit(pulseFilterHoldEdges[0]);
|
out = pulseFilterHoldEdges[0];
|
||||||
pulseFilterShiftLeft(1);
|
pulseFilterShiftLeft(1);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t IR_DecoderRaw::preambleJitterTolUs(uint32_t baselineUs) const
|
uint32_t IR_DecoderRaw::preambleJitterTolUs(uint32_t baselineUs) const
|
||||||
|
|||||||
@ -52,7 +52,7 @@ public:
|
|||||||
inline bool isOverflow() { return isBufferOverflow; }; // Буффер переполнился
|
inline bool isOverflow() { return isBufferOverflow; }; // Буффер переполнился
|
||||||
bool isSubOverflow();
|
bool isSubOverflow();
|
||||||
volatile inline bool isReciving() { return isRecive; }; // Возвращает true, если происходит приём пакета
|
volatile inline bool isReciving() { return isRecive; }; // Возвращает true, если происходит приём пакета
|
||||||
uint32_t pulseFilterDroppedByFilteredOverflow() const { return pulseFilterDropFilteredOverflow; }
|
uint32_t pulseFilterDroppedByFilteredOverflow() const { return 0; }
|
||||||
uint32_t pulseFilterDroppedByHoldOverflow() const { return pulseFilterDropHoldOverflow; }
|
uint32_t pulseFilterDroppedByHoldOverflow() const { return pulseFilterDropHoldOverflow; }
|
||||||
uint32_t pulseFilterDroppedGlitchPairs() const { return pulseFilterDropGlitchPairs; }
|
uint32_t pulseFilterDroppedGlitchPairs() const { return pulseFilterDropGlitchPairs; }
|
||||||
void pulseFilterResetStats();
|
void pulseFilterResetStats();
|
||||||
@ -122,8 +122,6 @@ private:
|
|||||||
// volatile FrontStorage subBuffer[subBufferSize]; // вспомогательный буфер для хранения необработанных фронтов/спадов
|
// volatile FrontStorage subBuffer[subBufferSize]; // вспомогательный буфер для хранения необработанных фронтов/спадов
|
||||||
|
|
||||||
RingBuffer<FrontStorage, subBufferSize> subBuffer;
|
RingBuffer<FrontStorage, subBufferSize> subBuffer;
|
||||||
/** Очередь фронтов после потокового анти-глитча; tick() читает из неё при включённом фильтре. */
|
|
||||||
RingBuffer<FrontStorage, subBufferSize> filteredSubBuffer;
|
|
||||||
IR_Encoder *pairMuteEncoders[IR_PAIR_MUTE_MAX_ENCODERS]{};
|
IR_Encoder *pairMuteEncoders[IR_PAIR_MUTE_MAX_ENCODERS]{};
|
||||||
uint8_t pairMuteEncoderCount = 0;
|
uint8_t pairMuteEncoderCount = 0;
|
||||||
static constexpr uint8_t kPulseFilterHoldCap = 6;
|
static constexpr uint8_t kPulseFilterHoldCap = 6;
|
||||||
@ -131,7 +129,6 @@ private:
|
|||||||
uint8_t pulseFilterHoldCount = 0;
|
uint8_t pulseFilterHoldCount = 0;
|
||||||
bool pulseFilterLastRawValid = false;
|
bool pulseFilterLastRawValid = false;
|
||||||
uint32_t pulseFilterLastRawTime = 0;
|
uint32_t pulseFilterLastRawTime = 0;
|
||||||
uint32_t pulseFilterDropFilteredOverflow = 0;
|
|
||||||
uint32_t pulseFilterDropHoldOverflow = 0;
|
uint32_t pulseFilterDropHoldOverflow = 0;
|
||||||
uint32_t pulseFilterDropGlitchPairs = 0;
|
uint32_t pulseFilterDropGlitchPairs = 0;
|
||||||
static constexpr uint8_t kPreambleLockNeed = (uint8_t)IR_PREAMBLE_LOCK_RISE_PERIODS;
|
static constexpr uint8_t kPreambleLockNeed = (uint8_t)IR_PREAMBLE_LOCK_RISE_PERIODS;
|
||||||
@ -197,12 +194,12 @@ bool isReciveRaw = false;
|
|||||||
void checkTimeout(); //
|
void checkTimeout(); //
|
||||||
/** В очередях/hold фильтра ещё есть фронты — не оценивать таймаут по micros()-lastEdgeTime (ложный TIMEOUT). */
|
/** В очередях/hold фильтра ещё есть фронты — не оценивать таймаут по micros()-lastEdgeTime (ложный TIMEOUT). */
|
||||||
bool rxTimeoutPipelineBusy() const;
|
bool rxTimeoutPipelineBusy() const;
|
||||||
/** Один сырой фронт из subBuffer -> потоковый holdback-антиглитч. */
|
void pulseFilterPushRaw(const FrontStorage &e);
|
||||||
void pulseFilterFeedOneRaw(const FrontStorage &e);
|
bool pulseFilterTryTakeConfirmed(FrontStorage &out, uint32_t logTime = 0);
|
||||||
void pulseFilterFlushTimeout(uint32_t nowUs);
|
bool pulseFilterTryFlushOne(uint32_t nowUs, FrontStorage &out);
|
||||||
bool pulseFilterEmit(const FrontStorage &e);
|
|
||||||
void pulseFilterShiftLeft(uint8_t n);
|
void pulseFilterShiftLeft(uint8_t n);
|
||||||
void pulseFilterReset();
|
void pulseFilterReset();
|
||||||
|
void processDecodedFront(const FrontStorage ¤tFront);
|
||||||
static uint32_t absDiffU32(uint32_t a, uint32_t b);
|
static uint32_t absDiffU32(uint32_t a, uint32_t b);
|
||||||
bool registerPairMuteEncoder(IR_Encoder *enc);
|
bool registerPairMuteEncoder(IR_Encoder *enc);
|
||||||
void refreshPairMuteState();
|
void refreshPairMuteState();
|
||||||
|
|||||||
347
IR_Encoder.cpp
347
IR_Encoder.cpp
@ -1,7 +1,24 @@
|
|||||||
#include "IR_Encoder.h"
|
#include "IR_Encoder.h"
|
||||||
#include "IR_DecoderRaw.h"
|
#include "IR_DecoderRaw.h"
|
||||||
|
#include "IrTxIsrBufferedStorage.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
#define IRPROTO_PRAGMA_MESSAGE(text) __pragma(message(text))
|
||||||
|
#else
|
||||||
|
#define IRPROTO_PRAGMA_MESSAGE(text) _Pragma(#text)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_STM32)
|
||||||
|
#if defined(STM32G4xx)
|
||||||
|
IRPROTO_PRAGMA_MESSAGE(message("[IR-protocol] TX backends: ISR + built-in DMA"))
|
||||||
|
#elif defined(STM32F4xx)
|
||||||
|
IRPROTO_PRAGMA_MESSAGE(message("[IR-protocol] TX backends: ISR only"))
|
||||||
|
#else
|
||||||
|
IRPROTO_PRAGMA_MESSAGE(message("[IR-protocol] TX backends: ISR"))
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#define LoopOut 12
|
#define LoopOut 12
|
||||||
#define ISR_Out 10
|
#define ISR_Out 10
|
||||||
#define TestOut 13
|
#define TestOut 13
|
||||||
@ -14,6 +31,7 @@ IR_Encoder::IR_Encoder(uint8_t pin, uint16_t addr, IR_DecoderRaw *decPair, bool
|
|||||||
{
|
{
|
||||||
setPin(pin);
|
setPin(pin);
|
||||||
id = addr;
|
id = addr;
|
||||||
|
txIsrMode_ = txIsrLegacyMode_ ? TxIsrMode::Legacy : TxIsrMode::Buffered;
|
||||||
this->decPair = decPair;
|
this->decPair = decPair;
|
||||||
if (decPair != nullptr)
|
if (decPair != nullptr)
|
||||||
{
|
{
|
||||||
@ -45,9 +63,44 @@ HardwareTimer* IR_Encoder::IR_Timer = nullptr;
|
|||||||
IR_Encoder::ExternalTxStartFn IR_Encoder::externalTxStartFn = nullptr;
|
IR_Encoder::ExternalTxStartFn IR_Encoder::externalTxStartFn = nullptr;
|
||||||
IR_Encoder::ExternalTxBusyFn IR_Encoder::externalTxBusyFn = nullptr;
|
IR_Encoder::ExternalTxBusyFn IR_Encoder::externalTxBusyFn = nullptr;
|
||||||
void *IR_Encoder::externalTxCtx = nullptr;
|
void *IR_Encoder::externalTxCtx = nullptr;
|
||||||
bool IR_Encoder::txIsrLegacyMode_ = false;
|
bool IR_Encoder::txIsrLegacyMode_ = true;
|
||||||
uint16_t IR_Encoder::s_carrierMultiply = 2;
|
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)
|
void IR_Encoder::setCarrierMultiply(uint16_t multiply)
|
||||||
{
|
{
|
||||||
if (multiply < 2)
|
if (multiply < 2)
|
||||||
@ -146,6 +199,11 @@ bool IR_Encoder::scaleGateRunsToPhysical(IR_TxGateRun* runs, size_t* ioCount, si
|
|||||||
void IR_Encoder::setTxIsrLegacyMode(bool legacy)
|
void IR_Encoder::setTxIsrLegacyMode(bool legacy)
|
||||||
{
|
{
|
||||||
txIsrLegacyMode_ = legacy;
|
txIsrLegacyMode_ = legacy;
|
||||||
|
const TxIsrMode mode = legacy ? TxIsrMode::Legacy : TxIsrMode::Buffered;
|
||||||
|
for (IR_Encoder *p = head; p != nullptr; p = p->next)
|
||||||
|
{
|
||||||
|
p->txIsrMode_ = mode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IR_Encoder::txIsrLegacyMode()
|
bool IR_Encoder::txIsrLegacyMode()
|
||||||
@ -153,6 +211,54 @@ bool IR_Encoder::txIsrLegacyMode()
|
|||||||
return txIsrLegacyMode_;
|
return txIsrLegacyMode_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IR_Encoder::attachBufferedIsrStorage(IrTxIsrBufferedStorageBase& storage)
|
||||||
|
{
|
||||||
|
txBufferedCtx_ = &storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IR_Encoder::detachBufferedIsrStorage()
|
||||||
|
{
|
||||||
|
txBufferedCtx_ = nullptr;
|
||||||
|
if (!isSending)
|
||||||
|
{
|
||||||
|
txActiveBufferedCtx_ = nullptr;
|
||||||
|
txUseBufferedIsr_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IR_Encoder::hasBufferedIsrStorage() const
|
||||||
|
{
|
||||||
|
return txBufferedCtx_ != nullptr && txBufferedCtx_->isValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IR_Encoder::enableBufferedIsr(IrTxIsrBufferedStorageBase& storage)
|
||||||
|
{
|
||||||
|
attachBufferedIsrStorage(storage);
|
||||||
|
txIsrMode_ = TxIsrMode::Buffered;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IR_Encoder::disableBufferedIsr()
|
||||||
|
{
|
||||||
|
txIsrMode_ = TxIsrMode::Legacy;
|
||||||
|
if (!isSending)
|
||||||
|
{
|
||||||
|
txActiveBufferedCtx_ = nullptr;
|
||||||
|
txUseBufferedIsr_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IR_Encoder::TxIsrMode IR_Encoder::txIsrMode() const
|
||||||
|
{
|
||||||
|
return txIsrMode_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IR_Encoder::shouldUseBufferedIsr() const
|
||||||
|
{
|
||||||
|
return txIsrMode_ == TxIsrMode::Buffered &&
|
||||||
|
txBufferedCtx_ != nullptr &&
|
||||||
|
txBufferedCtx_->isValid();
|
||||||
|
}
|
||||||
|
|
||||||
bool IR_Encoder::txAdvanceBoundary(TxFsmState &st, const uint8_t *sendBufferLocal)
|
bool IR_Encoder::txAdvanceBoundary(TxFsmState &st, const uint8_t *sendBufferLocal)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
@ -336,6 +442,8 @@ void IR_Encoder::externalFinishSend()
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSending = false;
|
isSending = false;
|
||||||
|
txUseBufferedIsr_ = false;
|
||||||
|
txActiveBufferedCtx_ = nullptr;
|
||||||
refreshBlindDecoderMuteState();
|
refreshBlindDecoderMuteState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,6 +500,107 @@ size_t IR_Encoder::buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRu
|
|||||||
return runCount;
|
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<uint16_t>(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()
|
void IR_Encoder::enable()
|
||||||
{
|
{
|
||||||
@ -482,7 +691,7 @@ IR_SendResult IR_Encoder::sendDataFULL(uint16_t addrFrom, uint16_t addrTo, uint8
|
|||||||
if (len > bytePerPack)
|
if (len > bytePerPack)
|
||||||
{
|
{
|
||||||
Serial.println("IR Pack to big");
|
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;
|
constexpr uint8_t dataStart = msgBytes + addrBytes + addrBytes;
|
||||||
memset(sendBuffer, 0x00, dataByteSizeMax);
|
memset(sendBuffer, 0x00, dataByteSizeMax);
|
||||||
@ -532,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);
|
uint32_t sendTime = calculateSendTime(packSize);
|
||||||
return IR_SendResult(true, sendTime);
|
return IR_SendResult(true, sendTime, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -560,11 +773,15 @@ IR_SendResult IR_Encoder::sendAccept(uint16_t addrTo, uint8_t customByte)
|
|||||||
sendBuffer[4] = crc8(sendBuffer, 0, 4, poly1) & 0xFF;
|
sendBuffer[4] = crc8(sendBuffer, 0, 4, poly1) & 0xFF;
|
||||||
sendBuffer[5] = crc8(sendBuffer, 0, 5, poly2) & 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);
|
uint32_t sendTime = calculateSendTime(packsize);
|
||||||
return IR_SendResult(true, sendTime);
|
return IR_SendResult(true, sendTime, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
IR_SendResult IR_Encoder::sendRequest(uint16_t addrTo)
|
IR_SendResult IR_Encoder::sendRequest(uint16_t addrTo)
|
||||||
@ -586,11 +803,15 @@ IR_SendResult IR_Encoder::sendRequest(uint16_t addrTo)
|
|||||||
sendBuffer[5] = crc8(sendBuffer, 0, 5, poly1) & 0xFF;
|
sendBuffer[5] = crc8(sendBuffer, 0, 5, poly1) & 0xFF;
|
||||||
sendBuffer[6] = crc8(sendBuffer, 0, 6, poly2) & 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);
|
uint32_t sendTime = calculateSendTime(packsize);
|
||||||
return IR_SendResult(true, sendTime);
|
return IR_SendResult(true, sendTime, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
IR_SendResult IR_Encoder::sendBack(uint8_t data)
|
IR_SendResult IR_Encoder::sendBack(uint8_t data)
|
||||||
@ -612,7 +833,7 @@ IR_SendResult IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *d
|
|||||||
{
|
{
|
||||||
if (len > bytePerPack)
|
if (len > bytePerPack)
|
||||||
{
|
{
|
||||||
return IR_SendResult(false, 0);
|
return IR_SendResult(false, 0, IR_SendStatus::PayloadTooLarge);
|
||||||
}
|
}
|
||||||
memset(sendBuffer, 0x00, dataByteSizeMax);
|
memset(sendBuffer, 0x00, dataByteSizeMax);
|
||||||
uint8_t dataStart = msgBytes + addrBytes + (isAdressed ? addrBytes : 0);
|
uint8_t dataStart = msgBytes + addrBytes + (isAdressed ? addrBytes : 0);
|
||||||
@ -643,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;
|
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);
|
uint32_t sendTime = calculateSendTime(packSize);
|
||||||
return IR_SendResult(true, sendTime);
|
return IR_SendResult(true, sendTime, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IR_Encoder::registerWithBlindDecoders()
|
void IR_Encoder::registerWithBlindDecoders()
|
||||||
@ -674,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)
|
if (isSending)
|
||||||
{
|
{
|
||||||
// TODO: Обработка повторной отправки
|
// TODO: Обработка повторной отправки
|
||||||
return;
|
return IR_SendStatus::EncoderBusy;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка на переполнение буфера
|
// Проверка на переполнение буфера
|
||||||
if (len > dataByteSizeMax)
|
if (len > dataByteSizeMax)
|
||||||
{
|
{
|
||||||
return;
|
return IR_SendStatus::BufferTooLarge;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serial.print("IR tx hex: ");
|
// Serial.print("IR tx hex: ");
|
||||||
@ -700,25 +925,27 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
|
|||||||
{
|
{
|
||||||
if (externalTxBusyFn != nullptr && externalTxBusyFn(externalTxCtx))
|
if (externalTxBusyFn != nullptr && externalTxBusyFn(externalTxCtx))
|
||||||
{
|
{
|
||||||
return;
|
return IR_SendStatus::ExternalBackendBusy;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendLen = len;
|
sendLen = len;
|
||||||
|
txUseBufferedIsr_ = false;
|
||||||
|
txActiveBufferedCtx_ = nullptr;
|
||||||
isSending = true;
|
isSending = true;
|
||||||
refreshBlindDecoderMuteState();
|
refreshBlindDecoderMuteState();
|
||||||
|
|
||||||
const bool ok = externalTxStartFn(externalTxCtx, this, ptr, len);
|
const IR_SendStatus status = externalTxStartFn(externalTxCtx, this, ptr, len);
|
||||||
if (!ok)
|
if (status != IR_SendStatus::Success)
|
||||||
{
|
{
|
||||||
isSending = false;
|
isSending = false;
|
||||||
refreshBlindDecoderMuteState();
|
refreshBlindDecoderMuteState();
|
||||||
}
|
}
|
||||||
return;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (port == nullptr || mask == 0)
|
if (port == nullptr || mask == 0)
|
||||||
{
|
{
|
||||||
return;
|
return IR_SendStatus::EncoderPinUnavailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ptr != sendBuffer)
|
if (ptr != sendBuffer)
|
||||||
@ -727,7 +954,11 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
|
|||||||
}
|
}
|
||||||
sendLen = len;
|
sendLen = len;
|
||||||
|
|
||||||
if (txIsrLegacyMode_)
|
const bool useBufferedIsr = shouldUseBufferedIsr();
|
||||||
|
txUseBufferedIsr_ = useBufferedIsr;
|
||||||
|
txActiveBufferedCtx_ = useBufferedIsr ? txBufferedCtx_ : nullptr;
|
||||||
|
|
||||||
|
if (!useBufferedIsr)
|
||||||
{
|
{
|
||||||
toggleCounter = preambToggle;
|
toggleCounter = preambToggle;
|
||||||
dataBitCounter = bitPerByte - 1;
|
dataBitCounter = bitPerByte - 1;
|
||||||
@ -753,40 +984,43 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
|
|||||||
isSending = true;
|
isSending = true;
|
||||||
refreshBlindDecoderMuteState();
|
refreshBlindDecoderMuteState();
|
||||||
IR_Encoder::carrierResume();
|
IR_Encoder::carrierResume();
|
||||||
return;
|
return IR_SendStatus::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t nRuns = buildGateRuns(sendBuffer, len, txGateRuns_, irproto::kIsrTxMaxGateRuns);
|
IrTxIsrBufferedStorageBase* buf = txActiveBufferedCtx_;
|
||||||
|
if (buf == nullptr || !buf->isValid())
|
||||||
|
{
|
||||||
|
txUseBufferedIsr_ = false;
|
||||||
|
txActiveBufferedCtx_ = nullptr;
|
||||||
|
return IR_SendStatus::BufferedStorageInvalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->resetRuntimeState();
|
||||||
|
|
||||||
|
txMultiplySnap_ = carrierMultiply();
|
||||||
|
size_t nRuns = buildPhysicalGateRuns(sendBuffer, len, buf->gateRuns, buf->maxGateRuns, txMultiplySnap_);
|
||||||
if (nRuns == 0U)
|
if (nRuns == 0U)
|
||||||
{
|
{
|
||||||
return;
|
txUseBufferedIsr_ = false;
|
||||||
}
|
txActiveBufferedCtx_ = nullptr;
|
||||||
if (!scaleGateRunsToPhysical(txGateRuns_, &nRuns, irproto::kIsrTxMaxGateRuns, carrierMultiply()))
|
return IR_SendStatus::BuildGateRunsFailed;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t total = 0;
|
uint32_t total = 0;
|
||||||
for (size_t i = 0; i < nRuns; i++)
|
for (size_t i = 0; i < nRuns; i++)
|
||||||
{
|
{
|
||||||
total += txGateRuns_[i].lenTicks;
|
total += buf->gateRuns[i].lenTicks;
|
||||||
}
|
}
|
||||||
txBsrrTotalTicks_ = total;
|
buf->totalTicks = total;
|
||||||
|
|
||||||
const uint32_t setW = (uint32_t)mask;
|
const uint32_t setW = (uint32_t)mask;
|
||||||
const uint32_t resetW = ((uint32_t)mask) << 16U;
|
const uint32_t resetW = ((uint32_t)mask) << 16U;
|
||||||
txMultiplySnap_ = carrierMultiply();
|
|
||||||
{
|
{
|
||||||
const uint16_t cap = maxPowerNumerator();
|
const uint16_t cap = maxPowerNumerator();
|
||||||
txPowerSnap_ = (powerNumerator_ > cap) ? cap : powerNumerator_;
|
txPowerSnap_ = (powerNumerator_ > cap) ? cap : powerNumerator_;
|
||||||
}
|
}
|
||||||
txBsrrWave_.configure(setW, resetW, txGateRuns_, nRuns, txMultiplySnap_, txPowerSnap_);
|
buf->wave.configure(setW, resetW, buf->gateRuns, nRuns, txMultiplySnap_, txPowerSnap_);
|
||||||
|
buf->wave.fill(buf->bsrrWords, buf->wordCount);
|
||||||
txBsrrHalfLen_ = (uint16_t)(irproto::kIsrTxBsrrWordCount / 2U);
|
|
||||||
txBsrrWave_.fill(txBsrrWords_, irproto::kIsrTxBsrrWordCount);
|
|
||||||
|
|
||||||
txBsrrReadIdx_ = 0;
|
|
||||||
txBsrrTicksSent_ = 0;
|
|
||||||
|
|
||||||
isSending = true;
|
isSending = true;
|
||||||
refreshBlindDecoderMuteState();
|
refreshBlindDecoderMuteState();
|
||||||
@ -795,6 +1029,7 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
|
|||||||
port->BSRR = resetW;
|
port->BSRR = resetW;
|
||||||
}
|
}
|
||||||
IR_Encoder::carrierResume();
|
IR_Encoder::carrierResume();
|
||||||
|
return IR_SendStatus::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IR_Encoder::isr()
|
void IR_Encoder::isr()
|
||||||
@ -815,7 +1050,7 @@ void IR_Encoder::_isr()
|
|||||||
if (port == nullptr)
|
if (port == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (txIsrLegacyMode_)
|
if (!txUseBufferedIsr_)
|
||||||
{
|
{
|
||||||
const uint32_t setW = (uint32_t)mask;
|
const uint32_t setW = (uint32_t)mask;
|
||||||
const uint32_t resetW = ((uint32_t)mask) << 16U;
|
const uint32_t resetW = ((uint32_t)mask) << 16U;
|
||||||
@ -850,33 +1085,49 @@ void IR_Encoder::_isr()
|
|||||||
{
|
{
|
||||||
port->BSRR = resetW;
|
port->BSRR = resetW;
|
||||||
isSending = false;
|
isSending = false;
|
||||||
|
txUseBufferedIsr_ = false;
|
||||||
|
txActiveBufferedCtx_ = nullptr;
|
||||||
refreshBlindDecoderMuteState();
|
refreshBlindDecoderMuteState();
|
||||||
carrierStopPending = true;
|
carrierStopPending = true;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
port->BSRR = txBsrrWords_[txBsrrReadIdx_];
|
IrTxIsrBufferedStorageBase* buf = txActiveBufferedCtx_;
|
||||||
txBsrrReadIdx_++;
|
if (buf == nullptr || !buf->isValid())
|
||||||
txBsrrTicksSent_++;
|
|
||||||
|
|
||||||
if (txBsrrTicksSent_ >= txBsrrTotalTicks_)
|
|
||||||
{
|
{
|
||||||
port->BSRR = ((uint32_t)mask) << 16U;
|
port->BSRR = ((uint32_t)mask) << 16U;
|
||||||
isSending = false;
|
isSending = false;
|
||||||
|
txUseBufferedIsr_ = false;
|
||||||
|
txActiveBufferedCtx_ = nullptr;
|
||||||
refreshBlindDecoderMuteState();
|
refreshBlindDecoderMuteState();
|
||||||
carrierStopPending = true;
|
carrierStopPending = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (txBsrrReadIdx_ == txBsrrHalfLen_)
|
port->BSRR = buf->bsrrWords[buf->readIdx];
|
||||||
|
buf->readIdx++;
|
||||||
|
buf->ticksSent++;
|
||||||
|
|
||||||
|
if (buf->ticksSent >= buf->totalTicks)
|
||||||
{
|
{
|
||||||
txBsrrWave_.fill(&txBsrrWords_[0], txBsrrHalfLen_);
|
port->BSRR = ((uint32_t)mask) << 16U;
|
||||||
|
isSending = false;
|
||||||
|
txUseBufferedIsr_ = false;
|
||||||
|
txActiveBufferedCtx_ = nullptr;
|
||||||
|
refreshBlindDecoderMuteState();
|
||||||
|
carrierStopPending = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (txBsrrReadIdx_ >= irproto::kIsrTxBsrrWordCount)
|
|
||||||
|
if (buf->readIdx == buf->halfLen)
|
||||||
{
|
{
|
||||||
txBsrrReadIdx_ = 0;
|
buf->wave.fill(&buf->bsrrWords[0], buf->halfLen);
|
||||||
txBsrrWave_.fill(&txBsrrWords_[txBsrrHalfLen_], txBsrrHalfLen_);
|
}
|
||||||
|
else if (buf->readIdx >= buf->wordCount)
|
||||||
|
{
|
||||||
|
buf->readIdx = 0;
|
||||||
|
buf->wave.fill(&buf->bsrrWords[buf->halfLen], buf->halfLen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
68
IR_Encoder.h
68
IR_Encoder.h
@ -1,19 +1,41 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "IR_config.h"
|
#include "IR_config.h"
|
||||||
#include "IrTxBsrrWave.h"
|
#include "IrTxGateTypes.h"
|
||||||
|
|
||||||
// TODO: Отложенная передача после завершения приема
|
// 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 {
|
struct IR_SendResult {
|
||||||
bool success; // Флаг успешности отправки
|
bool success; // Флаг успешности отправки
|
||||||
uint32_t sendTimeMs; // Время отправки пакета в миллисекундах
|
uint32_t sendTimeMs; // Время отправки пакета в миллисекундах
|
||||||
|
IR_SendStatus status; // Детализированный статус старта передачи
|
||||||
|
|
||||||
IR_SendResult(bool success = false, uint32_t sendTimeMs = 0)
|
IR_SendResult(bool success = false,
|
||||||
: success(success), sendTimeMs(sendTimeMs) {}
|
uint32_t sendTimeMs = 0,
|
||||||
|
IR_SendStatus status = IR_SendStatus::ExternalStartFailed)
|
||||||
|
: success(success), sendTimeMs(sendTimeMs), status(status) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class IR_DecoderRaw;
|
class IR_DecoderRaw;
|
||||||
|
class IrTxIsrBufferedStorageBase;
|
||||||
class IR_Encoder : public IR_FOX
|
class IR_Encoder : public IR_FOX
|
||||||
{
|
{
|
||||||
friend IR_DecoderRaw;
|
friend IR_DecoderRaw;
|
||||||
@ -24,9 +46,13 @@ public:
|
|||||||
static HardwareTimer* IR_Timer;
|
static HardwareTimer* IR_Timer;
|
||||||
|
|
||||||
using IR_TxGateRun = IrTxGateRun;
|
using IR_TxGateRun = IrTxGateRun;
|
||||||
|
enum class TxIsrMode : uint8_t {
|
||||||
|
Legacy = 0,
|
||||||
|
Buffered = 1
|
||||||
|
};
|
||||||
|
|
||||||
using ExternalTxBusyFn = bool (*)(void *ctx);
|
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:
|
private:
|
||||||
// uint16_t id; /// @brief Адрес передатчика
|
// uint16_t id; /// @brief Адрес передатчика
|
||||||
public:
|
public:
|
||||||
@ -57,7 +83,7 @@ public:
|
|||||||
/** p∈[0,100] → ближайший допустимый числитель; 100% даёт N = maxPowerNumerator(). */
|
/** p∈[0,100] → ближайший допустимый числитель; 100% даёт N = maxPowerNumerator(). */
|
||||||
void setPowerPercent(uint8_t p);
|
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);
|
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. */
|
/** Configure timer frequency for TX clock (carrierFrec × multiply) without attaching ISR. */
|
||||||
@ -67,12 +93,21 @@ public:
|
|||||||
static void tick();
|
static void tick();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Режим внутреннего TX без DMA: false — BSRR + кольцо (buildGateRuns + scaleGateRunsToPhysical);
|
* Режим внутреннего TX без DMA: false — BSRR + кольцо (direct physical gate-runs builder);
|
||||||
* true — FSM «налету» + скважность несущей как у буферного пути (подшаги multiply/2 на шаг FSM).
|
* true — FSM «налету» + скважность несущей как у буферного пути (подшаги multiply/2 на шаг FSM).
|
||||||
* Выставить до begin/rawSend (глобально на все IR_Encoder). Игнорируется при externalTxStartFn.
|
* По умолчанию включён legacy=true для обратной совместимости. Вызов меняет default и обновляет
|
||||||
|
* все зарегистрированные encoder-объекты. Buffered ISR реально используется только если у encoder
|
||||||
|
* привязан storage через attachBufferedIsrStorage()/enableBufferedIsr().
|
||||||
|
* Выставить до begin/rawSend. Игнорируется при externalTxStartFn.
|
||||||
*/
|
*/
|
||||||
static void setTxIsrLegacyMode(bool legacy);
|
static void setTxIsrLegacyMode(bool legacy);
|
||||||
static bool txIsrLegacyMode();
|
static bool txIsrLegacyMode();
|
||||||
|
void attachBufferedIsrStorage(IrTxIsrBufferedStorageBase& storage);
|
||||||
|
void detachBufferedIsrStorage();
|
||||||
|
bool hasBufferedIsrStorage() const;
|
||||||
|
void enableBufferedIsr(IrTxIsrBufferedStorageBase& storage);
|
||||||
|
void disableBufferedIsr();
|
||||||
|
TxIsrMode txIsrMode() const;
|
||||||
|
|
||||||
/** Optional: register external TX backend (e.g. DMA driver). */
|
/** Optional: register external TX backend (e.g. DMA driver). */
|
||||||
static void setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx);
|
static void setExternalTxBackend(ExternalTxStartFn startFn, ExternalTxBusyFn busyFn, void *ctx);
|
||||||
@ -80,8 +115,10 @@ public:
|
|||||||
/** Called by external TX backend on actual end of transmission. */
|
/** Called by external TX backend on actual end of transmission. */
|
||||||
void externalFinishSend();
|
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);
|
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 enable();
|
||||||
void disable();
|
void disable();
|
||||||
@ -94,7 +131,7 @@ public:
|
|||||||
"IR_Encoder::setBlindDecoders: array size exceeds IR_PAIR_MUTE_MAX_ENCODERS");
|
"IR_Encoder::setBlindDecoders: array size exceeds IR_PAIR_MUTE_MAX_ENCODERS");
|
||||||
setBlindDecoders(decoders, static_cast<uint8_t>(N));
|
setBlindDecoders(decoders, static_cast<uint8_t>(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 dataByte, bool needAccept = false);
|
||||||
IR_SendResult sendData(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false);
|
IR_SendResult sendData(uint16_t addrTo, uint8_t *data = nullptr, uint8_t len = 0, bool needAccept = false);
|
||||||
@ -175,14 +212,7 @@ private:
|
|||||||
static bool txEmitTick(TxFsmState &st, const uint8_t *sendBufferLocal, bool &gateOut);
|
static bool txEmitTick(TxFsmState &st, const uint8_t *sendBufferLocal, bool &gateOut);
|
||||||
void loadTxFsmFromMembers(TxFsmState &st) const;
|
void loadTxFsmFromMembers(TxFsmState &st) const;
|
||||||
void storeTxFsmToMembers(const TxFsmState &st);
|
void storeTxFsmToMembers(const TxFsmState &st);
|
||||||
|
bool shouldUseBufferedIsr() const;
|
||||||
IrTxBsrrWave txBsrrWave_{};
|
|
||||||
IR_TxGateRun txGateRuns_[irproto::kIsrTxMaxGateRuns]{};
|
|
||||||
uint32_t txBsrrWords_[irproto::kIsrTxBsrrWordCount]{};
|
|
||||||
uint16_t txBsrrReadIdx_ = 0;
|
|
||||||
uint16_t txBsrrHalfLen_ = 0;
|
|
||||||
uint32_t txBsrrTotalTicks_ = 0;
|
|
||||||
uint32_t txBsrrTicksSent_ = 0;
|
|
||||||
|
|
||||||
/** Снимок на старт TX (буферный и legacy путь). */
|
/** Снимок на старт TX (буферный и legacy путь). */
|
||||||
uint16_t txPowerSnap_ = 1;
|
uint16_t txPowerSnap_ = 1;
|
||||||
@ -194,6 +224,10 @@ private:
|
|||||||
uint16_t legacySlotInPeriod_ = 0;
|
uint16_t legacySlotInPeriod_ = 0;
|
||||||
|
|
||||||
volatile uint16_t powerNumerator_ = 1;
|
volatile uint16_t powerNumerator_ = 1;
|
||||||
|
IrTxIsrBufferedStorageBase* txBufferedCtx_ = nullptr;
|
||||||
|
IrTxIsrBufferedStorageBase* txActiveBufferedCtx_ = nullptr;
|
||||||
|
TxIsrMode txIsrMode_ = TxIsrMode::Legacy;
|
||||||
|
bool txUseBufferedIsr_ = false;
|
||||||
|
|
||||||
IR_DecoderRaw *decPair = nullptr;
|
IR_DecoderRaw *decPair = nullptr;
|
||||||
IR_DecoderRaw *singleBlindDecoder = nullptr;
|
IR_DecoderRaw *singleBlindDecoder = nullptr;
|
||||||
|
|||||||
@ -21,7 +21,7 @@ static_assert((kIsrTxBsrrWordCount & 1U) == 0U, "kIsrTxBsrrWordCount must be eve
|
|||||||
// Краткий лог причин, почему физический сигнал не дошёл до распознанного пакета.
|
// Краткий лог причин, почему физический сигнал не дошёл до распознанного пакета.
|
||||||
// Формат и коды: ref/IR_RX_BRIEF_LOG.md
|
// Формат и коды: ref/IR_RX_BRIEF_LOG.md
|
||||||
#ifndef IR_RX_BRIEF_LOG
|
#ifndef IR_RX_BRIEF_LOG
|
||||||
#define IR_RX_BRIEF_LOG 1
|
#define IR_RX_BRIEF_LOG 0
|
||||||
#endif
|
#endif
|
||||||
// 1: печатать только отклонённые/ошибочные события; успехи и шумовые PREAMB скрыть.
|
// 1: печатать только отклонённые/ошибочные события; успехи и шумовые PREAMB скрыть.
|
||||||
#ifndef IR_RX_BRIEF_LOG_REJECT_ONLY
|
#ifndef IR_RX_BRIEF_LOG_REJECT_ONLY
|
||||||
|
|||||||
@ -5,6 +5,14 @@
|
|||||||
|
|
||||||
#if defined(ARDUINO_ARCH_STM32) && defined(STM32G4xx)
|
#if defined(ARDUINO_ARCH_STM32) && defined(STM32G4xx)
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
#define IRPROTO_DMA_PRAGMA_MESSAGE(text) __pragma(message(text))
|
||||||
|
#else
|
||||||
|
#define IRPROTO_DMA_PRAGMA_MESSAGE(text) _Pragma(#text)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
IRPROTO_DMA_PRAGMA_MESSAGE(message("[IR-protocol] TX path available: built-in DMA"))
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <HardwareTimer.h>
|
#include <HardwareTimer.h>
|
||||||
|
|
||||||
@ -98,14 +106,14 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool start(IR_Encoder* enc, const uint8_t* packet, uint8_t len) {
|
IR_SendStatus start(IR_Encoder* enc, const uint8_t* packet, uint8_t len) {
|
||||||
if (enc == nullptr) return false;
|
if (enc == nullptr) return IR_SendStatus::ExternalNoStream;
|
||||||
for (uint8_t i = 0; i < streamCount_; i++) {
|
for (uint8_t i = 0; i < streamCount_; i++) {
|
||||||
if (streams_[i].enc == enc) {
|
if (streams_[i].enc == enc) {
|
||||||
return startStream(streams_[i], packet, len);
|
return startStream(streams_[i], packet, len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return IR_SendStatus::ExternalNoStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
void irqForStream(size_t streamIndex) {
|
void irqForStream(size_t streamIndex) {
|
||||||
@ -282,28 +290,22 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool startStream(TxStream& s, const uint8_t* packet, uint8_t len) {
|
IR_SendStatus startStream(TxStream& s, const uint8_t* packet, uint8_t len) {
|
||||||
if (s.enc == nullptr || s.port == nullptr || s.mask == 0) return false;
|
if (s.enc == nullptr || s.port == nullptr || s.mask == 0) return IR_SendStatus::ExternalInvalidConfig;
|
||||||
if (s.active) return false;
|
if (s.active) return IR_SendStatus::EncoderBusy;
|
||||||
if (s.dmaBuf == nullptr || s.bufLen < 2 || s.halfLen == 0) return false;
|
if (s.dmaBuf == nullptr || s.bufLen < 2 || s.halfLen == 0) return IR_SendStatus::ExternalInvalidConfig;
|
||||||
if (s.runs == nullptr || s.maxRuns == 0) return false;
|
if (s.runs == nullptr || s.maxRuns == 0) return IR_SendStatus::ExternalInvalidConfig;
|
||||||
|
|
||||||
s.resetWave();
|
s.resetWave();
|
||||||
|
|
||||||
s.runCount = IR_Encoder::buildGateRuns(packet, len, s.runs, s.maxRuns);
|
const uint16_t mult = IR_Encoder::carrierMultiply();
|
||||||
if (s.runCount == 0) return false;
|
s.runCount = IR_Encoder::buildPhysicalGateRuns(packet, len, s.runs, s.maxRuns, mult);
|
||||||
|
if (s.runCount == 0) return IR_SendStatus::BuildGateRunsFailed;
|
||||||
size_t rc = s.runCount;
|
|
||||||
if (!IR_Encoder::scaleGateRunsToPhysical(s.runs, &rc, s.maxRuns, IR_Encoder::carrierMultiply())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
s.runCount = rc;
|
|
||||||
|
|
||||||
uint32_t total = 0;
|
uint32_t total = 0;
|
||||||
for (size_t i = 0; i < s.runCount; i++) total += s.runs[i].lenTicks;
|
for (size_t i = 0; i < s.runCount; i++) total += s.runs[i].lenTicks;
|
||||||
s.totalTicks = total;
|
s.totalTicks = total;
|
||||||
|
|
||||||
const uint16_t mult = IR_Encoder::carrierMultiply();
|
|
||||||
uint16_t pwr = mult / 2U;
|
uint16_t pwr = mult / 2U;
|
||||||
if (s.enc != nullptr) {
|
if (s.enc != nullptr) {
|
||||||
const uint16_t want = s.enc->powerNumerator();
|
const uint16_t want = s.enc->powerNumerator();
|
||||||
@ -319,13 +321,13 @@ private:
|
|||||||
|
|
||||||
const uint32_t dst = u32ptr(&s.port->BSRR);
|
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) {
|
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;
|
s.active = true;
|
||||||
activeCount_++;
|
activeCount_++;
|
||||||
startTimerIfNeeded();
|
startTimerIfNeeded();
|
||||||
return true;
|
return IR_SendStatus::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void stopStream(TxStream& s) {
|
void stopStream(TxStream& s) {
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Один RLE-сегмент огибающей несущей.
|
* Один RLE-сегмент огибающей несущей.
|
||||||
* В buildGateRuns: lenTicks в тактах логической шкалы 2×carrierFrec (как раньше).
|
* В legacy buildGateRuns: lenTicks в тактах логической шкалы 2×carrierFrec.
|
||||||
* После IR_Encoder::scaleGateRunsToPhysical — в физических тиках carrierFrec×multiply.
|
* В современном DMA/buffered ISR пути buildPhysicalGateRuns строит lenTicks сразу в физических тиках carrierFrec×multiply.
|
||||||
*/
|
*/
|
||||||
struct IrTxGateRun {
|
struct IrTxGateRun {
|
||||||
uint16_t lenTicks;
|
uint16_t lenTicks;
|
||||||
|
|||||||
64
IrTxIsrBufferedStorage.h
Normal file
64
IrTxIsrBufferedStorage.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IR_config.h"
|
||||||
|
#include "IrTxBsrrWave.h"
|
||||||
|
|
||||||
|
class IrTxIsrBufferedStorageBase {
|
||||||
|
public:
|
||||||
|
IrTxGateRun* gateRuns = nullptr;
|
||||||
|
size_t maxGateRuns = 0;
|
||||||
|
uint32_t* bsrrWords = nullptr;
|
||||||
|
uint16_t wordCount = 0;
|
||||||
|
|
||||||
|
IrTxBsrrWave wave{};
|
||||||
|
uint16_t readIdx = 0;
|
||||||
|
uint16_t halfLen = 0;
|
||||||
|
uint32_t totalTicks = 0;
|
||||||
|
uint32_t ticksSent = 0;
|
||||||
|
|
||||||
|
bool isValid() const {
|
||||||
|
return gateRuns != nullptr &&
|
||||||
|
maxGateRuns != 0U &&
|
||||||
|
bsrrWords != nullptr &&
|
||||||
|
wordCount >= 2U &&
|
||||||
|
(wordCount & 1U) == 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetRuntimeState() {
|
||||||
|
readIdx = 0;
|
||||||
|
halfLen = static_cast<uint16_t>(wordCount / 2U);
|
||||||
|
totalTicks = 0;
|
||||||
|
ticksSent = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class IrTxIsrBufferedStorageView : public IrTxIsrBufferedStorageBase {
|
||||||
|
public:
|
||||||
|
IrTxIsrBufferedStorageView(IrTxGateRun* runs, size_t runCount, uint32_t* words, uint16_t wordsCount) {
|
||||||
|
gateRuns = runs;
|
||||||
|
maxGateRuns = runCount;
|
||||||
|
bsrrWords = words;
|
||||||
|
wordCount = wordsCount;
|
||||||
|
resetRuntimeState();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<size_t MaxGateRuns = irproto::kIsrTxMaxGateRuns, uint16_t WordCount = irproto::kIsrTxBsrrWordCount>
|
||||||
|
class IrTxIsrBufferedStorage : public IrTxIsrBufferedStorageBase {
|
||||||
|
static_assert(MaxGateRuns > 0U, "IrTxIsrBufferedStorage: MaxGateRuns > 0");
|
||||||
|
static_assert(WordCount >= 2U, "IrTxIsrBufferedStorage: WordCount >= 2");
|
||||||
|
static_assert((WordCount & 1U) == 0U, "IrTxIsrBufferedStorage: WordCount must be even");
|
||||||
|
|
||||||
|
public:
|
||||||
|
IrTxIsrBufferedStorage() {
|
||||||
|
gateRuns = gateRunsStorage_;
|
||||||
|
maxGateRuns = MaxGateRuns;
|
||||||
|
bsrrWords = bsrrWordsStorage_;
|
||||||
|
wordCount = WordCount;
|
||||||
|
resetRuntimeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
IrTxGateRun gateRunsStorage_[MaxGateRuns]{};
|
||||||
|
uint32_t bsrrWordsStorage_[WordCount]{};
|
||||||
|
};
|
||||||
@ -1,5 +1,7 @@
|
|||||||
# Контракт бэкенда DMA-TX ИК (`IrDmaTxStm32`)
|
# Контракт бэкенда DMA-TX ИК (`IrDmaTxStm32`)
|
||||||
|
|
||||||
|
См. также: [IR_TX_MODES.md](IR_TX_MODES.md) — общая схема выбора `legacy ISR`, `buffered ISR` и `external backend`.
|
||||||
|
|
||||||
Платформа: **STM32G4**, Arduino STM32. Передача: **DMA memory → GPIO BSRR**, запрос от **TIM UPDATE** (частота `carrierFrec×2` из `IR_Encoder::beginClockOnly`).
|
Платформа: **STM32G4**, Arduino STM32. Передача: **DMA memory → GPIO BSRR**, запрос от **TIM UPDATE** (частота `carrierFrec×2` из `IR_Encoder::beginClockOnly`).
|
||||||
|
|
||||||
### Число потоков (шаблон)
|
### Число потоков (шаблон)
|
||||||
|
|||||||
@ -55,7 +55,7 @@ IRRX t=1234988 rsn=MUTE_END cnt=42
|
|||||||
## Когда смотреть подробный debug
|
## Когда смотреть подробный debug
|
||||||
|
|
||||||
- `listenStart` / `checkTimeout` — в конце обработки фронта (`END:`) и во ветке «нет фронта» в `tick()`; не в начале до `pop`, иначе после таймаута `lastEdgeTime` расходится с метками ISR из очереди → ложные `TIMEOUT` (`bits=0`).
|
- `listenStart` / `checkTimeout` — в конце обработки фронта (`END:`) и во ветке «нет фронта» в `tick()`; не в начале до `pop`, иначе после таймаута `lastEdgeTime` расходится с метками ISR из очереди → ложные `TIMEOUT` (`bits=0`).
|
||||||
- Пока в `subBuffer` / `filteredSubBuffer` или в hold фильтра есть необработанные фронты, таймаут по `micros() - lastEdgeTime` **не оценивается** (`rxTimeoutPipelineBusy`): иначе при хвосте очереди «тихая пауза» считается слишком длинной и снова ложный `TIMEOUT`.
|
- Пока в `subBuffer` или в hold фильтра есть необработанные фронты, таймаут по `micros() - lastEdgeTime` **не оценивается** (`rxTimeoutPipelineBusy`): иначе при хвосте очереди «тихая пауза» считается слишком длинной и снова ложный `TIMEOUT`.
|
||||||
- Если нужен полный поток битов и sync: включать `IRDEBUG_SERIAL_PACK`
|
- Если нужен полный поток битов и sync: включать `IRDEBUG_SERIAL_PACK`
|
||||||
- Если нужно понять, какие именно фронты пришли в ISR: включать `IR_EDGE_TRACE`
|
- Если нужно понять, какие именно фронты пришли в ISR: включать `IR_EDGE_TRACE`
|
||||||
- `IR_RX_BRIEF_LOG` нужен как короткий always-on-ish индикатор сути проблемы, без длинного дампа
|
- `IR_RX_BRIEF_LOG` нужен как короткий always-on-ish индикатор сути проблемы, без длинного дампа
|
||||||
|
|||||||
146
ref/IR_TX_MODES.md
Normal file
146
ref/IR_TX_MODES.md
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
# Режимы IR TX в библиотеке
|
||||||
|
|
||||||
|
Этот документ описывает, как в библиотеке выбирается путь передачи IR и как его правильно использовать в проектах Arduino STM32.
|
||||||
|
|
||||||
|
## Кратко
|
||||||
|
|
||||||
|
У библиотеки есть три варианта TX:
|
||||||
|
|
||||||
|
- `legacy ISR` — внутренний ISR-путь без внешнего backend. Это путь по умолчанию для обратной совместимости.
|
||||||
|
- `buffered ISR` — внутренний ISR-путь с предварительной подготовкой BSRR-слов и кольцевым буфером.
|
||||||
|
- `external backend` — передача делегируется проекту через `IR_Encoder::setExternalTxBackend(...)`, например в DMA backend.
|
||||||
|
|
||||||
|
Порядок выбора такой:
|
||||||
|
|
||||||
|
1. Если зарегистрирован `external backend`, используется он.
|
||||||
|
2. Иначе используется внутренний ISR библиотеки.
|
||||||
|
3. Для внутреннего ISR:
|
||||||
|
- по умолчанию включён `legacy ISR`
|
||||||
|
- `buffered ISR` включается явно: нужно привязать storage к encoder и переключить режим
|
||||||
|
|
||||||
|
## 1. Legacy ISR
|
||||||
|
|
||||||
|
Это режим по умолчанию. Старые проекты могут ничего не менять:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
static HardwareTimer timer(TIM11);
|
||||||
|
static IR_Encoder enc(PA9, 42, &dec);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
IR_Encoder::begin(&timer, 1, TIM11_IRQn, 0);
|
||||||
|
enc.enable();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Если проект не регистрирует внешний backend и не переключает режим явно, библиотека работает в `legacy ISR`.
|
||||||
|
|
||||||
|
Для явного выбора можно написать:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
IR_Encoder::setTxIsrLegacyMode(true);
|
||||||
|
IR_Encoder::begin(&timer, 1, TIM11_IRQn, 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Buffered ISR
|
||||||
|
|
||||||
|
Этот режим использует внутренний буферный ISR-путь библиотеки. Он включается только явно:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <IrTxIsrBufferedStorage.h>
|
||||||
|
|
||||||
|
static IrTxIsrBufferedStorage<> txStorage;
|
||||||
|
|
||||||
|
enc.enableBufferedIsr(txStorage);
|
||||||
|
IR_Encoder::begin(&timer, 1, TIM11_IRQn, 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
Нижнеуровневый вариант API — отдельно привязать storage через `attachBufferedIsrStorage(...)`, но в обычном проекте удобнее использовать `enableBufferedIsr(...)`.
|
||||||
|
|
||||||
|
Смысл режима:
|
||||||
|
|
||||||
|
- пакет сначала превращается в `gate runs`
|
||||||
|
- затем в поток слов `GPIO->BSRR`
|
||||||
|
- ISR выдаёт готовые слова из кольцевого буфера
|
||||||
|
|
||||||
|
### Важно про RAM
|
||||||
|
|
||||||
|
В текущей реализации память под буферный ISR вынесена из `IR_Encoder` в отдельный storage-объект.
|
||||||
|
|
||||||
|
То есть:
|
||||||
|
|
||||||
|
- `legacy ISR` не тянет buffered-буферы в RAM самого `IR_Encoder`
|
||||||
|
- память под `gate runs` и `BSRR words` появляется только там, где проект сам создал `IrTxIsrBufferedStorage<>`
|
||||||
|
|
||||||
|
Это важно для STM32 с небольшим объёмом RAM, например для `STM32F401`.
|
||||||
|
|
||||||
|
## 3. External backend
|
||||||
|
|
||||||
|
Если проект хочет полностью взять TX на себя, библиотека позволяет зарегистрировать внешний backend:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
static bool txBusy(void* ctx);
|
||||||
|
static bool txStart(void* ctx, IR_Encoder* enc, const uint8_t* packet, uint8_t len);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
IR_Encoder::beginClockOnly(&timer);
|
||||||
|
IR_Encoder::setExternalTxBackend(txStart, txBusy, nullptr);
|
||||||
|
enc.enable();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
После вызова `setExternalTxBackend(...)` библиотека больше не использует свои внутренние ISR-пути для фактической передачи.
|
||||||
|
|
||||||
|
В этом режиме:
|
||||||
|
|
||||||
|
- `setTxIsrLegacyMode(true/false)` игнорируется
|
||||||
|
- завершение передачи должен сигнализировать сам backend через `enc->externalFinishSend()`
|
||||||
|
|
||||||
|
Подробности по встроенному DMA backend для `STM32G4xx`: см. [IR_DMA_TX_backend.md](IR_DMA_TX_backend.md).
|
||||||
|
|
||||||
|
## Когда какой режим использовать
|
||||||
|
|
||||||
|
### Старый проект, который ничего не настраивает
|
||||||
|
|
||||||
|
Использовать как есть:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
IR_Encoder::begin(...);
|
||||||
|
```
|
||||||
|
|
||||||
|
Итог: `legacy ISR`
|
||||||
|
|
||||||
|
### Нужен новый внутренний буферный ISR
|
||||||
|
|
||||||
|
Включить явно:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <IrTxIsrBufferedStorage.h>
|
||||||
|
|
||||||
|
static IrTxIsrBufferedStorage<> txStorage;
|
||||||
|
|
||||||
|
enc.enableBufferedIsr(txStorage);
|
||||||
|
IR_Encoder::begin(...);
|
||||||
|
```
|
||||||
|
|
||||||
|
Итог: `buffered ISR`
|
||||||
|
|
||||||
|
### Нужен проектный DMA или другой свой транспорт
|
||||||
|
|
||||||
|
Подключить внешний backend:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
IR_Encoder::beginClockOnly(...);
|
||||||
|
IR_Encoder::setExternalTxBackend(...);
|
||||||
|
```
|
||||||
|
|
||||||
|
Итог: `external backend`
|
||||||
|
|
||||||
|
## Рекомендация для совместимости
|
||||||
|
|
||||||
|
Для старых проектов безопаснее не вызывать `setTxIsrLegacyMode(false)`, если нет явной причины переходить на buffered ISR.
|
||||||
|
|
||||||
|
Если задача — сохранить старое поведение без неожиданного роста нагрузки на TX-логику, оставляйте default `legacy ISR` или задавайте его явно:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
IR_Encoder::setTxIsrLegacyMode(true);
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user