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 <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)
{
setPin(pin);
@ -44,6 +67,13 @@ bool IR_DecoderRaw::availableRaw()
}
};
void IR_DecoderRaw::pulseFilterResetStats()
{
pulseFilterDropFilteredOverflow = 0;
pulseFilterDropHoldOverflow = 0;
pulseFilterDropGlitchPairs = 0;
}
//////////////////////////////////// isr ///////////////////////////////////////////
volatile uint32_t time_;
@ -101,11 +131,13 @@ void IR_DecoderRaw::firstRX()
wrCounter = 0;
#endif
memset(dataBuffer, 0x00, dataByteSizeMax);
pulseFilterReset();
preambleResetToIdle();
}
void IR_DecoderRaw::listenStart()
{
if (isReciveRaw && ((micros() - prevRise) > IR_timeout * 2))
if (isReciveRaw && ((micros() - lastEdgeTime) > IR_timeout * 2U))
{
#if defined(IRDEBUG_SERIAL_PACK)
packTraceOnTimeoutOrAbort(true);
@ -136,49 +168,119 @@ inline void IR_DecoderRaw::checkTimeout()
void IR_DecoderRaw::tick()
{
// FrontStorage *currentFrontPtr;
// noInterrupts();
// currentFrontPtr = subBuffer.pop();
// interrupts();
FrontStorage currentFront;
bool hasCurrentFront = false;
FrontStorage rawFront;
bool hasRawFront = false;
noInterrupts();
listenStart();
FrontStorage *currentFrontPtr;
currentFrontPtr = subBuffer.pop();
if (currentFrontPtr == nullptr)
FrontStorage *rawPtr = subBuffer.pop();
if (rawPtr != 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;
checkTimeout(); // <--- новое место проверки
bool rawQueueHasPending = false;
bool filteredQueueHasPending = false;
noInterrupts();
rawQueueHasPending = !subBuffer.isEmpty();
if (IR_INPUT_MIN_PULSE_US > 0U)
filteredQueueHasPending = !filteredSubBuffer.isEmpty();
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)
while (edgeTraceFlushChunk(Serial, 48) > 0) {}
#endif
return;
} // Если данных нет - ничего не делаем
currentFront = *currentFrontPtr;
interrupts();
// ---------- буфер пуст: фронтов нет, проверяем тайм-аут ----------
// if (currentFrontPtr == nullptr)
// {
// isSubBufferOverflow = false;
// return;
// }
if (preambleProcessEdge(currentFront))
{
lastEdgeTime = currentFront.time;
goto END;
}
// // ---------- есть фронт: продолжаем обработку ----------
// FrontStorage currentFront = *currentFrontPtr;
lastEdgeTime = currentFront.time; // запоминаем любой фронт
////////////////////////////////////////////////////////////////////////////////////////////////////////////
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;
highTime = currentFront.time - prevFall;
lowTime = prevFall - prevRise;
risePeriod = candRp;
highTime = candHt;
lowTime = candLt;
prevRise = currentFront.time;
if (
@ -231,79 +333,6 @@ void IR_DecoderRaw::tick()
digitalWrite(errOut, currentFront.dir);
#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)
// ~Мы в пределах таймаута и буффер не переполнен и fix дроблёных единиц
{
@ -487,6 +516,7 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
{
isRecive = false;
isReciveRaw = false;
preambleResetToIdle();
msgTypeReceive = 0;
return;
}
@ -598,6 +628,7 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
isRecive = false;
isReciveRaw = false;
preambleResetToIdle();
msgTypeReceive = 0;
isAvailable = crcCheck(packSize - crcBytes, crcValue);
@ -1147,6 +1178,265 @@ __attribute__((weak)) void irPackTracePrintOkCommand(const uint8_t *buf, uint8_t
#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
#ifdef IRDEBUG
inline void IR_DecoderRaw::errPulse(uint8_t pin, uint8_t count)