# IR DMA vs ISR: анализ согласованности сигнала и ответа версии Связка с остальным пультом (модули, настройки): **[`ARCHITECTURE.md`](ARCHITECTURE.md)**. Документ фиксирует наблюдения по переходу машинки (проект Car) на **DMA-передачу** ИК через `IR_Encoder::setExternalTxBackend` и `IrDmaBackend`, сравнение со **старым путём** (таймер + **`_isr()`**), ручную проверку CRC по логу пульта и роль **`buildGateRuns`** в библиотеке **IR-protocol**. --- ## 1. Контекст - До введения DMA передача шла через **`IR_Encoder::begin(..., IR_Encoder::isr)`**: на каждый тик таймера (`carrierFrec * 2`) вызывается **`_isr()`**, формируются преамбула, данные, синхробиты. - После коммита с **IR_DMA** (`Car`, `IR.cpp`): **`beginClockOnly`**, **`setExternalTxBackend`**, фактическая модуляция — **`IrDmaBackend::start`** → **`IR_Encoder::buildGateRuns`** + DMA в **BSRR**. - Ответ версии — один из самых **длинных** кадров (до **31 байта** полного кадра по заголовку). Короткие пакеты (эхо `Version_Query`, 8 байт) в логе остаются **FrameOK**; длинный ответ версии даёт **CRC fail** / `Frame reject`. --- ## 2. Два пути: ISR и DMA | Этап | Старый ISR | DMA | |------|------------|-----| | Байты пакета + CRC | `sendDataFULL` → `sendBuffer` | То же; в `buildGateRuns` — `memcpy` в локальный буфер размером `dataByteSizeMax` | | Развёртка в импульсы | **`_isr()`**: счётчик `toggleCounter`, ветки preamb / data / sync | **`buildGateRuns`**: RLE-сегменты `(gate, lenTicks)` → **`nextWord()`** по тикам таймера | | Останов передачи | `signal == noSignal`, `isSending = false` | `ticksOutput >= totalTicks`, `sum(runs[i].lenTicks)` | Идея `buildGateRuns`: **эмулировать** шаги FSM, которые в ISR выполняются при **`toggleCounter == 0`** (см. комментарий в `IR_Encoder.cpp` рядом с внутренним `while`). --- ## 3. Ключевое наблюдение: `runLenTicks = toggleCounter + 1` В **`IR_Encoder::buildGateRuns`** на каждой итерации внешнего цикла: ```cpp const uint16_t runLenTicks = (uint16_t)toggleCounterLocal + 1U; ``` В **`_isr()`** при стартовом **`toggleCounter == N`** выполняется **ровно N** раз ветка `if (toggleCounter) { toggleCounter--; }` подряд, пока счётчик не станет **0**; **следующий** тик попадает в `else` и делает один шаг `switch (signal)`. Между двумя такими визитами в `else` проходит **N тиков таймера**, не **N+1**. В `buildGateRuns` для того же начального `toggleCounterLocal` в run записывается **`N + 1` тик**. Это даёт **систематическое удлинение каждого сегмента на 1 тик** относительно модели «счётчик убывает N раз до нуля». **Следствие:** - `totalTicks = Σ lenTicks` в **`IrDmaBackend::startStream`** **больше**, чем число тиков, которое дал бы чистый ISR при том же пакете. - Число внешних итераций `buildGateRuns` (шагов FSM) совпадает с числом таких сегментов; приближённо: `totalTicks ≈ totalTicks_ISR + (число_внешних_шагов)`. Короткий кадр: ошибка может «теряться» в допусках приёмника. Длинный (версия) — **накопление** ошибки по времени → сдвиг границ битов → **неверные байты**, в том числе **CRC**. --- ## 4. Ручная проверка CRC по логу (пульт) Алгоритм: **`IR_FOX::crc8`** (`IR_config.cpp`), два байта как в **`sendDataFULL`**: - первый байт CRC = `crc8(data, 0, packSize - 2, poly1)`; - второй = `crc8(data, 0, packSize - 1, poly2)` (в расчёт второго входит уже первый байт CRC). Пример **31-байтного** кадра из лога `Frame reject`: - Тело **0…28** (29 байт). - Байты **29…30** — CRC на проводе. Для фиксированного дампа байтов **0…28** корректная пара CRC по формуле библиотеки — **`6E 54`**, в логе на проводе — **`96 62`** → **не совпадает**; приёмник обоснованно отклоняет кадр. Это **не** объясняется разницей AVR vs STM32: счёт идёт по массиву `uint8_t` побайтно. Эхо **8 байт** `C8 FA 2A FD E8 5D AA B4`: пересчёт даёт **`AA B4`** — совпадает с последними байтами кадра → для этого пакета цепочка **байт → CRC** согласована. --- ## 5. Скрипт симуляции В репозитории: **`docs/scripts/ir_protocol_gate_runs_sim.py`**. Запуск: ```bash python docs/scripts/ir_protocol_gate_runs_sim.py ``` Скрипт: 1. Считает **CRC** для примеров пакетов (8 байт эха и 31 байт из reject). 2. Воспроизводит логику **`buildGateRuns`** (с дополнением буфера до `dataByteSizeMax`, как в C++). 3. Печатает **`totalTicks`**, число **внешних шагов** FSM и связь **`totalTicks - outer_steps`** как оценку «тиков в модели ISR без +1 на каждый шаг». Пример вывода (значения могут слегка отличаться при смене констант в `IR_config.h`): - `preambToggle = 97` - для 8-байт пакета: сотни шагов FSM, `totalTicks` порядка тысяч тиков - для 31-байт: больше шагов и `totalTicks` (~25k+ тиков для текущих констант) --- ## 6. Связь с проектами - **Car** (`Executer.cpp`): ответ версии через **`IR_Module::getENC().sendData(...)`** — тот же **`sendDataFULL`**, затем **`rawSend`** → DMA. - **ControlPointUnion** (`CustomCmd.h`, слоты): запрос версии через **`sendResp`** с **`version_query`** — задержка **`IR_ResponseDelay`**, затем **`sendData`** на адрес машинки. - **ControlPointUnion** (`Plan_B.ino`): разбор **`version_response`** из **`gotData` / `gotBackData`** только после **успешного CRC** в декодере. --- ## 7. Выводы 1. **Байты в RAM** на передаче формируются корректно библиотекой; проблема «после DMA» укладывается в **расхождение тайминговой развёртки** (`buildGateRuns` + DMA) со **старой** развёрткой ISR, а не в «другой CRC на машинке» при неизменённой библиотеке. 2. **Подозрение №1:** `runLenTicks = toggleCounter + 1` в **`buildGateRuns`** не совпадает с числом тиков ISR между шагами FSM (**`N`** vs **`N+1`**). Требуется сверка с эталонной трассой ISR или логическим анализатором. 3. **Проверка на будущее:** сравнить побитово выходы ISR и DMA на **одном** буфере (8 и 31 байт); при необходимости поправить формулу длины run в **`IR-protocol`** и пересобрать Car и пульт. --- ## 8. Ссылки на файлы | Файл | Назначение | |------|------------| | `Documents/Arduino/libraries/IR-protocol/IR_Encoder.cpp` | `buildGateRuns`, `_isr`, `rawSend` | | `Documents/Arduino/libraries/IR-protocol/IR_config.cpp` | `crc8` | | `Car/src/IR/IR.cpp` | `setExternalTxBackend`, `txStart` | | `Car/src/IR/IrDmaBackend.cpp` | `startStream`, `totalTicks`, `nextWord` | | `ControlPointUnion/Plan_B/TestPoints/CustomCmd.h` | `sendResp` / `version_query` для тестовых слотов | --- *Документ составлен по обсуждению в чате; при смене версии IR-protocol числа констант и `totalTicks` пересчитывайте скриптом.*