Files
IR-protocol/IR_config.h

446 lines
27 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

#pragma once
#include <Arduino.h>
#include <list>
// #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<uint8_t>((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<uint8_t>(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<uint32_t>(kRxTimingToleranceUs)) *
static_cast<uint32_t>(kRxInterEdgeTimeoutBitWindows);
}
/** Silence after which an unfinished RX candidate is retired. */
constexpr uint32_t rxSilenceTimeoutUs(uint32_t adaptiveBitPeriodUs)
{
return rxInterEdgeTimeoutUs(adaptiveBitPeriodUs) *
static_cast<uint32_t>(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<size_t>(preambPulse * 2U) +
static_cast<size_t>(kMaxWireFrameBytes) *
static_cast<size_t>((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<uint32_t>(preambPulse * 2U) *
static_cast<uint32_t>(preambToggle + 1U);
constexpr uint32_t kEncodedBitLogicalTicks =
static_cast<uint32_t>(bitTakts * 2U);
constexpr uint32_t kMaxLogicalTransmissionTicks =
kPreambleLogicalTicks +
static_cast<uint32_t>(kMaxWireFrameBytes) *
static_cast<uint32_t>(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<uint64_t>(kMaxLogicalTransmissionTicks) *
static_cast<uint64_t>(normalizedCarrierMultiply(multiply)) +
1U) /
2U;
}
constexpr size_t maxPhysicalGateRunCapacity(uint16_t multiply)
{
return kMaxLogicalGateRuns +
static_cast<size_t>(maxPhysicalTransmissionTicks(multiply) /
static_cast<uint64_t>(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);
};