mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-04-28 03:08:08 +00:00
Filters and stable
This commit is contained in:
@ -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)
|
||||||
|
|||||||
@ -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 Длина в байтах проверяемых данных
|
||||||
|
|||||||
48
IR_config.h
48
IR_config.h
@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user