STM32 Direct Memory Access DMA

Publisher:hylh2008Latest update time:2018-06-05 Source: eefocusKeywords:STM32 Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

The first time I came into contact with DMA was when I was learning ARM9 bare board programming at school. It has been almost 2 years since then. Now let's take a look at the DMA of the STM32 platform. Similarly, with the support of the standard peripheral library, the DMA programming of STM32 is very simple, but since it is a study, it is better to spend some time to understand the relevant concepts and principles of DMA.

1. Introduction to DMA

DMA is the abbreviation of Direct Memory Access, which means direct memory access. DMA is one of the peripherals of STM32 microcontrollers, and its main function is to move data. Moving data through DMA does not require direct CPU control, nor does it require the preservation and restoration of the scene like the interrupt processing method. While transmitting data, the CPU can do other things.

Data transfer without using DMA: 
Write the picture description here

Data transfer using DMA: 
Write the picture description here

DMA data transfer supports from peripherals to memory, memory to peripherals, and memory to memory (the memory mentioned here can be SRAM or FLASH). The DMA controller includes DMA1 controller and DMA2 controller, which have 7 and 5 channels for data transfer respectively. Each channel is dedicated to managing the memory access requests from one or more peripherals, and an arbitrator is used to coordinate the priority of each peripheral for DMA transfer requests. Note that DMA2 only exists in large-capacity or interconnected STM32 microcontrollers.

2. DMA functional block diagram

Write the picture description here

2.1 STM32 peripherals' requests and channels for DMA

The request and channel correspond to the numbers 1 and 2 in the figure: If the STM32 peripheral wants to transfer data through DMA, it must first send a DMA request to the DMA controller. After receiving the DMA request from the peripheral, the controller will send a response signal to the peripheral. After the peripheral responds and the DMA controller receives the response from the peripheral, the DMA starts the transmission until the transmission is completed. 
Why do we need to send requests, respond, and receive responses? As can be seen from the blue box in the figure, DMA transmission and CPU share the system bus. The premise for starting DMA transmission is that the system bus is idle. In other words, the CPU does not occupy the system bus. Therefore, the above response mechanisms are required before starting DMA transmission. The bottom layer is that the DMA controller and the CPU are coordinating for the system bus. DMA1 has 7 channels and DMA2 has 5 channels. Different peripheral requests must be sent to the DMA controller through the corresponding DMA channels. Transmitting different peripheral requests to the corresponding channels is what we set in software programming.

DMA1 open channels and corresponding requests: 
Write the picture description here 
DMA2 open channels and corresponding requests: 
Write the picture description here 
Although each channel can receive requests from multiple peripherals, it can only receive one at a time.

2.2 Arbitrator

The arbiter corresponds to the number 3 in the figure: When DMA requests occur on multiple channels of the DMA controller, the arbiter is required to manage the order of response processing. The arbiter manages DMA requests through software and hardware: Software refers to the code we write, which is set in the DMA_CCRx (x refers to the channel number) register, with 4 levels, very high (DMA_Priority_VeryHigh), high (DMA_Priority_High), medium (DMA_Priority_Medium) and low (DMA_Priority_Low). Hardware means that if two or more DMA channel requests have the same priority, their response order depends on the channel number, and the one with a lower number has a higher priority. In the STM32 with DMA2, the DMA1 controller has a higher response priority than DMA2.

2.3 Configuring the DMA Controller

Configuring the DMA controller is nothing more than the following registers: 
Write the picture description here

As mentioned earlier, the DMA data transfer mechanism does not require the participation of the CPU, but for the DMA controller to work properly and data to be transmitted correctly, three necessary conditions are required: source address, destination address and data size. For data to be transmitted in batches, the data size condition also includes the size and unit of each transmission.

(1) Source address and destination address 
DMA has three data transfer directions: from peripheral to memory, from memory to peripheral, and from memory to memory. BIT[4]DIR of DMA_CCR is used to configure the data transfer direction: 
Write the picture description here
a value of 0 indicates from peripheral to memory, and a value of 1 indicates from memory to peripheral. The peripheral address is configured in the DMA_CPAR register, and the memory address is configured in the DMA_CMAR register. 
Write the picture description here

