Найти в Дзене

STM32. DMA. Посылаем на три буквы. И принимаем

DMA можно настроить для разных интерфейсов: i2C, SPI, UART
DMA можно настроить для разных интерфейсов: i2C, SPI, UART

Получить или отправить микроконтроллером данные через какой-нибудь интерфейс дело серьезное. Возможности МК и моя фантазия предлагают три варианта:

  • Линейный путь прямой как стрела
  • Более надёжный через прерывания
  • Экзотический через DMA

Коротко пробежимся по первым двум. В целом, они достаточно похожи: мы загружаем данные в соответствующий регистр, а далее проверяем регистр, который подтверждает нам, что данные ушли. Только в первом случае бесконечный цикл проверяет регистр, будто вода точит камень. Так вот, как долго ждать пока вода наточит камень? Очень долго, так и МК может зависнуть на этом процессе надолго.

Во втором случае, мы регистрируем прерывание, которое сработает как только данные уйдут.

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

Микроконтроллер реагирует на все ваши важные прерывания
Микроконтроллер реагирует на все ваши важные прерывания

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

Алгоритм следующий:

  • Вы загружаете данные в целевой массив или устанавливаете количество данных, которое надо принять
  • Разрешаете отправку или получение
  • Ожидание выполнения отправки или заполнения целевого буфера

Это можно сравнить с отправкой корабля, в то время как первые способы это курьерская служба.

HOWTO:

Настройка отправки данных по SPI практически полностью повторяет процедуру для приема данных. Поэтому здесь будет приведена только часть TX. Также здесь не будет приведена часть для инициализации самого интерфейса SPI.

LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);

Для начала включаем тактирование для DMA.

LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_5, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);

Указываем, что данные будут переходить от памяти МК в периферию вовне.

LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_5, LL_DMA_PRIORITY_LOW);

Устанавливаем приоритет.

LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_5, LL_DMA_MODE_NORMAL);

Режим не циклический.

LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_5, LL_DMA_PERIPH_NOINCREMENT);
LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_5, LL_DMA_MEMORY_INCREMENT);

Указатели могут автоматически инкрементироваться после передачи данных.

LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_5, LL_DMA_PDATAALIGN_BYTE);
LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_5, LL_DMA_MDATAALIGN_BYTE);

Устанавливаем выравнивание для периферии и памяти.

Тоже самое на STM32CubeMX
Тоже самое на STM32CubeMX

LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_5,

Функция конфигурирования адреса

(uint32_t)BufferForDataTransmitting,
LL_SPI_DMA_GetRegAddr(SPI2),

Указываем сам буфер, из которого будут читаться данные. Так как он -- источник данных он указывается раньше. Указывается источник данных -- в нашем случае SPI.

Для приема данных необходимо сначала указать интерфейс SPI как источника данных, а потом буфер, куда данные прибудут.

LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_5));

Берем значения направления из характеристик канала DMA, аналогично, можно указать LL_DMA_DIRECTION_MEMORY_TO_PERIPH.

LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, Length);

Указываем количество данных, которые должны быть пересланы

LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_5);
LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_5);
LL_SPI_EnableDMAReq_TX(SPI2);

Не забываем включать прерывания, которые запустят наши DMA1_Channel5_IRQHandler (в зависимости от startup_stm32f103xb, например)

После этого можно свободно пользоваться интерфейсом SPI

LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_5); //transmit

Отключаем канал на всякий случай, чтобы загрузить в буфер BufferForDataTransmitting передачи важные сведения.

LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, how_many_data_to_transmit);

Указываем сколько байт передать из буфера.

LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5); //transmit

Включаем канал и ждем, когда сработает