mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-04-28 03:08:08 +00:00
281 lines
14 KiB
C++
281 lines
14 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, если происходит приём пакета
|
||
uint32_t pulseFilterDroppedByFilteredOverflow() const { return pulseFilterDropFilteredOverflow; }
|
||
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
|
||
|
||
/// Кадр собран по длине из заголовка, но 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;
|
||
/** Очередь фронтов после потокового анти-глитча; 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;
|
||
bool pulseFilterLastRawValid = false;
|
||
uint32_t pulseFilterLastRawTime = 0;
|
||
uint32_t pulseFilterDropFilteredOverflow = 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;
|
||
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;
|
||
/** Один сырой фронт из subBuffer -> потоковый holdback-антиглитч. */
|
||
void pulseFilterFeedOneRaw(const FrontStorage &e);
|
||
void pulseFilterFlushTimeout(uint32_t nowUs);
|
||
bool pulseFilterEmit(const FrontStorage &e);
|
||
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();
|
||
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);
|
||
|
||
#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);
|
||
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
|
||
};
|