(2) The size and unit of the transmitted data. 
Taking the serial port sending data to the computer as an example (memory->peripheral direction), the development board software can send a large amount of data to the computer at one time. The specific amount is configured in DMA_CNDTR: 
Write the picture description here
The lower 16 bits of DMA_CNDTR are valid, and a maximum of 65535 data can be transmitted at one time. 
To transmit data correctly, the data width of the source and target storage must be consistent. The serial port data register is 8 bits, that is, the value of BIT[9:8]PSIZE of the 
Write the picture description here 
peripheral data width setting register DMA_CCRx is 0: The value of BIT[11:10]MSIZE of the memory data width setting register DMA_CCRx is also 0: When 
Write the picture description here 
DMA transmits data, it is also necessary to set the incremental mode of the data sending pointer on the source address and the data storage pointer on the destination address. When the development board serial port sends data to the computer, assuming that there is a lot of data to be sent, the data sending pointer on the memory (source address) needs to be increased by 1 each time it is sent, but the serial port data register does not need to be increased because there is only one register. The data on the data register is cleared after being transmitted to the computer (even if it is not cleared, it does not matter if the data is directly overwritten). The address pointer increment mode of the peripheral is configured by PINC of DMA_CCRx, and the address pointer of the memory is configured by MINC. 
Write the picture description here

(3) Transmission end 
DMA interrupt status register DMA_ISR can set each DMA channel to generate corresponding flags when the transmission is halfway  through, the transmission is completed, and the transmission error
Write the picture description here 
Write the picture description here 
occurs. In DMA_CCRx bits 1, 2, and 3, interrupts can be generated when the transmission is halfway through, the transmission is completed, and the transmission error occurs: 
Write the picture description here 
In addition, bit 0 is used to enable DMA transmission. The 
Write the picture description here 
transmission is divided into two modes: one-time transmission and cyclic transmission. One-time transmission means that the transmission stops after one transmission. If you want to transmit again, you need to turn off the DMA enable and reconfigure it before you can continue the transmission. Cyclic transmission means that after one transmission is completed, the configuration of the first transmission is restored to the cyclic transmission, and so on. The setting bit is CIRC in the DMA_CCRx register.

3. DMA function module description structure

In the consistent style of the standard library, the DMA_InitTypeDef initialization structure is defined in the stm32f10x_dma.h file, and the DMA_Init() function is defined in stm32f10x_dma.c.

typedef struct

{

  uint32_t DMA_PeripheralBaseAddr; //Peripheral address

  uint32_t DMA_MemoryBaseAddr; //Memory address

  uint32_t DMA_DIR; //Transmission direction

  uint32_t DMA_BufferSize; //The amount of data transferred

  uint32_t DMA_PeripheralInc; //Peripheral address increment mode

  uint32_t DMA_MemoryInc; //Memory address increment mode

  uint32_t DMA_PeripheralDataSize; //Peripheral data width

  uint32_t DMA_MemoryDataSize; //Memory data width

  uint32_t DMA_Mode; //Mode selection

  uint32_t DMA_Priority; //Channel priority

  uint32_t DMA_M2M; //Memory to memory mode

}DMA_InitTypeDef;

(1)DMA_PeripheralBaseAddr: Peripheral address. If it is memory-to-memory mode, this member is set to the address of one of the memories, otherwise it is set to the address of the peripheral. 
(2)DMA_MemoryBaseAddr: Memory address. It is generally set to the first address of the container (array) that stores data in the program. 
(3)DMA_DIR: Transfer direction. It can be set to peripheral to memory or memory to peripheral. Note that there is no memory-to-memory option here. When using memory-to-memory, you only need to use one of the memories as a peripheral. 
(4)DMA_BufferSize: Set the number of data to be transferred. (5)DMA_PeripheralInc: Peripheral address increment mode. If the value is DMA_PeripheralInc_Enable, the automatic increment function of the peripheral address is enabled. Generally, peripherals have only one data register, so this bit is not enabled. 
(6)DMA_MemoryInc: If configured as DMA_MemoryInc_Enable, the automatic increment function of the memory address is enabled. Generally, memories are customized by us and multiple data are stored in the area, so this bit is generally enabled. 
(7)DMA_MemoryDataSize: Peripheral data width, optional 8 bits (byte), 16 bits (half word), 32 bits (word) 
(8)DMA_MemoryDataSize: Memory data width, optional 8 bits (byte), 16 bits (half word), 32 bits (word) 
(9)DMA_Mode: Transfer mode selection, one-time transfer or cyclic transfer 
(10)DMA_Priority: Channel priority setting, optional very high, high, medium, low 
(11)DMA_M2M: Memory to memory mode

