fix overflow and mute

This commit is contained in:
2026-04-14 09:36:27 +03:00
parent ad1e16cfda
commit 8631f23b53
9 changed files with 466 additions and 69 deletions

View File

@ -129,7 +129,7 @@ void IrFoxDecoder::write_to_buffer(bool bit, bool pack_trace_invert_fix, uint64_
if (buf_bit_pos == next_control_bit) if (buf_bit_pos == next_control_bit)
{ {
next_control_bit = static_cast<uint8_t>(next_control_bit + (is_data ? irfox::kSyncBits : irfox::kBitPerByte)); next_control_bit = next_control_bit + (is_data ? irfox::kSyncBits : irfox::kBitPerByte);
is_data = !is_data; is_data = !is_data;
i_sync_bit = 0; i_sync_bit = 0;
err_sync_bit = 0; err_sync_bit = 0;

View File

@ -127,7 +127,7 @@ private:
int16_t buf_bit_pos = 0; int16_t buf_bit_pos = 0;
bool is_data = true; bool is_data = true;
uint16_t i_data_buffer = 0; uint16_t i_data_buffer = 0;
uint8_t next_control_bit = irfox::kBitPerByte; uint16_t next_control_bit = irfox::kBitPerByte;
uint8_t i_sync_bit = 0; uint8_t i_sync_bit = 0;
uint8_t err_sync_bit = 0; uint8_t err_sync_bit = 0;
uint16_t error_counter = 0; uint16_t error_counter = 0;

View File

@ -26,6 +26,11 @@ static inline uint8_t irPulseFilterHoldbackEdges()
return hb; return hb;
} }
static inline uint16_t irClampU16(uint32_t v)
{
return (v > 0xFFFFU) ? 0xFFFFU : (uint16_t)v;
}
IR_DecoderRaw::IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPair) : encoder(encPair) IR_DecoderRaw::IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPair) : encoder(encPair)
{ {
setPin(pin); setPin(pin);
@ -74,6 +79,198 @@ void IR_DecoderRaw::pulseFilterResetStats()
pulseFilterDropGlitchPairs = 0; pulseFilterDropGlitchPairs = 0;
} }
bool IR_DecoderRaw::registerPairMuteEncoder(IR_Encoder *enc)
{
if (enc == nullptr)
return false;
for (uint8_t i = 0; i < pairMuteEncoderCount; ++i)
{
if (pairMuteEncoders[i] == enc)
return true;
}
if (pairMuteEncoderCount >= IR_PAIR_MUTE_MAX_ENCODERS)
return false;
pairMuteEncoders[pairMuteEncoderCount++] = enc;
return true;
}
void IR_DecoderRaw::refreshPairMuteState()
{
uint16_t active = 0;
for (uint8_t i = 0; i < pairMuteEncoderCount; ++i)
{
IR_Encoder *enc = pairMuteEncoders[i];
if (enc != nullptr && enc->isBusy())
++active;
}
const uint32_t nowUs = micros();
noInterrupts();
const bool wasActive = (isPairSending != 0);
isPairSending = active;
#if IR_RX_BRIEF_LOG
if (!wasActive && active != 0)
{
rxBriefMuteBlockedEdges = 0;
rxBriefMuteBeginPending = true;
rxBriefMuteBeginUs = nowUs;
}
else if (wasActive && active == 0)
{
rxBriefMuteEndPending = true;
rxBriefMuteEndUs = nowUs;
rxBriefMuteEndCount = rxBriefMuteBlockedEdges;
rxBriefMuteBlockedEdges = 0;
}
#endif
interrupts();
}
#if IR_RX_BRIEF_LOG
const __FlashStringHelper *IR_DecoderRaw::rxBriefReasonTag(RxBriefReason reason)
{
switch (reason)
{
case RxBriefReason::MuteBegin: return F("MUTE_BEGIN");
case RxBriefReason::MuteEnd: return F("MUTE_END");
case RxBriefReason::RawOverflow: return F("QRAW");
case RxBriefReason::FilterOverflow: return F("QFLT");
case RxBriefReason::HoldOverflow: return F("HOLD");
case RxBriefReason::Glitch: return F("GLITCH");
case RxBriefReason::Timing: return F("TIME");
case RxBriefReason::Preamble: return F("PREAMB");
case RxBriefReason::Sync: return F("SYNC");
case RxBriefReason::BufferOverflow: return F("BUF");
case RxBriefReason::Timeout: return F("TIMEOUT");
case RxBriefReason::Crc: return F("CRC");
case RxBriefReason::Ok: return F("OK");
default: return F("UNK");
}
}
void IR_DecoderRaw::rxBriefLog(RxBriefReason reason, uint16_t a, uint16_t b, uint32_t tUs)
{
#if IR_RX_BRIEF_LOG_REJECT_ONLY
if (reason == RxBriefReason::Ok || reason == RxBriefReason::Preamble)
return;
#endif
if (tUs == 0U)
tUs = micros();
Serial.print(F("IRRX t="));
Serial.print((unsigned long)tUs);
Serial.print(F(" rsn="));
Serial.print(rxBriefReasonTag(reason));
switch (reason)
{
case RxBriefReason::MuteBegin:
break;
case RxBriefReason::MuteEnd:
case RxBriefReason::RawOverflow:
Serial.print(F(" cnt="));
Serial.print(a);
break;
case RxBriefReason::FilterOverflow:
case RxBriefReason::HoldOverflow:
case RxBriefReason::Glitch:
Serial.print(F(" total="));
Serial.print(a);
break;
case RxBriefReason::Timing:
Serial.print(F(" rp="));
Serial.print(a);
if (b)
{
Serial.print(F(" hp="));
Serial.print(b);
}
break;
case RxBriefReason::Preamble:
Serial.print(F(" good="));
Serial.print(a);
if (b)
{
Serial.print(F(" per="));
Serial.print(b);
}
break;
case RxBriefReason::Sync:
Serial.print(F(" err="));
Serial.print(a);
break;
case RxBriefReason::BufferOverflow:
Serial.print(F(" bits="));
Serial.print(a);
break;
case RxBriefReason::Timeout:
Serial.print(F(" bits="));
Serial.print(a);
if (b)
{
Serial.print(F(" exp="));
Serial.print(b);
}
break;
case RxBriefReason::Crc:
case RxBriefReason::Ok:
Serial.print(F(" len="));
Serial.print(a);
if (b)
{
Serial.print(F(" err="));
Serial.print(b);
}
break;
}
Serial.println();
}
void IR_DecoderRaw::rxBriefNoteMuteBlockedIsr(uint32_t tUs)
{
(void)tUs;
if (rxBriefMuteBlockedEdges != UINT16_MAX)
++rxBriefMuteBlockedEdges;
}
void IR_DecoderRaw::rxBriefNoteRawOverflowIsr(uint32_t tUs)
{
if (rxBriefRawOverflowDrops != UINT16_MAX)
++rxBriefRawOverflowDrops;
rxBriefRawOverflowLastUs = tUs;
}
void IR_DecoderRaw::rxBriefFlushDeferredIsrLogs()
{
bool muteBeginPending = false;
uint32_t muteBeginUs = 0;
bool muteEndPending = false;
uint32_t muteEndUs = 0;
uint16_t muteEndCnt = 0;
uint16_t rawCnt = 0;
uint32_t rawLastUs = 0;
noInterrupts();
muteBeginPending = rxBriefMuteBeginPending;
muteBeginUs = rxBriefMuteBeginUs;
rxBriefMuteBeginPending = false;
rxBriefMuteBeginUs = 0;
muteEndPending = rxBriefMuteEndPending;
muteEndUs = rxBriefMuteEndUs;
muteEndCnt = rxBriefMuteEndCount;
rxBriefMuteEndPending = false;
rxBriefMuteEndUs = 0;
rxBriefMuteEndCount = 0;
rawCnt = rxBriefRawOverflowDrops;
rawLastUs = rxBriefRawOverflowLastUs;
rxBriefRawOverflowDrops = 0;
rxBriefRawOverflowLastUs = 0;
interrupts();
if (muteBeginPending)
rxBriefLog(RxBriefReason::MuteBegin, 0, 0, muteBeginUs);
if (muteEndPending)
rxBriefLog(RxBriefReason::MuteEnd, muteEndCnt, 0, muteEndUs);
if (rawCnt)
rxBriefLog(RxBriefReason::RawOverflow, rawCnt, 0, rawLastUs);
}
#endif
//////////////////////////////////// isr /////////////////////////////////////////// //////////////////////////////////// isr ///////////////////////////////////////////
volatile uint32_t time_; volatile uint32_t time_;
@ -99,10 +296,19 @@ void IR_DecoderRaw::isr()
if (isPairSending) if (isPairSending)
{ {
#if IR_RX_BRIEF_LOG
rxBriefNoteMuteBlockedIsr(edge.time);
#endif
return; return;
} }
subBuffer.push(edge); if (!subBuffer.push(edge))
{
isSubBufferOverflow = true;
#if IR_RX_BRIEF_LOG
rxBriefNoteRawOverflowIsr(edge.time);
#endif
}
} }
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
@ -123,6 +329,7 @@ void IR_DecoderRaw::firstRX()
i_dataBuffer = 0; i_dataBuffer = 0;
nextControlBit = bitPerByte; nextControlBit = bitPerByte;
i_syncBit = 0; i_syncBit = 0;
err_syncBit = 0;
isWrongPack = false; isWrongPack = false;
isPreamb = true; isPreamb = true;
@ -157,6 +364,10 @@ inline void IR_DecoderRaw::checkTimeout()
{ {
#if defined(IRDEBUG_SERIAL_PACK) #if defined(IRDEBUG_SERIAL_PACK)
packTraceOnTimeoutOrAbort(false); packTraceOnTimeoutOrAbort(false);
#endif
#if IR_RX_BRIEF_LOG
const uint16_t expected = (i_dataBuffer >= 8U) ? uint16_t(dataBuffer[0] & IR_MASK_MSG_INFO) : 0U;
rxBriefLog(RxBriefReason::Timeout, i_dataBuffer, expected, micros());
#endif #endif
isRecive = false; // приём завершён isRecive = false; // приём завершён
msgTypeReceive = 0; msgTypeReceive = 0;
@ -168,6 +379,9 @@ inline void IR_DecoderRaw::checkTimeout()
void IR_DecoderRaw::tick() void IR_DecoderRaw::tick()
{ {
#if IR_RX_BRIEF_LOG
rxBriefFlushDeferredIsrLogs();
#endif
FrontStorage currentFront; FrontStorage currentFront;
bool hasCurrentFront = false; bool hasCurrentFront = false;
FrontStorage rawFront; FrontStorage rawFront;
@ -250,6 +464,9 @@ void IR_DecoderRaw::tick()
if (short_low_glitch) if (short_low_glitch)
{ {
errors.other++; errors.other++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Glitch, 1, 0, currentFront.time);
#endif
#if IR_GLITCH_REJECT_PHASE_NUDGE #if IR_GLITCH_REJECT_PHASE_NUDGE
irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise); irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise);
#endif #endif
@ -264,6 +481,9 @@ void IR_DecoderRaw::tick()
if (micro_gap_rise) if (micro_gap_rise)
{ {
errors.other++; errors.other++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Glitch, 1, 0, currentFront.time);
#endif
#if IR_GLITCH_REJECT_PHASE_NUDGE #if IR_GLITCH_REJECT_PHASE_NUDGE
irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise); irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise);
#endif #endif
@ -273,6 +493,9 @@ void IR_DecoderRaw::tick()
if (candRp <= riseTimeMax / 4U && !highCount && !lowCount) if (candRp <= riseTimeMax / 4U && !highCount && !lowCount)
{ {
errors.other++; errors.other++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Timing, irClampU16(candRp), 0, currentFront.time);
#endif
goto END; goto END;
} }
@ -336,6 +559,11 @@ void IR_DecoderRaw::tick()
if (risePeriod > IR_timeout || isBufferOverflow || risePeriod < riseTimeMin || isWrongPack) if (risePeriod > IR_timeout || isBufferOverflow || risePeriod < riseTimeMin || isWrongPack)
// ~Мы в пределах таймаута и буффер не переполнен и fix дроблёных единиц // ~Мы в пределах таймаута и буффер не переполнен и fix дроблёных единиц
{ {
#if IR_RX_BRIEF_LOG
if (!isBufferOverflow && !isWrongPack)
rxBriefLog(RxBriefReason::Timing, irClampU16((uint32_t)risePeriod),
irClampU16((uint32_t)highTime), currentFront.time);
#endif
goto END; goto END;
} }
@ -494,6 +722,9 @@ void IR_DecoderRaw::tick()
//////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////
END:; END:;
#if IR_RX_BRIEF_LOG
rxBriefFlushDeferredIsrLogs();
#endif
#if defined(IR_EDGE_TRACE) #if defined(IR_EDGE_TRACE)
while (edgeTraceFlushChunk(Serial, 48) > 0) {} while (edgeTraceFlushChunk(Serial, 48) > 0) {}
#endif #endif
@ -507,6 +738,9 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
if (i_dataBuffer > dataByteSizeMax * 8) if (i_dataBuffer > dataByteSizeMax * 8)
{ // проверка переполнения { // проверка переполнения
isBufferOverflow = true; isBufferOverflow = true;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::BufferOverflow, i_dataBuffer, 0, micros());
#endif
#if defined(IRDEBUG_SERIAL_PACK) #if defined(IRDEBUG_SERIAL_PACK)
if (packTraceOpen) if (packTraceOpen)
packTraceEmitErrorFlash(F("ERROR: buffer overflow")); packTraceEmitErrorFlash(F("ERROR: buffer overflow"));
@ -581,6 +815,9 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
#endif #endif
{ {
isWrongPack = true; isWrongPack = true;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Sync, err_syncBit, 0, micros());
#endif
#if defined(IRDEBUG_SERIAL_PACK) #if defined(IRDEBUG_SERIAL_PACK)
packTraceEmitErrorFlash(F("ERROR: Wrong sync bit")); packTraceEmitErrorFlash(F("ERROR: Wrong sync bit"));
#endif #endif
@ -674,6 +911,14 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
packTraceEmitEndOk(static_cast<uint8_t>(packSize)); packTraceEmitEndOk(static_cast<uint8_t>(packSize));
else else
packTraceEmitEndBadCrc(static_cast<uint8_t>(packSize)); packTraceEmitEndBadCrc(static_cast<uint8_t>(packSize));
#endif
const uint16_t errSum =
uint16_t(errors.lowSignal) + uint16_t(errors.highSignal) + uint16_t(errors.other);
#if IR_RX_BRIEF_LOG
if (isAvailable)
rxBriefLog(RxBriefReason::Ok, packSize, errSum, micros());
else
rxBriefLog(RxBriefReason::Crc, packSize, errSum, micros());
#endif #endif
if (!isAvailable && packSize > 0 && packSize <= dataByteSizeMax) { if (!isAvailable && packSize > 0 && packSize <= dataByteSizeMax) {
memcpy(rejectBuffer, dataBuffer, packSize); memcpy(rejectBuffer, dataBuffer, packSize);
@ -1203,6 +1448,9 @@ bool IR_DecoderRaw::pulseFilterEmit(const FrontStorage &e)
if (filteredSubBuffer.isFull()) if (filteredSubBuffer.isFull())
{ {
pulseFilterDropFilteredOverflow++; pulseFilterDropFilteredOverflow++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::FilterOverflow, irClampU16(pulseFilterDropFilteredOverflow), 0, e.time);
#endif
return false; return false;
} }
filteredSubBuffer.push(e); filteredSubBuffer.push(e);
@ -1232,6 +1480,9 @@ void IR_DecoderRaw::pulseFilterFeedOneRaw(const FrontStorage &e)
if (pulseFilterHoldCount >= kPulseFilterHoldCap) if (pulseFilterHoldCount >= kPulseFilterHoldCap)
{ {
pulseFilterDropHoldOverflow++; pulseFilterDropHoldOverflow++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::HoldOverflow, irClampU16(pulseFilterDropHoldOverflow), 0, e.time);
#endif
pulseFilterEmit(pulseFilterHoldEdges[0]); pulseFilterEmit(pulseFilterHoldEdges[0]);
pulseFilterShiftLeft(1); pulseFilterShiftLeft(1);
} }
@ -1248,6 +1499,9 @@ void IR_DecoderRaw::pulseFilterFeedOneRaw(const FrontStorage &e)
if (dt < minUs) if (dt < minUs)
{ {
pulseFilterDropGlitchPairs++; pulseFilterDropGlitchPairs++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Glitch, irClampU16(pulseFilterDropGlitchPairs), 0, e.time);
#endif
pulseFilterShiftLeft(2); pulseFilterShiftLeft(2);
continue; continue;
} }
@ -1276,6 +1530,9 @@ void IR_DecoderRaw::pulseFilterFlushTimeout(uint32_t nowUs)
if (dt < IR_INPUT_MIN_PULSE_US) if (dt < IR_INPUT_MIN_PULSE_US)
{ {
pulseFilterDropGlitchPairs++; pulseFilterDropGlitchPairs++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Glitch, irClampU16(pulseFilterDropGlitchPairs), 0, nowUs);
#endif
pulseFilterShiftLeft(2); pulseFilterShiftLeft(2);
continue; continue;
} }
@ -1343,7 +1600,12 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
if (preambleState == PreambleState::Candidate) if (preambleState == PreambleState::Candidate)
{ {
if ((uint32_t)(front.time - preambleCandidateLastEdgeTime) > candTimeout) if ((uint32_t)(front.time - preambleCandidateLastEdgeTime) > candTimeout)
{
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Preamble, preambleGoodPeriods, 0, front.time);
#endif
preambleStartCandidate(front); preambleStartCandidate(front);
}
preambleCandidateLastEdgeTime = front.time; preambleCandidateLastEdgeTime = front.time;
if (!front.dir) if (!front.dir)
@ -1362,6 +1624,9 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
{ {
preambleGoodPeriods = 0; preambleGoodPeriods = 0;
preambleMeanPeriod = 0; preambleMeanPeriod = 0;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Preamble, 0, irClampU16(period), front.time);
#endif
return true; return true;
} }
@ -1382,6 +1647,9 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
} }
else else
{ {
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Preamble, preambleGoodPeriods, irClampU16(period), front.time);
#endif
preambleGoodPeriods = 1; preambleGoodPeriods = 1;
preambleMeanPeriod = (uint16_t)period; preambleMeanPeriod = (uint16_t)period;
} }

