Direct memory Access

Direct Memory Access (DMA) или Прямой Доступ к Памяти (ПДП) — это аппаратный модуль микроконтроллера, позволяющий пересылать данные между периферийными устройствами и памятью (или между областями памяти) без постоянного вмешательства центрального процессора (CPU).

Несмотря на то, что большинство современных разработчиков встроенных систем знают о существовании DMA, на практике этот инструмент часто используется не в полную силу.

Данная статья призвана восполнить пробелы в понимании архитектуры DMA, объяснить выгоды его применения и рассмотреть типовые сценарии использования, а также подводные камни при реализации драйверов.

1. Зачем нужен DMA?

В классической архитектуре без DMA обмен данными между периферией (например, UART, ADC, SPI) и памятью происходит под управлением процессора. Существует два основных подхода:

  1. Опрос (Polling): Процессор в цикле проверяет флаг готовности данных. Это приводит к простоям CPU и неэффективному расходу тактов.
  2. Прерывания (Interrupts): Процессор реагирует на событие. При высоких скоростях потока данных (например, быстрый ADC или высокоскоростной UART) частота прерываний становится настолько большой, что процессор тратит 100% времени на обслуживание прерываний, не успевая выполнять полезную логику.

Преимущества использования DMA:

  • Разгрузка процессора: CPU свободен для выполнения вычислений или перехода в режим энергосбережения во время передачи данных.
  • Детерминизм: Передача данных происходит с постоянной скоростью, не зависящей от задержек в коде приложения.
  • Энергоэффективность: Возможность будить процессор только по факту завершения передачи блока данных, а не каждого байта.

2. Архитектура и Принцип Работы

Контроллер DMA работает как независимый мастер системной шины. Он запрашивает доступ к шине у арбитра, и на время транзакции процессор может быть приостановлен (или использовать другую шину, если архитектура многошинная).

2.1. Каналы и Запросы

Контроллер DMA состоит из нескольких независимых каналов. Каждый канал может быть закреплен за определенным периферийным устройством (запросом).

  • Количество каналов: Определяется конкретной моделью МК (от 1 до 32 и более).
  • Приоритеты: Каналы имеют приоритеты для арбитража доступа к шине в случае одновременных запросов.

2.2. Блок Управления Передачей (TCB / Descriptor)

Современные контроллеры DMA используют структуру данных в оперативной памяти, называемую Transfer Control Block (TCB) или дескриптор. В ней хранятся параметры транзакции:

  • Адрес источника.
  • Адрес назначения.
  • Количество элементов для передачи.
  • Размер элемента (байт, полуслово, слово).
  • Режим инкремента адресов (увеличение, фиксация, циклический буфер).
  • Указатель на следующий TCB (для цепочек передач).

Преимущество использования TCB в памяти: Процессор может подготовить следующий блок данных (следующий TCB) пока текущая передача еще выполняется. По завершении текущей транзакции DMA просто переключает указатель на новый дескриптор. Это позволяет реализовать непрерывную потоковую передачу без задержек на перепрограммирование регистров DMA.

3. Типовые Сценарии Использования

3.1. Сбор данных с АЦП (ADC) в буфер

Самый распространенный кейс. Требуется оцифровать сигнал с частотой, например, 10 кГц.

  • Без DMA: Прерывание каждые 100 мкс. Высокая нагрузка на CPU.
  • С DMA: АЦП запускается по таймеру, данные автоматически пишутся в массив в памяти. Прерывание генерируется только после заполнения всего массива (например, 1000 отсчетов).

3.2. Передача данных по интерфейсам (UART, SPI)

  • Прием: Запись входящего потока в кольцевой буфер.
  • Передача: Отправка большого пакета данных без копирования байтов процессором в регистр данных.

3.3. Копирование памяти (Memory-to-Memory)

Используется для быстрого клонирования структур данных, очистки буферов (заполнение нулями) или перемещения данных между разными областями памяти (например, из Flash в RAM при старте).

4. Пример Реализации (Концептуальный код на C)

Рассмотрим настройку DMA для приема данных от АЦП в массив. Код приведен в обобщенном виде (стиль CMSIS), так как регистры зависят от вендора.

// Глобальный буфер для данных АЦП
volatile uint16_t adc_buffer[64];

// Структура конфигурации DMA
typedef struct {
void* src_addr; // Адрес источника (регистр АЦП)
void* dst_addr; // Адрес назначения (буфер в RAM)
uint16_t size; // Размер передачи
uint8_t data_width; // Ширина данных (байт, слово)
bool inc_src; // Инкремент адреса источника
bool inc_dst; // Инкремент адреса назначения
} DMA_Config_t;

