Page 1 of 1

STM32 Programming. Part 11: Copying Arrays via DMA

Posted: 17 Oct 2023, 04:36
by Oleg
In this short article we will look at the MEM2MEM mode and learn how to copy one memory location to another using DMA. All examples are, as always, for the stm32f103c8 microcontroller.

MEM2MEM mode has three features compared to normal DMA operation when we send any data to or receive anything from a peripheral:
  1. MEM2MEM mode does not require any request from the peripheral modules. Once the DMA channel is enabled, the transfer process starts immediately.
  • The memory address register DMA_CMARx and the peripheral address register DMA_CPARx are populated with memory area addresses.
  • MEM2MEM mode and CIRC ring mode cannot be used at the same time.
So here we go! For MEM2MEM transfer you can use any free DMA channel, for examples we will choose the 1st one. Let's create 2 arrays data[] and buff[]:

Code: Select all

uint8_t data[10];
uint8_t buff[sizeof(data)];
We will copy data[] to buff[] via DMA. Initialize the arrays in this way:

for(int i=0; i<sizeof(data); i++)
{
data = i+1;
buff = 0;
}
Don't forget to enable DMA1 clocking and disable the selected DMA channel just in case:

Code: Select all

RCC->AHBENR |= RCC_AHBENR_DMA1EN; //Enable DMA1 clocking
  DMA1_Channel1->CCR &= ~DMA_CCR_EN; //Disable the channel before tuning.
Configuring the transfer registers:

Code: Select all

DMA1_Channel1->CNDTR = sizeof(data); //how many elements to copy
  DMA1_Channel1->CMAR = (uint32_t)(data); //what we copy (memory address)
  DMA1_Channel1->CPAR = (uint32_t)(buff); //where we copy (address of "periphery")
DMA channel setup:

Code: Select all

  DMA1_Channel1->CCR =.
      DMA_CCR_MEM2MEM //from memory to memory
    | DMA_CCR_MINC //memory increment
    | DMA_CCR_PINC // peripheral increment
    | DMA_CCR_DIR; //direction from "memory" to "peripheral".
Pay attention to the direction of data transfer. Since we have entered the address of the array we are going to copy into the memory address register, the direction of data transfer is from "memory" to "periphery". Also, in comparison with the "normal" mode of DMA channel operation, here we use both memory address increment and peripheral address increment.

You can start the process with this line:

Code: Select all

DMA1_Channel1->CCR |= DMA_CCR_EN; //start the process
Full function code:

Code: Select all

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; //Enable DMA1 clocking
  
  DMA1_Channel1->CCR &= ~DMA_CCR_EN; //Disable channel before tuning
  
  DMA1_Channel1->CNDTR = sizeof(data); //how many elements to copy
  DMA1_Channel1->CMAR = (uint32_t)(data); //what we copy (memory address)
  DMA1_Channel1->CPAR = (uint32_t)(buff); //where we copy (address of "periphery")
  DMA1_Channel1->CCR = (uint32_t(buff); //where we copy (address of "periphery").
      DMA_CCR_MEM2MEM //from memory to memory
    | DMA_CCR_MINC //memory increment
    | DMA_CCR_PINC // peripheral increment
    | DMA_CCR_DIR; //direction from "memory" to "periphery"
  DMA1_Channel1->CCR |= DMA_CCR_EN; //start the process
  
  for(;;)
  {
  }
}
If you look at the values of data[] and buff[] arrays in the IAR debugger, they had these values before DMA-copying was started:
image.png
image.png (23.03 KiB)
Viewed 3571 times
and after the process is complete, it's like this:
image.png
image.png (24.04 KiB)
Viewed 3571 times
It's all working!

It's worth noting that if instead of

Code: Select all

  DMA1_Channel1->CMAR = (uint32_t)(data); //what we copy (address of "memory")
  DMA1_Channel1->CPAR = (uint32_t)(buff); //where we copy (address of "periphery")
do it like this:

Code: Select all

  DMA1_Channel1->CMAR = (uint32_t)(buff); //where we copy (address of "memory")
  DMA1_Channel1->CPAR = (uint32_t)(data); //what we copy (address of "periphery")
and remove DMA_CCR_DIR in DMA channel initialization:

Code: Select all

  DMA1_Channel1->CCR =.
      DMA_CCR_MEM2MEM //from memory to memory
    | DMA_CCR_MINC //memory increment
    | DMA_CCR_PINC; //increment of peripherals.
Then everything will work exactly the same way.