View File

@ -72,15 +72,32 @@ public:
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
private: private:
enum class RxBriefReason : uint8_t
{
MuteBegin = 1,
MuteEnd = 2,
RawOverflow = 3,
FilterOverflow = 4,
HoldOverflow = 5,
Glitch = 6,
Timing = 7,
Preamble = 8,
Sync = 9,
BufferOverflow = 10,
Timeout = 11,
Crc = 12,
Ok = 13
};
bool isRejectAvailable = false; bool isRejectAvailable = false;
uint8_t rejectPackSize = 0; uint8_t rejectPackSize = 0;
uint8_t rejectBuffer[dataByteSizeMax]{}; uint8_t rejectBuffer[dataByteSizeMax]{};
ErrorsStruct errors; ErrorsStruct errors;
bool isAvailable = false; bool isAvailable = false;
uint16_t packSize; uint16_t packSize = 0;
uint16_t crcValue; uint16_t crcValue = 0;
volatile uint16_t isPairSending = 0; // Флаг передачи парного передатчика volatile uint16_t isPairSending = 0; // Число активных TX, временно глушащих этот RX.
volatile bool isRecive = false; // Флаг приёма volatile bool isRecive = false; // Флаг приёма
volatile bool isPreamb = false; // флаг начальной последовости volatile bool isPreamb = false; // флаг начальной последовости
volatile bool isSubBufferOverflow = false; volatile bool isSubBufferOverflow = false;
@ -107,6 +124,8 @@ private:
RingBuffer<FrontStorage, subBufferSize> subBuffer; RingBuffer<FrontStorage, subBufferSize> subBuffer;
/** Очередь фронтов после потокового анти-глитча; tick() читает из неё при включённом фильтре. */ /** Очередь фронтов после потокового анти-глитча; tick() читает из неё при включённом фильтре. */
RingBuffer<FrontStorage, subBufferSize> filteredSubBuffer; RingBuffer<FrontStorage, subBufferSize> filteredSubBuffer;
IR_Encoder *pairMuteEncoders[IR_PAIR_MUTE_MAX_ENCODERS]{};
uint8_t pairMuteEncoderCount = 0;
static constexpr uint8_t kPulseFilterHoldCap = 6; static constexpr uint8_t kPulseFilterHoldCap = 6;
FrontStorage pulseFilterHoldEdges[kPulseFilterHoldCap]{}; FrontStorage pulseFilterHoldEdges[kPulseFilterHoldCap]{};
uint8_t pulseFilterHoldCount = 0; uint8_t pulseFilterHoldCount = 0;
@ -143,6 +162,17 @@ private:
volatile bool edgeTrace_overflow = false; volatile bool edgeTrace_overflow = false;
#endif #endif
#if IR_RX_BRIEF_LOG
volatile bool rxBriefMuteBeginPending = false;
volatile uint32_t rxBriefMuteBeginUs = 0;
volatile bool rxBriefMuteEndPending = false;
volatile uint32_t rxBriefMuteEndUs = 0;
volatile uint16_t rxBriefMuteEndCount = 0;
volatile uint16_t rxBriefMuteBlockedEdges = 0;
volatile uint16_t rxBriefRawOverflowDrops = 0;
volatile uint32_t rxBriefRawOverflowLastUs = 0;
#endif
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
uint8_t dataBuffer[dataByteSizeMax]{0}; // Буффер данных uint8_t dataBuffer[dataByteSizeMax]{0}; // Буффер данных
volatile uint32_t prevRise, prevPrevRise, prevFall, prevPrevFall; // Время предыдущих фронтов/спадов volatile uint32_t prevRise, prevPrevRise, prevFall, prevPrevFall; // Время предыдущих фронтов/спадов
@ -163,7 +193,7 @@ private:
int16_t bufBitPos = 0; // Позиция для записи бита в буффер int16_t bufBitPos = 0; // Позиция для записи бита в буффер
private: private:
bool isReciveRaw; bool isReciveRaw = false;
void listenStart(); void listenStart();
void checkTimeout(); // void checkTimeout(); //
/** Один сырой фронт из subBuffer -> потоковый holdback-антиглитч. */ /** Один сырой фронт из subBuffer -> потоковый holdback-антиглитч. */
@ -173,6 +203,8 @@ bool isReciveRaw;
void pulseFilterShiftLeft(uint8_t n); void pulseFilterShiftLeft(uint8_t n);
void pulseFilterReset(); void pulseFilterReset();
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);
void refreshPairMuteState();
uint32_t preambleJitterTolUs(uint32_t baselineUs) const; uint32_t preambleJitterTolUs(uint32_t baselineUs) const;
bool preambleRisePeriodCoarseOk(uint32_t periodUs) const; bool preambleRisePeriodCoarseOk(uint32_t periodUs) const;
void preambleResetToIdle(); void preambleResetToIdle();
@ -187,10 +219,10 @@ bool isReciveRaw;
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
bool isData = true; // Флаг относится ли бит к данным, или битам синхронизации bool isData = true; // Флаг относится ли бит к данным, или битам синхронизации
uint16_t i_dataBuffer; // Счётчик буфера данных uint16_t i_dataBuffer = 0; // Счётчик буфера данных
uint16_t nextControlBit = bitPerByte; // Метка для смены флага isData; uint16_t нужен для длинных кадров (>24 байт total) uint16_t nextControlBit = bitPerByte; // Метка для смены флага isData; uint16_t нужен для длинных кадров (>24 байт total)
uint8_t i_syncBit; // Счётчик битов синхронизации uint8_t i_syncBit = 0; // Счётчик битов синхронизации
uint8_t err_syncBit; // Счётчик ошибок синхронизации uint8_t err_syncBit = 0; // Счётчик ошибок синхронизации
/// @brief Запиь бита в буффер, а так же проверка битов синхранизации и их фильтрация /// @brief Запиь бита в буффер, а так же проверка битов синхранизации и их фильтрация
/// @param packTraceInvertFix если true — в IRDEBUG_SERIAL_PACK бит в трассе пишется как `0`/`1` (исправление по фронтам) /// @param packTraceInvertFix если true — в IRDEBUG_SERIAL_PACK бит в трассе пишется как `0`/`1` (исправление по фронтам)
@ -205,6 +237,14 @@ bool isReciveRaw;
/// @return Результат /// @return Результат
uint16_t ceil_div(uint16_t val, uint16_t divider); uint16_t ceil_div(uint16_t val, uint16_t divider);
#if IR_RX_BRIEF_LOG
static const __FlashStringHelper *rxBriefReasonTag(RxBriefReason reason);
void rxBriefLog(RxBriefReason reason, uint16_t a = 0, uint16_t b = 0, uint32_t tUs = 0);
void rxBriefNoteMuteBlockedIsr(uint32_t tUs);
void rxBriefNoteRawOverflowIsr(uint32_t tUs);
void rxBriefFlushDeferredIsrLogs();
#endif
#ifdef IRDEBUG #ifdef IRDEBUG
uint32_t wrCounter; uint32_t wrCounter;
inline void errPulse(uint8_t pin, uint8_t count); inline void errPulse(uint8_t pin, uint8_t count);

