#pragma once #include #include // #define IRDEBUG_INFO /** Число потоков DMA-TX задаётся шаблоном: IrDmaTxStm32<2>, см. IrDmaTxStm32.h и irproto::kDefaultDmaTxMaxStreams. */ namespace irproto { constexpr size_t kDefaultDmaTxMaxStreams = 4U; /** NVIC-приоритет IRQ DMA-каналов TX (refill кольца BSRR-слов). Refill длинный (полубуфер * 2048 слов = миллисекунды на небыстрых SYSCLK), а его дедлайн мягкий (~9 мс при 38кГц×6), * поэтому он обязан сидеть НИЖЕ SysTick (0) и EXTI приёмника (6): с приоритетом по умолчанию * (0, HAL_NVIC_EnableIRQ без SetPriority) он не вытеснялся SysTick'ом, тики слипались и * millis() замедлялся в 1.6×/2.2× (один/два потока) на всё время передачи — мерено на стенде * TofSickHunt 2026-08-11. Меньше число = выше приоритет; 8 = ниже EXTI, выше самого дна. */ constexpr uint32_t kDmaTxIrqPriority = 8U; /** Кольцевой буфер BSRR-слов для ISR-TX (как у DMA: два полублока). Чётное число. */ constexpr uint16_t kIsrTxBsrrWordCount = 256U; static_assert((kIsrTxBsrrWordCount & 1U) == 0U, "kIsrTxBsrrWordCount must be even"); } // Пошаговый разбор кадра на Serial (по умолчанию выключено). Пульсы IRDEBUG на пинах не меняют. // #define IRDEBUG_SERIAL_PACK // Не обрывать приём сразу при накопленной sync-ошибке — «дописывать» до таймаута (только вместе с IRDEBUG_SERIAL_PACK). // #define IRDEBUG_SERIAL_SOFT_REJECT // Краткий лог причин, почему физический сигнал не дошёл до распознанного пакета. // Формат и коды: ref/IR_RX_BRIEF_LOG.md #ifndef IR_RX_BRIEF_LOG #define IR_RX_BRIEF_LOG 0 #endif // 1: печатать только отклонённые/ошибочные события; успехи и шумовые PREAMB скрыть. #ifndef IR_RX_BRIEF_LOG_REJECT_ONLY #define IR_RX_BRIEF_LOG_REJECT_ONLY 1 #endif // Журнал фронтов ИК в ISR; сброс строк @IRF1v1: в IR_DecoderRaw::tick(). См. ref/IR_EDGE_TRACE_FORMAT.md // Расход RAM ≈ IR_EDGE_TRACE_CAPACITY * 6 байт на декодер. Выключить — закомментировать: // #define IR_EDGE_TRACE #if defined(IR_EDGE_TRACE) #ifndef IR_EDGE_TRACE_CAPACITY #define IR_EDGE_TRACE_CAPACITY 512u #endif /** Запись в edgeTrace: фронт не передан в decode (isPairSending). */ #define IR_EDGE_TRACE_F_SKIP_DECODE 0x01u #endif // Пример в скетче: void irPackTracePrintOkCommand(const uint8_t* b, uint8_t n) { Serial.print(F("CarCmd::...")); } #if defined(IRDEBUG_SERIAL_PACK) /** Слабая реализация в IR_DecoderRaw.cpp; в скетче определите свою для вывода вроде CarCmd::... */ void irPackTracePrintOkCommand(const uint8_t *buf, uint8_t packSize); #endif /*////////////////////////////////////////////////////////////////////////////////////// Для работы в паре положить декодер в энкодер */ // Адресация с 1 до 65 499 #define IR_Broadcast 65000 // 65 500 ~ 65 535 - широковещательные пакеты (всем) /* *Адресное пространство: Адрес 0 запрещен и зарезервирован под NULL, либо тесты IR_MSG_ACCEPT с адреса 0 воспринимается всеми устройствами */ //**** Контрольные точки ****** #define IR_MAX_ADDR_CPU 63999 #define IR_MIN_ADDR_CPU 32000 // //***** Группы машинок ******** // #define IR_MAX_CAR_GROUP 31999 // #define IR_MIN_CAR_GROUP 30000 // //********** FREE ************* // #define IR_MAX_FREE 31999 // #define IR_MIN_FREE 2000 //********* Машинки *********** #define IR_MAX_CAR 31999 #define IR_MIN_CAR 1 //***** Пульты управления ***** #define IR_MAX_CONTROLLER 64999 #define IR_MIN_CONTROLLER 64000 /* /```````````````````````````````````````````````` data pack `````````````````````````````````````````````\                                                                                                                                             {``````````} [````````````````````````] [````````````````````] [````````````````````````] [``````````````] { msg type } [ addr_from uint16_t ] [ addr_to uint16_t ] [====== data bytes ======] [ CRC Bytes ] {..........} [........................] [....................] [........................] [..............]                                                                                                            { aka size } [addr_from_H][addr_from_L] [addr_to_H][addr_to_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ] |     0           1            2              3         4          5                         |       |     \____________________________________________________________________________________________/       |     |                                                                                                    |     \____________________________________________________________________________________________________/     msg type:                                 //  __________                                 // | 01234567 |                                 //  ----------                                 // | xxx..... | = тип сообщения (биты 7..5)                                 // | ...xxxxx | = полная длина кадра в байтах (5 бит, 0..31, IR_MASK_MSG_INFO), не «31 бит» и не отдельный лимит «24 байта» // Полезная нагрузка в data pack: до irproto::kMaxDataPayloadBytes байт.                                 //  ---------- */ #define IR_MSG_BACK 0U // | 000...... | = Задний сигнал машинки #define IR_MSG_ACCEPT 1U // | 001..... | = подтверждение #define IR_MSG_REQUEST 2U // | 010..... | = запрос // #define IR_MSG_ 3U // | 011..... | = ?? #define IR_MSG_BACK_TO 4U // | 100..... | = Задний сигнал машинки c адресацией // #define IR_MSG_ 5U // | 101..... | = ?? #define IR_MSG_DATA_NOACCEPT 6U // | 110..... | = данные, не требующие подтверждения #define IR_MSG_DATA_ACCEPT 7U // | 111..... | = данные требующие подтверждения /*   // ---------- /``````````````````````````````` подтверждение `````````````````````````````\      /``````````````````````````````````````` запрос ``````````````````````````````````\                                                                                                                        {``````````} [````````````````````````] [``````````````````] [``````````````]      {``````````} [````````````````````````] [````````````````````````] [``````````````] { msg type } [ addr_from uint16_t ] [=== customByte ===] [ CRC Bytes ]      { msg type } [ addr_from uint16_t ] [ addr_to uint16_t ] [ CRC Bytes ] {..........} [........................] [..................] [..............]      {..........} [........................] [........................] [..............]                                                                                                                                                              { 001..... } [addr_from_H][addr_from_L] [=== customByte ===] [ crc1 ][ crc2 ]      { 010..... } [addr_from_H][addr_from_L] [addr_from_H][addr_from_L] [ crc1 ][ crc2 ] |     0            1           2                  3              4       5          |     0            1           2              3           4           5       6     \________________________________________________________________/       |          \_____________________________________________________________________/       |     |                                                                        |          |                                                                             |     \________________________________________________________________________/          \_____________________________________________________________________________/     customByte - контрольная сумма принятых данных по poly1 /`````````````````````` Задний сигнал машинки без адресации ``````````````````````\         // Первый байт: (IR_MSG_BACK<<5) | (packSize & IR_MASK_MSG_INFO) — как у data pack (тип + длина 0..31).                                                                                             {``````````} [````````````````````````] [````````````````````````] [``````````````]         { msg type } [ addr_from uint16_t ] [====== data bytes ======] [ CRC Bytes ]         {..........} [........................] [........................] [..............]                                                                                                     { xxx..|..xxxxx } [addr_from_H][addr_from_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ]         |     0           1            2            3                         |       |             \_____________________________________________________________________/       |             |                                                                             |             \_____________________________________________________________________________/             /```````````````````````````````````` Задний сигнал машинки с адресацией ````````````````````````````````````\  // Первый байт: (IR_MSG_BACK_TO<<5) | (packSize & IR_MASK_MSG_INFO) — IR_MSG_BACK_TO в битах 7..5, длина 0..31.                                                                                            {``````````} [````````````````````````] [````````````````````````] [````````````````````````] [``````````````]  { msg type } [ addr_from uint16_t ] [ addr_to uint16_t ] [====== data bytes ======] [ CRC Bytes ]  {..........} [........................] [........................] [........................] [..............]                                                                                                                   { xxx..|..xxxxx } [addr_from_H][addr_from_L] [addr_to_H][addr_to_L] [data_H][data_n..][data_L] [ crc1 ][ crc2 ]  |     0           1            2              3           4            5                         |       |      \________________________________________________________________________________________________/       |      |                                                                                                        |      \________________________________________________________________________________________________________/      */ namespace irproto { /** Three high header bits, shifted down, encode the message type. */ constexpr uint8_t kMessageTypeMask = 0x07U; /** Five low header bits encode the complete on-wire frame length. */ constexpr uint8_t kWireFrameLengthBits = 5U; constexpr uint8_t kWireFrameLengthMask = static_cast((1U << kWireFrameLengthBits) - 1U); constexpr uint8_t kMaxWireFrameBytes = kWireFrameLengthMask; } // Source-compatible aliases. New code should use the typed irproto constants. #define IR_MASK_MSG_TYPE (::irproto::kMessageTypeMask) #define IR_MASK_MSG_INFO (::irproto::kWireFrameLengthMask) /* /////////////////////////////////////////////////////////////////////////////////////*/ typedef uint16_t crc_t; // #define BRUTEFORCE_CHECK // Перепроверяет пакет на 1 битные ошибки //TODO: зависает #ifndef freeFrec #define freeFrec false #endif #ifndef subBufferSize #define subBufferSize 250 // Буфер для складирования фронтов, пока их не обработают (передатчик) #endif /** Максимальное число передатчиков, способных временно заглушить один декодер. */ #ifndef IR_PAIR_MUTE_MAX_ENCODERS #define IR_PAIR_MUTE_MAX_ENCODERS 8U #endif /** Минимальная длительность удержания уровня (мкс): короче — импульс/пара фронтов выкидывается до tick() * (иголки на плато, дребезг). 0 — фильтр выключен, фронты идут в декодер как с ISR. */ #ifndef IR_INPUT_MIN_PULSE_US #define IR_INPUT_MIN_PULSE_US 0 #endif /** Сколько сырых фронтов забирать из ISR-буфера за один tick() (батч-выемка). * 1 фронт/tick привязывал RX-пропускную способность к частоте loop: медленный * loop (телеметрия/дисплей) переполнял буфер 250 фронтов (~120мс эфира) и * кадры терялись молча. Батч развязывает приём от темпа loop. */ #ifndef IR_RX_TICK_BATCH #define IR_RX_TICK_BATCH 16U #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 bitPerByte 8U // Колличество бит в байте #define addrBytes 2 #define msgBytes 1 #define crcBytes 2 #define poly1 0x31 #define poly2 0x8C #define syncBits 3U // количество битов синхронизации #define preambFronts (preambPulse * 2) // количество фронтов преамбулы (Приём) #define preambToggle ((bitPauseTakts * 2 + bitActiveTakts) * 2 - 1) // колличество переключений преамбулы (Передача) #define carrierFrec 38000U // частота несущей (Приём/Передача) #define carrierPeriod (1000000U / carrierFrec) // период несущей в us (Приём) // В процессе работы значения будут отклонятся в соответствии с предыдущим битом #define bitActiveTakts 25U // длительность высокого уровня в тактах #define bitPauseTakts 12U // длительность низкого уровня в тактах #define bitTakts (bitActiveTakts + bitPauseTakts) // Общая длительность бита в тактах #define bitTime (bitTakts * carrierPeriod) // Общая длительность бита namespace irproto { constexpr uint8_t kDataFrameOverheadBytes = msgBytes + addrBytes + addrBytes + crcBytes; constexpr uint8_t kBackFrameOverheadBytes = msgBytes + addrBytes + crcBytes; constexpr uint8_t kBackToFrameOverheadBytes = msgBytes + addrBytes + addrBytes + crcBytes; constexpr uint8_t kMaxDataPayloadBytes = kMaxWireFrameBytes - kDataFrameOverheadBytes; constexpr uint8_t kMaxBackPayloadBytes = kMaxWireFrameBytes - kBackFrameOverheadBytes; constexpr uint8_t kMaxBackToPayloadBytes = kMaxWireFrameBytes - kBackToFrameOverheadBytes; /** RX timing geometry shared by adaptive and nominal decoder paths. */ constexpr uint16_t kRxTimingToleranceUs = 300U; constexpr uint8_t kRxInterEdgeTimeoutGuardBitWindows = 1U; constexpr uint8_t kRxInterEdgeTimeoutBitWindows = static_cast(bitPerByte + syncBits + kRxInterEdgeTimeoutGuardBitWindows); constexpr uint8_t kRxSilenceTimeoutInterEdgeWindows = 2U; /** * Largest accepted rise-to-rise interval for one decoder byte window. * adaptiveBitPeriodUs is riseSyncTime when free-frequency tracking is used. */ constexpr uint32_t rxInterEdgeTimeoutUs(uint32_t adaptiveBitPeriodUs) { return (adaptiveBitPeriodUs + static_cast(kRxTimingToleranceUs)) * static_cast(kRxInterEdgeTimeoutBitWindows); } /** Silence after which an unfinished RX candidate is retired. */ constexpr uint32_t rxSilenceTimeoutUs(uint32_t adaptiveBitPeriodUs) { return rxInterEdgeTimeoutUs(adaptiveBitPeriodUs) * static_cast(kRxSilenceTimeoutInterEdgeWindows); } constexpr uint32_t microsToMillisCeil(uint32_t us) { return (us + 999U) / 1000U; } constexpr uint32_t kNominalRxInterEdgeTimeoutUs = rxInterEdgeTimeoutUs(bitTime); constexpr uint32_t kNominalRxSilenceTimeoutUs = rxSilenceTimeoutUs(bitTime); /** * Deployed response/ACK turn-around policy. * * This is empirical, not a PHY invariant. Commit 1353ab6 replaced the older * fixed 75 ms with a floating expression whose only reproducible result at the * nominal PHY is 42 ms; no measurement or physical derivation was recorded. * Keep the deployed value until a hardware gap campaign establishes a new * channel-turn-around contract. */ constexpr uint16_t kDefaultResponseTurnaroundDelayMs = 42U; /** * Conservative logical run bound: preamble transitions plus two gate runs for * every data/sync bit. Physical splitting for unusually large multiply values * is reported by IR_TxPlan::gateRunCount and may require custom storage. */ constexpr size_t kMaxLogicalGateRuns = static_cast(preambPulse * 2U) + static_cast(kMaxWireFrameBytes) * static_cast((bitPerByte + syncBits) * 2U); constexpr size_t kIsrTxMaxGateRuns = kMaxLogicalGateRuns; /** * Compile-time PHY storage contract. * * Every data and sync bit occupies bitTakts * 2 ticks on the logical * 2*carrierFrec clock, independently of its value. A physical gate run is * stored in uint16_t and can therefore split at UINT16_MAX ticks. The bound * below includes the worst possible number of such split pieces; applications * can size fixed DMA/ISR storage from the protocol instead of duplicating a * packet-size constant. */ constexpr uint32_t kPreambleLogicalTicks = static_cast(preambPulse * 2U) * static_cast(preambToggle + 1U); constexpr uint32_t kEncodedBitLogicalTicks = static_cast(bitTakts * 2U); constexpr uint32_t kMaxLogicalTransmissionTicks = kPreambleLogicalTicks + static_cast(kMaxWireFrameBytes) * static_cast(bitPerByte + syncBits) * kEncodedBitLogicalTicks; constexpr uint16_t normalizedCarrierMultiply(uint16_t multiply) { return multiply < 2U ? 2U : multiply; } constexpr uint64_t maxPhysicalTransmissionTicks(uint16_t multiply) { return (static_cast(kMaxLogicalTransmissionTicks) * static_cast(normalizedCarrierMultiply(multiply)) + 1U) / 2U; } constexpr size_t maxPhysicalGateRunCapacity(uint16_t multiply) { return kMaxLogicalGateRuns + static_cast(maxPhysicalTransmissionTicks(multiply) / static_cast(UINT16_MAX)); } static_assert(kMaxDataPayloadBytes == 24U, "IR DATA payload contract changed"); static_assert(kMaxBackPayloadBytes == 26U, "IR BACK payload contract changed"); static_assert(kNominalRxInterEdgeTimeoutUs == 15144U, "IR RX timeout contract changed"); static_assert(kNominalRxSilenceTimeoutUs == 30288U, "IR RX silence contract changed"); static_assert(kMaxLogicalTransmissionTicks <= UINT32_MAX, "IR maximum transmission no longer fits IR_TxPlan"); } // Deprecated source-compatible names. They are aliases only and no longer // define independent storage/payload limits. bytePerPack historically meant // 31; preserving that value avoids silently changing external sketches. #define bytePerPack (::irproto::kMaxWireFrameBytes) #define dataByteSizeMax (::irproto::kMaxWireFrameBytes) #define IR_TIMING_TOLERANCE_US (::irproto::kRxTimingToleranceUs) 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); constexpr uint16_t test_low = ((bitPauseTakts / 2 + bitActiveTakts) * 2 - 0) + ((bitPauseTakts)-0); class IR_FOX { public: struct PackOffsets { uint8_t msgOffset; uint8_t addrFromOffset; uint8_t addrToOffset; uint8_t dataOffset; uint8_t crcOffset; }; struct ErrorsStruct { uint8_t lowSignal = 0; uint8_t highSignal = 0; uint8_t other = 0; void reset() { lowSignal = 0; highSignal = 0; other = 0; } uint16_t all() { return lowSignal + highSignal + other; } }; struct PackInfo { uint8_t *buffer = nullptr; uint8_t packSize = 0; uint16_t crc = 0; ErrorsStruct err; uint16_t rTime = 0; }; inline uint16_t getId() const { return id; } inline void setId(uint16_t id) { this->id = id; } static void checkAddressRuleApply(uint16_t address, uint16_t id, bool &flag); void setPin(uint8_t pin); inline uint8_t getPin() { return pin; }; inline GPIO_TypeDef *getPort() const { return port; } inline uint16_t getPinMask() const { return mask; } protected: uint16_t id; uint8_t pin; GPIO_TypeDef *port; uint16_t mask; ErrorsStruct errors; uint8_t crc8(uint8_t *data, uint8_t start, uint8_t end, uint8_t poly); };