Наш канал в telegram

Программирование ARM-контроллеров STM32 на ядре Cortex-M3. Часть 14. Использование DMA

Что такое DMA и зачем это нужно

DMA (Direct Memory Access) — технология прямого доступа к памяти. Эта технология позволяет быстро и без использования центрального процессора пересылать данные из одной области памяти в другую. При этом для такой пересылки вместо ЦП используется свой специальный контроллер, который называется контроллером DMA.

Контроллер DMA работает независимо от ЦП и параллельно с ним. Таким образом использование DMA позволяет экономить ресурсы ЦП, который во время операций пересылки данных с помощью DMA может заниматься какими-то другими полезными делами.

Несмотря на то, что ЦП и DMA работают независимо друг от друга, DMA может приостанавливать доступ ЦП к системной шине на несколько тактов в случаях когда они оба пытаются обратиться к одним и тем же адресам памяти.

Возможности и работа DMA в stm32

В микроконтроллеры stm32 может быть встроено до 2-х контроллеров DMA — DMA1 и DMA2. Они имеют следующие особенности:

  • 12 независимо конфигурируемых каналов: 7 для DMA1 и 5 для DMA2
  • Возможность использования в качестве источника или приёмника данных Flash, SRAM и периферийных модулей, подключенных к шинам APB1, APB2 и AHB
  • Аппаратный запрос от каждого периферийного модуля подключен к одному из 12 каналов
  • Возможность генерации программного запроса по каждому каналу
  • Приоритеты между запросами от разных каналов одного контроллера DMA настраиваются программно и могут иметь 4 уровня: very high, high, medium, low. В случае, если для двух каналов установлен одинаковый приоритет, более приоритетным считается канал с меньшим номером
  • Независимая установка размера порции данных для источника и приёмника (байт, 16-битное полуслово или 32-битное слово)
  • Поддержка кольцевого буфера (когда данные пишутся в буфер по кругу)
  • Возможности передачи данных из памяти в память, из памяти в периферию, из периферии в память или из периферии в периферию
  • 3 флага событий (Half Transfer, Transfer Complete и Transfer Error) логически объединённых в один запрос прерывания для каждого канала
  • Программируемое количество передаваемых данных (количество порций) — до 65535 (то есть если данные передавать 32-битными словами, то максимум можно передать 65535*4 = 262140 байт, — почти 256 кбайт)

Карта запросов модуля DMA1:

карта запросов модуля DMA1

Карта запросов модуля DMA2:

карта запросов модуля DMA2

Как вообще работает DMA? Всё происходит следующим образом:

  • В периферийном модуле происходит событие, по которому через определённый для этого периферийного модуля канал генерируется запрос к DMA (генерация запроса к DMA должна быть разрешена в настройках самого периферийного модуля)
  • Контроллер DMA обрабатывает пришедший запрос в соответствии с настройками и установленным приоритетом канала, через который пришёл запрос
  • Как только контроллер DMA получает доступ к периферии (выполняет передачу одной единицы информации) — он посылает сгенерировавшему запрос периферийному модулю сигнал подтверждения
  • При получении сигнала подтверждения периферийный модуль снимает свой запрос
  • Как только периферийный модуль снимает свой запрос — DMA контроллер отменяет сигнал подтверждения
  • Если есть ещё запросы от периферии — начинает обрабатываться следующий запрос (опять же в соответствии с установленным приоритетом)

Сама передача данных состоит из трех шагов:

  • Загрузка данных из регистра периферии или из памяти по адресу, записанному во внутреннем регистре текущего адреса источника (этот регистр программно недоступен). Начальный адрес, используемый для первой передачи, прописывается в регистр DMA_CPARx или в регистр DMA_CMARx (в зависимости от направления передачи)
  • Сохранение загруженных данных в регистре периферии или в памяти по адресу, указанному во внутреннем регистре текущего адреса приёмника (этот регистр программно недоступен). Начальный адрес, используемый для первой передачи, прописывается в регистр DMA_CPARx или в регистр DMA_CMARx (в зависимости от направления передачи)
  • уменьшение значения регистра-счётчика DMA_CNDTRx, который содержит количество оставшихся транзакций

В зависимости от настроек, после каждой транзакции модуль DMA может автоматически инкрементировать адреса источника и/или приёмника. Эта возможность настраивается для источника и приёмника независимо друг от друга установкой/сбросом битов PINC и MINC в регистре DMA_CCRx. Причём, в зависимости от установленных для источника и приёмника размеров порции данных адрес автоматически инкрементируется на 1, 2 или 4.

Если канал сконфигурированв нормальном режиме, то после обнуления счётчика транзакций новые запросы к DMA обслуживаться не будут. Чтобы включить обслуживание новых запросов — нужно сначала программно взвести счётчик (записать в регистр DMA_CNDTRx новое значение). Сделать это можно только предварительно выключив соответствующий канал DMA (при выключении канала его настройки не сбрасываются).

