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

@ -25,7 +25,30 @@ class Print;
#define riseTimeMin (riseTime - riseTolerance)
#define aroundRise(t) (riseTimeMin < t && t < riseTimeMax)
#define IR_timeout (riseTimeMax * (8 + syncBits + 1)) // us // таймаут в 8 data + 3 sync + 1
constexpr uint16_t IR_ResponseDelay = ((uint16_t)(((bitTime+riseTolerance) * (8 + syncBits + 1))*2.7735))/1000;
constexpr uint16_t IR_ResponseDelay = irproto::kMandatoryInterPacketQuietMs;
/** Why the most recent observable receive attempt reached a terminal state. */
enum class IR_RxTerminalReason : uint8_t
{
None = 0,
FrameOk,
FrameCrcError,
LockedTimeout,
DecodeAbort,
CandidateTimeout
};
/**
* Monotonic receive-completion snapshot for schedulers polling after decoder.tick().
* seq is allowed to wrap; consumers only compare it with their previous snapshot.
*/
struct IR_RxTerminalInfo
{
uint32_t seq = 0;
IR_RxTerminalReason reason = IR_RxTerminalReason::None;
uint8_t msgType = 0xFFU;
bool hadLock = false;
};
class IR_Encoder;
class IR_DecoderRaw : virtual public IR_FOX
@ -52,14 +75,28 @@ public:
inline bool isOverflow() { return isBufferOverflow; }; // Буффер переполнился
bool isSubOverflow();
volatile inline bool isReciving() { return isRecive; }; // Возвращает true, если происходит приём пакета
// Активность линии по СОСТОЯНИЮ (не по хардкод-длительности): кадр залочен ИЛИ формируется
// ВАЛИДНАЯ преамбула (>=1 совпавший по периоду фронт — отличает реальный кадр от одиночного
// шумового фронта, который лишь заводит Candidate, но не набирает goodPeriods). Для гейта заднего:
// «не стрелять, пока на линии идёт/формируется кадр (напр. ответ точки)». Аддитивно, const.
// Активность линии по СОСТОЯНИЮ (не по хардкод-длительности): кадр залочен ИЛИ открыт
// Candidate после первого post-silence rise. Даже сильно искажённый ответ может не дать ни
// одного coarse-valid периода, поэтому Candidate остаётся активным до lock/terminal либо
// доказанной тишины по candidate timeout. Для гейта заднего: «не стрелять, пока на линии
// идёт/формируется потенциальный кадр (напр. ответ точки)». Аддитивно, const.
inline bool rxLineActive() const {
return isRecive ||
(preambleState == PreambleState::Candidate && preambleGoodPeriods >= 1U);
(preambleState == PreambleState::Candidate && preambleWasObservable);
}
/**
* True while a real frame is active or ISR/filter work is still queued.
* This closes the one-loop ordering gap when Timer::tick() runs before
* decoder.tick(): a transmitter must not start while an unprocessed edge
* is already waiting in the receive pipeline.
*/
bool rxPipelineActive() const;
/**
* Last terminal RX transition. Updated from tick()/decode context, never from ISR.
* A frame that starts and finishes within one tick is observable through seq.
*/
IR_RxTerminalInfo rxLastTerminal() const { return rxTerminalInfo; }
uint32_t rxTerminalSeq() const { return rxTerminalInfo.seq; }
// Объявленная длина ПРИНИМАЕМОГО кадра (байт) из ПЕРВОГО байта, если он уже принят и валиден;
// иначе 0 (ещё не знаем / битый). До CRC это НЕДОВЕРЕННОЕ значение — потребитель, получив 0
// или чрезмерное, обязан брать rxMaxPackSize() (безопасно держать задний до конца макс.кадра).
@ -67,7 +104,9 @@ public:
return (isRecive && packSize && !isWrongPack) ? packSize : 0;
}
// Протокольный МАКСИМУМ длины кадра (байт) — верхняя граница бюджета удержания заднего.
static constexpr uint16_t rxMaxPackSize() { return (uint16_t)dataByteSizeMax; }
static constexpr uint16_t rxMaxPackSize() {
return static_cast<uint16_t>(irproto::kMaxWireFrameBytes);
}
uint32_t pulseFilterDroppedByFilteredOverflow() const { return 0; }
uint32_t pulseFilterDroppedByHoldOverflow() const { return pulseFilterDropHoldOverflow; }
uint32_t pulseFilterDroppedGlitchPairs() const { return pulseFilterDropGlitchPairs; }
@ -127,6 +166,7 @@ private:
volatile bool isSubBufferOverflow = false;
bool isBufferOverflow = false; // Флаг переполнения буффера данных
bool isWrongPack = false; // Флаг битого пакета
IR_RxTerminalInfo rxTerminalInfo;
uint16_t riseSyncTime = bitTime; // Подстраиваемое время бита в мкс
@ -164,6 +204,10 @@ private:
};
PreambleState preambleState = PreambleState::Idle;
uint8_t preambleGoodPeriods = 0;
// Sticky potential-frame latch for one Candidate epoch. After one plausible
// rise period, coarse-invalid activity remains busy until proven silence;
// the scheduler's hard deadline may skip an optional tail under noise.
bool preambleWasObservable = false;
uint16_t preambleMeanPeriod = 0;
uint32_t preambleCandidateLastEdgeTime = 0;
uint32_t preambleCandidateFirstRiseTime = 0;
@ -232,6 +276,9 @@ bool isReciveRaw = false;
void preambleResetToIdle();
void preambleStartCandidate(const FrontStorage &front);
bool preambleProcessEdge(const FrontStorage &front);
void expirePreambleCandidateIfIdle(uint32_t nowUs);
uint8_t currentRxMsgType() const;
void noteRxTerminal(IR_RxTerminalReason reason, uint8_t msgType, bool hadLock);
/// @brief Проверка CRC. Проверяет len байт со значением crc, пришедшим в пакете
/// @param len Длина в байтах проверяемых данных