1. Тема дня

Архитектура непрерывных потоков:

text
периферия
→ FIFO
→ DMA или ISR-драйвер
→ кольцевой буфер
→ descriptor/event
→ задача-владелец
→ parser/filter
→ прикладное событие

Главная мысль: DMA решает проблему перемещения байтов, но не решает проблему владения буфером. Надёжность появляется только тогда, когда в каждый момент известно, кто имеет право читать или изменять конкретную область памяти.

2. Зачем это нужно

EC25

При 921600 baud, 8N1:

text
921600 / 10 ≈ 92160 байт/с

Буфер 4096 байт заполняется примерно за 44 мс. Одна блокировка на 50-100 мс может привести к потере данных.

STM32 ADC

text
таймер
→ ADC trigger
→ scan sequence
→ DMA circular buffer
→ half/full transfer event
→ adc_task
→ min/max/avg/filter

STM32 UART/RS-485

DMA принимает байты, пока CPU разбирает предыдущий пакет. Но CPU не должен читать область, которую DMA уже перезаписывает.

3. Теория

DMA

Без DMA:

text
периферия получила байт
→ interrupt
→ CPU читает регистр
→ CPU кладёт байт в RAM

С DMA:

text
периферия получила байт
→ DMA сам переносит байт в RAM
→ CPU получает interrupt на границе блока

DMA не понимает AT-строки, CRC, MQTT, parser state и deadlines.

Normal, circular, ping-pong

Normal DMA переносит N элементов и останавливается. Circular DMA после конца буфера возвращается к началу. Ping-pong: пока DMA пишет A, CPU обрабатывает B, затем наоборот.

Ownership

c
typedef enum {
    DMA_BLOCK_FREE = 0,
    DMA_BLOCK_OWNED_BY_DMA,
    DMA_BLOCK_READY_FOR_CPU,
    DMA_BLOCK_OWNED_BY_CPU,
} dma_block_state_t;

Запрещено, чтобы DMA и CPU одновременно изменяли один блок.

Zero-copy

Zero-copy безопасен только при явном lifetime буфера. Pointer в queue опасен, если DMA уже успел перезаписать память до обработки задачи. Иногда одна контролируемая копия безопаснее, чем сложный zero-copy.

ESP32 UART

Стандартный UART-драйвер ESP-IDF использует внутренние RX/TX ring buffers. Для EC25 сначала оптимизируй:

text
- размер UART RX buffer;
- owner task;
- parser;
- event queue;
- диагностику overflow.

Размер буфера

text
buffer_bytes ≥ input_rate_bytes_per_second
             × worst_case_block_time_seconds
             × safety_factor

Cache coherency STM32F7/H7

CPU может видеть cache, DMA - RAM. CPU→DMA:

text
CPU подготовил TX buffer → cache clean → start DMA

DMA→CPU:

text
DMA записал RX buffer → cache invalidate → CPU read

Буферы выравнивай по cache line, часто 32 байта.

4. Типичные ошибки

  • Считать DMA-buffer обычным массивом.
  • Называть ESP32 UART zero-copy DMA.
  • Queue содержит pointer без lifetime contract.
  • Не считать полный оборот circular buffer.
  • Parser запускается в DMA callback.
  • Нет политики overrun/desync.
  • Перепутать clean и invalidate.
  • Cache operation по невыравненному диапазону.
  • DMA-buffer в недоступном RAM-bank.
  • Увеличивать buffer вместо устранения блокировки consumer.

5. Практическое задание

Создай DMA_BUFFER_POLICY.md.

markdown
# DMA and stream buffer policy
1. Every stream has one owner task.
2. DMA and CPU ownership is explicitly documented.
3. ISR transfers descriptors, not large buffers.
4. Every circular buffer has overrun detection.
5. Buffer size is calculated from measured worst-case service gap.
6. Parser enters DESYNC after data loss.
7. DMA buffers are placed in memory accessible by selected DMA.
8. Cacheable STM32 DMA buffers use explicit clean/invalidate.
9. Cache operations cover full aligned cache lines.
10. Every stream exposes diagnostics through CLI.

Добавь CLI streams. Пример:

text
MODEM_UART
baud=921600
rx_buffer=8192
rx_bytes=12845092
fifo_overrun=0
ring_full=0
max_service_gap=18400us
estimated_fill_time=88888us
margin=70488us

6. Что попробовать дальше

  • Измерить max_service_gap_us для EC25.
  • Рассчитать нужный UART buffer.
  • Добавить sequence для STM32 DMA blocks.
  • Добавить cache helpers для STM32H7/F7.
  • Проверить overrun/desync HIL-тестом.

Задание

По примеру 921600 baud, 8N1 и 4096-byte объясните опасность 50 ms service gap. Определите измерение для выбора buffer.

Критерии самопроверки: Отделить baud от bytes/s; связать capacity с worst-case delay, а не average throughput.

Показать ответ автора

Byte rate примерно 92160 bytes/s, поэтому 4096 bytes покрывают около 44 ms. Интервал 50 ms превышает capacity. Измерить worst-case consumer service gaps под нагрузкой, применить buffer formula с safety factor и сохранять overflow counters.

Задание

Queue содержит pointer на DMA RX block. Опишите ownership transfer и cache action перед чтением task, а также поведение при опоздании task.

Критерии самопроверки: Указать владельца block на каждом шаге, lifetime и late-consumer policy; один pointer не задаёт ownership.

Показать ответ автора

Передать completed block по явному descriptor/sequence contract. На cache-equipped target выполнить invalidate правильного aligned RX range перед чтением CPU данных DMA согласно target memory policy. Обнаруживать reuse/overrun и resynchronize вместо незаметного чтения перезаписанных данных.