# Контракт бэкенда DMA-TX ИК (`IrDmaTxStm32`)

См. также: [IR_TX_MODES.md](IR_TX_MODES.md) — общая схема выбора `legacy ISR`, `buffered ISR` и `external backend`.

Платформа: **STM32G4**, Arduino STM32. Передача: **DMA memory → GPIO BSRR**, запрос от **TIM UPDATE** (частота `carrierFrec×2` из `IR_Encoder::beginClockOnly`).

### Число потоков (шаблон)

Класс: **`IrDmaTxStm32<MaxStreams>`**. Число слотов в `Config::streams[]` и внутреннем массиве задаётся **в коде**, без `-D` и без макроса до инклюда:

```cpp
constexpr size_t kStreams = 2;
static IrDmaTxStm32<kStreams> dma;
IrDmaTxStm32<kStreams>::Config cfg;
// IRQ: IrDmaTxStm32<kStreams>::instance()
```

По умолчанию: **`IrDmaTxStm32<>`** эквивалентно **`IrDmaTxStm32<irproto::kDefaultDmaTxMaxStreams>`** (`IR_config.h`, обычно 4). Реализация в заголовке (отдельного `.cpp` нет).

## Роль библиотеки

- Разбор пакета в RLE-пробеги: `IR_Encoder::buildGateRuns`.
- Генерация слов для BSRR (несущая/тишина по тикам), предзаполнение буфера и дозаполнение по прерываниям half/complete.
- Настройка канала DMA, DMAMUX, кольцевой режим, старт/стоп DMA и таймера, колбэки HAL.
- В `begin()` только `HAL_NVIC_EnableIRQ` для каналов DMA (без `SetPriority`).

## Роль прошивки (клиента)

### Буферы и размеры

На **каждый** поток в `StreamCfg` клиент передаёт:

| Поле | Смысл |
|------|--------|
| `dmaWords` | Указатель на массив `uint32_t` — слова для записи в BSRR. |
| `dmaWordCount` | Число **слов** (32-bit), **чётное**, ≥ 2. Половина — один «полубуфер» для HT/TC IRQ. |
| `gateRuns` | Массив `IR_Encoder::IR_TxGateRun` для выхода `buildGateRuns`. |
| `maxGateRuns` | Длина этого массива. Должен быть достаточен для самого длинного кадра. |

Память и выравнивание — ответственность клиента; типичные порядки: 4096 слов DMA, 1024 ранов (как в машинке).

### Таймер и DMA

- `HardwareTimer` / тот же TIM, что и `beginClockOnly`, без конкурирующего `attachInterrupt` на UPDATE.
- `instance`, `irq`, `dmamuxRequest` (например `DMA_REQUEST_TIM17_UP`) — из схемы платы; оба потока на одном TIM обычно используют **один** `TIMx_UP` в DMAMUX.

### Приоритеты NVIC

Не задаются в библиотеке. После `begin()` клиент выставляет preempt/sub для `DMA1_ChannelN` (и согласует с приёмом EXTI и др.), например общей функцией вроде `Car_applyInterruptPriorities()`.

### Прерывания DMA

Библиотека **не** объявляет `DMA1_ChannelN_IRQHandler`. В одном `.cpp` прошивки — единственное определение на канал, внутри:

`IrDmaTxStm32<N>::instance()->irqForStream(i)` (тот же **N**, что у объекта бэкенда) или `IrDmaTxStm32_onDmaHandle(hdma)`.

## Контракт `IR_Encoder::setExternalTxBackend`

Подключение: `setExternalTxBackend(startFn, busyFn, ctx)`.

- **`startFn(ctx, enc, packet, len)`** — должен вызвать `IrDmaTxStm32<N>::start(enc, packet, len)` (или обёртку). Возвращает успех старта DMA.
- **`busyFn(ctx)`** — пока возвращает «занято», новая отправка не стартует. У `IrDmaTxStm32<N>::busy()`: **true**, если **все** настроенные потоки в передаче (для двух передатчиков — оба активны); иначе можно запустить второй канал.

## Сбой `begin()`

При ошибке `HAL_DMA_Init` и т.п. `begin()` возвращает `false`, `instance()` не используется для IRQ до успешного `begin()`.