void DMA_Init(DMA_Config_t* cfg) {
// 1. Тактирование контроллера DMA
RCC_EnableClock(DMA_PERIPH);

// 2. Настройка канала
DMA_Channel->CPAR = (uint32_t)cfg->src_addr; // Peripheral Address
DMA_Channel->CMAR = (uint32_t)cfg->dst_addr; // Memory Address
DMA_Channel->CNDTR = cfg->size; // Number of Data

// 3. Настройка режима работы
uint32_t mode = 0;
if (cfg->inc_dst) mode |= DMA_MINC_ENABLE; // Инкремент памяти
if (!cfg->inc_src) mode |= DMA_PINC_DISABLE; // Адрес периферии фиксирован
mode |= DMA_CIRC_ENABLE; // Циклический режим (для постоянного сбора)

DMA_Channel->CCR = mode | DMA_CHANNEL_PRIORITY_HIGH;

// 4. Настройка прерываний
DMA_Channel->CCR |= DMA_IT_TC; // Прерывание по завершении передачи (Transfer Complete)

// 5. Включение канала
DMA_Channel->CCR |= DMA_ENABLE;
}

// Обработчик прерывания
void DMA1_Channel1_IRQHandler(void) {
if (DMA_GetFlag(DMA_FLAG_TC)) {
// Данные в буфере adc_buffer готовы к обработке
// Важно: сброс флага прерывания
DMA_ClearFlag(DMA_FLAG_TC);

// Вызов функции обработки данных
Process_ADC_Data(adc_buffer, 64);
}
}

5. Архитектурные Особенности и Подводные Камни

При разработке драйверов для DMA необходимо учитывать ряд нюансов, которые часто недостаточно освещены в документации.

5.1. Обработка Прерываний

  • Общий вектор прерывания: В некоторых архитектурах (например, отдельные серии МК) все каналы DMA могут генерировать прерывание на один вектор. В обработчике необходимо программно опрашивать флаги завершения всех каналов, что увеличивает время входа-выхода из прерывания (ISR latency).
  • Сброс прерываний: Механизм сброса флага прерывания может быть нетривиальным. В ряде случаев флаг сбрасывается не в регистре DMA, а через отключение запроса в самом периферийном устройстве. Это нарушает принцип инкапсуляции: драйвер DMA должен знать о регистрах периферии.
    • Рекомендация: Всегда внимательно изучайте раздел «Interrupts» в Reference Manual. Реализуйте сброс флагов сразу после входа в ISR, чтобы избежать повторных заходов.

5.2. Целостность Данных (Race Conditions)

Процессор не должен обращаться к области памяти, с которой в данный момент работает DMA, без должной синхронизации.

  • Проблема: Если процессор читает буфер, в который DMA пишет новые данные, можно получить «рваные» данные (часть старых, часть новых).
  • Решение: Использовать двойную буферизацию (Ping-Pong buffers) или флаги готовности. Процессор работает с одним буфером, пока DMA заполняет второй.

5.3. Приоритеты и Арбитраж

Если несколько каналов DMA активны одновременно, они конкурируют за шину.

  • Проблема: Канал с низким приоритетом может не успеть обслужить быструю периферию, что приведет к переполнению буфера (Overrun).
  • Рекомендация: Назначайте высокий приоритет каналам, обслуживающим устройства с жесткими требованиями к реальному времени (например, высокоскоростной ADC или Ethernet), и низкий — для фоновых задач (логирование в Flash).

5.4. Документация и Примеры

К сожалению, производители МК часто предоставляют лишь фрагментарные примеры настройки регистров, не демонстрируя полноценную архитектуру драйвера.

  • Совет: Не копируйте слепо примеры из HAL-библиотек, если они неэффективны. Изучайте регистрами уровень (Register Level) для понимания того, как именно очищаются флаги и как работает арбитраж в конкретном кристалле.

6. Заключение

DMA — это мощный инструмент, который переводит разработку встроенных систем на уровень выше, позволяя создавать эффективные и отзывчивые приложения. Ключ к успешному применению заключается в понимании того, что DMA не является «черным ящиком».

Основные правила успешного внедрения:

  1. Используйте DMA для всех потоковых данных (ADC, UART, SPI, I2S).
  2. Организуйте буферы так, чтобы минимизировать участие процессора (двойная буферизация).
  3. Критически относитесь к документации: проверяйте механизмы сброса прерываний и приоритеты на этапе прототипирования.
  4. Соблюдайте инкапсуляцию в драйверах, даже если аппаратура требует нестандартных решений (например, через слой абстракции HAL).

Понимание этих принципов позволит избежать типичных ошибок и создать надежную систему, использующую аппаратные ресурсы микроконтроллера максимально эффективно.

Was this article helpful?
YesNo

от Евгений Рычков

Основатель сообщества разработчиков Технофея, фулстек-программист, тим лидер, инженер 1 категории, в прошлом главный специалист по робототехнике в Кванториум Мосгормаш,

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest
0 комментариев
Старые
Новые Популярные
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x