4. Common programming functions

4.1 DMA clock enable

4.2 Initialize DMA function module description structure


Function prototype: void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)

DMAy_Channelx specifies which DMA channel, and DMA_InitStruct is the description structure parsed above

Example of use: DMA_Init(DMAy_Channel1, &DMA_InitStruct);


4.3 Enable peripheral DMA transmission


Take starting the DMA sending function as an example:


Function prototype: void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState 

NewState)

Example of use:

USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);


DMA transfers to peripherals require corresponding settings, but not to memory. For memory to memory, the DMA_M2M member in the DMA_InitTypeDef structure needs to be enabled.


4.4 Enable DMA channel and start DMA transmission


Function prototype: void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

Example of use: DMA_Cmd(DMAy_Channel1, ENABLE);

After enabling, the DMA controller starts working and starts data transfer under DMA control at the appropriate time (CPU does not occupy the bus).


4.5 Query DMA transfer status 

During DMA transmission, we can query the status of the transmission channel through the function:


Function prototype: FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)

Suppose you want to query whether DMA channel 4 transfer is completed:

DMA_GetFlagStatus(DMA1_FLAG_TC4);

A return value of RESET indicates that the transfer has not been completed, while a return value of SET indicates that the transfer is complete.


Function to get the current remaining data size:

Function prototype: uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

Example: Get the amount of data that has not been transferred on DMA channel 4:

DMA_GetCurrDataCounter(DMAy_Channel4);


5. Programming Practice


5.1 DMA Transfer – Memory to Memory Mode


The hardware platform is the Zhengdian Atom MiniSTM32, which has two LEDs on board: red and green. 

The program function is to copy the built-in FLASH data of STM32 to the built-in SRAM: define a const static variable as the source data, use DMA transfer to copy the source data to the target address, and compare whether the source data and the target data are the same. If they are the same, the green LED light will be on, otherwise the red LED light will be on. 

The core of DMA programming lies in 

(1) Enable DMA clock 

(2) Configure DMA initialization structure parameters 

(3) Enable DMA and start data transfer 

(4) Wait for data transmission to complete

The project structure is: 
Write the picture description here

BSP_LED.c implements configuration of LED pins:

#include


void LED_Configuration(void)

{

    GPIO_InitTypeDef GPIO_InitTypeStu;


    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD, ENABLE);


    //³õʼ»¯PA8ÍÆÍìÊä³ö

    GPIO_InitTypeStu.GPIO_Mode = GPIO_Mode_Out_PP;

    GPIO_InitTypeStu.GPIO_Pin = GPIO_Pin_8;

    GPIO_InitTypeStu.GPIO_Speed ​​= GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitTypeStu);        

    GREEN_LED_OFF;


    GPIO_InitTypeStu.GPIO_Pin = GPIO_Pin_2;

    GPIO_Init(GPIOD, &GPIO_InitTypeStu);

    RED_LED_OFF;

}


BSP_USART.c implements the configuration of USART1 function:



#include "BSP_USART.h"


#pragma import(__use_no_semihosting)                             

struct __FILE 

    int handle; 


}; 


FILE __stdout;          

_sys_exit(int x) 

    x = x; 


int fputc(int ch, FILE *f)

{      

    while((USART1->SR&0X40) == RESET);

    USART1->DR = (u8)ch;      

    return ch;

}


void NVIC_Configuration(void)

{

    NVIC_InitTypeDef NVIC_InitStu;

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);


    NVIC_InitStu.NVIC_IRQChannel = USART1_IRQn;

    NVIC_InitStu.NVIC_IRQChannelPreemptionPriority = 1;

    NVIC_InitStu.NVIC_IRQChannelSubPriority = 1;

    NVIC_InitStu.NVIC_IRQChannelCmd = ENABLE;


    NVIC_Init(&NVIC_InitStu);

}


