Перейти к содержанию

DMA в STM32F1 и STM32F4: устройство и настройка

Контроллер прямого доступа к памяти пересылает данные между периферией и памятью без участия ядра. Здесь — чем DMA STM32F1 с каналами отличается от DMA STM32F4 с потоками и FIFO, как найти канал для периферии, как настроить передачу, кольцевой буфер, двойную буферизацию и копирование память–память, и какие ограничения RM0008 и RM0090 чаще всего нарушают.

Обновлено Редакция GAW

DMA (Direct Memory Access) — контроллер, который сам пересылает данные между периферией и памятью или между двумя областями памяти, пока процессор занят другим. Типичные задачи: приём UART в буфер, вывод данных в SPI-дисплей, непрерывное сканирование каналов АЦП, генерация сигнала ЦАП по таймеру, изменение скважности ШИМ на каждом периоде.

DMA в STM32F1 и STM32F4 устроены по-разному. У STM32F1 простые каналы с закреплёнными запросами (RM0008, раздел 13), у STM32F4 — потоки с выбором запроса, FIFO, пакетными передачами и двойным буфером (RM0090, раздел 10). Поля всех регистров описаны на страницах DMA STM32F1 и STM32F4.

Коротко

  • STM32F1: DMA1 на 7 каналов, у high-density, XL-density и connectivity line ещё DMA2 на 5 каналов; запросы закреплены за каналами.
  • STM32F4: DMA1 и DMA2 по 8 потоков, у потока выбор из 8 запросов, FIFO на 4 слова, пакеты по 4, 8 и 16 передач, двойной буфер.
  • Счётчик передач — до 65 535 элементов, единица — элемент размера PSIZE.
  • Режимы: обычный, кольцевой, память–память (у STM32F4 только DMA2), двойной буфер (STM32F4).
  • Флаги: половина передачи HT, конец TC, ошибка TE; у STM32F4 ещё ошибки FIFO и прямого режима.
  • Не достаёт до CCM RAM STM32F4 — она доступна только процессору.

STM32F1 и STM32F4: что общего и что разного

STM32F1STM32F4
КонтроллерыDMA1 — 7 каналов; DMA2 — 5 каналов у high-density, XL-density и connectivity lineDMA1 и DMA2 — по 8 потоков
Выбор запросазакреплён за каналом, несколько запросов на канал объединены по ИЛИ8 запросов на поток, выбор полем CHSEL
Направленияпериферия–память, память–периферия, периферия–периферия, память–память на любом каналепериферия–память, память–периферия; память–память только у DMA2
БуфернетFIFO 4 слова на поток или прямой режим
Пакетынет4, 8 или 16 передач подряд
Двойной буфернетесть, переключение адресов M0AR и M1AR
СчётчикCNDTR, до 65 535NDTR, до 65 535
Флаги и сбросISR и IFCR, по 4 бита на каналLISR и HISR, LIFCR и HIFCR, по 6 бит на поток

Каждая передача DMA — это чтение элемента из источника, запись в приёмник и уменьшение счётчика на единицу (RM0008, раздел 13.3.1). Источник и приёмник могут иметь разную разрядность — байт, полуслово или слово (поля PSIZE и MSIZE), а адреса по каждому порту могут увеличиваться после передачи на размер элемента (биты PINC и MINC) или оставаться неизменными, как у регистра данных периферии. Адреса должны быть выровнены на размер элемента.

DMA делит шину с ядром. У STM32F1 матрица шин обслуживает ядро и DMA по очереди и оставляет процессору не меньше половины пропускной способности шины, если оба обращаются к одной и той же памяти или периферии (RM0008, раздел 13.3). У STM32F4 матрица многослойная: DMA и ядро работают одновременно, пока обращаются к разным блокам памяти.

Какой канал или поток выбрать

Запросы периферии подключены к определённым каналам. У STM32F1 их назначение для DMA1 (RM0008, таблица 78):

