buildGateRuns/buildPhysicalGateRuns крутили автомат передачи по одному такту (пинг 10 байт =
8728 тактов) — на 12 МГц это ~30 мс между вызовом send и стартом DMA, на каждой передаче
(задний ~20 мс). Обход по границам (txWalkRuns: ран = toggleCounter+1 тактов уровня state)
даёт ту же последовательность за число шагов = число ранов (225). Эквивалентность проверена на
хосте: 12480 случаев, len 1..38, множитель 2..8, логические и физические раны совпадают
бит-в-бит (len=39 — чтение за буфером в sync-фазе автомата у обеих версий, на эфире ≤31).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeA1K5dBUKrnoXVwyoVjzq
- 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
_sendBack и testSendBack считали packSize с min(uint8_t(1),len) → в кадр входил только 1 байт данных, остальные обрезались (и затирались CRC). Многобайтовый back (speed + customBackData/sendOrderedBack) слался битым. Теперь packSize = ...+len+crcBytes, плюс валидация полного packSize<=31 (IR_MASK_MSG_INFO). Для len=1 поведение не меняется.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
B5 (MEDIUM): RingBuffer::pop(T&) копирует под одной критсекцией; tick() перешёл на неё → нет торн-рида (внутренний interrupts() в T* pop снимал внешнюю защиту до чтения *ptr). T* pop() оставлен (не используется).
B6 (MEDIUM): убрана лишняя noInterrupts/interrupts вокруг micros() в EXTI-ISR (снимала PRIMASK посреди ISR). std::function-диспетчеризация attachInterrupt — структурна, не трогаю.
B7 (LOW): скобки в crcCheck (== & 0xFF по приоритету).
B10 (LOW): guard деления на 0 в ceil_div (актуально только при freeFrec — НЕ включаю).
send: sendDataFULL — валидация полного packSize<=31 (было len>bytePerPack=31, packSize=7+len оборачивался → тихая потеря Data payload 25..31).
B8 (isSubBufferOverflow) — уже volatile, потеря флага безвредна (диагностика), не трогаю. B9 (prevRise==0 при micros()==0) — уже обработан веткой в preambleProcessEdge, намеренно.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>