void USART_Configuration(void)

{

    GPIO_InitTypeDef GPIO_InitStu;

    USART_InitTypeDef USART_InitStu;


    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);


    GPIO_InitStu.GPIO_Mode = GPIO_Mode_AF_PP;

    GPIO_InitStu.GPIO_Pin = GPIO_Pin_9;

    GPIO_InitStu.GPIO_Speed ​​= GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStu);


    GPIO_InitStu.GPIO_Mode = GPIO_Mode_IN_FLOATING;

    GPIO_InitStu.GPIO_Pin = GPIO_Pin_10;

    GPIO_Init(GPIOA, &GPIO_InitStu);


    USART_InitStu.USART_BaudRate = 115200;

    USART_InitStu.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

    USART_InitStu.USART_Parity = USART_Parity_No;

    USART_InitStu.USART_StopBits = USART_StopBits_1;

    USART_InitStu.USART_WordLength = USART_WordLength_8b;

    USART_InitStu.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

    USART_Init(USART1, &USART_InitStu);


    NVIC_Configuration();


    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);



    USART_Cmd(USART1, ENABLE);

}


void USART_SendChar(USART_TypeDef* pUSARTx, uint8_t c)

{

    USART_SendData(pUSARTx, c);


    while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);

}


void USART_SendString(USART_TypeDef* pUSARTx, char* str)

{

    uint32_t n = 0;


    while (*(str + n) != '\0')

    {

        USART_SendChar(pUSARTx, *(str + n));

        n++;

    }


    while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);

}


The above has been written in the previous related articles. Let’s look at BSP_LED.c to implement the configuration of DMA function:


#include


void DMA_Configuration(const uint32_t *SrcBuf, uint32_t *destBuf, uint32_t BUF_SZ)

{

    DMA_InitTypeDef DMA_InitTypeStu;


    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);


    DMA_InitTypeStu.DMA_BufferSize = BUF_SZ;

    DMA_InitTypeStu.DMA_DIR = DMA_DIR_PeripheralDST;

    DMA_InitTypeStu.DMA_M2M = DMA_M2M_Enable;

    DMA_InitTypeStu.DMA_MemoryBaseAddr = (uint32_t)SrcBuf;

    DMA_InitTypeStu.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;

    DMA_InitTypeStu.DMA_MemoryInc = DMA_MemoryInc_Enable;

    DMA_InitTypeStu.DMA_Mode = DMA_Mode_Normal;

    DMA_InitTypeStu.DMA_PeripheralBaseAddr = (uint32_t)destBuf;

    DMA_InitTypeStu.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;

    DMA_InitTypeStu.DMA_PeripheralInc = DMA_PeripheralInc_Enable;

    DMA_InitTypeStu.DMA_Priority = DMA_Priority_VeryHigh;


    DMA_Init(DMA1_Channel1, &DMA_InitTypeStu);


    DMA_Cmd(DMA1_Channel1, ENABLE);

}


uint8_t BufCmp(const uint32_t* pSrc, uint32_t* pDest, uint32_t len)

{

    int i;


    for (i = 0; i < len; i++)

    {

        if (*(pSrc + i) != *(pDest + i))

            return 1;

    }

    return 0;

}


main() function:


#include

#include

#include


#define BUF_SZ 32


const uint32_t SrcBuf[BUF_SZ] = {0x12, 0x23, 0x45, 0x86, 0x45, 0x63, 0x89, 0x87, 

                                0x22, 0x23, 0x26, 0x27, 0x28, 0x31, 0x33, 0x86,

                                0x12, 0x23, 0x45, 0x86, 0x45, 0x63, 0x89, 0x87, 

                                0x22, 0x23, 0x26, 0x27, 0x28, 0x31, 0x33, 0x86};


uint32_t destBuf[BUF_SZ];


void delay_time(void)

{

    int i, j;


    for (i = 0; i < 500; i++)

        for (j = 0; j < 6000; j++);

}



int main(void)

