#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; /** Максимум RLE-сегментов для buildGateRuns при ISR-TX. */ constexpr size_t kIsrTxMaxGateRuns = 512U; 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: до bytePerPack байт (см. #define bytePerPack).                                 //  ---------- */ #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                         |       |      \________________________________________________________________________________________________/       |      |                                                                                                        |      \________________________________________________________________________________________________________/      */ #define IR_MASK_MSG_TYPE 0b00000111 #define IR_MASK_MSG_INFO 0b00011111 /* /////////////////////////////////////////////////////////////////////////////////////*/ typedef uint16_t crc_t; // #define BRUTEFORCE_CHECK // Перепроверяет пакет на 1 битные ошибки //TODO: зависает #define bytePerPack (31) // колличество байтов в пакете #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 dataByteSizeMax (msgBytes + addrBytes + addrBytes + bytePerPack + crcBytes) #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) // Общая длительность бита #define tolerance 300U namespace irproto { /** Maximum complete frame length representable by the five header bits. */ constexpr uint8_t kMaxWireFrameBytes = static_cast(IR_MASK_MSG_INFO); 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 kAcceptFrameBytes = msgBytes + addrBytes + 1U + crcBytes; constexpr uint8_t kRequestFrameBytes = msgBytes + addrBytes + addrBytes + crcBytes; constexpr uint8_t kMaxDataPayloadBytes = kMaxWireFrameBytes - kDataFrameOverheadBytes; constexpr uint8_t kMaxBackPayloadBytes = kMaxWireFrameBytes - kBackFrameOverheadBytes; constexpr uint8_t kMaxBackToPayloadBytes = kMaxWireFrameBytes - kBackToFrameOverheadBytes; /** Complete DATA frame size, or zero when payloadBytes cannot fit on wire. */ constexpr uint8_t dataWireBytes(uint8_t payloadBytes) { return payloadBytes <= kMaxDataPayloadBytes ? static_cast(kDataFrameOverheadBytes + payloadBytes) : 0U; } /** Complete non-addressed BACK frame size, or zero when it cannot fit. */ constexpr uint8_t backWireBytes(uint8_t payloadBytes) { return payloadBytes <= kMaxBackPayloadBytes ? static_cast(kBackFrameOverheadBytes + payloadBytes) : 0U; } /** Complete addressed BACK_TO frame size, or zero when it cannot fit. */ constexpr uint8_t backToWireBytes(uint8_t payloadBytes) { return payloadBytes <= kMaxBackToPayloadBytes ? static_cast(kBackToFrameOverheadBytes + payloadBytes) : 0U; } /** Minimum complete frame size for a known message type; zero means reserved/unknown. */ constexpr uint8_t minimumWireBytes(uint8_t msgType) { return (msgType == IR_MSG_DATA_ACCEPT || msgType == IR_MSG_DATA_NOACCEPT) ? kDataFrameOverheadBytes : msgType == IR_MSG_BACK ? kBackFrameOverheadBytes : (msgType == IR_MSG_BACK_TO || msgType == IR_MSG_REQUEST) ? kRequestFrameBytes : msgType == IR_MSG_ACCEPT ? kAcceptFrameBytes : 0U; } constexpr bool isTypedWireSizeValid(uint8_t msgType, uint8_t wireBytes) { return minimumWireBytes(msgType) != 0U && wireBytes >= minimumWireBytes(msgType) && wireBytes <= kMaxWireFrameBytes; } /* * TX FSM timing contract. * * The FSM runs on 2*carrierFrec. The preamble contains preambPulse*2 * constant runs; each run is preambToggle+1 ticks. Every data bit and every * per-byte sync bit occupies bitTakts*2 ticks, independently of its value. */ constexpr uint32_t kTxLogicalClockHz = static_cast(carrierFrec) * 2U; constexpr uint32_t kPreambleLogicalTicks = static_cast(preambPulse * 2U) * static_cast(preambToggle + 1U); constexpr uint32_t kEncodedBitLogicalTicks = static_cast(bitTakts * 2U); constexpr uint32_t kWireByteLogicalTicks = static_cast(bitPerByte + syncBits) * kEncodedBitLogicalTicks; constexpr uint32_t wireLogicalTicks(uint8_t wireBytes) { return wireBytes != 0U && wireBytes <= kMaxWireFrameBytes ? kPreambleLogicalTicks + static_cast(wireBytes) * kWireByteLogicalTicks : 0U; } constexpr uint32_t logicalTicksToUsCeil(uint32_t logicalTicks) { return logicalTicks == 0U ? 0U : static_cast( (static_cast(logicalTicks) * 1000000ULL + static_cast(kTxLogicalClockHz) - 1ULL) / static_cast(kTxLogicalClockHz)); } constexpr uint32_t preambleAirtimeUsCeil() { return logicalTicksToUsCeil(kPreambleLogicalTicks); } /** Complete nominal on-air duration, rounded up to a whole microsecond. */ constexpr uint32_t wireAirtimeUsCeil(uint8_t wireBytes) { return logicalTicksToUsCeil(wireLogicalTicks(wireBytes)); } constexpr uint32_t wireAirtimeMsCeil(uint8_t wireBytes) { return wireAirtimeUsCeil(wireBytes) == 0U ? 0U : (wireAirtimeUsCeil(wireBytes) + 999U) / 1000U; } /* Preserve the deployed library turn-around policy, but expose it by name. */ constexpr uint16_t kMandatoryInterPacketQuietMs = static_cast( static_cast( (static_cast(bitTime + tolerance) * static_cast(bitPerByte + syncBits + 1U)) * 2.7735) / 1000U); constexpr uint32_t kMandatoryInterPacketQuietUs = static_cast(kMandatoryInterPacketQuietMs) * 1000U; constexpr uint16_t kDefaultTimingGuardPermille = 1150U; constexpr uint32_t addTimingGuardUs(uint32_t durationUs, uint16_t marginPermille = kDefaultTimingGuardPermille) { return marginPermille == 0U ? 0U : static_cast( (static_cast(durationUs) * marginPermille + 999ULL) / 1000ULL); } /** Deadline for seeing enough preamble to know that a response has started. */ constexpr uint32_t responseStartGuardUs( uint16_t marginPermille = kDefaultTimingGuardPermille) { return addTimingGuardUs(kMandatoryInterPacketQuietUs + preambleAirtimeUsCeil(), marginPermille); } /** Conservative deadline for receiving a complete response of maxWireBytes. */ constexpr uint32_t responseFrameGuardUs( uint8_t maxWireBytes, uint16_t marginPermille = kDefaultTimingGuardPermille) { return wireAirtimeUsCeil(maxWireBytes) == 0U ? 0U : addTimingGuardUs(kMandatoryInterPacketQuietUs + wireAirtimeUsCeil(maxWireBytes), marginPermille); } static_assert(kMaxDataPayloadBytes == 24U, "DATA payload contract changed"); static_assert(kMaxBackPayloadBytes == 26U, "BACK payload contract changed"); static_assert(kPreambleLogicalTicks == 588U, "preamble timing contract changed"); static_assert(kWireByteLogicalTicks == 814U, "wire-byte timing contract changed"); static_assert(kMandatoryInterPacketQuietMs == 42U, "inter-packet quiet policy changed"); } 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); };