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.
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.
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.
Here is a code of DMA memory to memory transfer:
#include <stm32f4xx.h> #include "math.h" #include "stm32f4xx_usart.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_adc.h" #include "stm32f4xx_dma.h" #include "misc.h" #include "stm32f4xx_rcc.h" #include "HD44780.h" #include "stdlib.h" #include "stdio.h" // #define ARRAYSIZE 20 volatile uint32_t status = 0; volatile uint32_t i; uint32_t source[ARRAYSIZE]; uint32_t destination[ARRAYSIZE]; // char buf[15]; // void usart_puts(char *data); void USART3_PutChar(char c); void initDMA(); void ledInit(void); void switchInit(void); // void Delay(__IO uint32_t nCount) { while(nCount--) { } } //
void ledInit(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14 ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); }
void switchInit(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6 ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz; GPIO_Init(GPIOE, &GPIO_InitStructure); } // void initDMA() { //init DMA --> lihat tabel di ref manual DMA_InitTypeDef DMA_InitStructure; DMA_StructInit(&DMA_InitStructure); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); // DMA_DeInit(DMA2_Stream0); DMA_InitStructure.DMA_Channel = DMA_Channel_0; DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; /* config of memory */ DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; //DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /* config of peripheral */ DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Word; //DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; DMA_InitStructure.DMA_BufferSize = ARRAYSIZE; DMA_InitStructure.DMA_PeripheralBaseAddr = source; DMA_InitStructure.DMA_Memory0BaseAddr = destination; DMA_Init(DMA2_Stream0,&DMA_InitStructure); DMA_ITConfig(DMA2_Stream0,DMA_IT_TC,ENABLE); // NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0; NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // DMA_Cmd(DMA2_Stream0, ENABLE); } //
void init_USART3(uint32_t baudrate) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOD, &GPIO_InitStruct); GPIO_PinAFConfig(GPIOD, GPIO_PinSource8, GPIO_AF_USART3); GPIO_PinAFConfig(GPIOD, GPIO_PinSource9, GPIO_AF_USART3); USART_InitStruct.USART_BaudRate = baudrate; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_Parity = USART_Parity_No; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART3, &USART_InitStruct); USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_Cmd(USART3, ENABLE); } void USART_puts(USART_TypeDef* USARTx, volatile char *s) { while(*s) { while( !(USARTx->SR & 0x00000040) ); USART_SendData(USARTx, *s); *s++; } } void DMA2_Stream0_IRQHandler(void) { //Test on DMA1 Channel1 Transfer Complete interrupt if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0)) { status=1; GPIO_SetBits(GPIOB,GPIO_Pin_12); GPIO_SetBits(GPIOB,GPIO_Pin_13); GPIO_SetBits(GPIOB,GPIO_Pin_14); //Clear DMA1 Channel1 Half Transfer, Transfer Complete and Global interrupt //pending bits for (i=0;i<ARRAYSIZE;i++) { sprintf(buf,"%d",destination[i]); USART_puts(USART3,buf); Delay(100000); USART_SendData(USART3,13); } DMA_ClearITPendingBit(DMA2_Stream0,DMA_IT_TCIF0); DMA_Cmd(DMA2_Stream0,DISABLE); DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, DISABLE); } } // int main(void) { for (i=0; i<ARRAYSIZE;i++) { source[i]=i; } init_USART3(115200); ledInit(); switchInit(); initDMA(); GPIO_ResetBits(GPIOB,GPIO_Pin_12); GPIO_ResetBits(GPIOB,GPIO_Pin_13); GPIO_ResetBits(GPIOB,GPIO_Pin_14); // while (1) { } }