Filters and stable

This commit is contained in:
2026-04-08 16:11:10 +03:00
parent c29fe2cf7c
commit ddb8a9e143
3 changed files with 475 additions and 98 deletions

View File

@ -3,6 +3,29 @@
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#if IR_GLITCH_REJECT_PHASE_NUDGE
/** Подтяжка опоры фазы после отброса шипа (как irfoxGlitchPhaseNudgeUs в плагине). */
static inline void irGlitchPhaseNudge(uint32_t edgeTime, uint16_t riseSync, volatile uint32_t &prevRise)
{
if (edgeTime > riseSync)
{
const uint32_t nudged = edgeTime - riseSync;
if (nudged > prevRise && nudged < edgeTime)
prevRise = nudged;
}
}
#endif
static inline uint8_t irPulseFilterHoldbackEdges()
{
uint8_t hb = (uint8_t)IR_INPUT_FILTER_HOLDBACK_EDGES;
if (hb < 2U)
hb = 2U;
if (hb > 4U)
hb = 4U;
return hb;
}
IR_DecoderRaw::IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPair) : encoder(encPair) IR_DecoderRaw::IR_DecoderRaw(const uint8_t pin, uint16_t addr, IR_Encoder *encPair) : encoder(encPair)
{ {
setPin(pin); setPin(pin);
@ -44,6 +67,13 @@ bool IR_DecoderRaw::availableRaw()
} }
}; };
void IR_DecoderRaw::pulseFilterResetStats()
{
pulseFilterDropFilteredOverflow = 0;
pulseFilterDropHoldOverflow = 0;
pulseFilterDropGlitchPairs = 0;
}
//////////////////////////////////// isr /////////////////////////////////////////// //////////////////////////////////// isr ///////////////////////////////////////////
volatile uint32_t time_; volatile uint32_t time_;
@ -101,11 +131,13 @@ void IR_DecoderRaw::firstRX()
wrCounter = 0; wrCounter = 0;
#endif #endif
memset(dataBuffer, 0x00, dataByteSizeMax); memset(dataBuffer, 0x00, dataByteSizeMax);
pulseFilterReset();
preambleResetToIdle();
} }
void IR_DecoderRaw::listenStart() void IR_DecoderRaw::listenStart()
{ {
if (isReciveRaw && ((micros() - prevRise) > IR_timeout * 2)) if (isReciveRaw && ((micros() - lastEdgeTime) > IR_timeout * 2U))
{ {
#if defined(IRDEBUG_SERIAL_PACK) #if defined(IRDEBUG_SERIAL_PACK)
packTraceOnTimeoutOrAbort(true); packTraceOnTimeoutOrAbort(true);
@ -136,49 +168,119 @@ inline void IR_DecoderRaw::checkTimeout()
void IR_DecoderRaw::tick() void IR_DecoderRaw::tick()
{ {
// FrontStorage *currentFrontPtr;
// noInterrupts();
// currentFrontPtr = subBuffer.pop();
// interrupts();
FrontStorage currentFront; FrontStorage currentFront;
bool hasCurrentFront = false;
FrontStorage rawFront;
bool hasRawFront = false;
noInterrupts(); noInterrupts();
listenStart(); FrontStorage *rawPtr = subBuffer.pop();
FrontStorage *currentFrontPtr; if (rawPtr != nullptr)
currentFrontPtr = subBuffer.pop(); {
if (currentFrontPtr == nullptr) rawFront = *rawPtr;
hasRawFront = true;
}
interrupts();
if (IR_INPUT_MIN_PULSE_US > 0U)
{
if (hasRawFront)
pulseFilterFeedOneRaw(rawFront);
else
pulseFilterFlushTimeout(micros());
noInterrupts();
FrontStorage *flt = filteredSubBuffer.pop();
if (flt != nullptr)
{
currentFront = *flt;
hasCurrentFront = true;
}
interrupts();
}
else if (hasRawFront)
{
currentFront = rawFront;
hasCurrentFront = true;
}
if (!hasCurrentFront)
{ {
isSubBufferOverflow = false; isSubBufferOverflow = false;
checkTimeout(); // <--- новое место проверки bool rawQueueHasPending = false;
bool filteredQueueHasPending = false;
noInterrupts();
rawQueueHasPending = !subBuffer.isEmpty();
if (IR_INPUT_MIN_PULSE_US > 0U)
filteredQueueHasPending = !filteredSubBuffer.isEmpty();
interrupts(); interrupts();
const bool filterHoldHasPending = (IR_INPUT_MIN_PULSE_US > 0U) && (pulseFilterHoldCount > 0U);
const bool hasPendingEdges = hasRawFront || rawQueueHasPending || filteredQueueHasPending || filterHoldHasPending;
if (!hasPendingEdges)
{
listenStart();
checkTimeout();
}
#if defined(IR_EDGE_TRACE) #if defined(IR_EDGE_TRACE)
while (edgeTraceFlushChunk(Serial, 48) > 0) {} while (edgeTraceFlushChunk(Serial, 48) > 0) {}
#endif #endif
return; return;
} // Если данных нет - ничего не делаем } // Если данных нет - ничего не делаем
currentFront = *currentFrontPtr;
interrupts();
// ---------- буфер пуст: фронтов нет, проверяем тайм-аут ---------- if (preambleProcessEdge(currentFront))
// if (currentFrontPtr == nullptr) {
// { lastEdgeTime = currentFront.time;
// isSubBufferOverflow = false; goto END;
// return; }
// }
// // ---------- есть фронт: продолжаем обработку ----------
// FrontStorage currentFront = *currentFrontPtr;
lastEdgeTime = currentFront.time; // запоминаем любой фронт lastEdgeTime = currentFront.time; // запоминаем любой фронт
//////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (currentFront.dir) if (currentFront.dir)
{ // Если __/``` ↑ { // Если __/``` ↑
if (currentFront.time - prevRise > riseTimeMax / 4 || highCount || lowCount) const uint32_t candRp = currentFront.time - prevRise;
const uint32_t candHt = currentFront.time - prevFall;
const uint32_t candLt = prevFall - prevRise;
#if IR_SHORT_LOW_GLITCH_REJECT
const bool short_low_glitch =
isRecive && !isPreamb && candHt < (riseTimeMin / 8U) && candLt >= riseTimeMin &&
candRp >= riseTimeMin && candRp <= IR_timeout;
if (short_low_glitch)
{
errors.other++;
#if IR_GLITCH_REJECT_PHASE_NUDGE
irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise);
#endif
goto END;
}
#endif
#if IR_MICRO_GAP_RISE_REJECT
const bool micro_gap_cand_lt_ok =
(candLt >= riseTimeMin) || (candLt >= (riseTimeMin / 4U) && candLt < riseTimeMin);
const bool micro_gap_rise = isRecive && !isPreamb && candHt < (riseTimeMin / 8U) && micro_gap_cand_lt_ok &&
candRp >= (riseTimeMin / 4U) && candRp < riseTimeMin && candRp <= IR_timeout;
if (micro_gap_rise)
{
errors.other++;
#if IR_GLITCH_REJECT_PHASE_NUDGE
irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise);
#endif
goto END;
}
#endif
if (candRp <= riseTimeMax / 4U && !highCount && !lowCount)
{
errors.other++;
goto END;
}
if (candRp > riseTimeMax / 4 || highCount || lowCount)
{ // комплексный фикс рваной единицы { // комплексный фикс рваной единицы
risePeriod = currentFront.time - prevRise; risePeriod = candRp;
highTime = currentFront.time - prevFall; highTime = candHt;
lowTime = prevFall - prevRise; lowTime = candLt;
prevRise = currentFront.time; prevRise = currentFront.time;
if ( if (
@ -231,79 +333,6 @@ void IR_DecoderRaw::tick()
digitalWrite(errOut, currentFront.dir); digitalWrite(errOut, currentFront.dir);
#endif #endif
if (currentFront.time > prevRise && currentFront.time - prevRise > IR_timeout * 2 && !isReciveRaw)
{ // первый
#ifdef IRDEBUG
errPulse(up, 50);
errPulse(down, 50);
errPulse(up, 150);
errPulse(down, 150);
#endif
preambFrontCounter = preambFronts - 1U;
isPreamb = true;
isRecive = true;
isReciveRaw = true;
isWrongPack = false;
#if defined(IRDEBUG_SERIAL_PACK)
packTraceResetFrame();
packTraceOpen = true;
#endif
}
//-------------------------------------------------------------------------------------------------------
if (preambFrontCounter)
{ // в преамбуле
#ifdef IRDEBUG
// Serial.print("risePeriod: ");
// Serial.println(risePeriod);
#endif
if (currentFront.dir && risePeriod < IR_timeout)
{ // __/``` ↑ и мы в внутри пакета
if (risePeriod < riseTimeMin / 2)
{ // fix рваной единицы
preambFrontCounter += 2;
errors.other++;
#ifdef IRDEBUG
errPulse(down, 350);
#endif
}
else
{
if (freeFrec)
{
riseSyncTime = (riseSyncTime + risePeriod / 2) / 2;
} // tuner
}
}
else
{ /* riseSyncTime = bitTime; */
} // сброс тюнера
preambFrontCounter--;
// Serial.print("preambFrontCounter: "); Serial.println(preambFrontCounter);
}
else
{
if (isPreamb)
{ // первый фронт после
// gotTune.set(riseSyncTime);
isPreamb = false;
#ifdef IRDEBUG
errPulse(up, 50);
errPulse(down, 50);
#endif
prevRise += risePeriod / 2;
// prevRise = currentFront.time + riseTime;
goto END;
}
}
if (isPreamb)
{
goto END;
}
if (risePeriod > IR_timeout || isBufferOverflow || risePeriod < riseTimeMin || isWrongPack) if (risePeriod > IR_timeout || isBufferOverflow || risePeriod < riseTimeMin || isWrongPack)
// ~Мы в пределах таймаута и буффер не переполнен и fix дроблёных единиц // ~Мы в пределах таймаута и буффер не переполнен и fix дроблёных единиц
{ {
@ -487,6 +516,7 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
{ {
isRecive = false; isRecive = false;
isReciveRaw = false; isReciveRaw = false;
preambleResetToIdle();
msgTypeReceive = 0; msgTypeReceive = 0;
return; return;
} }
@ -598,6 +628,7 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
isRecive = false; isRecive = false;
isReciveRaw = false; isReciveRaw = false;
preambleResetToIdle();
msgTypeReceive = 0; msgTypeReceive = 0;
isAvailable = crcCheck(packSize - crcBytes, crcValue); isAvailable = crcCheck(packSize - crcBytes, crcValue);
@ -1147,6 +1178,265 @@ __attribute__((weak)) void irPackTracePrintOkCommand(const uint8_t *buf, uint8_t
#endif // IRDEBUG_SERIAL_PACK #endif // IRDEBUG_SERIAL_PACK
uint32_t IR_DecoderRaw::absDiffU32(uint32_t a, uint32_t b)
{
return (a > b) ? (a - b) : (b - a);
}
void IR_DecoderRaw::pulseFilterShiftLeft(uint8_t n)
{
if (n == 0 || pulseFilterHoldCount == 0)
return;
if (n >= pulseFilterHoldCount)
{
pulseFilterHoldCount = 0;
return;
}
const uint8_t newCount = static_cast<uint8_t>(pulseFilterHoldCount - n);
for (uint8_t i = 0; i < newCount; ++i)
pulseFilterHoldEdges[i] = pulseFilterHoldEdges[static_cast<uint8_t>(i + n)];
pulseFilterHoldCount = newCount;
}
bool IR_DecoderRaw::pulseFilterEmit(const FrontStorage &e)
{
if (filteredSubBuffer.isFull())
{
pulseFilterDropFilteredOverflow++;
return false;
}
filteredSubBuffer.push(e);
return true;
}
void IR_DecoderRaw::pulseFilterReset()
{
pulseFilterHoldCount = 0;
pulseFilterLastRawValid = false;
pulseFilterLastRawTime = 0;
while (filteredSubBuffer.pop() != nullptr) {}
}
void IR_DecoderRaw::pulseFilterFeedOneRaw(const FrontStorage &e)
{
const uint32_t minUs = IR_INPUT_MIN_PULSE_US;
if (minUs == 0U)
{
pulseFilterEmit(e);
return;
}
pulseFilterLastRawTime = e.time;
pulseFilterLastRawValid = true;
if (pulseFilterHoldCount >= kPulseFilterHoldCap)
{
pulseFilterDropHoldOverflow++;
pulseFilterEmit(pulseFilterHoldEdges[0]);
pulseFilterShiftLeft(1);
}
pulseFilterHoldEdges[pulseFilterHoldCount++] = e;
const uint8_t holdback = irPulseFilterHoldbackEdges();
for (;;)
{
if (pulseFilterHoldCount < 2)
return;
const uint32_t dt = pulseFilterHoldEdges[1].time - pulseFilterHoldEdges[0].time;
if (dt < minUs)
{
pulseFilterDropGlitchPairs++;
pulseFilterShiftLeft(2);
continue;
}
if (pulseFilterHoldCount <= holdback)
return;
pulseFilterEmit(pulseFilterHoldEdges[0]);
pulseFilterShiftLeft(1);
}
}
void IR_DecoderRaw::pulseFilterFlushTimeout(uint32_t nowUs)
{
if (IR_INPUT_MIN_PULSE_US == 0U || !pulseFilterLastRawValid || pulseFilterHoldCount == 0)
return;
const uint32_t waitUs = IR_INPUT_MIN_PULSE_US * (uint32_t)IR_INPUT_FILTER_TIMEOUT_MULT;
if ((uint32_t)(nowUs - pulseFilterLastRawTime) < waitUs)
return;
while (pulseFilterHoldCount > 0)
{
if (pulseFilterHoldCount >= 2)
{
const uint32_t dt = pulseFilterHoldEdges[1].time - pulseFilterHoldEdges[0].time;
if (dt < IR_INPUT_MIN_PULSE_US)
{
pulseFilterDropGlitchPairs++;
pulseFilterShiftLeft(2);
continue;
}
}
pulseFilterEmit(pulseFilterHoldEdges[0]);
pulseFilterShiftLeft(1);
}
}
uint32_t IR_DecoderRaw::preambleJitterTolUs(uint32_t baselineUs) const
{
const uint32_t pct = (baselineUs * (uint32_t)IR_PREAMBLE_JITTER_PCT) / 100U;
return (pct > (uint32_t)IR_PREAMBLE_JITTER_US_MIN) ? pct : (uint32_t)IR_PREAMBLE_JITTER_US_MIN;
}
bool IR_DecoderRaw::preambleRisePeriodCoarseOk(uint32_t periodUs) const
{
const uint32_t base = (uint32_t)bitTime;
const uint32_t minP = (base * (uint32_t)IR_PREAMBLE_PERIOD_MIN_FACTOR_PCT) / 100U;
const uint32_t maxP = (base * (uint32_t)IR_PREAMBLE_PERIOD_MAX_FACTOR_PCT) / 100U;
return periodUs >= minP && periodUs <= maxP;
}
void IR_DecoderRaw::preambleResetToIdle()
{
preambleState = PreambleState::Idle;
preambleGoodPeriods = 0;
preambleMeanPeriod = 0;
preambleCandidateLastEdgeTime = 0;
preambleCandidateFirstRiseTime = 0;
preambleCandidateFirstRiseValid = false;
preambFrontCounter = 0;
isPreamb = false;
isWrongPack = false;
isBufferOverflow = false;
}
void IR_DecoderRaw::preambleStartCandidate(const FrontStorage &front)
{
preambleState = PreambleState::Candidate;
preambleGoodPeriods = 0;
preambleMeanPeriod = 0;
preambleCandidateLastEdgeTime = front.time;
preambleCandidateFirstRiseTime = front.time;
preambleCandidateFirstRiseValid = front.dir;
preambFrontCounter = 0;
isPreamb = true;
isRecive = false;
isReciveRaw = false;
}
bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
{
const uint32_t longSilence = IR_timeout * 2U;
const uint32_t candTimeout = IR_timeout * (uint32_t)IR_PREAMBLE_CANDIDATE_TIMEOUT_MULT;
if (preambleState == PreambleState::Idle)
{
if (isReciveRaw)
return false;
if (!isReciveRaw && front.dir && front.time > prevRise && (front.time - prevRise) > longSilence)
preambleStartCandidate(front);
}
if (preambleState == PreambleState::Candidate)
{
if ((uint32_t)(front.time - preambleCandidateLastEdgeTime) > candTimeout)
preambleStartCandidate(front);
preambleCandidateLastEdgeTime = front.time;
if (!front.dir)
return true;
if (!preambleCandidateFirstRiseValid)
{
preambleCandidateFirstRiseValid = true;
preambleCandidateFirstRiseTime = front.time;
return true;
}
const uint32_t period = front.time - preambleCandidateFirstRiseTime;
preambleCandidateFirstRiseTime = front.time;
if (!preambleRisePeriodCoarseOk(period))
{
preambleGoodPeriods = 0;
preambleMeanPeriod = 0;
return true;
}
if (preambleGoodPeriods == 0)
{
preambleGoodPeriods = 1;
preambleMeanPeriod = (uint16_t)period;
}
else
{
const uint32_t tol = preambleJitterTolUs(preambleMeanPeriod);
if (absDiffU32(period, preambleMeanPeriod) <= tol)
{
if (preambleGoodPeriods < 255U)
++preambleGoodPeriods;
preambleMeanPeriod =
(uint16_t)(((uint32_t)preambleMeanPeriod * 3U + period) / 4U);
}
else
{
preambleGoodPeriods = 1;
preambleMeanPeriod = (uint16_t)period;
}
}
if (freeFrec)
riseSyncTime = (riseSyncTime + period / 2U) / 2U;
if (preambleGoodPeriods >= kPreambleLockNeed)
{
// Новый кадр обязан стартовать с чистого state, иначе возможны CRC/sync срывы
// при lock без промежуточного listenStart()->firstRX().
errors.reset();
packSize = 0;
isBufferOverflow = false;
isAvailable = false;
bufBitPos = 0;
isData = true;
i_dataBuffer = 0;
nextControlBit = bitPerByte;
i_syncBit = 0;
err_syncBit = 0;
isWrongPack = false;
msgTypeReceive = 0;
memset(dataBuffer, 0x00, dataByteSizeMax);
preambleState = PreambleState::Locked;
isPreamb = false;
isRecive = true;
isReciveRaw = true;
risePeriod = preambleMeanPeriod;
#if defined(IRDEBUG_SERIAL_PACK)
packTraceResetFrame();
packTraceOpen = true;
#endif
#ifdef IRDEBUG
errPulse(up, 50);
errPulse(down, 50);
#endif
prevRise = front.time + preambleMeanPeriod / 2U;
return true;
}
return true;
}
if (preambleState == PreambleState::Locked)
{
if (!isReciveRaw)
preambleResetToIdle();
return false;
}
return !isReciveRaw;
}
// IRDEBUG FUNC // IRDEBUG FUNC
#ifdef IRDEBUG #ifdef IRDEBUG
inline void IR_DecoderRaw::errPulse(uint8_t pin, uint8_t count) inline void IR_DecoderRaw::errPulse(uint8_t pin, uint8_t count)

View File

@ -52,6 +52,10 @@ public:
inline bool isOverflow() { return isBufferOverflow; }; // Буффер переполнился inline bool isOverflow() { return isBufferOverflow; }; // Буффер переполнился
bool isSubOverflow(); bool isSubOverflow();
volatile inline bool isReciving() { return isRecive; }; // Возвращает true, если происходит приём пакета 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) #if defined(IR_EDGE_TRACE)
void edgeTraceClear(); void edgeTraceClear();
@ -101,6 +105,29 @@ private:
// volatile FrontStorage subBuffer[subBufferSize]; // вспомогательный буфер для хранения необработанных фронтов/спадов // volatile FrontStorage subBuffer[subBufferSize]; // вспомогательный буфер для хранения необработанных фронтов/спадов
RingBuffer<FrontStorage, subBufferSize> subBuffer; RingBuffer<FrontStorage, subBufferSize> subBuffer;
/** Очередь фронтов после потокового анти-глитча; tick() читает из неё при включённом фильтре. */
RingBuffer<FrontStorage, subBufferSize> filteredSubBuffer;
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) #if defined(IR_EDGE_TRACE)
struct IrEdgeTraceRec struct IrEdgeTraceRec
@ -139,6 +166,18 @@ private:
bool isReciveRaw; bool isReciveRaw;
void listenStart(); void listenStart();
void checkTimeout(); // void checkTimeout(); //
/** Один сырой фронт из 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);
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, пришедшим в пакете /// @brief Проверка CRC. Проверяет len байт со значением crc, пришедшим в пакете
/// @param len Длина в байтах проверяемых данных /// @param len Длина в байтах проверяемых данных

View File

@ -154,6 +154,54 @@ typedef uint16_t crc_t;
#define subBufferSize 250 // Буфер для складирования фронтов, пока их не обработают (передатчик) #define subBufferSize 250 // Буфер для складирования фронтов, пока их не обработают (передатчик)
#endif #endif
/** Минимальная длительность удержания уровня (мкс): короче — импульс/пара фронтов выкидывается до tick()
* (иголки на плато, дребезг). 0 — фильтр выключен, фронты идут в декодер как с ISR. */
#ifndef IR_INPUT_MIN_PULSE_US
#define IR_INPUT_MIN_PULSE_US 0
#endif
/** Сколько подтверждённых фронтов держать перед выпуском в декодер (потоковая задержка). */
#ifndef IR_INPUT_FILTER_HOLDBACK_EDGES
#define IR_INPUT_FILTER_HOLDBACK_EDGES 3U
#endif
/** Если новых фронтов нет, через minPulse*mult держатель принудительно сбрасывается в декодер. */
#ifndef IR_INPUT_FILTER_TIMEOUT_MULT
#define IR_INPUT_FILTER_TIMEOUT_MULT 5U
#endif
/** Синхронно с IrFoxProtocolConstants.h / IrFoxDecoder (плагин Saleae). */
#ifndef IR_SHORT_LOW_GLITCH_REJECT
#define IR_SHORT_LOW_GLITCH_REJECT 1
#endif
#ifndef IR_GLITCH_REJECT_PHASE_NUDGE
#define IR_GLITCH_REJECT_PHASE_NUDGE 1
#endif
#ifndef IR_MICRO_GAP_RISE_REJECT
#define IR_MICRO_GAP_RISE_REJECT 1
#endif
/** Лок преамбулы: сколько одинаковых подряд периодов подъёма нужно для старта кадра. */
#ifndef IR_PREAMBLE_LOCK_RISE_PERIODS
#define IR_PREAMBLE_LOCK_RISE_PERIODS 2U
#endif
/** Допуск одинаковости периода преамбулы (проценты) + минимальная абсолютная полка в мкс. */
#ifndef IR_PREAMBLE_JITTER_PCT
#define IR_PREAMBLE_JITTER_PCT 18U
#endif
#ifndef IR_PREAMBLE_JITTER_US_MIN
#define IR_PREAMBLE_JITTER_US_MIN 80U
#endif
/** Грубое окно валидности периода преамбулы RISE->RISE (в процентах от bitTime).
* Для текущего протокола преамбула заметно длиннее обычного битового периода. */
#ifndef IR_PREAMBLE_PERIOD_MIN_FACTOR_PCT
#define IR_PREAMBLE_PERIOD_MIN_FACTOR_PCT 220U
#endif
#ifndef IR_PREAMBLE_PERIOD_MAX_FACTOR_PCT
#define IR_PREAMBLE_PERIOD_MAX_FACTOR_PCT 340U
#endif
/** Таймаут окна кандидата преамбулы: IR_timeout * mult. */
#ifndef IR_PREAMBLE_CANDIDATE_TIMEOUT_MULT
#define IR_PREAMBLE_CANDIDATE_TIMEOUT_MULT 3U
#endif
#define preambPulse 3 #define preambPulse 3
#define disablePairDec false // Отключать парный приёмник, возможны баги, используйте setBlindDecoders() #define disablePairDec false // Отключать парный приёмник, возможны баги, используйте setBlindDecoders()