{

    int i;


    LED_Configuration();

    USART_Configuration();

    DMA_Configuration(SrcBuf, destBuf, BUF_SZ);


    delay_time();

#if 0   

    for (i = 0; i < BUF_SZ; i++)

    {

        printf("SrcBuf[%d] = %u\r\n", i, SrcBuf[i]);

    }   


    printf("\r\n");

    for (i = 0; i < BUF_SZ; i++)

    {

        printf("destBuf[%d] = %u\r\n", i, destBuf[i]);

    }

#endif


#if 1   

    if (BufCmp(SrcBuf, destBuf, BUF_SZ) == 0)

    {

        GREEN_LED_ON;

        RED_LED_OFF;

    }


    else

    {

        GREEN_LED_OFF;

        RED_LED_ON;

    }


#endif

    while (1);  


    return 0;

}


5.2 Memory to Serial Port (Peripheral) Mode


//BSP_DMA.c

void DMA_Configuration(const uint32_t *SrcBuf, uint32_t *destBuf, uint32_t BUF_SZ)

{

    DMA_InitTypeDef DMA_InitTypeStu;


    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);


    DMA_InitTypeStu.DMA_BufferSize = BUF_SZ;

    DMA_InitTypeStu.DMA_DIR = DMA_DIR_PeripheralDST;

    DMA_InitTypeStu.DMA_M2M = DMA_M2M_Disable;

    DMA_InitTypeStu.DMA_MemoryBaseAddr = (uint32_t)SrcBuf;

    DMA_InitTypeStu.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;

    DMA_InitTypeStu.DMA_MemoryInc = DMA_MemoryInc_Enable;

    //DMA_InitTypeStu.DMA_Mode = DMA_Mode_Normal;

    DMA_InitTypeStu.DMA_Mode = DMA_Mode_Circular; //Circular sending

    DMA_InitTypeStu.DMA_PeripheralBaseAddr = (uint32_t)destBuf; //(uint32_t)(USART1_BASE + 0x04);

    DMA_InitTypeStu.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

    DMA_InitTypeStu.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

    DMA_InitTypeStu.DMA_Priority = DMA_Priority_VeryHigh;


    DMA_Init(DMA1_Channel4, &DMA_InitTypeStu);


    DMA_Cmd(DMA1_Channel4, ENABLE);

}


//main.c

int main(void)

{

    LED_Configuration();

    USART_Configuration();

    DMA_Configuration((const uint32_t*)SrcBuf, (uint32_t*)(&USART1->DR), BUF_SZ); //(uint32_t*)(USART1_BASE + 0x04)


    USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);


    while(1)

    {

        GREEN_LED_ON;

        RED_LED_OFF;


        delay_time();


        GREEN_LED_OFF;

        RED_LED_ON;


        delay_time();


    }

    return 0;

}


Under DMA control, the data on SRAM is continuously sent to the DR register of USART1. The CPU does not need to participate in this process, so the LED flashes all the time.


DMA transfer is actually very simple, I have said it in a long-winded way. The key lies in the understanding of the concept, the use of library functions is still the key, and register operations are basically not needed.

Keywords:STM32 Reference address:STM32 Direct Memory Access DMA

Previous article:Difference between bootloader in stm32 system memory and bootloader in flash
Next article:Address mapping in stm32 library

Latest Microcontroller Articles
  • Download from the Internet--ARM Getting Started Notes
    A brief introduction: From today on, the ARM notebook of the rookie is open, and it can be regarded as a place to store these notes. Why publish it? Maybe you are interested in it. In fact, the reason for these notes is ...
  • Learn ARM development(22)
    Turning off and on interrupts Interrupts are an efficient dialogue mechanism, but sometimes you don't want to interrupt the program while it is running. For example, when you are printing something, the program suddenly interrupts and another ...
  • Learn ARM development(21)
    First, declare the task pointer, because it will be used later. Task pointer volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • Learn ARM development(20)
    With the previous Tick interrupt, the basic task switching conditions are ready. However, this "easterly" is also difficult to understand. Only through continuous practice can we understand it. ...
  • Learn ARM development(19)
    After many days of hard work, I finally got the interrupt working. But in order to allow RTOS to use timer interrupts, what kind of interrupts can be implemented in S3C44B0? There are two methods in S3C44B0. ...
  • Learn ARM development(14)
  • Learn ARM development(15)
  • Learn ARM development(16)
  • Learn ARM development(17)
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号