Канал DMA11234567
АЦПADC1
SPI, I2SSPI1_RXSPI1_TXSPI2_RXSPI2_TX
USARTUSART3_TXUSART3_RXUSART1_TXUSART1_RXUSART2_RXUSART2_TX
I²CI2C2_TXI2C2_RXI2C1_TXI2C1_RX
TIM1TIM1_CH1TIM1_CH4, TRIG, COMTIM1_UPTIM1_CH3
TIM2TIM2_CH3TIM2_UPTIM2_CH1TIM2_CH2, CH4
TIM3TIM3_CH3TIM3_CH4, UPTIM3_CH1, TRIG
TIM4TIM4_CH1TIM4_CH2TIM4_CH3TIM4_UP

Запросы одного канала объединены по ИЛИ, поэтому одновременно у канала может быть включён только один из них — например, USART1_TX и SPI2_RX на канале 4 вместе не работают. Если два нужных запроса попали на один канал, один из блоков придётся обслуживать прерываниями.

У STM32F4 каждый из восьми потоков выбирает один из восьми запросов полем CHSEL регистра DMA_SxCR, и у многих блоков есть два варианта потока (RM0090, таблицы 43 и 44). Часто используемые:

ПериферияКонтроллерПотокКанал
ADC1DMA20 или 40
SPI1_RX / SPI1_TXDMA20 или 2 / 3 или 53
USART1_RX / USART1_TXDMA22 или 5 / 74
USART2_RX / USART2_TXDMA15 / 64
I2C1_RX / I2C1_TXDMA10 или 5 / 6 или 71
TIM1_UPDMA256
DAC1 / DAC2DMA15 / 67

Один поток одновременно обслуживает только один запрос, а приоритет между потоками задаётся полем PL: четыре уровня, при равенстве побеждает поток с меньшим номером. Тот же принцип у каналов STM32F1; там же DMA1 имеет приоритет над DMA2 (RM0008, раздел 13.3.2).

Порядок настройки

У STM32F1 канал настраивают при выключенном бите EN (RM0008, раздел 13.3.3):

  1. адрес периферии — в CPAR;
  2. адрес памяти — в CMAR;
  3. число элементов — в CNDTR;
  4. приоритет, направление (DIR), кольцевой режим, приращения адресов, разрядности и прерывания — в CCR;
  5. включение — бит EN в CCR.

После включения канал обслуживает запросы своей периферии, а саму периферию надо отдельно настроить на выдачу запросов: бит DMAR и DMAT у USART, бит DMA у АЦП, биты xDE у таймеров. Регистры CPAR и CMAR во время передачи хранят начальные адреса, а текущие недоступны программе.

У STM32F4 порядок такой же, но к нему добавляются выбор канала, FIFO и обязательное ожидание остановки (RM0090, раздел 10.3.17): если поток включён, сбросьте EN и дождитесь, пока он прочитается нулём, — поток останавливается только после текущей передачи. Затем сбросьте флаги прошлой передачи в LIFCR или HIFCR, запишите PAR, M0AR (и M1AR для двойного буфера), NDTR, выберите канал CHSEL, приоритет, FIFO в регистре FCR и остальные поля CR и только после этого установите EN. RM0090 предупреждает: чтобы выключить периферию, сначала выключите связанный с ней поток DMA и дождитесь EN = 0.

Режимы работы

Обычный режим. Канал передаёт CNDTR элементов, ставит флаг TC и перестаёт обслуживать запросы. Чтобы задать новое число элементов, канал надо выключить и включить снова (RM0008, раздел 13.3.3).

Кольцевой режим (бит CIRC). После последней передачи счётчик и адреса перезагружаются начальными значениями, и передача продолжается бесконечно. Так работают непрерывное сканирование АЦП, приём UART в кольцевой буфер и вывод сигнала в ЦАП. Флаг половины передачи HT позволяет обрабатывать одну половину буфера, пока DMA заполняет другую. У STM32F4 при пакетных передачах со стороны памяти NDTR в кольцевом режиме должен быть кратен MBURST · MSIZE/PSIZE (RM0090, раздел 10.3.8).

