fix msgTypeReceive and isReceive

This commit is contained in:
2026-03-30 13:34:04 +03:00
parent fc1a3bacef
commit af3e012aac
7 changed files with 347 additions and 20 deletions

View File

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