View File

@ -15,19 +15,14 @@ IR_Encoder::IR_Encoder(uint8_t pin, uint16_t addr, IR_DecoderRaw *decPair, bool
setPin(pin); setPin(pin);
id = addr; id = addr;
this->decPair = decPair; this->decPair = decPair;
signal = noSignal;
isSending = false;
#if disablePairDec
if (decPair != nullptr) if (decPair != nullptr)
{ {
blindDecoders = new IR_DecoderRaw *[1]{decPair}; singleBlindDecoder = decPair;
blindDecoders = &singleBlindDecoder;
decodersCount = 1; decodersCount = 1;
}
#endif
if (decPair != nullptr)
{
decPair->encoder = this; decPair->encoder = this;
} }
registerWithBlindDecoders();
if (autoHandle) if (autoHandle)
{ {
@ -233,7 +228,7 @@ void IR_Encoder::externalFinishSend()
} }
isSending = false; isSending = false;
setDecoder_isSending(); refreshBlindDecoderMuteState();
} }
size_t IR_Encoder::buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns) size_t IR_Encoder::buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns)
@ -351,12 +346,16 @@ void IR_Encoder::disable()
void IR_Encoder::setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count) void IR_Encoder::setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count)
{ {
#if disablePairDec if (count > IR_PAIR_MUTE_MAX_ENCODERS)
if (blindDecoders != nullptr) {
delete[] blindDecoders; decodersCount = 0;
#endif blindDecoders = nullptr;
return;
}
decodersCount = count; decodersCount = count;
blindDecoders = decoders; blindDecoders = decoders;
registerWithBlindDecoders();
refreshBlindDecoderMuteState();
} }
IR_Encoder::~IR_Encoder(){}; IR_Encoder::~IR_Encoder(){};
@ -543,19 +542,28 @@ IR_SendResult IR_Encoder::_sendBack(bool isAdressed, uint16_t addrTo, uint8_t *d
return IR_SendResult(true, sendTime); return IR_SendResult(true, sendTime);
} }
void IR_Encoder::setDecoder_isSending() void IR_Encoder::registerWithBlindDecoders()
{
if (decodersCount)
{ {
if (!decodersCount || blindDecoders == nullptr)
return;
for (uint8_t i = 0; i < decodersCount; i++) for (uint8_t i = 0; i < decodersCount; i++)
{ {
blindDecoders[i]->isPairSending ^= id; if (blindDecoders[i] != nullptr)
// Serial.print("setDecoder_isSending() id = "); blindDecoders[i]->registerPairMuteEncoder(this);
// Serial.print(id);
// Serial.print(" isPairSending = ");
// Serial.println(blindDecoders[i]->isPairSending);
} }
} }
void IR_Encoder::refreshBlindDecoderMuteState()
{
if (!decodersCount || blindDecoders == nullptr)
return;
for (uint8_t i = 0; i < decodersCount; i++)
{
if (blindDecoders[i] != nullptr)
blindDecoders[i]->refreshPairMuteState();
}
} }
void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len) void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
@ -572,13 +580,13 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
return; return;
} }
Serial.print("IR tx hex: "); // Serial.print("IR tx hex: ");
for (uint8_t i = 0; i < len; i++) // for (uint8_t i = 0; i < len; i++)
{ // {
if (ptr[i] < 0x10) Serial.print("0"); // if (ptr[i] < 0x10) Serial.print("0");
Serial.print(ptr[i], HEX); // Serial.print(ptr[i], HEX);
} // }
Serial.println(); // Serial.println();
if (externalTxStartFn != nullptr) if (externalTxStartFn != nullptr)
{ {
@ -587,24 +595,18 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
return; return;
} }
// Mark as sending and delegate actual signal output to external backend.
setDecoder_isSending();
sendLen = len; sendLen = len;
isSending = true; isSending = true;
refreshBlindDecoderMuteState();
const bool ok = externalTxStartFn(externalTxCtx, this, ptr, len); const bool ok = externalTxStartFn(externalTxCtx, this, ptr, len);
if (!ok) if (!ok)
{ {
isSending = false; isSending = false;
setDecoder_isSending(); refreshBlindDecoderMuteState();
} }
return; return;
} }
IR_Encoder::carrierResume();
// Serial.println("START");
setDecoder_isSending();
// noInterrupts();
sendLen = len; sendLen = len;
toggleCounter = preambToggle; // Первая генерация для первого signal toggleCounter = preambToggle; // Первая генерация для первого signal
@ -618,8 +620,9 @@ void IR_Encoder::rawSend(uint8_t *ptr, uint8_t len)
signal = preamb; signal = preamb;
isSending = true; isSending = true;
state = HIGH; state = HIGH;
currentBitSequence = bitHigh; currentBitSequence = bitHigh;
refreshBlindDecoderMuteState();
IR_Encoder::carrierResume();
// interrupts(); // interrupts();
} }
@ -651,7 +654,7 @@ void IR_Encoder::_isr()
if (!active) if (!active)
{ {
isSending = false; isSending = false;
setDecoder_isSending(); refreshBlindDecoderMuteState();
carrierStopPending = true; carrierStopPending = true;
} }
} }

