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)
{
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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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++)
{
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;
}
}

View File

@ -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;
};

View File

@ -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 // Колличество бит в байте

View File

@ -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
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`, оставляя только отклонения/ошибки