Память–память. У STM32F1 бит MEM2MEM запускает передачу сразу после включения канала, без запросов периферии; кольцевой режим при этом недоступен. У STM32F4 то же делается кодом направления DIR = 10, но только на потоках DMA2: порт периферии DMA1 не подключён к матрице шин (RM0090, рисунок 33). Прямой режим для память–память запрещён — нужен FIFO.

Двойной буфер (бит DBM, только STM32F4). У потока два адреса памяти, M0AR и M1AR, и после каждой полной передачи он переключается на другой; текущий показывает бит CT. Пока DMA заполняет один буфер, программа обрабатывает другой, а адрес неактивного буфера можно менять на ходу. Запись в регистр активного буфера ставит ошибку TEIF и выключает поток, поэтому новый адрес пишут сразу после флага TC (RM0090, раздел 10.3.9). Кольцевой режим при двойном буфере включается автоматически.

Управление потоком от периферии (бит PFCTRL, STM32F4). Число элементов задаёт не NDTR, а сама периферия — так работает SDIO, когда длина передачи заранее неизвестна.

Разрядность, FIFO и пакеты

Если разрядности источника и приёмника разные, STM32F1 расширяет или обрезает каждый элемент (RM0008, таблица 76): байт, записанный в 16-битный приёмник, дополняется нулями до 0x00B0, а из 16-битного источника в 8-битный приёмник попадает только младший байт. Счётчик считает элементы, а не байты.

У STM32F4 между портами стоит FIFO на четыре 32-битных слова. В режиме FIFO (бит DMDIS = 1 в регистре FCR) поток накапливает данные до порога — четверть, половина, три четверти или весь FIFO — и выдаёт их в память пакетом. Только в этом режиме возможны разные разрядности портов с упаковкой: например, четыре байта от периферии уходят в память одним словом. По умолчанию FIFO работает в прямом режиме: каждый запрос сразу вызывает одну передачу, разрядности портов должны совпадать, пакеты недоступны (RM0090, раздел 10.3.12). Для прямого режима RM0090 советует высокий приоритет потока, иначе FIFO переполнится.

Пакетные передачи (поля MBURST и PBURST) пересылают 4, 8 или 16 элементов подряд, и матрица шин не отдаёт шину другим, пока пакет не закончится. Размер пакета — число передач, а не байтов. Адреса должны быть выровнены так, чтобы пакет не пересекал границу 1 КБ: иначе возникнет ошибка шины AHB, которую регистры DMA не покажут (RM0090, раздел 10.3.11). Порог FIFO должен соответствовать размеру пакета, иначе ставится флаг ошибки FIFO.

Флаги, прерывания и ошибки

ФлагSTM32F1STM32F4Когда ставится
HTIFестьестьпередана половина данных
TCIFестьестьпередача закончена
TEIFестьестьошибка шины — обращение к зарезервированной области; у STM32F4 ещё запись в адрес активного буфера при DBM
FEIFнетестьнедогрузка или переполнение FIFO, порог несовместим с пакетом
DMEIFнетестьпрямой режим: новый запрос пришёл раньше, чем записан предыдущий элемент
GIFестьнетлюбой из флагов канала

Флаги сбрасываются записью единицы в регистр очистки: IFCR у STM32F1, LIFCR (потоки 0–3) и HIFCR (потоки 4–7) у STM32F4. У STM32F4 биты флагов в LISR и HISR расположены неравномерно: у потока 0 — биты 0–5, у потока 1 — 6–11, у потока 2 — 16–21, у потока 3 — 22–27; например, TCIF0 — бит 5, TCIF2 — бит 21. Удобнее пользоваться именами CMSIS, чем вычислять сдвиги.

При ошибке передачи канал выключается аппаратно — бит EN сбрасывается, — и ставится TEIF (RM0008, раздел 13.3.5; RM0090, раздел 10.3.18). Типичная причина — неверный адрес: опечатка в CPAR, указатель на освобождённый буфер или область, к которой у DMA нет доступа.