View File

@ -35,7 +35,8 @@ public:
/// @brief Класс передатчика /// @brief Класс передатчика
/// @param addr Адрес передатчика /// @param addr Адрес передатчика
/// @param pin Вывод передатчика /// @param pin Вывод передатчика
/// @param decPair Приёмник, для которого отключается приём в момент передачи передатчиком /// @param decPair Если задан, конструктор регистрирует этот один приёмник как blind-decoder
/// (аналог setBlindDecoders() для одного RX).
IR_Encoder(uint8_t pin, uint16_t addr = 0, IR_DecoderRaw *decPair = nullptr, bool autoHandle = true); IR_Encoder(uint8_t pin, uint16_t addr = 0, IR_DecoderRaw *decPair = nullptr, bool autoHandle = true);
static void isr(); static void isr();
static void begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, uint8_t priority, void(*isrCallback)() = nullptr); static void begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, uint8_t priority, void(*isrCallback)() = nullptr);
@ -58,6 +59,13 @@ public:
void disable(); void disable();
void setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count); void setBlindDecoders(IR_DecoderRaw *decoders[], uint8_t count);
template <size_t N>
void setBlindDecoders(IR_DecoderRaw *(&decoders)[N])
{
static_assert(N <= IR_PAIR_MUTE_MAX_ENCODERS,
"IR_Encoder::setBlindDecoders: array size exceeds IR_PAIR_MUTE_MAX_ENCODERS");
setBlindDecoders(decoders, static_cast<uint8_t>(N));
}
void rawSend(uint8_t *ptr, uint8_t len); void 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);
@ -99,7 +107,8 @@ private:
static void *externalTxCtx; static void *externalTxCtx;
IR_SendResult _sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len); IR_SendResult _sendBack(bool isAdressed, uint16_t addrTo, uint8_t *data, uint8_t len);
void setDecoder_isSending(); void refreshBlindDecoderMuteState();
void registerWithBlindDecoders();
void sendByte(uint8_t byte, bool *prev, bool LOW_FIRST); void sendByte(uint8_t byte, bool *prev, bool LOW_FIRST);
void addSync(bool *prev, bool *next); void addSync(bool *prev, bool *next);
uint32_t calculateSendTime(uint8_t packSize) const; uint32_t calculateSendTime(uint8_t packSize) const;
@ -137,25 +146,26 @@ private:
void loadTxFsmFromMembers(TxFsmState &st) const; void loadTxFsmFromMembers(TxFsmState &st) const;
void storeTxFsmToMembers(const TxFsmState &st); void storeTxFsmToMembers(const TxFsmState &st);
IR_DecoderRaw *decPair; IR_DecoderRaw *decPair = nullptr;
IR_DecoderRaw **blindDecoders; IR_DecoderRaw *singleBlindDecoder = nullptr;
uint8_t decodersCount; IR_DecoderRaw **blindDecoders = nullptr;
uint8_t decodersCount = 0;
uint8_t sendLen; uint8_t sendLen = 0;
uint8_t sendBuffer[dataByteSizeMax]{0}; /// @brief Буффер данных для отправки uint8_t sendBuffer[dataByteSizeMax]{0}; /// @brief Буффер данных для отправки
volatile bool isSending; volatile bool isSending = false;
volatile bool state; /// @brief Текущий уровень генерации volatile bool state = LOW; /// @brief Текущий уровень генерации
volatile uint8_t dataByteCounter; volatile uint8_t dataByteCounter = 0;
volatile uint8_t toggleCounter; /// @brief Счётчик переключений volatile uint8_t toggleCounter = 0; /// @brief Счётчик переключений
volatile uint8_t dataBitCounter; volatile uint8_t dataBitCounter = 0;
volatile uint8_t preambFrontCounter; volatile uint8_t preambFrontCounter = 0;
volatile uint8_t dataSequenceCounter; volatile uint8_t dataSequenceCounter = 0;
volatile uint8_t syncSequenceCounter; volatile uint8_t syncSequenceCounter = 0;
volatile bool syncLastBit; volatile bool syncLastBit = false;
struct BitSequence struct BitSequence
{ {
@ -165,5 +175,5 @@ private:
static uint8_t bitHigh[2]; static uint8_t bitHigh[2];
static uint8_t bitLow[2]; static uint8_t bitLow[2];
uint8_t *currentBitSequence = bitLow; uint8_t *currentBitSequence = bitLow;
volatile SignalPart signal; volatile SignalPart signal = noSignal;
}; };