В кольцевом режиме (circular mode) значение счётчика автоматически устанавливается к начальному значению после выполнения последней запланированной транзакции (то есть после достижения нуля). Одновременно с этим адреса внутренних регистров текущих адресов сбрасываются к начальным адресам, прописанным в регистрах DMA_CPARx, DMA_CMARx. Кольцевой режим можно включить/выключить установкой/сбросом бита CIRC регистра DMA_CCRx.

Режим пересылки данных из памяти в память выбирается установкой бита MEM2MEM в регистре DMA_CCRx. В этом режиме контроллер DMA начинает выполнять передачу данных сразу после включения канала установкой бита EN регистра DMA_CCRx, то есть не дожидаясь внешнего аппаратного запроса. Не знаю зачем этому режиму дали такое название, я бы лучше назвал его режимом без внешнего триггера, поскольку фактически в этом режиме вы можете как и раньше пересылать данные не только из памяти в память, но и из памяти в регистры периферии, и из регистров периферии в память, и из регистров в регистры, просто теперь каждая транзакция запускается не по событию от периферии, а автоматически, пока не обнулится счётчик (или вообще бесконечно, если для канала выбран кольцевой режим).

Регистры

DMA_ISR — регистр статуса прерываний. Биты этого регистра содержат флаги событий для каждого канала. Они доступны только для чтения, а сбрасываются записью 1 в соответствующий бит регистра DMA_IFCR.

регистр DMA_ISR

  • GIFx: глобальный флаг прерывания для канала x (x=1..7). Устанавливается в 1 аппаратно в случае возникновения одного из прерываний TE, HT или TC. Сбрасывается программно, записью единицы в соответствующий бит регистра DMA_IFCR.
  • TCIFx: флаг окончания передачи. Устанавливается в 1 аппаратно после обнуления счётчика передаваемых данных. Сбрасывается программно, записью единицы в соответствующий бит регистра DMA_IFCR.
  • HTIFx: флаг окончания передачи половины данных. Устанавливается в 1 аппаратно после отправки половины запланированных данных (когда счётчик уменьшается наполовину). Сбрасывается программно, записью единицы в соответствующий бит регистра DMA_IFCR.
  • TEIFx: флаг ошибки передачи. Устанавливается в 1 аппаратно при возникновении ошибок передачи (при попытках работы с физически нереализованными или зарезервированными адресами памяти). Сбрасывается программно, записью единицы в соответствующий бит регистра DMA_IFCR.

DMA_IFCR — регистр сброса флагов статуса прерываний. Установка битов этого регистра приводит к сбросу флагов прерываний в регистре DMA_ISR. Биты регистра DMA_IFCR доступны только для записи.

регистр DMA_IFCR

  • CGIFx: установка этого бита в 1 приводит к сбросу флагов GIFx, TCIFx, HTIFx и TEIFx (x=1..7 — номер канала) в регистре DMA_ISR
  • CTCIFx: установка этого бита в 1 приводит к сбросу флага TCIFx (x=1..7 — номер канала) в регистре DMA_ISR
  • CHTIFx: установка этого бита в 1 приводит к сбросу флага HTIFx (x=1..7 — номер канала) в регистре DMA_ISR
  • CTEIFx: установка этого бита в 1 приводит к сбросу флага TEIFx (x=1..7 — номер канала) в регистре DMA_ISR.

DMA_CCRx — регистры настройки каналов (x=1..7 — номер канала).

регистры DMA_CCRx

  • EN: включение/выключение канала
  • TCIE: разрешение(1)/запрет(0) прерывания по событию TC (переданы все данные, счётчик обнулился)
  • HTIE: разрешение(1)/запрет(0) прерывания по событию HT (передана половина данных, счётчик уменьшился наполовину)
  • TEIE: разрешение прерывания по событию TE (ошибка передачи)
  • DIR: направление передачи данных. 0 — читать из периферии (и писать в память), 1 — читать из памяти (и писать в периферию)
  • CIRC: включение/выключение циклического режима передачи (1 — circular mode, 0 — normal mode)
  • PINC: включает(1)/выключает(0) инкрементирование адреса периферии после каждой транзакции
  • MINC: включает(1)/выключает(0) инкрементирование адреса памяти после каждой транзакции
  • PSIZE[1:0]: определение размера единицы данных для периферии. Может принимать следующие значения:
    • 00: 8 бит
    • 01: 16 бит
    • 10: 32 бита
    • 11: зарезервировано
  • MSIZE[1:0]: определение размера единицы данных для памяти. Может принимать следующие значения:
    • 00: 8 бит
    • 01: 16 бит
    • 10: 32 бита
    • 11: зарезервировано
  • PL[1:0]: определение уровня приоритета канала. Может принимать следующие значения:
    • 00: Low (низкий)
    • 01: Medium (средний)
    • 10: High (высокий)
    • 11: Very high (очень высокий)
  • MEM2MEM: включает(1)/выключает(0) режим передачи из памяти в память

