mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-09-18 19:13:58 +00:00
330 lines
18 KiB
C++
330 lines
18 KiB
C++
#pragma once
|
||
#include "IR_config.h"
|
||
#include "RingBuffer.h"
|
||
|
||
class Print;
|
||
|
||
#define IRDEBUG
|
||
|
||
#ifdef IRDEBUG
|
||
#define wrHigh 255 // Запись HIGH инициирована // green
|
||
#define wrLow 255 // Запись LOW инициирована // blue
|
||
#define writeOp 255 // Операция записи, 1 пульс для 0 и 2 для 1 // orange
|
||
// Исправленные ошибки // purle
|
||
// 1 пульс: fix
|
||
#define errOut 255
|
||
#define up 255
|
||
#define down 255
|
||
#endif
|
||
|
||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
#define riseTime riseSyncTime //* bitTime */ 893U // TODO: Должно высчитываться медианой
|
||
#define riseTolerance tolerance /* 250U */ // погрешность
|
||
#define riseTimeMax (riseTime + riseTolerance)
|
||
#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;
|
||
|
||
class IR_Encoder;
|
||
class IR_DecoderRaw : virtual public IR_FOX
|
||
{
|
||
friend IR_Encoder;
|
||
|
||
protected:
|
||
PackInfo packInfo;
|
||
uint8_t msgTypeReceive = 0;
|
||
IR_Encoder *encoder; // Указатель на парный передатчик
|
||
bool availableRaw();
|
||
|
||
public:
|
||
//////////////////////////////////////////////////////////////////////////
|
||
/// @brief Конструктор
|
||
/// @param pin Номер вывода прерывания/данных от приёмника (2 или 3 для atmega 328p)
|
||
/// @param addr Адрес приёмника
|
||
/// @param encPair Указатель на передатчик, работающий в паре
|
||
IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPair = nullptr);
|
||
|
||
void isr(); // Функция прерывания
|
||
void tick(); // Обработка приёмника, необходима для работы
|
||
|
||
inline bool isOverflow() { return isBufferOverflow; }; // Буффер переполнился
|
||
bool isSubOverflow();
|
||
volatile inline bool isReciving() { return isRecive; }; // Возвращает true, если происходит приём пакета
|
||
// Активность линии по СОСТОЯНИЮ (не по хардкод-длительности): кадр залочен ИЛИ формируется
|
||
// ВАЛИДНАЯ преамбула (>=1 совпавший по периоду фронт — отличает реальный кадр от одиночного
|
||
// шумового фронта, который лишь заводит Candidate, но не набирает goodPeriods). Для гейта заднего:
|
||
// «не стрелять, пока на линии идёт/формируется кадр (напр. ответ точки)». Аддитивно, const.
|
||
inline bool rxLineActive() const {
|
||
return isRecive ||
|
||
(preambleState == PreambleState::Candidate && preambleGoodPeriods >= 1U);
|
||
}
|
||
// Объявленная длина ПРИНИМАЕМОГО кадра (байт) из ПЕРВОГО байта, если он уже принят и валиден;
|
||
// иначе 0 (ещё не знаем / битый). До CRC это НЕДОВЕРЕННОЕ значение — потребитель, получив 0
|
||
// или чрезмерное, обязан брать rxMaxPackSize() (безопасно держать задний до конца макс.кадра).
|
||
inline uint16_t rxDeclaredPackSize() const {
|
||
return (isRecive && packSize && !isWrongPack) ? packSize : 0;
|
||
}
|
||
// Протокольный МАКСИМУМ длины кадра (байт) — верхняя граница бюджета удержания заднего.
|
||
static constexpr uint16_t rxMaxPackSize() { return (uint16_t)irMaxPackSize; }
|
||
|
||
// ---- Наблюдаемость приёма по СОСТОЯНИЮ: лок / ожидаемый конец / факт завершения с причиной ----
|
||
enum class RxEndReason : uint8_t { None = 0, Ok, Crc, Timeout, Abort };
|
||
struct RxEndInfo {
|
||
uint16_t seq = 0; // номер завершения (растёт на каждом терминале)
|
||
RxEndReason reason = RxEndReason::None;
|
||
uint8_t msgType = 0xFF; // 0xFF = первый байт не был принят
|
||
uint8_t packSize = 0; // объявленная длина (0 = неизвестна)
|
||
uint32_t tUs = 0; // micros() терминала
|
||
uint32_t expectedEndUs = 0; // расчётный конец кадра в эфире (0 = неизвестен)
|
||
};
|
||
uint16_t rxLockSeq() const { return rxLockSeqCnt; } // ++ в момент лока преамбулы
|
||
uint32_t rxLockTimeUs() const { return rxLockTimeUsVal; } // метка фронта лока (ISR-время)
|
||
/// Тип принимаемого кадра (3 бита) после первого байта; 0xFF пока неизвестен / приём не идёт.
|
||
uint8_t rxMsgType() const { return (isRecive && packSize) ? (uint8_t)((dataBuffer[0] >> 5) & IR_MASK_MSG_TYPE) : 0xFF; }
|
||
const RxEndInfo &rxLastEnd() const { return rxEnd; }
|
||
/// Расчётный момент последнего бита данных текущего кадра (по объявленной длине); 0 = не Locked / длина неизвестна.
|
||
uint32_t rxExpectedEndUs() const;
|
||
uint32_t pulseFilterDroppedByFilteredOverflow() const { return 0; }
|
||
uint32_t pulseFilterDroppedByHoldOverflow() const { return pulseFilterDropHoldOverflow; }
|
||
uint32_t pulseFilterDroppedGlitchPairs() const { return pulseFilterDropGlitchPairs; }
|
||
void pulseFilterResetStats();
|
||
|
||
#if defined(IR_EDGE_TRACE)
|
||
void edgeTraceClear();
|
||
bool edgeTraceOverflow() const { return edgeTrace_overflow; }
|
||
uint16_t edgeTracePendingCount() const;
|
||
/** При непустом кольце: перевод строки + @IRF1v1: + hex; в tick() сброс на Serial автоматически. См. ref/IR_EDGE_TRACE_FORMAT.md */
|
||
uint16_t edgeTraceFlushChunk(Print &out, uint16_t maxRec = 48);
|
||
#endif
|
||
|
||
/// Always-on счётчики RX-событий по причинам (см. RxBriefReason: 6=Glitch,
|
||
/// 7=Timing, 8=Preamble, 9=Sync, 10=BufOverflow, 11=Timeout, 12=Crc, 13=Ok).
|
||
/// MuteBegin/End и RawOverflow(1..3) тикают только при IR_RX_BRIEF_LOG (ISR-агрегат).
|
||
const uint16_t *rxReasonCounters() const { return rxReasonCnt; }
|
||
void rxReasonCountersClear() { for (uint8_t i = 0; i < 14; i++) rxReasonCnt[i] = 0; }
|
||
/// Однострочная сводка: "RXSTAT,GLITCH=..,TIME=..,PREAMB=..,SYNC=..,BUF=..,TIMEOUT=..,CRC=..,OK=.."
|
||
void printRxReasonStats(Print &out) const;
|
||
|
||
/// Кадр собран по длине из заголовка, но CRC не сошёлся — один раз можно прочитать копию сырых байтов.
|
||
bool availableReject();
|
||
uint8_t getRejectSize() const { return rejectPackSize; }
|
||
const uint8_t* getRejectBuffer() const { return rejectBuffer; }
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
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 = 0;
|
||
uint16_t crcValue = 0;
|
||
volatile uint16_t isPairSending = 0; // Число активных TX, временно глушащих этот RX.
|
||
volatile bool isRecive = false; // Флаг приёма
|
||
volatile bool isPreamb = false; // флаг начальной последовости
|
||
volatile bool isSubBufferOverflow = false;
|
||
bool isBufferOverflow = false; // Флаг переполнения буффера данных
|
||
bool isWrongPack = false; // Флаг битого пакета
|
||
|
||
uint16_t riseSyncTime = bitTime; // Подстраиваемое время бита в мкс
|
||
|
||
volatile uint32_t lastEdgeTime = 0; // время последнего фронта
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
volatile uint32_t currentSubBufferIndex; // Счетчик текущей позиции во вспомогательном буфере фронтов/спадов
|
||
|
||
struct FrontStorage
|
||
{ // Структура для хранения времени и направления фронта/спада
|
||
volatile uint32_t time = 0; // Время
|
||
volatile bool dir = false; // Направление (true = ↑; false = ↓)
|
||
// volatile FrontStorage *next = nullptr; // Указатель на следующий связанный фронт/спад, или nullptr если конец
|
||
};
|
||
volatile FrontStorage *lastFront = nullptr; // Указатель последнего фронта/спада
|
||
volatile FrontStorage *firstUnHandledFront = nullptr; // Указатель первого необработанного фронта/спада
|
||
// volatile FrontStorage subBuffer[subBufferSize]; // вспомогательный буфер для хранения необработанных фронтов/спадов
|
||
|
||
RingBuffer<FrontStorage, subBufferSize> subBuffer;
|
||
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;
|
||
bool pulseFilterLastRawValid = false;
|
||
uint32_t pulseFilterLastRawTime = 0;
|
||
uint32_t pulseFilterDropHoldOverflow = 0;
|
||
uint32_t pulseFilterDropGlitchPairs = 0;
|
||
static constexpr uint8_t kPreambleLockNeed = (uint8_t)IR_PREAMBLE_LOCK_RISE_PERIODS;
|
||
enum class PreambleState : uint8_t
|
||
{
|
||
Idle = 0,
|
||
Candidate = 1,
|
||
Locked = 2
|
||
};
|
||
PreambleState preambleState = PreambleState::Idle;
|
||
uint16_t rxLockSeqCnt = 0;
|
||
uint32_t rxLockTimeUsVal = 0;
|
||
RxEndInfo rxEnd;
|
||
void noteRxEnd(RxEndReason reason, uint32_t tUs); // терминал: фиксирует тип/длину/расчётный конец
|
||
void abortFrame(uint32_t tUs); // немедленный сброс битого кадра (sync/длина/overflow)
|
||
void expirePreambleCandidate(); // кандидат без фронтов дольше таймаута → Idle
|
||
uint8_t preambleGoodPeriods = 0;
|
||
uint16_t preambleMeanPeriod = 0;
|
||
uint32_t preambleCandidateLastEdgeTime = 0;
|
||
uint32_t preambleCandidateFirstRiseTime = 0;
|
||
bool preambleCandidateFirstRiseValid = false;
|
||
|
||
#if defined(IR_EDGE_TRACE)
|
||
struct IrEdgeTraceRec
|
||
{
|
||
uint32_t t_us;
|
||
uint8_t level;
|
||
uint8_t flags;
|
||
};
|
||
void edgeTracePush(uint32_t t_us, uint8_t level, uint8_t flags);
|
||
IrEdgeTraceRec edgeTrace_buf[IR_EDGE_TRACE_CAPACITY]{};
|
||
volatile uint16_t edgeTrace_w = 0;
|
||
volatile uint16_t edgeTrace_r = 0;
|
||
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; // Время предыдущих фронтов/спадов
|
||
|
||
volatile uint32_t risePeriod;
|
||
volatile uint32_t highTime;
|
||
volatile uint32_t lowTime;
|
||
|
||
uint16_t wrongCounter;
|
||
|
||
int8_t highCount;
|
||
int8_t lowCount;
|
||
int8_t allCount;
|
||
|
||
uint16_t errorCounter = 0; // Счётчик ошибок
|
||
int8_t preambFrontCounter = 0; // Счётчик __/``` ↑ преамбулы
|
||
int16_t bufBitPos = 0; // Позиция для записи бита в буффер
|
||
|
||
private:
|
||
bool isReciveRaw = false;
|
||
void listenStart();
|
||
void checkTimeout(); //
|
||
/** В очередях/hold фильтра ещё есть фронты — не оценивать таймаут по micros()-lastEdgeTime (ложный TIMEOUT). */
|
||
bool rxTimeoutPipelineBusy() const;
|
||
void pulseFilterPushRaw(const FrontStorage &e);
|
||
bool pulseFilterTryTakeConfirmed(FrontStorage &out, uint32_t logTime = 0);
|
||
bool pulseFilterTryFlushOne(uint32_t nowUs, FrontStorage &out);
|
||
void pulseFilterShiftLeft(uint8_t n);
|
||
void pulseFilterReset();
|
||
void processDecodedFront(const FrontStorage ¤tFront);
|
||
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();
|
||
void preambleStartCandidate(const FrontStorage &front);
|
||
bool preambleProcessEdge(const FrontStorage &front);
|
||
|
||
/// @brief Проверка CRC. Проверяет len байт со значением crc, пришедшим в пакете
|
||
/// @param len Длина в байтах проверяемых данных
|
||
/// @param crc Результат рассчёта crc (Выходной параметр)
|
||
/// @return true если crc верно
|
||
bool crcCheck(uint8_t len, uint16_t &crc);
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
bool isData = true; // Флаг относится ли бит к данным, или битам синхронизации
|
||
uint16_t i_dataBuffer = 0; // Счётчик буфера данных
|
||
uint16_t nextControlBit = bitPerByte; // Метка для смены флага isData; uint16_t нужен для длинных кадров (>24 байт total)
|
||
uint8_t i_syncBit = 0; // Счётчик битов синхронизации
|
||
uint8_t err_syncBit = 0; // Счётчик ошибок синхронизации
|
||
|
||
/// @brief Запиь бита в буффер, а так же проверка битов синхранизации и их фильтрация
|
||
/// @param packTraceInvertFix если true — в IRDEBUG_SERIAL_PACK бит в трассе пишется как `0`/`1` (исправление по фронтам)
|
||
void writeToBuffer(bool bit, bool packTraceInvertFix = false);
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
void firstRX(); /// @brief Установка и сброс начальных значений и флагов в готовность к приёму данных
|
||
|
||
/// @brief Целочисленное деление с округлением вверх
|
||
/// @param val Значение
|
||
/// @param divider Делитель
|
||
/// @return Результат
|
||
uint16_t ceil_div(uint16_t val, uint16_t divider);
|
||
|
||
// rxBriefLog безусловен: ВСЕГДА инкрементирует rxReasonCnt[reason]
|
||
// (always-on наблюдаемость по контракту живучести), печать события —
|
||
// только при IR_RX_BRIEF_LOG. Вызовы в местах отказов тоже безусловны.
|
||
void rxBriefLog(RxBriefReason reason, uint16_t a = 0, uint16_t b = 0, uint32_t tUs = 0);
|
||
uint16_t rxReasonCnt[14] = {}; // индекс = (uint8_t)RxBriefReason, 1..13
|
||
#if IR_RX_BRIEF_LOG
|
||
static const __FlashStringHelper *rxBriefReasonTag(RxBriefReason reason);
|
||
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);
|
||
inline void infoPulse(uint8_t pin, uint8_t count);
|
||
#endif
|
||
|
||
#if defined(IRDEBUG_SERIAL_PACK)
|
||
static constexpr uint16_t kPackTraceBufCap =
|
||
uint16_t(dataByteSizeMax) * (uint16_t(bitPerByte) + uint16_t(syncBits)) + 48u;
|
||
|
||
void packTraceResetFrame();
|
||
void packTracePushBit(bool bit);
|
||
void packTracePushChar(char c);
|
||
/** Помечает в packTraceBitBuf бит (после BRUTEFORCE_CHECK) обёрткой `0`/`1` по финальному значению в dataBuffer. */
|
||
void packTraceWrapDataBitInBackticks(uint16_t byteIndex, uint8_t bitInByte);
|
||
/** IR hex: все байты dataBuffer[0 .. byteCount-1] в hex. */
|
||
void packTraceEmitHex(uint8_t byteCount) const;
|
||
/** IR raw: биты и синхра; тройной пробел между блоками msg/from/to/data/CRC; первый байт 3+пробел+5. endWithNewline — перевод строки после сырой строки. */
|
||
void packTraceEmitRawBitsLine(bool endWithNewline = true) const;
|
||
void packTraceEmitErrorFlash(const __FlashStringHelper *msg);
|
||
void packTraceEmitEndOk(uint8_t packSize);
|
||
void packTraceEmitEndBadCrc(uint8_t packSize);
|
||
void packTraceOnTimeoutOrAbort(bool fromListenStart);
|
||
void packTraceForceEndSyncPhase();
|
||
bool packTraceSoftReject() const;
|
||
|
||
bool packTraceOpen = false;
|
||
bool packTraceHadWrongSync = false;
|
||
char packTraceBitBuf[kPackTraceBufCap]{};
|
||
uint16_t packTraceLen = 0;
|
||
#endif
|
||
};
|