Встраиваемые системы
DMA и оптимизация
Цели урока
- Настроить DMA канал для UART/ADC в Normal и Circular режимах
- Реализовать double-buffer схему с HalfTransfer и TransferComplete прерываниями
- Понять проблему cache coherency на Cortex-M7 и способы её решения
- Настроить MPU для защиты памяти и создания non-cacheable DMA-регионов
MCU гонит данные с АЦП на 1 МГц. Если CPU обрабатывает каждый сэмпл вручную - он занят только этим. DMA освобождает CPU: железо само копирует байты, а процессор занимается алгоритмами. Но есть подвох - кеш Cortex-M7 может «соврать» о содержимом буфера.
- **Audio DSP:** непрерывный поток I2S/SAI с DMA circular - ни одного сэмпла не пропускается
- **ADC oversampling:** DMA накапливает 64 сэмпла в буфере, CPU делает DSP по завершении
- **Ethernet на STM32H7:** DMA дескрипторы в non-cacheable RAM - без этого пакеты теряются
- **RTOS hardening:** MPU stack guard под каждой задачей вместо молчаливого stack overflow
DMA каналы и конфигурация
Представь конвейер на заводе: раньше инженер лично носил детали с одного стола на другой, теряя время. DMA (Direct Memory Access) - это конвейерная лента: CPU говорит «перенеси N байт из ADC в RAM», запускает DMA и занимается полезной работой, пока перенос идёт аппаратно.
На STM32 каждый DMA-контроллер имеет несколько **каналов** (или **потоков** в DMA2). Каждый канал жёстко привязан к конкретным периферийным устройствам - нельзя, например, подключить UART1_RX к произвольному каналу. Настройка канала включает: источник, приёмник, размер слова, режим (normal / circular) и приоритет.
**Арбитраж:** если несколько DMA-каналов одновременно запрашивают шину, приоритет решает, кто пройдёт первым. Настраивается: Very High / High / Medium / Low. Каналы одного приоритета разрешаются аппаратно по номеру канала.
DMA настроен PeriphInc=DISABLE, MemInc=ENABLE. Что это означает?
Circular Buffer для потоков данных
Normal-режим DMA останавливается по завершении, и нужно вручную перезапускать. Это создаёт окно, когда данные могут теряться. **Circular (кольцевой) режим** решает проблему: дойдя до конца буфера, DMA автоматически возвращается к началу и продолжает. CPU читает данные, пока DMA пишет следующий блок.
**NDTR (Number of Data to Transfer):** текущий счётчик DMA доступен через `__HAL_DMA_GET_COUNTER(&hdma)`. Зная BUF_SIZE и NDTR, можно вычислить, сколько байт ещё не прочитано, без ожидания прерываний.
Зачем использовать DMA в Circular режиме вместо Normal?
Cache coherency в embedded
На Cortex-M7 (STM32H7, iMXRT) есть L1 D-Cache. Это поднимает производительность CPU, но создаёт ловушку с DMA: **DMA пишет данные напрямую в RAM, минуя кеш**. Если CPU читает те же адреса из кеша - он видит **старые данные**.
Второй вариант требует, чтобы размер и адрес буфера были выровнены на размер cache line (32 байта на Cortex-M7). Частичная инвалидация опасна: если в той же cache line есть другие переменные, они сбросятся вместе с буфером.
**Для TX (CPU -> DMA):** нужна обратная операция - `SCB_CleanDCache_by_Addr` перед стартом DMA, чтобы вытолкнуть данные из кеша в RAM, откуда DMA их заберёт.
DMA записал данные в dma_buf. CPU читает dma_buf и видит старые значения. Причина:
MPU: Memory Protection Unit
В классической RTOS-задаче ошибка в одном потоке может перезаписать стек другого - и система падает непредсказуемо. **MPU (Memory Protection Unit)** - аппаратный сторож: он делит адресное пространство на регионы и задаёт права доступа. Запись за пределы - немедленный MemManage Fault.
**Stack overflow guard:** помести регион MPU с правами no-access прямо под стек каждой RTOS-задачи. Переполнение стека немедленно вызовет MemManage Fault вместо молчаливого повреждения данных соседней задачи.
MPU регион настроен как non-cacheable для DMA-буферов. Что это даёт?
DMA и оптимизация памяти
- DMA переносит данные периферия<->RAM аппаратно, CPU в это время свободен
- Circular режим + HalfTransfer/TransferComplete IRQ = непрерывный стриминг без потерь
- На Cortex-M7: DMA пишет в RAM мимо кеша - нужна инвалидация или non-cacheable регион
- Буферы для DMA: выровнять на 32 байта, размер кратен 32 (размер cache line)
- MPU создаёт non-cacheable регион для DMA-буферов и защищает стеки от overflow
Связанные темы
DMA - часть экосистемы эффективной работы с периферией в embedded.
- Прерывания и NVIC — DMA TC/HT прерывания настраиваются через NVIC
- UART и SPI — Главные потребители DMA в embedded проектах
- RTOS и многозадачность — DMA circular + очереди FreeRTOS - типичная связка
Вопросы для размышления
- В чём разница между инвалидацией и очисткой (clean) кеша, и когда нужна каждая операция?
- Почему размер и адрес DMA-буфера должны быть кратны размеру cache line, а не просто выровнены?
- Как MPU stack guard обнаруживает переполнение стека быстрее, чем любая программная проверка?