View File

@ -13,6 +13,16 @@ constexpr size_t kDefaultDmaTxMaxStreams = 4U;
// Не обрывать приём сразу при накопленной sync-ошибке — «дописывать» до таймаута (только вместе с IRDEBUG_SERIAL_PACK). // Не обрывать приём сразу при накопленной sync-ошибке — «дописывать» до таймаута (только вместе с IRDEBUG_SERIAL_PACK).
// #define IRDEBUG_SERIAL_SOFT_REJECT // #define IRDEBUG_SERIAL_SOFT_REJECT
// Краткий лог причин, почему физический сигнал не дошёл до распознанного пакета.
// Формат и коды: ref/IR_RX_BRIEF_LOG.md
#ifndef IR_RX_BRIEF_LOG
#define IR_RX_BRIEF_LOG 1
#endif
// 1: печатать только отклонённые/ошибочные события; успехи и шумовые PREAMB скрыть.
#ifndef IR_RX_BRIEF_LOG_REJECT_ONLY
#define IR_RX_BRIEF_LOG_REJECT_ONLY 1
#endif
// Журнал фронтов ИК в ISR; сброс строк @IRF1v1: в IR_DecoderRaw::tick(). См. ref/IR_EDGE_TRACE_FORMAT.md // Журнал фронтов ИК в ISR; сброс строк @IRF1v1: в IR_DecoderRaw::tick(). См. ref/IR_EDGE_TRACE_FORMAT.md
// Расход RAM ≈ IR_EDGE_TRACE_CAPACITY * 6 байт на декодер. Выключить — закомментировать: // Расход RAM ≈ IR_EDGE_TRACE_CAPACITY * 6 байт на декодер. Выключить — закомментировать:
// #define IR_EDGE_TRACE // #define IR_EDGE_TRACE
@ -154,6 +164,11 @@ typedef uint16_t crc_t;
#define subBufferSize 250 // Буфер для складирования фронтов, пока их не обработают (передатчик) #define subBufferSize 250 // Буфер для складирования фронтов, пока их не обработают (передатчик)
#endif #endif
/** Максимальное число передатчиков, способных временно заглушить один декодер. */
#ifndef IR_PAIR_MUTE_MAX_ENCODERS
#define IR_PAIR_MUTE_MAX_ENCODERS 8U
#endif
/** Минимальная длительность удержания уровня (мкс): короче — импульс/пара фронтов выкидывается до tick() /** Минимальная длительность удержания уровня (мкс): короче — импульс/пара фронтов выкидывается до tick()
* (иголки на плато, дребезг). 0 — фильтр выключен, фронты идут в декодер как с ISR. */ * (иголки на плато, дребезг). 0 — фильтр выключен, фронты идут в декодер как с ISR. */
#ifndef IR_INPUT_MIN_PULSE_US #ifndef IR_INPUT_MIN_PULSE_US
@ -204,8 +219,6 @@ typedef uint16_t crc_t;
#define preambPulse 3 #define preambPulse 3
#define disablePairDec false // Отключать парный приёмник, возможны баги, используйте setBlindDecoders()
///////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////
#define bitPerByte 8U // Колличество бит в байте #define bitPerByte 8U // Колличество бит в байте

