Files
IR-protocol/IR_DecoderRaw.cpp
2026-04-08 16:11:10 +03:00

1461 lines
42 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "IR_DecoderRaw.h"
#include "IR_Encoder.h"
#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);
id = addr;
prevRise = prevFall = prevPrevFall = prevPrevRise = 0;
if (encPair != nullptr)
{
encPair->decPair = this;
}
#ifdef IRDEBUG
pinMode(wrHigh, OUTPUT);
pinMode(wrLow, OUTPUT);
pinMode(writeOp, OUTPUT);
pinMode(errOut, OUTPUT);
pinMode(up, OUTPUT);
pinMode(down, OUTPUT);
#endif
}
bool IR_DecoderRaw::isSubOverflow()
{
noInterrupts();
volatile bool ret = isSubBufferOverflow;
interrupts();
return ret;
}
bool IR_DecoderRaw::availableRaw()
{
if (isAvailable)
{
isAvailable = false;
return true;
}
else
{
return false;
}
};
void IR_DecoderRaw::pulseFilterResetStats()
{
pulseFilterDropFilteredOverflow = 0;
pulseFilterDropHoldOverflow = 0;
pulseFilterDropGlitchPairs = 0;
}
//////////////////////////////////// isr ///////////////////////////////////////////
volatile uint32_t time_;
void IR_DecoderRaw::isr()
{
noInterrupts();
time_ = micros();
interrupts();
if (time_ < oldTime)
{
time_ += 1000;
}
oldTime = time_;
FrontStorage edge;
edge.dir = port->IDR & mask;
edge.time = time_;
#if defined(IR_EDGE_TRACE)
edgeTracePush(edge.time, edge.dir ? 1u : 0u,
isPairSending ? (uint8_t)IR_EDGE_TRACE_F_SKIP_DECODE : 0u);
#endif
if (isPairSending)
{
return;
}
subBuffer.push(edge);
}
////////////////////////////////////////////////////////////////////////////////////
void IR_DecoderRaw::firstRX()
{
#if defined(IRDEBUG_SERIAL_PACK)
packTraceResetFrame();
#endif
errors.reset();
packSize = 0;
isBufferOverflow = false;
isAvailable = false;
bufBitPos = 0;
isData = true;
i_dataBuffer = 0;
nextControlBit = bitPerByte;
i_syncBit = 0;
isWrongPack = false;
isPreamb = true;
riseSyncTime = bitTime /* 1100 */;
#ifdef IRDEBUG
wrCounter = 0;
#endif
memset(dataBuffer, 0x00, dataByteSizeMax);
pulseFilterReset();
preambleResetToIdle();
}
void IR_DecoderRaw::listenStart()
{
if (isReciveRaw && ((micros() - lastEdgeTime) > IR_timeout * 2U))
{
#if defined(IRDEBUG_SERIAL_PACK)
packTraceOnTimeoutOrAbort(true);
#endif
isReciveRaw = false;
firstRX();
}
}
// ---- быстрая проверка конца пакета ---------------------------------
inline void IR_DecoderRaw::checkTimeout()
{
if (!isRecive) return; // уже не принимаем нечего проверять
if (micros() - lastEdgeTime > IR_timeout * 2U)
{
#if defined(IRDEBUG_SERIAL_PACK)
packTraceOnTimeoutOrAbort(false);
#endif
isRecive = false; // приём завершён
msgTypeReceive = 0;
// firstRX(); // подготовка к новому пакету
lastEdgeTime = micros(); // защита от повторного срабатывания
}
}
// ====================================================================
void IR_DecoderRaw::tick()
{
FrontStorage currentFront;
bool hasCurrentFront = false;
FrontStorage rawFront;
bool hasRawFront = false;
noInterrupts();
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;
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;
} // Если данных нет - ничего не делаем
if (preambleProcessEdge(currentFront))
{
lastEdgeTime = currentFront.time;
goto END;
}
lastEdgeTime = currentFront.time; // запоминаем любой фронт
////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (currentFront.dir)
{ // Если __/``` ↑
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 = candRp;
highTime = candHt;
lowTime = candLt;
prevRise = currentFront.time;
if (
risePeriod > UINT32_MAX - IR_timeout * 10 ||
highTime > UINT32_MAX - IR_timeout * 10 ||
lowTime > UINT32_MAX - IR_timeout * 10 ||
prevRise > UINT32_MAX - IR_timeout * 10)
{
#ifdef IRDEBUG
errPulse(down, 50);
// Serial.print("\n");
// Serial.print("risePeriod: ");
// Serial.println(risePeriod);
// Serial.print("highTime: ");
// Serial.println(highTime);
// Serial.print("lowTime: ");
// Serial.println(lowTime);
// Serial.print("prevRise: ");
// Serial.println(prevRise);
#endif
}
}
else
{
errors.other++;
}
}
else
{ // Если ```\__ ↓
if (currentFront.time - prevFall > riseTimeMin / 4)
{
prevFall = currentFront.time;
}
else
{
errors.other++;
}
}
#ifdef IRDEBUG
// goto END; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#endif
//----------------------------------------------------------------------------------
#ifdef IRDEBUG
digitalWrite(errOut, currentFront.dir);
#endif
if (risePeriod > IR_timeout || isBufferOverflow || risePeriod < riseTimeMin || isWrongPack)
// ~Мы в пределах таймаута и буффер не переполнен и fix дроблёных единиц
{
goto END;
}
// определить направление фронта
if (currentFront.dir)
{ // Если __/``` ↑
highCount = 0;
lowCount = 0;
allCount = 0;
bool invertErr = false;
// #ifdef IRDEBUG
// Serial.print("\n");
// Serial.print("wrCounter: ");
// Serial.println(wrCounter++);
// Serial.print("risePeriod: ");
// Serial.println(risePeriod);
// Serial.print("highTime: ");
// Serial.println(highTime);
// Serial.print("lowTime: ");
// Serial.println(lowTime);
// #endif
if (aroundRise(risePeriod))
{ // тактирование есть, сигнал хороший - без ошибок(?)
if (highTime > lowTime)
{ // 1
#ifdef IRDEBUG
errPulse(wrHigh, 1);
#endif
writeToBuffer(HIGH);
}
else
{ // 0
#ifdef IRDEBUG
errPulse(wrLow, 1);
#endif
writeToBuffer(LOW);
}
}
else
{ // пропущены такты! сигнал средний // ошибка пропуска
highCount = ceil_div(highTime, riseTime); // предполагаемое колличество HIGH битов
lowCount = ceil_div(lowTime, riseTime); // предполагаемое колличество LOW битов
allCount = ceil_div(risePeriod, riseTime); // предполагаемое колличество всего битов
if (highCount == 0 && highTime > riseTime / 3)
{ // fix короткой единицы (?)после пропуска нулей(?)
highCount++;
errors.other++;
#ifdef IRDEBUG
errPulse(up, 50);
#endif
}
if (lowCount + highCount > allCount)
{ // fix ошибочных сдвигов
if (lowCount > highCount)
{ // Лишние нули
lowCount = allCount - highCount;
errors.lowSignal += lowCount;
#ifdef IRDEBUG
// errPulse(errOut, 3);
errPulse(down, 40);
errPulse(up, 10);
errPulse(down, 40);
#endif
}
else if (lowCount < highCount)
{ // Лишние единицы
highCount = allCount - lowCount;
errors.highSignal += highCount;
#ifdef IRDEBUG
errPulse(down, 10);
errPulse(up, 40);
errPulse(down, 10);
// errPulse(errOut, 4);
#endif
// неизвестный случай Инверсит след бит или соседние
// Очень редко
// TODO: Отловить проверить
}
else if (lowCount == highCount)
{
#ifdef IRDEBUG
errPulse(down, 40);
errPulse(up, 40);
errPulse(down, 40);
#endif
invertErr = true;
// Serial.print("...");
errors.other += allCount;
}
// errorCounter += allCount;
}
// errorCounter += allCount;
// errors.other+=allCount;
if (lowCount < highCount)
{
errors.highSignal += highCount;
}
else
{
errors.lowSignal += lowCount;
}
// errPulse(errOut, 1);
for (int8_t i = 0; i < lowCount && 8 - i; i++)
{ // отправка LOW битов, если есть
if (i == lowCount - 1 && invertErr)
{
invertErr = false;
writeToBuffer(HIGH, true);
#ifdef IRDEBUG
errPulse(wrHigh, 1);
#endif
}
else
{
writeToBuffer(LOW);
#ifdef IRDEBUG
errPulse(wrLow, 1);
#endif
}
}
for (int8_t i = 0; i < highCount && 8 - i; i++)
{ // отправка HIGH битов, если есть
if (i == highCount - 1 && invertErr)
{
invertErr = false;
writeToBuffer(LOW, true);
#ifdef IRDEBUG
errPulse(wrLow, 1);
#endif
}
else
{
writeToBuffer(HIGH);
#ifdef IRDEBUG
errPulse(wrHigh, 1);
#endif
}
}
}
}
else
{ // Если ```\__ ↓
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
END:;
#if defined(IR_EDGE_TRACE)
while (edgeTraceFlushChunk(Serial, 48) > 0) {}
#endif
}
void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
{
#if !defined(IRDEBUG_SERIAL_PACK)
(void)packTraceInvertFix;
#endif
if (i_dataBuffer > dataByteSizeMax * 8)
{ // проверка переполнения
isBufferOverflow = true;
#if defined(IRDEBUG_SERIAL_PACK)
if (packTraceOpen)
packTraceEmitErrorFlash(F("ERROR: buffer overflow"));
#endif
}
if (isBufferOverflow || isPreamb || isWrongPack)
{
isRecive = false;
isReciveRaw = false;
preambleResetToIdle();
msgTypeReceive = 0;
return;
}
// Переключение флага, data или syncBit
if (bufBitPos == nextControlBit)
{
nextControlBit += (isData ? syncBits : bitPerByte); // маркер следующего переключения
isData = !isData;
i_syncBit = 0; // сброс счетчика битов синхронизации
err_syncBit = 0; // сброс счетчика ошибок синхронизации
}
if (isData)
{ // Запись битов в dataBuffer
dataBuffer[(i_dataBuffer / 8)] |= bit << (7 - i_dataBuffer % 8); // Запись в буффер
i_dataBuffer++;
bufBitPos++;
#if defined(IRDEBUG_SERIAL_PACK)
if (packTraceInvertFix)
{
packTracePushChar('`');
packTracePushChar(bit ? '1' : '0');
packTracePushChar('`');
}
else
packTracePushBit(bit);
#endif
}
else
{
//********************************* Проверка контрольных sync битов *******************************//
////////////////////// Исправление лишнего нуля ///////////////////////
if (i_syncBit == 0)
{ // Первый бит синхронизации
if (bit != (dataBuffer[((i_dataBuffer - 1) / 8)] >> (7 - (i_dataBuffer - 1) % 8) & 1))
{
bufBitPos++;
i_syncBit++;
#if defined(IRDEBUG_SERIAL_PACK)
packTracePushBit(bit);
#endif
}
else
{
i_syncBit = 0;
errors.other++;
err_syncBit++;
const bool fatalSync = (err_syncBit >= syncBits);
if (fatalSync)
{
#if defined(IRDEBUG_SERIAL_PACK) && defined(IRDEBUG_SERIAL_SOFT_REJECT)
if (packTraceSoftReject())
{
packTraceHadWrongSync = true;
packTraceForceEndSyncPhase();
err_syncBit = 0;
i_syncBit = 0;
isWrongPack = false;
}
else
#endif
{
isWrongPack = true;
#if defined(IRDEBUG_SERIAL_PACK)
packTraceEmitErrorFlash(F("ERROR: Wrong sync bit"));
#endif
}
}
}
}
else
{ // Последующие биты синхронизации
bufBitPos++;
i_syncBit++;
#if defined(IRDEBUG_SERIAL_PACK)
packTracePushBit(bit);
#endif
}
isWrongPack = (err_syncBit >= syncBits);
} //**************************************************************************************************//
#ifdef IRDEBUG
bit ? infoPulse(writeOp, 2) : infoPulse(writeOp, 1);
#endif
if (!isAvailable && isData && !isWrongPack)
{
if (i_dataBuffer == 8 * msgBytes)
{ // Ппервый байт
packSize = dataBuffer[0] & IR_MASK_MSG_INFO;
}
// Тип приёма (для isReceive): выставляем сразу после первого байта, ДО проверки «Конец».
// Иначе при packSize==1 один и тот же шаг i_dataBuffer==8 одновременно «закрывает» кадр (msgTypeReceive=0)
// и снова выставляет msgTypeReceive ниже — флаг залипает, пока не придёт ошибка/другой кадр.
if (packSize && (i_dataBuffer == 8))
{
msgTypeReceive = (dataBuffer[0] >> 5) | 0b11111000;
}
if (packSize && (i_dataBuffer == packSize * bitPerByte))
{ // Конец
packInfo.buffer = dataBuffer;
packInfo.crc = crcValue;
packInfo.err = errors;
packInfo.packSize = packSize;
packInfo.rTime = riseSyncTime;
isRecive = false;
isReciveRaw = false;
preambleResetToIdle();
msgTypeReceive = 0;
isAvailable = crcCheck(packSize - crcBytes, crcValue);
#ifdef BRUTEFORCE_CHECK
{
uint16_t packTraceBfByte = 0;
uint8_t packTraceBfBit = 0;
bool packTraceBfMark = false;
if (!isAvailable) // Исправление первого бита // Очень большая затычка...
for (size_t i = 0; i < min(uint16_t(packSize - crcBytes * 2U), uint16_t(dataByteSizeMax)); ++i)
{
for (int j = 0; j < 8; ++j)
{
// инвертируем бит
dataBuffer[i] ^= 1 << j;
isAvailable =
crcCheck(min(uint16_t(packSize - crcBytes), uint16_t(dataByteSizeMax - 1U)), crcValue);
// обратно инвертируем бит в исходное состояние
if (isAvailable)
{
packTraceBfByte = static_cast<uint16_t>(i);
packTraceBfBit = static_cast<uint8_t>(j);
packTraceBfMark = true;
goto OUT_BRUTEFORCE;
}
else
{
dataBuffer[i] ^= 1 << j;
}
}
}
OUT_BRUTEFORCE:
#if defined(IRDEBUG_SERIAL_PACK)
if (packTraceBfMark)
packTraceWrapDataBitInBackticks(packTraceBfByte, packTraceBfBit);
#endif
}
#endif
#if defined(IRDEBUG_SERIAL_PACK)
if (isAvailable)
packTraceEmitEndOk(static_cast<uint8_t>(packSize));
else
packTraceEmitEndBadCrc(static_cast<uint8_t>(packSize));
#endif
if (!isAvailable && packSize > 0 && packSize <= dataByteSizeMax) {
memcpy(rejectBuffer, dataBuffer, packSize);
rejectPackSize = static_cast<uint8_t>(packSize);
isRejectAvailable = true;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
bool IR_DecoderRaw::crcCheck(uint8_t len, crc_t &crc)
{
bool crcOK = false;
crc = 0;
crc = (crc8(dataBuffer, 0, len, poly1) << 8) & ~((crc_t)0xFF);
crc |= crc8(dataBuffer, 0, len + 1, poly2) & (crc_t)0xFF;
if (dataBuffer[len] == (crc >> 8) & 0xFF &&
dataBuffer[len + 1] == (crc & 0xFF))
{
crcOK = true;
}
else
{
crcOK = false;
}
return crcOK;
}
bool IR_DecoderRaw::availableReject()
{
if (!isRejectAvailable)
return false;
isRejectAvailable = false;
return true;
}
uint16_t IR_DecoderRaw::ceil_div(uint16_t val, uint16_t divider)
{
int ret = val / divider;
if ((val << 4) / divider - (ret << 4) >= 8)
ret++;
return ret;
}
#if defined(IR_EDGE_TRACE)
void IR_DecoderRaw::edgeTracePush(uint32_t t_us, uint8_t level, uint8_t flags)
{
const uint16_t cap = static_cast<uint16_t>(IR_EDGE_TRACE_CAPACITY);
noInterrupts();
const uint16_t w = edgeTrace_w;
const uint16_t r = edgeTrace_r;
const uint16_t next = static_cast<uint16_t>((w + 1u) % cap);
if (next == r)
{
edgeTrace_overflow = true;
interrupts();
return;
}
edgeTrace_buf[w].t_us = t_us;
edgeTrace_buf[w].level = level ? 1u : 0u;
edgeTrace_buf[w].flags = flags;
edgeTrace_w = next;
interrupts();
}
void IR_DecoderRaw::edgeTraceClear()
{
noInterrupts();
edgeTrace_w = 0;
edgeTrace_r = 0;
edgeTrace_overflow = false;
interrupts();
}
uint16_t IR_DecoderRaw::edgeTracePendingCount() const
{
noInterrupts();
const uint16_t w = edgeTrace_w;
const uint16_t r = edgeTrace_r;
interrupts();
const uint16_t cap = static_cast<uint16_t>(IR_EDGE_TRACE_CAPACITY);
if (w >= r)
return static_cast<uint16_t>(w - r);
return static_cast<uint16_t>(cap - r + w);
}
uint16_t IR_DecoderRaw::edgeTraceFlushChunk(Print &out, uint16_t maxRec)
{
if (maxRec == 0)
maxRec = 48;
constexpr uint16_t kStackCap = 64;
if (maxRec > kStackCap)
maxRec = kStackCap;
const uint16_t cap = static_cast<uint16_t>(IR_EDGE_TRACE_CAPACITY);
noInterrupts();
const uint16_t w = edgeTrace_w;
const uint16_t r = edgeTrace_r;
uint16_t avail = (w >= r) ? static_cast<uint16_t>(w - r) : static_cast<uint16_t>(cap - r + w);
uint16_t toCopy = (avail > maxRec) ? maxRec : avail;
const bool truncated = (avail > toCopy);
if (toCopy == 0)
{
interrupts();
return 0;
}
uint8_t tmp[kStackCap * 6];
for (uint16_t i = 0; i < toCopy; ++i)
{
const uint16_t idx = static_cast<uint16_t>((r + i) % cap);
memcpy(tmp + i * 6u, &edgeTrace_buf[idx], 6u);
}
edgeTrace_r = static_cast<uint16_t>((r + toCopy) % cap);
const bool ovf = edgeTrace_overflow;
interrupts();
uint8_t meta = 0;
if (ovf)
meta |= 0x01u;
if (truncated)
meta |= 0x02u;
uint8_t line[3 + kStackCap * 6];
line[0] = meta;
line[1] = static_cast<uint8_t>(toCopy & 0xFFu);
line[2] = static_cast<uint8_t>((toCopy >> 8) & 0xFFu);
memcpy(line + 3, tmp, toCopy * 6u);
out.print(F("\n@IRF1v1:"));
static const char hd[] = "0123456789abcdef";
const uint16_t lineLen = static_cast<uint16_t>(3u + toCopy * 6u);
for (uint16_t i = 0; i < lineLen; ++i)
{
const uint8_t b = line[i];
out.write(hd[b >> 4]);
out.write(hd[b & 0x0Fu]);
}
out.write('\n');
return toCopy;
}
#endif // IR_EDGE_TRACE
#if defined(IRDEBUG_SERIAL_PACK)
struct IrPackTraceSeg
{
uint8_t isSync;
uint8_t nbits; // число логических бит (данные) или символов синхры
uint8_t nchars; // сырых символов в b (данные: 8…24 из‑за `0`/`1`)
char b[24];
};
namespace {
void ptPrintHexU8(uint8_t v)
{
static const char hd[] = "0123456789ABCDEF";
Serial.print(hd[v >> 4]);
Serial.print(hd[v & 0x0Fu]);
}
/** Тройной пробел перед началом блока: msg→from, from→to, to→data, data→CRC. */
static bool ptRawLeadTriple(uint8_t byteIndex, uint8_t framePs)
{
if (byteIndex == 1 || byteIndex == 3)
return true;
if (framePs > 7 && byteIndex == 5)
return true;
if (framePs >= 7 && byteIndex == static_cast<uint8_t>(framePs - 2))
return true;
return false;
}
/** В IR raw: только 0/1 из flex-сегмента (без `), takeBits логических бит после skipLogical. */
static void ptEmitRawFlexSliceSerial(const char *s, uint8_t nbytes, uint8_t skipLogical, uint8_t takeBits)
{
size_t i = 0;
uint8_t logical = 0;
uint8_t emitted = 0;
while (i < nbytes && emitted < takeBits)
{
if (s[i] == '`' && i + 2u < nbytes && (s[i + 1] == '0' || s[i + 1] == '1') && s[i + 2] == '`')
{
if (logical >= skipLogical)
{
Serial.print(s[i + 1]);
++emitted;
}
i += 3;
++logical;
}
else if (s[i] == '0' || s[i] == '1')
{
if (logical >= skipLogical)
{
Serial.print(s[i]);
++emitted;
}
++i;
++logical;
}
else
break;
}
}
static bool ptTryConsumeFlexDataBit(const char *buf, uint16_t len, uint16_t &pos, IrPackTraceSeg &d)
{
if (pos + 2 < len && buf[pos] == '`' && (buf[pos + 1] == '0' || buf[pos + 1] == '1') && buf[pos + 2] == '`')
{
if (d.nchars + 3u > sizeof(d.b))
return false;
d.b[d.nchars++] = '`';
d.b[d.nchars++] = buf[pos + 1];
d.b[d.nchars++] = '`';
pos = static_cast<uint16_t>(pos + 3u);
return true;
}
if (pos < len && (buf[pos] == '0' || buf[pos] == '1'))
{
if (d.nchars + 1u > sizeof(d.b))
return false;
d.b[d.nchars++] = buf[pos++];
return true;
}
return false;
}
} // namespace
void IR_DecoderRaw::packTraceResetFrame()
{
packTraceOpen = false;
packTraceHadWrongSync = false;
packTraceLen = 0;
packTraceBitBuf[0] = '\0';
}
void IR_DecoderRaw::packTracePushChar(char c)
{
if (packTraceLen + 1u < kPackTraceBufCap)
{
packTraceBitBuf[packTraceLen++] = c;
packTraceBitBuf[packTraceLen] = '\0';
}
}
void IR_DecoderRaw::packTracePushBit(bool bit) { packTracePushChar(bit ? '1' : '0'); }
void IR_DecoderRaw::packTraceWrapDataBitInBackticks(uint16_t byteIndex, uint8_t bitInByte)
{
const uint32_t target = uint32_t(byteIndex) * 8u + bitInByte;
uint32_t dbit = 0;
uint16_t pos = 0;
bool inData = true;
uint16_t len = packTraceLen;
if (len >= kPackTraceBufCap)
len = kPackTraceBufCap - 1u;
while (pos < len)
{
if (inData)
{
uint8_t bitLen = 0;
if (pos + 2 < len && packTraceBitBuf[pos] == '`' && packTraceBitBuf[pos + 2] == '`' &&
(packTraceBitBuf[pos + 1] == '0' || packTraceBitBuf[pos + 1] == '1'))
bitLen = 3;
else if (packTraceBitBuf[pos] == '0' || packTraceBitBuf[pos] == '1')
bitLen = 1;
else
return;
if (dbit == target)
{
if (bitLen == 3)
return;
if (packTraceLen + 2u >= kPackTraceBufCap)
return;
const uint8_t finalBit =
static_cast<uint8_t>((dataBuffer[byteIndex] >> (7u - bitInByte)) & 1u);
const char ch = finalBit ? '1' : '0';
memmove(packTraceBitBuf + pos + 2, packTraceBitBuf + pos, packTraceLen - pos);
packTraceBitBuf[pos] = '`';
packTraceBitBuf[pos + 1] = ch;
packTraceBitBuf[pos + 2] = '`';
packTraceLen += 2;
if (packTraceLen < kPackTraceBufCap)
packTraceBitBuf[packTraceLen] = '\0';
return;
}
++dbit;
pos = static_cast<uint16_t>(pos + bitLen);
if ((dbit % 8u) == 0u)
inData = false;
}
else
{
uint8_t sc = 0;
while (pos < len && sc < syncBits)
{
const char c = packTraceBitBuf[pos];
if (c == '0' || c == '1' || c == '?')
{
++pos;
++sc;
}
else
break;
}
inData = true;
}
}
}
bool IR_DecoderRaw::packTraceSoftReject() const
{
#if defined(IRDEBUG_SERIAL_SOFT_REJECT)
return true;
#else
return false;
#endif
}
void IR_DecoderRaw::packTraceForceEndSyncPhase()
{
for (uint8_t i = 0; i < syncBits; i++)
packTracePushChar('?');
const uint8_t cycLen = bitPerByte + syncBits;
const uint16_t cyc = uint16_t(bufBitPos / cycLen);
bufBitPos = uint16_t((cyc + 1u) * cycLen);
if (bufBitPos == nextControlBit)
{
nextControlBit += (isData ? syncBits : bitPerByte);
isData = !isData;
i_syncBit = 0;
err_syncBit = 0;
}
}
void IR_DecoderRaw::packTraceEmitHex(uint8_t byteCount) const
{
Serial.print(F("IR hex:"));
for (uint8_t i = 0; i < byteCount && i < dataByteSizeMax; i++)
{
Serial.print(' ');
ptPrintHexU8(dataBuffer[i]);
}
Serial.println();
}
void IR_DecoderRaw::packTraceEmitRawBitsLine(bool endWithNewline) const
{
Serial.print(F("IR raw: "));
uint16_t len = packTraceLen;
if (len >= kPackTraceBufCap)
len = kPackTraceBufCap - 1u;
const char *buf = packTraceBitBuf;
uint16_t pos = 0;
uint8_t byteIndex = 0;
bool firstSeg = true;
const uint8_t framePs =
(i_dataBuffer >= 8) ? static_cast<uint8_t>(dataBuffer[0] & IR_MASK_MSG_INFO) : 0;
while (pos < len)
{
IrPackTraceSeg d{};
d.isSync = 0;
while (pos < len && d.nbits < 8)
{
const uint16_t posBefore = pos;
if (!ptTryConsumeFlexDataBit(buf, len, pos, d))
break;
if (pos == posBefore)
break;
++d.nbits;
}
if (!d.nbits)
break;
if (!firstSeg)
{
if (ptRawLeadTriple(byteIndex, framePs))
Serial.print(F(" "));
else
Serial.print(' ');
}
firstSeg = false;
if (d.nbits < 8)
{
ptEmitRawFlexSliceSerial(d.b, d.nchars, 0, d.nbits);
break;
}
if (byteIndex == 0u)
{
ptEmitRawFlexSliceSerial(d.b, d.nchars, 0, 3);
Serial.print(' ');
ptEmitRawFlexSliceSerial(d.b, d.nchars, 3, 5);
}
else
ptEmitRawFlexSliceSerial(d.b, d.nchars, 0, 8);
++byteIndex;
if (pos >= len)
break;
IrPackTraceSeg s{};
s.isSync = 1;
while (pos < len && s.nbits < syncBits)
{
const char c = buf[pos];
if (c != '0' && c != '1' && c != '?')
break;
if (s.nchars >= sizeof(s.b))
break;
s.b[s.nchars++] = c;
++s.nbits;
++pos;
}
if (s.nbits)
{
Serial.print(' ');
for (uint8_t i = 0; i < s.nbits; ++i)
Serial.print(s.b[i]);
}
}
if (endWithNewline)
Serial.println();
}
void IR_DecoderRaw::packTraceEmitErrorFlash(const __FlashStringHelper *msg)
{
Serial.println();
packTraceEmitRawBitsLine(false);
Serial.print(F(" => "));
Serial.println(msg);
{
uint16_t nb = i_dataBuffer / 8u;
if (nb > dataByteSizeMax)
nb = dataByteSizeMax;
packTraceEmitHex(static_cast<uint8_t>(nb));
}
packTraceResetFrame();
}
void IR_DecoderRaw::packTraceEmitEndOk(uint8_t packSize)
{
Serial.println();
packTraceEmitRawBitsLine(false);
Serial.print(F(" => OK: "));
irPackTracePrintOkCommand(dataBuffer, packSize);
Serial.println();
packTraceEmitHex(packSize);
packTraceResetFrame();
}
void IR_DecoderRaw::packTraceEmitEndBadCrc(uint8_t packSize)
{
Serial.println();
packTraceEmitRawBitsLine(false);
Serial.println(F(" => ERROR: Wrong CRC"));
packTraceEmitHex(packSize);
packTraceResetFrame();
}
void IR_DecoderRaw::packTraceOnTimeoutOrAbort(bool fromListenStart)
{
(void)fromListenStart;
if (!packTraceOpen)
return;
const uint16_t expected = (i_dataBuffer >= 8) ? uint16_t(dataBuffer[0] & IR_MASK_MSG_INFO) : 0;
uint16_t gotBytes = i_dataBuffer / 8;
if (gotBytes > dataByteSizeMax)
gotBytes = dataByteSizeMax;
Serial.println();
packTraceEmitRawBitsLine(false);
Serial.print(F(" => ERROR: TIMEOUT, rx_data_size = "));
Serial.print(expected);
Serial.print(F(", but only "));
Serial.print(gotBytes);
Serial.println(F(" bytes received"));
packTraceEmitHex(static_cast<uint8_t>(gotBytes));
packTraceResetFrame();
}
__attribute__((weak)) void irPackTracePrintOkCommand(const uint8_t *buf, uint8_t packSize)
{
(void)buf;
(void)packSize;
}
#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)
{
for (size_t i = 0; i < count; i++)
{
digitalWrite(pin, 1);
digitalWrite(pin, 0);
}
digitalWrite(pin, 0);
}
inline void IR_DecoderRaw::infoPulse(uint8_t pin, uint8_t count)
{
for (size_t i = 0; i < count; i++)
{
digitalWrite(pin, 1);
digitalWrite(pin, 0);
}
}
#endif