Ограничения, о которых легко забыть

  • CCM RAM STM32F4 — 64 КБ по адресу 0x1000 0000 — не подключена к матрице шин и доступна только процессору (RM0090, разделы 2.1 и 2.3). Буферы DMA, стек с локальными массивами, которые передаются в DMA, и кучу туда класть нельзя.
  • DMA1 STM32F4 не копирует память в память — только DMA2.
  • Регистры выключенного канала не сбрасываются — CCR, CPAR и CMAR сохраняют прошлые значения (RM0008, раздел 13.3.3). Перед новой настройкой записывайте CCR целиком.
  • Резервные регистры BKP STM32F1 16-битные, но выровнены по 32 битам: для записи в них DMA задают MSIZE = 16 бит и PSIZE = 32 бита (RM0008, раздел 13.3.4).
  • Периферия, которая не принимает байты и полуслова, получает от DMA размноженное значение: байт 0xAB записывается как 0xABABABAB (RM0008, раздел 13.3.4).

Примеры на CMSIS

Копирование массива память–память на STM32F103, канал 1 DMA1, слова по 32 бита:

#include "stm32f1xx.h"

void dma_copy_words(const uint32_t *src, uint32_t *dst, uint16_t count)
{
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;

    DMA1_Channel1->CCR = 0;                      /* disable and reset the configuration */
    DMA1->IFCR = DMA_IFCR_CGIF1;                 /* clear the channel 1 flags */
    DMA1_Channel1->CPAR = (uint32_t)src;         /* DIR = 0: read from CPAR, write to CMAR */
    DMA1_Channel1->CMAR = (uint32_t)dst;
    DMA1_Channel1->CNDTR = count;
    DMA1_Channel1->CCR = DMA_CCR_MEM2MEM | DMA_CCR_PL_1
                       | DMA_CCR_MSIZE_1 | DMA_CCR_PSIZE_1   /* 32-bit items */
                       | DMA_CCR_MINC | DMA_CCR_PINC
                       | DMA_CCR_EN;

    while (!(DMA1->ISR & (DMA_ISR_TCIF1 | DMA_ISR_TEIF1))) {}
    DMA1->IFCR = DMA_IFCR_CGIF1;
}

То же на STM32F407: поток 0 DMA2 с FIFO, потому что прямой режим для память–память запрещён:

#include "stm32f4xx.h"

void dma_copy_words(const uint32_t *src, uint32_t *dst, uint16_t count)
{
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;

    DMA2_Stream0->CR &= ~DMA_SxCR_EN;
    while (DMA2_Stream0->CR & DMA_SxCR_EN) {}    /* wait until the stream stops */
    DMA2->LIFCR = DMA_LIFCR_CTCIF0 | DMA_LIFCR_CHTIF0 | DMA_LIFCR_CTEIF0
                | DMA_LIFCR_CDMEIF0 | DMA_LIFCR_CFEIF0;

    DMA2_Stream0->PAR  = (uint32_t)src;          /* memory-to-memory: PAR is the source */
    DMA2_Stream0->M0AR = (uint32_t)dst;
    DMA2_Stream0->NDTR = count;                  /* in PSIZE units: words */
    DMA2_Stream0->FCR  = DMA_SxFCR_DMDIS | DMA_SxFCR_FTH;   /* FIFO on, full threshold */
    DMA2_Stream0->CR   = DMA_SxCR_DIR_1                     /* DIR = 10: memory-to-memory */
                       | DMA_SxCR_PINC | DMA_SxCR_MINC
                       | DMA_SxCR_PSIZE_1 | DMA_SxCR_MSIZE_1
                       | DMA_SxCR_PL_1
                       | DMA_SxCR_EN;

    while (!(DMA2->LISR & (DMA_LISR_TCIF0 | DMA_LISR_TEIF0))) {}
}

