mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-09-21 20:39:35 +00:00
Compare commits
15 Commits
archive/ex
...
STM32
| Author | SHA1 | Date | |
|---|---|---|---|
| 5759658d42 | |||
| 1b408b0de6 | |||
| baac9fbf46 | |||
| 5b220dadd8 | |||
| b375aa169e | |||
| 883c0b00cf | |||
| d103d2a3ae | |||
| 86956bcf99 | |||
| 5b9f73ec7c | |||
| 8f45f6e214 | |||
| 6c97d33c7c | |||
| 628c050702 | |||
| a589416cfc | |||
| e25feb6824 | |||
| b1d7016147 |
@ -162,7 +162,9 @@ void IR_Decoder::_tick()
|
||||
if (addrAcceptSendTo && addrAcceptSendTo < IR_Broadcast)
|
||||
isWaitingAcceptSend = true;
|
||||
}
|
||||
gotRaw.set(&packInfo, id);
|
||||
// Raw keeps the decoder's common minimum-size contract. Known packet
|
||||
// layouts are validated by their typed BasePack::set calls above.
|
||||
gotRaw.set(&packInfo, id, false);
|
||||
}
|
||||
if (isWaitingAcceptSend && millis() - acceptSendTimer > acceptDelay)
|
||||
{
|
||||
|
||||
@ -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,62 @@ 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();
|
||||
releasePreambleGuard(tUs);
|
||||
}
|
||||
|
||||
// После обрыва кадра «длинная тишина» (IR_timeout × 2 ≈ 30 мс) перед новым кандидатом преамбулы не требуется.
|
||||
// prevRise — последний ДЕКОДИРОВАННЫЙ фронт; после abort он свежий, а фронты, отброшенные гвардом, его не двигают,
|
||||
// поэтому валидный кадр, начавшийся через <30 мс после обрыва мусора, проглатывался целиком без счётчика
|
||||
// (стенд 09.09: КУ теряла пинг машинки после обрывков чужого заднего и всплеска её дальномера за 24 мс до пинга).
|
||||
// Ложных захватов это не добавляет: хвост оборванного кадра (период фронтов 962 мкс, синхробиты ~1100) не проходит
|
||||
// грубый фильтр периода преамбулы (2116…3270 мкс), а настоящая преамбула перезапускает кандидата по паузе > IR_timeout.
|
||||
// После чистого конца кадра гвард остаётся: там он отсекает хвост синхробитов.
|
||||
void IR_DecoderRaw::releasePreambleGuard(uint32_t tUs)
|
||||
{
|
||||
prevRise = tUs - IR_timeout * 2U - 1U; // «тишина уже была»: (front.time - prevRise) > longSilence для следующего фронта
|
||||
}
|
||||
|
||||
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 +528,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 +838,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 +906,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 +935,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 +964,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 +1684,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 +1768,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;
|
||||
|
||||
@ -52,6 +52,40 @@ public:
|
||||
inline bool isOverflow() { return isBufferOverflow; }; // Буффер переполнился
|
||||
bool isSubOverflow();
|
||||
volatile inline bool isReciving() { return isRecive; }; // Возвращает true, если происходит приём пакета
|
||||
// Активность линии по СОСТОЯНИЮ (не по хардкод-длительности): кадр залочен ИЛИ формируется
|
||||
// ВАЛИДНАЯ преамбула (>=1 совпавший по периоду фронт — отличает реальный кадр от одиночного
|
||||
// шумового фронта, который лишь заводит Candidate, но не набирает goodPeriods). Для гейта заднего:
|
||||
// «не стрелять, пока на линии идёт/формируется кадр (напр. ответ точки)». Аддитивно, const.
|
||||
inline bool rxLineActive() const {
|
||||
return isRecive ||
|
||||
(preambleState == PreambleState::Candidate && preambleGoodPeriods >= 1U);
|
||||
}
|
||||
// Объявленная длина ПРИНИМАЕМОГО кадра (байт) из ПЕРВОГО байта, если он уже принят и валиден;
|
||||
// иначе 0 (ещё не знаем / битый). До CRC это НЕДОВЕРЕННОЕ значение — потребитель, получив 0
|
||||
// или чрезмерное, обязан брать rxMaxPackSize() (безопасно держать задний до конца макс.кадра).
|
||||
inline uint16_t rxDeclaredPackSize() const {
|
||||
return (isRecive && packSize && !isWrongPack) ? packSize : 0;
|
||||
}
|
||||
// Протокольный МАКСИМУМ длины кадра (байт) — верхняя граница бюджета удержания заднего.
|
||||
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; }
|
||||
@ -147,6 +181,13 @@ 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 releasePreambleGuard(uint32_t tUs); // после abort: новый кандидат преамбулы без ожидания длинной тишины
|
||||
void expirePreambleCandidate(); // кандидат без фронтов дольше таймаута → Idle
|
||||
uint8_t preambleGoodPeriods = 0;
|
||||
uint16_t preambleMeanPeriod = 0;
|
||||
uint32_t preambleCandidateLastEdgeTime = 0;
|
||||
|
||||
109
IR_Encoder.cpp
109
IR_Encoder.cpp
@ -352,6 +352,29 @@ bool IR_Encoder::txEmitTick(TxFsmState &st, const uint8_t *sendBufferLocal, bool
|
||||
return txAdvanceAfterOutput(st, sendBufferLocal);
|
||||
}
|
||||
|
||||
// Обход кадра по границам ранов: между границами автомат выдаёт st.toggleCounter+1 тактов
|
||||
// уровня st.state (txAdvanceAfterOutput считает toggleCounter до нуля, затем txAdvanceBoundary
|
||||
// открывает следующий ран). Даёт ту же последовательность тактов, что потиковый обход, но за
|
||||
// число шагов = число ранов (пинг: ~230 вместо ~8700 тактов — на 12 МГц это ~30 мс перед стартом DMA).
|
||||
template <typename Emit>
|
||||
bool IR_Encoder::txWalkRuns(TxFsmState &st, const uint8_t *sendBufferLocal, Emit emit)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
const bool gate = st.state;
|
||||
const uint32_t lenTicks = (uint32_t)st.toggleCounter + 1U;
|
||||
if (!emit(gate, lenTicks))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
st.toggleCounter = 0;
|
||||
if (!txAdvanceBoundary(st, sendBufferLocal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IR_Encoder::loadTxFsmFromMembers(TxFsmState &st) const
|
||||
{
|
||||
st.sendLen = sendLen;
|
||||
@ -476,28 +499,27 @@ size_t IR_Encoder::buildGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRu
|
||||
st.currentBitSequence = bitHigh;
|
||||
|
||||
size_t runCount = 0;
|
||||
bool isActive = true;
|
||||
while (isActive)
|
||||
{
|
||||
bool gate = false;
|
||||
isActive = txEmitTick(st, sendBufferLocal, gate);
|
||||
|
||||
const bool ok = txWalkRuns(st, sendBufferLocal, [&](bool gate, uint32_t lenTicks) -> bool {
|
||||
if (runCount > 0 && outRuns[runCount - 1].gate == gate)
|
||||
{
|
||||
outRuns[runCount - 1].lenTicks = (uint16_t)(outRuns[runCount - 1].lenTicks + 1U);
|
||||
const uint32_t merged = (uint32_t)outRuns[runCount - 1].lenTicks + lenTicks;
|
||||
if (merged > 65535U)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
outRuns[runCount - 1].lenTicks = (uint16_t)merged;
|
||||
return true;
|
||||
}
|
||||
if (runCount >= maxRuns || lenTicks > 65535U)
|
||||
{
|
||||
if (runCount >= maxRuns)
|
||||
{
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
outRuns[runCount].gate = gate;
|
||||
outRuns[runCount].lenTicks = 1U;
|
||||
outRuns[runCount].lenTicks = (uint16_t)lenTicks;
|
||||
runCount++;
|
||||
}
|
||||
}
|
||||
return runCount;
|
||||
return true;
|
||||
});
|
||||
return ok ? runCount : 0;
|
||||
}
|
||||
|
||||
size_t IR_Encoder::buildPhysicalGateRuns(const uint8_t *packet, uint8_t len, IR_TxGateRun *outRuns, size_t maxRuns, uint16_t multiply)
|
||||
@ -564,40 +586,29 @@ size_t IR_Encoder::buildPhysicalGateRuns(const uint8_t *packet, uint8_t len, IR_
|
||||
bool currentGate = false;
|
||||
uint32_t currentLogicalLen = 0;
|
||||
bool havePendingRun = false;
|
||||
bool isActive = true;
|
||||
while (isActive)
|
||||
const bool ok = txWalkRuns(st, sendBufferLocal, [&](bool gate, uint32_t lenTicks) -> bool {
|
||||
if (havePendingRun && currentGate == gate)
|
||||
{
|
||||
bool gate = false;
|
||||
isActive = txEmitTick(st, sendBufferLocal, gate);
|
||||
|
||||
if (!havePendingRun)
|
||||
currentLogicalLen += lenTicks;
|
||||
return true;
|
||||
}
|
||||
if (havePendingRun && !appendPhysicalRun(currentGate, currentLogicalLen, runCount))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
currentGate = gate;
|
||||
currentLogicalLen = 1U;
|
||||
currentLogicalLen = lenTicks;
|
||||
havePendingRun = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentGate == gate)
|
||||
{
|
||||
currentLogicalLen++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!appendPhysicalRun(currentGate, currentLogicalLen, runCount))
|
||||
return true;
|
||||
});
|
||||
if (!ok)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
currentGate = gate;
|
||||
currentLogicalLen = 1U;
|
||||
}
|
||||
|
||||
if (havePendingRun && !appendPhysicalRun(currentGate, currentLogicalLen, runCount))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return runCount;
|
||||
}
|
||||
|
||||
@ -1183,26 +1194,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;
|
||||
}
|
||||
|
||||
// Функции для тестирования времени отправки без фактической отправки
|
||||
|
||||
@ -62,6 +62,10 @@ public:
|
||||
/// @param decPair Если задан, конструктор регистрирует этот один приёмник как blind-decoder
|
||||
/// (аналог setBlindDecoders() для одного RX).
|
||||
IR_Encoder(uint8_t pin, uint16_t addr = 0, IR_DecoderRaw *decPair = nullptr, bool autoHandle = true);
|
||||
/// Публичная оценка airtime кадра (мс) по его полной длине в байтах (packSize). Чистая функция
|
||||
/// протокольных констант — подходит и для приёма (напр. бюджет удержания заднего по объявленному
|
||||
/// в 1-м байте размеру принимаемого ответа). БЕЗ +30% компенсации занижения sync — добавляет потребитель.
|
||||
uint32_t packAirtimeMs(uint8_t packSize) const { return calculateSendTime(packSize); }
|
||||
static void isr();
|
||||
static void begin(HardwareTimer* timer, uint8_t channel, IRQn_Type IRQn, uint8_t priority, void(*isrCallback)() = nullptr);
|
||||
/**
|
||||
@ -210,6 +214,8 @@ private:
|
||||
static bool txAdvanceBoundary(TxFsmState &st, const uint8_t *sendBufferLocal);
|
||||
static bool txAdvanceAfterOutput(TxFsmState &st, const uint8_t *sendBufferLocal);
|
||||
static bool txEmitTick(TxFsmState &st, const uint8_t *sendBufferLocal, bool &gateOut);
|
||||
template <typename Emit>
|
||||
static bool txWalkRuns(TxFsmState &st, const uint8_t *sendBufferLocal, Emit emit);
|
||||
void loadTxFsmFromMembers(TxFsmState &st) const;
|
||||
void storeTxFsmToMembers(const TxFsmState &st);
|
||||
bool shouldUseBufferedIsr() const;
|
||||
|
||||
32
IR_config.h
32
IR_config.h
@ -231,9 +231,11 @@ typedef uint16_t crc_t;
|
||||
#ifndef IR_PREAMBLE_PERIOD_MAX_FACTOR_PCT
|
||||
#define IR_PREAMBLE_PERIOD_MAX_FACTOR_PCT 340U
|
||||
#endif
|
||||
/** Таймаут окна кандидата преамбулы: IR_timeout * mult. */
|
||||
/** Таймаут окна кандидата преамбулы: IR_timeout * mult. Кандидат без фронтов дольше таймаута байта
|
||||
преамбулой быть не может; при 3× линия считалась занятой (rxLineActive) ещё 45 мс после последнего
|
||||
паразитного фронта (напр. засветка своим дальномером) и откладывала передачу. */
|
||||
#ifndef IR_PREAMBLE_CANDIDATE_TIMEOUT_MULT
|
||||
#define IR_PREAMBLE_CANDIDATE_TIMEOUT_MULT 3U
|
||||
#define IR_PREAMBLE_CANDIDATE_TIMEOUT_MULT 1U
|
||||
#endif
|
||||
|
||||
#define preambPulse 3
|
||||
@ -264,6 +266,32 @@ 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);
|
||||
/// Таймаут байта декодера (как IR_timeout при номинальном bitTime) и тишина, по которой декодер обрывает приём.
|
||||
constexpr uint32_t irRxByteTimeoutUs = ((uint32_t)bitTime + tolerance) * ((uint32_t)bitPerByte + syncBits + 1U);
|
||||
constexpr uint32_t irRxAbortSilenceUs = 2U * irRxByteTimeoutUs;
|
||||
/// Протокольный максимум длины кадра (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);
|
||||
|
||||
@ -65,13 +65,63 @@ public:
|
||||
return out;
|
||||
}
|
||||
|
||||
// Заполнение по ранам, а не по словам: тот же поток слов, что даёт nextWord() (состояние
|
||||
// runIndex_/ticksLeftInRun_/slotInPeriod_ переносится через границы порций), но пауза (gate=0)
|
||||
// пишется одним циклом записи, а несущая — копией готового шаблона периода. На 12 МГц это
|
||||
// ~1 мс на 4096 слов вместо ~16 (пословный автомат) — и в предзаполнении перед стартом DMA,
|
||||
// и в ISR-дозаполнении половин буфера во время передачи.
|
||||
IR_TX_BSRR_WAVE_HOT void fill(uint32_t* dst, uint16_t count) {
|
||||
if (dst == nullptr || count == 0) {
|
||||
return;
|
||||
}
|
||||
do {
|
||||
*dst++ = nextWord();
|
||||
} while (--count != 0);
|
||||
while (count != 0) {
|
||||
if (runIndex_ >= runCount) {
|
||||
do { *dst++ = resetWord; } while (--count != 0);
|
||||
return;
|
||||
}
|
||||
const bool gate = runs[runIndex_].gate;
|
||||
uint16_t n = ticksLeftInRun_; // слов до конца текущего рана
|
||||
if (n == 0) n = 1; // ран нулевой длины: nextWord() выдаёт одно слово и переходит дальше
|
||||
if (n > count) n = count;
|
||||
if (!gate) {
|
||||
slotInPeriod_ = 0;
|
||||
uint16_t k = n;
|
||||
do { *dst++ = resetWord; } while (--k != 0);
|
||||
} else {
|
||||
uint16_t k = n;
|
||||
// добить текущий период до слота 0 (если ран начался посреди периода на границе порции)
|
||||
while (k != 0 && slotInPeriod_ != 0) {
|
||||
*dst++ = (slotInPeriod_ < powerN_) ? setWord : resetWord;
|
||||
if (++slotInPeriod_ >= multiply_) slotInPeriod_ = 0;
|
||||
k--;
|
||||
}
|
||||
// целые периоды: powerN_ слов setWord, остальные resetWord
|
||||
while (k >= multiply_) {
|
||||
uint16_t i = 0;
|
||||
for (; i < powerN_; ++i) *dst++ = setWord;
|
||||
for (; i < multiply_; ++i) *dst++ = resetWord;
|
||||
k = (uint16_t)(k - multiply_);
|
||||
}
|
||||
// хвост неполного периода
|
||||
while (k != 0) {
|
||||
*dst++ = (slotInPeriod_ < powerN_) ? setWord : resetWord;
|
||||
if (++slotInPeriod_ >= multiply_) slotInPeriod_ = 0;
|
||||
k--;
|
||||
}
|
||||
}
|
||||
count = (uint16_t)(count - n);
|
||||
if (ticksLeftInRun_ > n) {
|
||||
ticksLeftInRun_ = (uint16_t)(ticksLeftInRun_ - n);
|
||||
} else {
|
||||
ticksLeftInRun_ = 0;
|
||||
}
|
||||
if (ticksLeftInRun_ == 0) {
|
||||
runIndex_++;
|
||||
if (runIndex_ < runCount) {
|
||||
ticksLeftInRun_ = runs[runIndex_].lenTicks;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@ -2,12 +2,49 @@
|
||||
|
||||
namespace PacketTypes
|
||||
{
|
||||
bool BasePack::checkAddress() { return true; };
|
||||
void BasePack::set(IR_FOX::PackInfo *packInfo, uint16_t id)
|
||||
uint8_t minimumPacketSize(uint8_t msgType)
|
||||
{
|
||||
switch (msgType)
|
||||
{
|
||||
case IR_MSG_DATA_ACCEPT:
|
||||
case IR_MSG_DATA_NOACCEPT:
|
||||
case IR_MSG_BACK_TO:
|
||||
case IR_MSG_REQUEST:
|
||||
return uint8_t(msgBytes + addrBytes + addrBytes + crcBytes);
|
||||
case IR_MSG_BACK:
|
||||
return uint8_t(msgBytes + addrBytes + crcBytes);
|
||||
case IR_MSG_ACCEPT:
|
||||
return uint8_t(msgBytes + addrBytes + 1U + crcBytes);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool isTypedPacketSizeValid(uint8_t msgType, uint8_t packSize)
|
||||
{
|
||||
const uint8_t minimum = minimumPacketSize(msgType);
|
||||
return minimum != 0 && packSize >= minimum;
|
||||
}
|
||||
|
||||
bool BasePack::checkAddress() { return true; }
|
||||
bool BasePack::set(IR_FOX::PackInfo *packInfo, uint16_t id, bool requireTypedSize)
|
||||
{
|
||||
isAvailable = false;
|
||||
isRawAvailable = false;
|
||||
this->packInfo = packInfo;
|
||||
this->id = id;
|
||||
|
||||
if (packInfo == nullptr || packInfo->buffer == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t msgType = (packInfo->buffer[msgOffset] >> 5) & IR_MASK_MSG_TYPE;
|
||||
if (requireTypedSize && !isTypedPacketSizeValid(msgType, packInfo->packSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkAddress())
|
||||
{
|
||||
isAvailable = true;
|
||||
@ -23,29 +60,65 @@ namespace PacketTypes
|
||||
Serial.print(" NOT-OK ");
|
||||
#endif
|
||||
}
|
||||
return isAvailable;
|
||||
}
|
||||
|
||||
uint16_t BasePack::_getAddrFrom(BasePack *obj)
|
||||
{
|
||||
if (obj == nullptr || obj->packInfo == nullptr || obj->packInfo->buffer == nullptr ||
|
||||
obj->packInfo->packSize < crcBytes ||
|
||||
uint16_t(obj->addressFromOffset) + 1U >= uint16_t(obj->packInfo->packSize - crcBytes))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return (obj->packInfo->buffer[obj->addressFromOffset] << 8) | obj->packInfo->buffer[obj->addressFromOffset + 1];
|
||||
};
|
||||
}
|
||||
uint16_t BasePack::_getAddrTo(BasePack *obj)
|
||||
{
|
||||
if (obj == nullptr || obj->packInfo == nullptr || obj->packInfo->buffer == nullptr ||
|
||||
obj->packInfo->packSize < crcBytes ||
|
||||
uint16_t(obj->addressToOffset) + 1U >= uint16_t(obj->packInfo->packSize - crcBytes))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return (obj->packInfo->buffer[obj->addressToOffset] << 8) | obj->packInfo->buffer[obj->addressToOffset + 1];
|
||||
};
|
||||
}
|
||||
|
||||
uint8_t BasePack::_getDataSize(BasePack *obj)
|
||||
{
|
||||
return obj->packInfo->packSize - crcBytes - obj->DataOffset;
|
||||
};
|
||||
if (obj == nullptr || obj->packInfo == nullptr || obj->packInfo->buffer == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
const uint16_t frameOverhead = uint16_t(crcBytes) + uint16_t(obj->DataOffset);
|
||||
if (uint16_t(obj->packInfo->packSize) <= frameOverhead)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return uint8_t(uint16_t(obj->packInfo->packSize) - frameOverhead);
|
||||
}
|
||||
uint8_t *BasePack::_getDataPrt(BasePack *obj)
|
||||
{
|
||||
if (obj == nullptr || obj->packInfo == nullptr || obj->packInfo->buffer == nullptr ||
|
||||
obj->packInfo->packSize < crcBytes)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const uint16_t dataEnd = uint16_t(obj->packInfo->packSize) - uint16_t(crcBytes);
|
||||
if (uint16_t(obj->DataOffset) > dataEnd)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return obj->packInfo->buffer + obj->DataOffset;
|
||||
};
|
||||
}
|
||||
uint8_t BasePack::_getDataRawSize(BasePack *obj)
|
||||
{
|
||||
if (obj == nullptr || obj->packInfo == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return obj->packInfo->packSize;
|
||||
};
|
||||
}
|
||||
|
||||
bool BasePack::available()
|
||||
{
|
||||
@ -59,7 +132,7 @@ namespace PacketTypes
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
bool BasePack::availableRaw()
|
||||
{
|
||||
if (isRawAvailable)
|
||||
@ -71,7 +144,7 @@ namespace PacketTypes
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bool Data::checkAddress()
|
||||
{
|
||||
|
||||
@ -4,25 +4,34 @@
|
||||
class IR_Decoder;
|
||||
namespace PacketTypes
|
||||
{
|
||||
/**
|
||||
* Minimum complete frame size (header, addresses/data required by the type,
|
||||
* and CRC). Unknown/reserved message types return 0.
|
||||
*/
|
||||
uint8_t minimumPacketSize(uint8_t msgType);
|
||||
|
||||
/** True only for a known typed packet whose complete frame is long enough. */
|
||||
bool isTypedPacketSizeValid(uint8_t msgType, uint8_t packSize);
|
||||
|
||||
class BasePack
|
||||
{
|
||||
friend IR_Decoder;
|
||||
|
||||
protected:
|
||||
bool isAvailable;
|
||||
bool isRawAvailable;
|
||||
bool isNeedAccept;
|
||||
bool isAvailable = false;
|
||||
bool isRawAvailable = false;
|
||||
bool isNeedAccept = false;
|
||||
|
||||
uint8_t msgOffset;
|
||||
uint8_t addressFromOffset;
|
||||
uint8_t addressToOffset;
|
||||
uint8_t DataOffset;
|
||||
uint8_t msgOffset = 0;
|
||||
uint8_t addressFromOffset = 0;
|
||||
uint8_t addressToOffset = 0;
|
||||
uint8_t DataOffset = 0;
|
||||
|
||||
IR_FOX::PackInfo *packInfo;
|
||||
uint16_t id;
|
||||
IR_FOX::PackInfo *packInfo = nullptr;
|
||||
uint16_t id = 0;
|
||||
|
||||
virtual bool checkAddress();
|
||||
void set(IR_FOX::PackInfo *packInfo, uint16_t id);
|
||||
bool set(IR_FOX::PackInfo *packInfo, uint16_t id, bool requireTypedSize = true);
|
||||
|
||||
static uint16_t _getAddrFrom(BasePack *obj);
|
||||
static uint16_t _getAddrTo(BasePack *obj);
|
||||
|
||||
Reference in New Issue
Block a user