DMA (ПДП) в STM 32 — Прямой доступ к памяти

Контроллер прямого доступа к памяти (буржуи зовут его DMADirect Memory Access) в наше время уже далеко не роскошь в микроконтроллерной технике. И поскольку уж он есть, работать с ним нужно уметь.

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

В STM 32 контроллер DMA довольно навороченый, он может не просто пересылать данные, а и генерировать прерывания по довольно широкому спектру событий при передаче. Вы можете пересылать данные из периферии (АЦП, ЦАП.. ) в память (переменные), из памяти в периферию и из памяти в память.

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

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

Инклуды и некоторые данные об используемом DMA:

#include <stm32f4xx.h>
#include <stm32f4_discovery.h>

#define DMA_STREAM               DMA2_Stream0
#define DMA_CHANNEL              DMA_Channel_0
#define DMA_STREAM_CLOCK         RCC_AHB1Periph_DMA2
#define DMA_STREAM_IRQ           DMA2_Stream0_IRQn
#define DMA_IT_TCIF              DMA_IT_TCIF0
#define DMA_STREAM_IRQHANDLER    DMA2_Stream0_IRQHandler

#define BUFFER_SIZE              32
#define TIMEOUT_MAX              10000 /* Maximum timeout value */

Определим перечисление для проверки результата:

typedef enum {FAILED = 0, PASSED = !FAILED} TestStatus;

Переменная для хранения результата:

__IO TestStatus  TransferStatus = FAILED;

Буфер из которого будем переносить данные:

const uint32_t SRC_Const_Buffer[BUFFER_SIZE]= {
                                    0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
                                    0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
                                    0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
                                    0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
                                    0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
                                    0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
                                    0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
                                    0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80};

Буфер приёмник:

uint32_t DST_Buffer[BUFFER_SIZE];

Настраиваем DMA:

void DMA_Config(void){
  NVIC_InitTypeDef NVIC_InitStructure;
  DMA_InitTypeDef  DMA_InitStructure;
  __IO uint32_t    Timeout = TIMEOUT_MAX;
   
  RCC_AHB1PeriphClockCmd(DMA_STREAM_CLOCK, ENABLE);

  /* Сбрасываем регистры DMA */
  DMA_DeInit(DMA_STREAM);

  /* Проверяем выключен ли данный канал DMA. Это полезно когда вы используете его несколько раз */

  while (DMA_GetCmdStatus(DMA_STREAM) != DISABLE){}

  /* Настройка канала DMA */

  DMA_InitStructure.DMA_Channel = DMA_CHANNEL;  
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SRC_Const_Buffer;
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)DST_Buffer;
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory;
  DMA_InitStructure.DMA_BufferSize = (uint32_t)BUFFER_SIZE;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  DMA_Init(DMA_STREAM, &DMA_InitStructure);

  /* Разрешаем прерывание по окончанию передачи данных */
  DMA_ITConfig(DMA_STREAM, DMA_IT_TC, ENABLE);
  /* Включаем DMA */
  DMA_Cmd(DMA_STREAM, ENABLE);

  /* Проверяем запустился ли DMA */
  Timeout = TIMEOUT_MAX;
  while ((DMA_GetCmdStatus(DMA_STREAM) != ENABLE) && (Timeout-- > 0))
  {
  }

  /* Если таймаут принял нулевое значени, значит возникла ошибка */
  if (Timeout == 0)
  {

    /* При ошибке идём в безконечный цикл */
    while (1)
    {
    }

  }

  /* Настраиваем прерывание по окончанию передачи */
NVIC_InitStructure.NVIC_IRQChannel = DMA_STREAM_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

Функция для проверки результата пересылки:

TestStatus Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength)
{
  while(BufferLength--)
  {
    if(*pBuffer != *pBuffer1)
    {
      return FAILED;
    }
    pBuffer++;
    pBuffer1++;
  }

  return PASSED;  
}

Тут всё очевидно (на мой взгляд). Просто проходим все элементы массива и если хотябы 1 не совпадает выдаём сообщение о том, что тест не пройден.
Прерывание по окончании передачи:

void DMA_STREAM_IRQHANDLER(void)
{
  /* Проверяем наше ли это прерывание ;) */
  if(DMA_GetITStatus(DMA_STREAM, DMA_IT_TCIF))
  {
    /* Очищаем бит обработки прерывания */
    DMA_ClearITPendingBit(DMA_STREAM, DMA_IT_TCIF);  

    /* Зажишаем диод - конец передачи */
    STM_EVAL_LEDOn(LED5);
  }
}

Теперь осталась функция main:

int main(void)
{
  STM_EVAL_LEDInit(LED3);
  STM_EVAL_LEDInit(LED4);
  STM_EVAL_LEDInit(LED5);
  STM_EVAL_LEDInit(LED6);

  STM_EVAL_LEDOn(LED3); /* Зажигаем первый диод при старте */

  /* Запускаем настройку канала DMA */
  DMA_Config();
  STM_EVAL_LEDOn(LED4); /* Зажигаем второй диод - начало передачи */

 

  /* Ждём конца передачи. Просто для примера. Вы можете выполнять другие действия в это время */

  /* while (DMA_GetCurrentMemoryTarget(DMA_STREAM) != 0) */  /* Первый способ */

  while (DMA_GetCmdStatus(DMA_STREAM) != DISABLE)            /* Второй способ */
  {
  }

   

  /* Проверям данные на совпадение */

  TransferStatus = Buffercmp(SRC_Const_Buffer, DST_Buffer, BUFFER_SIZE);

  /* TransferStatus = PASSED, если данные одинаковые */

  /* TransferStatus = FAILED, если данные разные */

  if (TransferStatus != FAILED)
  {
    /* Включаем четвёртый диод если передача прошла успешно */
    STM_EVAL_LEDOn(LED6);
  }


  while (1)
  {
  }

}

Вот и всё о чем хотелось..

Пример проверенный не раз. Так что думаю проблем возникнуть не должно.

 

Похожий код:

Фото аватара
Алексей Петров

Программист, разработчик с 5 летним опытом работы. Учусь на разработчика игр на Unity и разработчика VR&AR реальности (виртуальной реальности). Основные языки программирования: C#, C++.

Оцените автора
Бла, бла код
Добавить комментарий