Using Direct Memory Access (DMA) in STM32 projects

In many microcontroller projects you need to read and write data. It can be reading data from peripheral unit like ADC and writing values to RAM. In other case maybe you need send chunks of data using SPI. Again you need to read it from RAM and constantly write to SPI data register and so on. When you do this using processor – you loose a significant amount of processing time. In order to avoid occupying CPU most advanced microcontrollers have Direct memory Access (DMA) unit. As its name says – DMA does data transfers between memory locations without need of CPU.

Low and medium density ST32 microcontrollers have single 7 channel DMA unit while high density devices have two DMA controllers with 12 independent channels. In STM32VLDiscovery there ST32F100RB microcontroller with single DMA unit having 7 channels.

DMA can do automated memory to memory data transfers, also do peripheral to memory and peripheral to peripheral. DMA channels can be assigned one of four priority level: very high, high, medium and low. And if two same priority channels are requested at same time – the lowest number channel gets priority. DMA channel can be configured to transfer data in to circular buffer. So DMA is ideal solution for any peripheral data stream.

Speaking of physical DMA buss access it is important to note, that DMA only access bus for actual data transfer. Because DMA request phase, address computation and Ack pulse are performed during other DMA channel bus transfer. So when one DMA channel finishes bus transfer, another channel is already ready to do transfer immediately. This ensures minimal bus occupation and fast transfers. Another interesting feature of DMA bus access is that it doesn’t occupy 100% of bus time. DMA takes 5 AHB bus cycles for single word transfer between memory – three of them are still left for CPU access. This means that DMA only takes maximum 40% of buss time. So even if DMA is doing intense data transfer CPU can access any memory area, peripheral at any time. If you look at block diagram you will see that CPU has separate Ibus for Flash access. So program fetch isn’t affected by DMA.

Programming DMA

Simply speaking programming DMA is easy. Each channel can be controlled using four registers: Memory address, peripheral address, number of data and configuration. And all channels have two dedicated registers: DMA interrupt status register and interrupt flag clear register. Once set DMA takes care of memory address increment without disturbing CPU. DMA channels can generate three interrupts: transfer finished, half-finished and transfer error.

As example lets write a simple program which transfers data between two arrays. To make it more interesting lets do same task using DMA and without it. Then we can compare the time taken in both cases.

First of all we create two arrays: source and destination. Size of length is determined by ARRAYSIZE which in our example is equal to 800

We use LED library from previous tutorial – they indicate start and stop transfer for both modes – DMA and CPU. As we see in code first of all we must turn on DMA1 clock to make it functional. Then we start loading settings in to DMA_InitStructure. For this example we selected DMA1 Channel1, so first of all we call DMA_DeInit(DMA1_Channel1) function which simply makes sure DMA is reset to its default values. Then turn on memory to memory mode, then we select normal DMA mode (also we could select circular buffer mode). As priority mode we assign Medium. Then we select data size t obe transferred (32-bit word). This need to be done for both – peripheral and memory addresses.

NOTE! if one of memory sizes would be different, say source 32-bit and destination 8- bit – then DMA would cycle four times in 8 bit chunks.

Then we load destination, source start addresses and amount of data to be sent. After load these values using DMA_Init(DMA_Channel1, &DMA_InitStructure). After this operation DMA is prepared to do transfers. Any time DMA can be fired using DMA_Cmd(DMA_Channel1, ENABLE) command.

In order to catch end of DMA transfer we initialized DMA transfer Complete on channel1 interrupt.

Since LEDG is connected to GPIOC pin 9 and LEDB is connected to GPIOC pin 8 we could track start and end pulses using scope:

So using 800 32-bit word transfer using DMA took 214μs:

While using CPU memory copy algorithm it took 544μs:

This shows significant increase of data transfer speed (more than two times). And with DMA biggest benefit is that CPU is totally unoccupied during transfer and may do other intense tasks or simply go in to sleep mode.

Hope this example gives an idea of DMA importance. With DMA we can do loads of work only in hardware level. We will get back to it when we get to other STM32 features like ADC.

Thanks, interesting. But I have a question – is there an efficient method to do multiple DMA->peripheral transfers, each – with different length ? By efficient I mean not to re-program the DMA from the beginning but only to change the length and start the transfer. In circular mode it must preserve the current memory address.

I have added additional line if code to test memcpy: LEDToggle(LEDG);
memcpy(destination, source, ARRAYSIZE*sizeof(uint32_t));
LEDToggle(LEDG);
Test results:
As you can see memcpy beats simple loop several times and also DMA. This is because DMA cannot occupy 100% of buss speed.
But there is a catch, because original memcpy algorithm copies bytes. Even tho it is faster than DMA.
So memcpy algorithm can be adapted to copy 32-bit words at once and be even faster. Do I need to go further testing?

Over all conclusion. DMA isn’t great for very fast memory copies, but it benefits as independent unit when CPU cannot be occupied.

I didn’t include any checks on limits and so, but it will do for our testing.

I call this function like this:
LEDToggle(LEDB);
memcpy32(destination, source, ARRAYSIZE);
LEDToggle(LEDB);
And here are results of my test
As you can see memcpy32 function copied 800 32-bit words in 76 microseconds. Hope you found this little test interesting.