DMA_CNDTRx — регистры-счётчики (x=1..7 — номер канала). В младшие 16 бит этих регистров прописывается количество данных для передачи (то есть сколько транзакций нужно выполнить с соответствующим каналом). Каждый из этих регистров обладает следующими особенностями:

  • Значение в регистр можно прописать только когда соответствующий канал выключен
  • После включения канала регистр становится недоступен для записи
  • Значение в регистре автоматически декрементируется после каждой транзакции
  • После того, как значение регистра станет равным нулю — новые запросы к DMA от соответствующего канала перестают обслуживаться
  • Если для соответствующего канала установлен циклический режим передачи, то после обнуления регистра в него автоматически загружается начальное значение

DMA_CPARx — регистры адреса (x=1..7 — номер канала). Здесь содержатся начальные адреса регистров периферии в которую или из которой нужно передавать данные по запросу от соответствующего канала. Доступ автоматически выравнивается на границу полуслова или слова, в зависимости от установленного для периферии размера порции данных (достигается игнорированием одного или двух младших бит регистра адреса). Регистры не могут быть перезаписаны пока соответствующий канал включен.

DMA_CMARx — регистры адреса (x=1..7 — номер канала). Здесь содержатся начальные адреса областей памяти в которую или из которой нужно передавать данные по запросу от соответствующего канала. Доступ автоматически выравнивается на границу полуслова или слова, в зависимости от установленного для памяти размера порции данных (достигается игнорированием одного или двух младших бит регистра адреса). Регистры не могут быть перезаписаны пока соответствующий канал включен.

Для модуля DMA2 существуют точно такие же регистры, только x в них может принимать значения 1..5, а не 1..7 (поскольку в DMA2, в отличии от DMA1, всего 5 каналов, а не 7).

Техника программирования DMA

Работать с DMA достаточно просто. Всё, что от Вас требуется — это настроить соответствующий канал и далее только обрабатывать события (которых как мы помним для канала всего 3) или прерывания от них, а также, возможно, взводить счётчик передаваемых данных.

Как обрабатывать события и прерывания — решать только Вам, а вот порядок настройки канала приведён ниже:

  1. Установить в регистре DMA_CPARx (x — номер канала) начальный адрес регистра периферии в которую/из которой будут передаваться данные
  2. Установить в регистре DMA_CMARx (x — номер канала) начальный адрес области памяти в которую/из которой будут передаваться данные
  3. Прописать в регистре DMA_CRDTRx (x — номер канала) количество передаваемых данных (количество транзакций)
  4. Установить в регистре DMA_CCRx (x — номер канала) приоритет настраиваемого канала (биты PL[0:1]), направление передачи данных, режим (circular/normal), отметить нужно или не нужно инкрементировать адреса периферии и памяти после каждой транзакции, установить размеры порций данных для периферии и для памяти, а также настроить прерывания
  5. Включить канал, установив в 1 бит EN регситра DMA_CCRx (x — номер канала)
  6. Ну и конечно нужно не забыть разрешить слать запросы к DMA в настройках самого периферийного модуля.

В библиотеке StdPeriph настройку канала можно выполнить одной функцией — DMA_Init, для включения/выключения используется функция DMA_Cmd.

Примеры работы с DMA приводить не буду, их можно посмотреть в примерах работы с другими модулями (например, в примерах работы с UART или ADC), так что на этом всё.

  1. Часть 1. Установка MDK, создание проекта, основы Keil uVision
  2. Часть 2. Команды и директивы ассемблера, структура и синтаксис программы. Первая программа для STM32
  3. Часть 3. Карта памяти контроллеров STM32, методы работы с памятью
  4. Часть 4. Регистры, старт и режимы работы контроллеров STM32
  5. Часть 5. Как залить прошивку в контроллер
  6. Часть 6. Настройка системы тактирования
  7. Часть 7. Работа с портами ввода-вывода
  8. Часть 8. Процедуры на ассемблере для STM32
  9. Часть 9. Система прерываний
  10. Часть 10. CMSIS, использование стандартных библиотек и функций
  11. Часть 11. Подключение и использование драйверов из пакета StdPeriph
  12. Часть 12. Работа с модулями USART и UART.
  13. Часть 13. Работа с модулями ADC
  14. Часть 14. Использование DMA
  15. Часть 15. Таймеры. Глава 1 — Введение. Простейшие таймеры
  16. Часть 15. Таймеры. Глава 2 — Таймеры общего назначения TIM9 — TIM14
  17. Часть 15. Таймеры. Глава 3 — Таймеры общего назначения TIM2 — TIM5
  18. Приложение 1. Набор инструкций THUMB-2 и особенности их использования
  19. Приложение 2. Таблица векторов прерываний для семейств STM32F101, STM32F102, STM32F103
  20. Приложение 3. Драйвера и функции библиотеки StdPeriph

Добавить комментарий