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

Опубликовано lamazavr - вс, 03/24/2013 - 11:52
Body

Контроллер прямого доступа к памяти (буржуи зовут его DMA - Direct 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)
  {
  }

}

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

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

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

Этот вопрос задается для того, чтобы выяснить, являетесь ли Вы человеком или представляете из себя автоматическую спам-рассылку.