Программирование STM32. Часть 11: Копирование массивов через DMA

В этой короткой части мы рассмотрим режим MEM2MEM и научится с помощью DMA копировать одну область памяти в другую. Все примеры, как и всегда, для микроконтроллера stm32f103c8. Предыдущая статья здесь, все статьи цикла можно посмотреть тут: https://dimoon.ru/category/obuchalka/stm32f1.

Режим MEM2MEM имеет три особенности в сравнении с обычным режимом работы DMA, когда мы отправляем какие-либо данные в периферию или принимаем что-либо из нее:

  1. Режим MEM2MEM не требует каких-либо запросов от периферийных модулей. После включения канала DMA процесс передачи начинается сразу.
  2. В регистр адреса памяти DMA_CMARx и регистр адреса периферии DMA_CPARx заносятся адреса областей памяти.
  3. Нельзя одновременно использовать режим MEM2MEM и кольцевой режим CIRC.

Итак, поехали! Для MEM2MEM передачи можно использовать любой свободный канал DMA, для примеры выберем 1-й. Создадим 2 массива data[] и buff[]:

uint8_t data[10];
uint8_t buff[sizeof(data)];

Через DMA будем data[] копировать в buff[]. Инициализируем массивы вот таким образом:

  for(int i=0; i<sizeof(data); i++)
  {
    data[i] = i+1;
    buff[i] = 0;
  }

Не забываем включить тактирование DMA1 и на всякий случай отключить выбранный нами канал DMA:

  RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Включаем тактирование DMA1
  DMA1_Channel1->CCR &= ~DMA_CCR_EN; //Отключаем канал перед настройкой

Настройка регистров передачи:

  DMA1_Channel1->CNDTR = sizeof(data); //сколько элементов копируем
  DMA1_Channel1->CMAR = (uint32_t)(data); //что копируем (адрес "памяти")
  DMA1_Channel1->CPAR = (uint32_t)(buff); //куда копируем (адрес "периферии")

Настройка канала DMA:

  DMA1_Channel1->CCR = 
      DMA_CCR_MEM2MEM //из памяти в память
    | DMA_CCR_MINC    //инкремент памяти
    | DMA_CCR_PINC    //инкремент периферии
    | DMA_CCR_DIR;    //направление из "памяти" в "периферию"

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

Запустить процесс можно вот такой строчкой:

DMA1_Channel1->CCR |= DMA_CCR_EN; //запускаем процесс

Полный код функции:

uint8_t data[10];
uint8_t buff[sizeof(data)];

void main()
{
  for(int i=0; i<sizeof(data); i++)
  {
    data[i] = i+1;
    buff[i] = 0;
  }
  
  
  RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Включаем тактирование DMA1
  
  DMA1_Channel1->CCR &= ~DMA_CCR_EN; //Отключаем канал перед настройкой
  
  DMA1_Channel1->CNDTR = sizeof(data); //сколько элементов копируем
  DMA1_Channel1->CMAR = (uint32_t)(data); //что копируем (адрес "памяти")
  DMA1_Channel1->CPAR = (uint32_t)(buff); //куда копируем (адрес "периферии")

  DMA1_Channel1->CCR = 
      DMA_CCR_MEM2MEM //из памяти в память
    | DMA_CCR_MINC    //инкремент памяти
    | DMA_CCR_PINC   //инкремент периферии
    | DMA_CCR_DIR;   //направление из "памяти" в "периферию"
  DMA1_Channel1->CCR |= DMA_CCR_EN; //запускаем процесс
  
  for(;;)
  {
  }
}

Если посмотреть в отладчике IAR-а значения массивов data[] и buff[], то перед запуском DMA-копирования они имели вот такие значения:

а после завершения процесса вот такие:

Все работает! 🙂

Стоит отметить, что если вместо

  DMA1_Channel1->CMAR = (uint32_t)(data); //что копируем (адрес "памяти")
  DMA1_Channel1->CPAR = (uint32_t)(buff); //куда копируем (адрес "периферии")

сделать вот так:

  DMA1_Channel1->CMAR = (uint32_t)(buff); //куда копируем (адрес "памяти")
  DMA1_Channel1->CPAR = (uint32_t)(data); //что копируем (адрес "периферии")

и в инициализации канала DMA убрать DMA_CCR_DIR:

  DMA1_Channel1->CCR = 
      DMA_CCR_MEM2MEM //из памяти в память
    | DMA_CCR_MINC    //инкремент памяти
    | DMA_CCR_PINC;   //инкремент периферии

То все будет работать точно так же.

На этом пока все, продолжение следует! 😉

Продолжение.

Метки: , , , . Закладка Постоянная ссылка.

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