mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-09-18 19:13:58 +00:00
feat(rx): наблюдаемость приёма по состоянию + airtime кадра формулами
- IR_config.h: irFrameAirtimeUs/irFrameDecodeEndUs/irLockToDecodeEndUs/irLockLatencyUs,
irMaxPackSize (31), irDataPackSize/irBackPackSize — все из констант FSM передатчика
(преамбула 6×98 тактов, байт 11 бит × 74 такта, такт = полпериода несущей).
- IR_Encoder::calculateSendTime по той же формуле (раньше синхробиты считались один раз
на кадр → занижение 21-26%, потребители компенсировали +30%).
- IR_DecoderRaw: rxLockSeq/rxLockTimeUs (лок преамбулы), rxMsgType, rxExpectedEndUs
(по объявленной длине), rxLastEnd {seq, reason Ok/Crc/Timeout/Abort, msgType, packSize,
tUs, expectedEndUs}. Терминалы: конец кадра, таймаут тишины, немедленный abort при
sync-ошибке / длине <3 (в т.ч. 0) / переполнении — битый кадр больше не держит
isReciving до конца чужой передачи + 30 мс.
- Порядок в tick: checkTimeout до listenStart (TIMEOUT-лог с реальной длиной), истечение
кандидата преамбулы без фронтов, ложный PREAMB-инкремент на старте кандидата убран.
- rxMaxPackSize() = протокольные 31 (было 38 = размер буфера).
Совместимость: только добавления; существующие сигнатуры не тронуты. Собрано для G4 (Car)
и F4 (КУ).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeA1K5dBUKrnoXVwyoVjzq
This commit is contained in:
@ -403,6 +403,7 @@ inline void IR_DecoderRaw::checkTimeout()
|
||||
#endif
|
||||
const uint16_t expected = (i_dataBuffer >= 8U) ? uint16_t(dataBuffer[0] & IR_MASK_MSG_INFO) : 0U;
|
||||
rxBriefLog(RxBriefReason::Timeout, i_dataBuffer, expected, micros());
|
||||
noteRxEnd(RxEndReason::Timeout, micros());
|
||||
isRecive = false; // приём завершён
|
||||
msgTypeReceive = 0;
|
||||
// Как после listenStart(): без сброса isReciveRaw + firstRX() декодер остаётся
|
||||
@ -417,6 +418,49 @@ inline void IR_DecoderRaw::checkTimeout()
|
||||
}
|
||||
// ====================================================================
|
||||
|
||||
void IR_DecoderRaw::noteRxEnd(RxEndReason reason, uint32_t tUs)
|
||||
{
|
||||
rxEnd.seq++;
|
||||
rxEnd.reason = reason;
|
||||
rxEnd.msgType = (i_dataBuffer >= 8U * msgBytes) ? (uint8_t)((dataBuffer[0] >> 5) & IR_MASK_MSG_TYPE) : 0xFF;
|
||||
rxEnd.packSize = (uint8_t)packSize;
|
||||
rxEnd.tUs = tUs;
|
||||
rxEnd.expectedEndUs = (packSize >= msgBytes + crcBytes)
|
||||
? rxLockTimeUsVal + irLockToDecodeEndUs((uint8_t)packSize) + irTicksToUs((uint32_t)syncBits * irBitTicks)
|
||||
: 0U;
|
||||
}
|
||||
|
||||
void IR_DecoderRaw::abortFrame(uint32_t tUs)
|
||||
{
|
||||
#if defined(IRDEBUG_SERIAL_PACK)
|
||||
packTraceOnTimeoutOrAbort(false);
|
||||
#endif
|
||||
noteRxEnd(RxEndReason::Abort, tUs);
|
||||
isRecive = false;
|
||||
isReciveRaw = false;
|
||||
msgTypeReceive = 0;
|
||||
firstRX();
|
||||
}
|
||||
|
||||
void IR_DecoderRaw::expirePreambleCandidate()
|
||||
{
|
||||
if (preambleState != PreambleState::Candidate || rxTimeoutPipelineBusy())
|
||||
return;
|
||||
if ((micros() - preambleCandidateLastEdgeTime) > IR_timeout * (uint32_t)IR_PREAMBLE_CANDIDATE_TIMEOUT_MULT)
|
||||
{
|
||||
if (preambleGoodPeriods)
|
||||
rxBriefLog(RxBriefReason::Preamble, preambleGoodPeriods, 0, micros());
|
||||
preambleResetToIdle();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t IR_DecoderRaw::rxExpectedEndUs() const
|
||||
{
|
||||
if (!isRecive || preambleState != PreambleState::Locked || isWrongPack || packSize < msgBytes + crcBytes)
|
||||
return 0;
|
||||
return rxLockTimeUsVal + irLockToDecodeEndUs((uint8_t)packSize);
|
||||
}
|
||||
|
||||
void IR_DecoderRaw::tick()
|
||||
{
|
||||
#if IR_RX_BRIEF_LOG
|
||||
@ -471,15 +515,16 @@ void IR_DecoderRaw::tick()
|
||||
if (!processedFront)
|
||||
{
|
||||
isSubBufferOverflow = false;
|
||||
listenStart();
|
||||
checkTimeout();
|
||||
listenStart();
|
||||
expirePreambleCandidate();
|
||||
#if defined(IR_EDGE_TRACE)
|
||||
while (edgeTraceFlushChunk(Serial, 48) > 0) {}
|
||||
#endif
|
||||
return;
|
||||
} // Если данных нет - ничего не делаем
|
||||
listenStart();
|
||||
checkTimeout();
|
||||
listenStart();
|
||||
#if IR_RX_BRIEF_LOG
|
||||
rxBriefFlushDeferredIsrLogs();
|
||||
#endif
|
||||
@ -780,10 +825,7 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
|
||||
{
|
||||
// Как checkTimeout/listenStart: firstRX() сбрасывает буфер битов, преамбулу и
|
||||
// pulseFilterReset() — при IR_INPUT_MIN_PULSE_US > 0 иначе остаётся «хвост» в hold/filtered.
|
||||
isRecive = false;
|
||||
isReciveRaw = false;
|
||||
msgTypeReceive = 0;
|
||||
firstRX();
|
||||
abortFrame(micros());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -851,6 +893,8 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
|
||||
#if defined(IRDEBUG_SERIAL_PACK)
|
||||
packTraceEmitErrorFlash(F("ERROR: Wrong sync bit"));
|
||||
#endif
|
||||
abortFrame(micros()); // битый кадр не удерживает приёмник до таймаута
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -878,8 +922,12 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
|
||||
// B1: под-минимальная длина (1..2) физически не несёт CRC (min кадр = msg+crc = 3 байта) → шум/битьё.
|
||||
// Без отсева packSize==1 даёт crcCheck(1-2) → len=255 → OOB-чтение dataBuffer[0..256] (массив 38).
|
||||
// packSize>=3 (в т.ч. будущие компактные кадры) обрабатываются как обычно.
|
||||
if (packSize != 0 && packSize < msgBytes + crcBytes)
|
||||
if (packSize < msgBytes + crcBytes) // 0..2: кадр физически не несёт CRC — шум/битьё
|
||||
{
|
||||
isWrongPack = true;
|
||||
abortFrame(micros());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Тип приёма (для isReceive): выставляем сразу после первого байта, ДО проверки «Конец».
|
||||
@ -903,6 +951,7 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
|
||||
preambleResetToIdle();
|
||||
msgTypeReceive = 0;
|
||||
isAvailable = crcCheck(packSize - crcBytes, crcValue);
|
||||
noteRxEnd(isAvailable ? RxEndReason::Ok : RxEndReason::Crc, micros());
|
||||
|
||||
#ifdef BRUTEFORCE_CHECK
|
||||
{
|
||||
@ -1622,7 +1671,10 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
|
||||
if (!isReciveRaw && front.dir &&
|
||||
((prevRise == 0U && front.time > longSilence) ||
|
||||
(prevRise != 0U && (uint32_t)(front.time - prevRise) > longSilence)))
|
||||
{
|
||||
preambleStartCandidate(front);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (preambleState == PreambleState::Candidate)
|
||||
@ -1703,6 +1755,8 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
|
||||
isRecive = true;
|
||||
isReciveRaw = true;
|
||||
risePeriod = preambleMeanPeriod;
|
||||
rxLockSeqCnt++;
|
||||
rxLockTimeUsVal = front.time;
|
||||
#if defined(IRDEBUG_SERIAL_PACK)
|
||||
packTraceResetFrame();
|
||||
packTraceOpen = true;
|
||||
|
||||
@ -67,7 +67,25 @@ public:
|
||||
return (isRecive && packSize && !isWrongPack) ? packSize : 0;
|
||||
}
|
||||
// Протокольный МАКСИМУМ длины кадра (байт) — верхняя граница бюджета удержания заднего.
|
||||
static constexpr uint16_t rxMaxPackSize() { return (uint16_t)dataByteSizeMax; }
|
||||
static constexpr uint16_t rxMaxPackSize() { return (uint16_t)irMaxPackSize; }
|
||||
|
||||
// ---- Наблюдаемость приёма по СОСТОЯНИЮ: лок / ожидаемый конец / факт завершения с причиной ----
|
||||
enum class RxEndReason : uint8_t { None = 0, Ok, Crc, Timeout, Abort };
|
||||
struct RxEndInfo {
|
||||
uint16_t seq = 0; // номер завершения (растёт на каждом терминале)
|
||||
RxEndReason reason = RxEndReason::None;
|
||||
uint8_t msgType = 0xFF; // 0xFF = первый байт не был принят
|
||||
uint8_t packSize = 0; // объявленная длина (0 = неизвестна)
|
||||
uint32_t tUs = 0; // micros() терминала
|
||||
uint32_t expectedEndUs = 0; // расчётный конец кадра в эфире (0 = неизвестен)
|
||||
};
|
||||
uint16_t rxLockSeq() const { return rxLockSeqCnt; } // ++ в момент лока преамбулы
|
||||
uint32_t rxLockTimeUs() const { return rxLockTimeUsVal; } // метка фронта лока (ISR-время)
|
||||
/// Тип принимаемого кадра (3 бита) после первого байта; 0xFF пока неизвестен / приём не идёт.
|
||||
uint8_t rxMsgType() const { return (isRecive && packSize) ? (uint8_t)((dataBuffer[0] >> 5) & IR_MASK_MSG_TYPE) : 0xFF; }
|
||||
const RxEndInfo &rxLastEnd() const { return rxEnd; }
|
||||
/// Расчётный момент последнего бита данных текущего кадра (по объявленной длине); 0 = не Locked / длина неизвестна.
|
||||
uint32_t rxExpectedEndUs() const;
|
||||
uint32_t pulseFilterDroppedByFilteredOverflow() const { return 0; }
|
||||
uint32_t pulseFilterDroppedByHoldOverflow() const { return pulseFilterDropHoldOverflow; }
|
||||
uint32_t pulseFilterDroppedGlitchPairs() const { return pulseFilterDropGlitchPairs; }
|
||||
@ -163,6 +181,12 @@ private:
|
||||
Locked = 2
|
||||
};
|
||||
PreambleState preambleState = PreambleState::Idle;
|
||||
uint16_t rxLockSeqCnt = 0;
|
||||
uint32_t rxLockTimeUsVal = 0;
|
||||
RxEndInfo rxEnd;
|
||||
void noteRxEnd(RxEndReason reason, uint32_t tUs); // терминал: фиксирует тип/длину/расчётный конец
|
||||
void abortFrame(uint32_t tUs); // немедленный сброс битого кадра (sync/длина/overflow)
|
||||
void expirePreambleCandidate(); // кандидат без фронтов дольше таймаута → Idle
|
||||
uint8_t preambleGoodPeriods = 0;
|
||||
uint16_t preambleMeanPeriod = 0;
|
||||
uint32_t preambleCandidateLastEdgeTime = 0;
|
||||
|
||||
@ -1183,26 +1183,8 @@ uint8_t IR_Encoder::bitLow[2] = {
|
||||
|
||||
uint32_t IR_Encoder::calculateSendTime(uint8_t packSize) const
|
||||
{
|
||||
// Расчет времени отправки пакета в миллисекундах
|
||||
|
||||
// Время преамбулы: preambPulse * 2 фронта * bitTakts тактов
|
||||
uint32_t preambTime = preambPulse * 2 * bitTakts;
|
||||
|
||||
// Время данных: количество бит * bitTakts тактов
|
||||
uint32_t dataTime = packSize * 8 * bitTakts;
|
||||
|
||||
// Время синхронизации: syncBits * 2 фронта * bitTakts тактов
|
||||
uint32_t syncTime = syncBits * 2 * bitTakts;
|
||||
|
||||
// Общее время в тактах
|
||||
uint32_t totalTakts = preambTime + dataTime + syncTime;
|
||||
|
||||
// Конвертируем в миллисекунды
|
||||
// carrierPeriod - период несущей в микросекундах
|
||||
// totalTakts * carrierPeriod / 1000 = время в миллисекундах
|
||||
uint32_t sendTimeMs = (totalTakts * carrierPeriod) / 1000;
|
||||
|
||||
return sendTimeMs;
|
||||
// Полное время кадра в эфире по формуле FSM (преамбула + байты с синхробитами), округление вверх до мс.
|
||||
return (irFrameAirtimeUs(packSize) + 999U) / 1000U;
|
||||
}
|
||||
|
||||
// Функции для тестирования времени отправки без фактической отправки
|
||||
|
||||
23
IR_config.h
23
IR_config.h
@ -264,6 +264,29 @@ typedef uint16_t crc_t;
|
||||
#define bitTime (bitTakts * carrierPeriod) // Общая длительность бита
|
||||
#define tolerance 300U
|
||||
|
||||
// ---- Длительности и размеры кадра ФОРМУЛАМИ из FSM передатчика (IR_Encoder::txAdvanceBoundary) ----
|
||||
// Логический такт TX = полпериода несущей (toggleCounter считает полупериоды). Преамбула = 6 ран по
|
||||
// (preambToggle+1) тактов; лок декодера — на 3-м RISE (конец 5-й раны); байт = (8 данных + 3 sync) бит по 74 такта.
|
||||
constexpr uint32_t irTxTickNs = 1000000000UL / (carrierFrec * 2U);
|
||||
constexpr uint32_t irPreambleTicks = (uint32_t)preambPulse * 2U * ((uint32_t)preambToggle + 1U);
|
||||
constexpr uint32_t irLockTicks = ((uint32_t)preambPulse * 2U - 1U) * ((uint32_t)preambToggle + 1U);
|
||||
constexpr uint32_t irBitTicks = (uint32_t)bitTakts * 2U;
|
||||
constexpr uint32_t irByteTicks = ((uint32_t)bitPerByte + (uint32_t)syncBits) * irBitTicks;
|
||||
constexpr uint32_t irTicksToUs(uint32_t ticks) { return (uint32_t)(((uint64_t)ticks * irTxTickNs + 500U) / 1000U); }
|
||||
/// Полное время кадра в эфире (от первой несущей до последнего sync-бита), мкс.
|
||||
constexpr uint32_t irFrameAirtimeUs(uint8_t packSize) { return irTicksToUs(irPreambleTicks + (uint32_t)packSize * irByteTicks); }
|
||||
/// От старта кадра до последнего БИТА ДАННЫХ (момент, когда декодер отдаёт кадр), мкс.
|
||||
constexpr uint32_t irFrameDecodeEndUs(uint8_t packSize) { return irTicksToUs(irPreambleTicks + (uint32_t)packSize * irByteTicks - (uint32_t)syncBits * irBitTicks); }
|
||||
/// От лока декодера (3-й RISE преамбулы) до последнего бита данных, мкс.
|
||||
constexpr uint32_t irLockToDecodeEndUs(uint8_t packSize) { return irTicksToUs(irPreambleTicks - irLockTicks + (uint32_t)packSize * irByteTicks - (uint32_t)syncBits * irBitTicks); }
|
||||
/// Латентность лока: от первой несущей чужого кадра до лока декодера, мкс.
|
||||
constexpr uint32_t irLockLatencyUs = irTicksToUs(irLockTicks);
|
||||
/// Протокольный максимум длины кадра (5-битное поле длины).
|
||||
constexpr uint8_t irMaxPackSize = IR_MASK_MSG_INFO;
|
||||
/// Размер кадра по полезной нагрузке: DATA (from+to) и BACK (только from).
|
||||
constexpr uint8_t irDataPackSize(uint8_t payload) { return (uint8_t)(msgBytes + addrBytes * 2 + payload + crcBytes); }
|
||||
constexpr uint8_t irBackPackSize(uint8_t payload) { return (uint8_t)(msgBytes + addrBytes + payload + crcBytes); }
|
||||
|
||||
constexpr uint16_t test_all_Time = bitTime;
|
||||
constexpr uint16_t test_all_Takts = bitTakts * 2;
|
||||
constexpr uint16_t test_hi = ((bitPauseTakts) * 2 - 0) + ((bitActiveTakts) * 2 - 0);
|
||||
|
||||
Reference in New Issue
Block a user