Files
IR-protocol/IR_DMA_ISR_signal_analysis.md

126 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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.

# 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` пересчитывайте скриптом.*