View File

@ -13,13 +13,16 @@ public:
return start == end; return start == end;
} }
void push(T element) { bool push(T element) {
bool pushed = false;
noInterrupts(); noInterrupts();
if (!isFull()) { if (!isFull()) {
data[end] = element; data[end] = element;
end = (end + 1) % BufferSize; end = (end + 1) % BufferSize;
pushed = true;
} }
interrupts(); interrupts();
return pushed;
} }
T* pop() { T* pop() {

60
ref/IR_RX_BRIEF_LOG.md Normal file
View File

@ -0,0 +1,60 @@
# IR RX Brief Log
Краткий лог включается через:
```cpp
#define IR_RX_BRIEF_LOG 1
#define IR_RX_BRIEF_LOG_REJECT_ONLY 1 // только отклонённые/ошибочные события
```
Лог печатается короткими строками вида:
```text
IRRX t=1234567 rsn=CRC len=25 err=3
IRRX t=1234000 rsn=MUTE_BEGIN
IRRX t=1234988 rsn=MUTE_END cnt=42
```
Где:
- `t` — uptime МК в `micros()`
- `rsn` — краткий код причины
- остальные поля зависят от причины
## Коды `rsn`
| Код | Смысл | Типичные поля |
|-----|-------|---------------|
| `MUTE_BEGIN` | Началось окно mute: RX временно игнорирует вход, пока активен связанный TX | - |
| `MUTE_END` | Окно mute завершилось; `cnt` показывает число заблокированных фронтов за всё окно | `cnt` |
| `QRAW` | Потеря фронтов из-за переполнения сырой очереди `subBuffer` | `cnt` |
| `QFLT` | Потеря фронтов из-за переполнения очереди после входного фильтра | `total` |
| `HOLD` | Переполнен holdback фильтра до выпуска фронтов | `total` |
| `GLITCH` | Фронт/пара фронтов отброшены как глитч | `total` |
| `TIME` | Плохой тайминг фронтов/битов, кадр не может нормально разбираться | `rp`, `hp` |
| `PREAMB` | Кандидат преамбулы не залочился или был перезапущен | `good`, `per` |
| `SYNC` | Ошибка sync-бита привела к reject кадра | `err` |
| `BUF` | Переполнен битовый буфер кадра | `bits` |
| `TIMEOUT` | Кадр оборвался по таймауту до завершения | `bits`, `exp` |
| `CRC` | Кадр дошёл до конца по длине, но CRC не сошёлся | `len`, `err` |
| `OK` | Кадр успешно распознан | `len`, `err` |
## Поля
- `cnt` — число событий/фронтов, накопленных за окно или пакетную группу
- `total` — накопленный счётчик отбраковок данного типа
- `rp``risePeriod`
- `hp``highTime`
- `good` — число подряд подходящих периодов преамбулы перед срывом
- `per` — период преамбулы/кандидата
- `err` — суммарные ошибки `lowSignal + highSignal + other` либо счётчик sync-ошибок
- `bits` — сколько data-бит успело накопиться
- `exp` — ожидаемая длина кадра из первого байта, если уже известна
- `len` — полная длина кадра в байтах
## Когда смотреть подробный debug
- Если нужен полный поток битов и sync: включать `IRDEBUG_SERIAL_PACK`
- Если нужно понять, какие именно фронты пришли в ISR: включать `IR_EDGE_TRACE`
- `IR_RX_BRIEF_LOG` нужен как короткий always-on-ish индикатор сути проблемы, без длинного дампа
- `IR_RX_BRIEF_LOG_REJECT_ONLY=1` скрывает `OK` и `PREAMB`, оставляя только отклонения/ошибки