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