Приём UART в кольцевой буфер через DMA и прерывание IDLE разобран в статье об UART STM32, обновление скважности ШИМ по запросу таймера — в статье о таймерах STM32.

Частые ошибки

  • Не включена периферия на выдачу запросов — канал DMA настроен, а бит DMAR, DMAT, DMA или UDE в самой периферии не установлен.
  • Не тот канал или поток — запрос подключён только к своему каналу, сверяйтесь с таблицами RM.
  • Два запроса на одном канале STM32F1 — работает только один.
  • Перенастройка без ожидания EN = 0 у STM32F4 — запись игнорируется или портит текущую передачу.
  • Не сброшены флаги перед повторным включением потока STM32F4 — поток не стартует.
  • Буфер в CCM RAM или в стеке функции, которая уже вернулась, — ошибка TEIF или испорченные данные.
  • Счёт в байтах вместо элементов — при PSIZE = 16 бит NDTR = 100 означает 200 байт.

Частые вопросы

Чем канал DMA STM32F1 отличается от потока STM32F4?

У STM32F1 запросы периферии жёстко закреплены за каналами: каждый из 7 каналов DMA1 слушает свой набор периферии, и одновременно разрешать можно только один из них. У STM32F4 у каждого контроллера 8 потоков, и каждый поток выбирает один из 8 запросов полем CHSEL; кроме того, у потока есть FIFO на 4 слова, пакетные передачи и двойной буфер (RM0008, раздел 13.3.7; RM0090, раздел 10.3).

Почему DMA STM32F4 не копирует память в память?

Скорее всего, используется DMA1. Порт периферии DMA1 не подключён к матрице шин, поэтому передачу память–память умеет только DMA2 (RM0090, рисунок 33). Ещё две причины: включён прямой режим — для память–память он запрещён, — или источник или приёмник лежит в CCM RAM по адресу 0x1000 0000, к которой есть доступ только у процессора.

Как узнать, сколько данных DMA уже передал?

По счётчику: CNDTR у STM32F1 и NDTR у STM32F4 уменьшается на единицу после каждой передачи, поэтому передано начальное значение минус текущее. В кольцевом режиме счётчик после нуля перезагружается начальным значением. Единица счёта — элемент данных размером PSIZE, а не байт.

Можно ли менять настройки DMA во время передачи?

Нет: у STM32F1 канал сначала выключают битом EN, у STM32F4 сбрасывают EN и ждут, пока он прочитается нулём, — поток останавливается только после текущей передачи. Флаги прошлой передачи в LISR и HISR тоже нужно сбросить до повторного включения. Исключение — двойной буфер STM32F4: адрес неактивного буфера можно менять на ходу (RM0090, раздел 10.3.9).

Источники

  1. STMicroelectronics. RM0008 Rev 21. Reference manual: STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx — 13.2 — возможности DMA; 13.3 — разделение шины с ядром; 13.3.1–13.3.3 — передача, арбитр, каналы, порядок настройки, кольцевой режим, память–память; 13.3.4 — разрядность и выравнивание (таблица 76); 13.3.5–13.3.6 — ошибки и прерывания (таблица 77); 13.3.7 — запросы периферии (таблицы 78 и 79)
  2. STMicroelectronics. RM0090 Rev 22. Reference manual: STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 — 2.1 и 2.3 — CCM RAM доступна только процессору; 10.2 — возможности DMA; 10.3 — устройство, рисунок 33 — память–память только у DMA2; 10.3.3 — выбор канала (таблицы 43 и 44); 10.3.9 — двойной буфер; 10.3.11 — пакетные передачи и граница 1 КБ; 10.3.12 — FIFO и прямой режим; 10.3.17 — порядок настройки потока; 10.3.18 — ошибки
  3. STMicroelectronics. CMSIS Device F1 и F4: stm32f103xb.h, stm32f407xx.h — имена битов DMA_CCR, DMA_SxCR, DMA_SxFCR и флагов LISR для примеров

Нашли ошибку?