Make IR timing and RX terminal state explicit

This commit is contained in:
2026-09-04 19:22:03 +03:00
parent 6c97d33c7c
commit 00e101990f
8 changed files with 869 additions and 29 deletions

View File

@ -374,15 +374,45 @@ bool IR_DecoderRaw::rxTimeoutPipelineBusy() const
return busy;
}
bool IR_DecoderRaw::rxPipelineActive() const
{
return rxLineActive() || rxTimeoutPipelineBusy();
}
uint8_t IR_DecoderRaw::currentRxMsgType() const
{
if (i_dataBuffer < static_cast<uint16_t>(msgBytes) * bitPerByte)
return 0xFFU;
return static_cast<uint8_t>((dataBuffer[0] >> 5U) & IR_MASK_MSG_TYPE);
}
void IR_DecoderRaw::noteRxTerminal(IR_RxTerminalReason reason, uint8_t msgType, bool hadLock)
{
++rxTerminalInfo.seq;
rxTerminalInfo.reason = reason;
rxTerminalInfo.msgType = msgType;
rxTerminalInfo.hadLock = hadLock;
}
void IR_DecoderRaw::listenStart()
{
if (rxTimeoutPipelineBusy())
return;
if (isReciveRaw && ((micros() - lastEdgeTime) > IR_timeout * 2U))
const uint32_t nowUs = micros();
if (isReciveRaw && ((nowUs - lastEdgeTime) > IR_timeout * 2U))
{
#if defined(IRDEBUG_SERIAL_PACK)
packTraceOnTimeoutOrAbort(true);
#endif
if (isRecive)
{
const uint16_t expected =
(i_dataBuffer >= 8U) ? uint16_t(dataBuffer[0] & IR_MASK_MSG_INFO) : 0U;
rxBriefLog(RxBriefReason::Timeout, i_dataBuffer, expected, nowUs);
noteRxTerminal(IR_RxTerminalReason::LockedTimeout, currentRxMsgType(), true);
isRecive = false;
msgTypeReceive = 0;
}
isReciveRaw = false;
firstRX();
}
@ -403,6 +433,7 @@ inline void IR_DecoderRaw::checkTimeout()
#endif
const uint16_t expected = (i_dataBuffer >= 8U) ? uint16_t(dataBuffer[0] & IR_MASK_MSG_INFO) : 0U;
rxBriefLog(RxBriefReason::Timeout, i_dataBuffer, expected, micros());
noteRxTerminal(IR_RxTerminalReason::LockedTimeout, currentRxMsgType(), true);
isRecive = false; // приём завершён
msgTypeReceive = 0;
// Как после listenStart(): без сброса isReciveRaw + firstRX() декодер остаётся
@ -473,6 +504,7 @@ void IR_DecoderRaw::tick()
isSubBufferOverflow = false;
listenStart();
checkTimeout();
expirePreambleCandidateIfIdle(micros());
#if defined(IR_EDGE_TRACE)
while (edgeTraceFlushChunk(Serial, 48) > 0) {}
#endif
@ -480,6 +512,7 @@ void IR_DecoderRaw::tick()
} // Если данных нет - ничего не делаем
listenStart();
checkTimeout();
expirePreambleCandidateIfIdle(micros());
#if IR_RX_BRIEF_LOG
rxBriefFlushDeferredIsrLogs();
#endif
@ -778,6 +811,13 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
}
if (isBufferOverflow || isPreamb || isWrongPack)
{
const bool hadLock =
isRecive || isReciveRaw || preambleState == PreambleState::Locked;
const bool wasObservable =
hadLock ||
(preambleState == PreambleState::Candidate && preambleWasObservable);
if (wasObservable)
noteRxTerminal(IR_RxTerminalReason::DecodeAbort, currentRxMsgType(), hadLock);
// Как checkTimeout/listenStart: firstRX() сбрасывает буфер битов, преамбулу и
// pulseFilterReset() — при IR_INPUT_MIN_PULSE_US > 0 иначе остаётся «хвост» в hold/filtered.
isRecive = false;
@ -941,6 +981,9 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
#endif
}
#endif
noteRxTerminal(isAvailable ? IR_RxTerminalReason::FrameOk
: IR_RxTerminalReason::FrameCrcError,
currentRxMsgType(), true);
#if defined(IRDEBUG_SERIAL_PACK)
if (isAvailable)
packTraceEmitEndOk(static_cast<uint8_t>(packSize));
@ -1583,6 +1626,7 @@ void IR_DecoderRaw::preambleResetToIdle()
{
preambleState = PreambleState::Idle;
preambleGoodPeriods = 0;
preambleWasObservable = false;
preambleMeanPeriod = 0;
preambleCandidateLastEdgeTime = 0;
preambleCandidateFirstRiseTime = 0;
@ -1597,6 +1641,10 @@ void IR_DecoderRaw::preambleStartCandidate(const FrontStorage &front)
{
preambleState = PreambleState::Candidate;
preambleGoodPeriods = 0;
// The first post-silence rise already opens a potential frame epoch.
// Keep the line busy until that epoch locks or expires after real silence:
// even a badly distorted response may contain no coarse-valid rise period.
preambleWasObservable = true;
preambleMeanPeriod = 0;
preambleCandidateLastEdgeTime = front.time;
preambleCandidateFirstRiseTime = front.time;
@ -1607,6 +1655,24 @@ void IR_DecoderRaw::preambleStartCandidate(const FrontStorage &front)
isReciveRaw = false;
}
void IR_DecoderRaw::expirePreambleCandidateIfIdle(uint32_t nowUs)
{
if (preambleState != PreambleState::Candidate || rxTimeoutPipelineBusy())
return;
const uint32_t candTimeout =
IR_timeout * static_cast<uint32_t>(IR_PREAMBLE_CANDIDATE_TIMEOUT_MULT);
if ((uint32_t)(nowUs - preambleCandidateLastEdgeTime) <= candTimeout)
return;
const uint8_t goodPeriods = preambleGoodPeriods;
const bool wasObservable = preambleWasObservable;
rxBriefLog(RxBriefReason::Preamble, goodPeriods, 0, nowUs);
preambleResetToIdle();
if (wasObservable)
noteRxTerminal(IR_RxTerminalReason::CandidateTimeout, 0xFFU, false);
}
bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
{
const uint32_t longSilence = IR_timeout * 2U;
@ -1622,7 +1688,10 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
if (!isReciveRaw && front.dir &&
((prevRise == 0U && front.time > longSilence) ||
(prevRise != 0U && (uint32_t)(front.time - prevRise) > longSilence)))
{
preambleStartCandidate(front);
return true;
}
}
if (preambleState == PreambleState::Candidate)
@ -1630,7 +1699,10 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
if ((uint32_t)(front.time - preambleCandidateLastEdgeTime) > candTimeout)
{
rxBriefLog(RxBriefReason::Preamble, preambleGoodPeriods, 0, front.time);
if (preambleWasObservable)
noteRxTerminal(IR_RxTerminalReason::CandidateTimeout, 0xFFU, false);
preambleStartCandidate(front);
return true;
}
preambleCandidateLastEdgeTime = front.time;
@ -1648,15 +1720,20 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
preambleCandidateFirstRiseTime = front.time;
if (!preambleRisePeriodCoarseOk(period))
{
rxBriefLog(RxBriefReason::Preamble, preambleGoodPeriods,
irClampU16(period), front.time);
preambleGoodPeriods = 0;
preambleMeanPeriod = 0;
rxBriefLog(RxBriefReason::Preamble, 0, irClampU16(period), front.time);
// Keep preambleWasObservable sticky: this edge proves the medium is
// still active, but not that a possible physical frame has ended.
// Only silence timeout or a real locked terminal releases it.
return true;
}
if (preambleGoodPeriods == 0)
{
preambleGoodPeriods = 1;
preambleWasObservable = true;
preambleMeanPeriod = (uint16_t)period;
}
else
@ -1673,6 +1750,7 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
{
rxBriefLog(RxBriefReason::Preamble, preambleGoodPeriods, irClampU16(period), front.time);
preambleGoodPeriods = 1;
preambleWasObservable = true;
preambleMeanPeriod = (uint16_t)period;
}
}