Introduction to DMA and Direct Memory Access for STM32, dmastm32
The first time I came into contact with DMA, I learned the arm9-bare-board program at school. I thought it was almost two years later. Now let's take a look at the dma of the STM32 platform. With the support of the standard peripheral library, the DMA programming of the STM32 is very simple, but it is not only learning, let's take a moment to learn about the concepts and principles of DMA.
1. Introduction to DMA
DMA is short for Direct Memory Access, which means Direct Memory Access. DMA is one of the peripherals of the STM32 microcontroller. It is mainly used to move data. When using DMA to move data, you do not need the CPU to directly participate in the control, nor do you need to retain and restore the site in the same way as the interrupt processing method. When transmitting data, the CPU can do other things.
Data transmission without DMA:
Data transmission after DMA:
DMA data transmission supports from peripherals to memory, memory to peripherals, memory to memory (the memory mentioned here can be SRAM or FLASH ). The DMA controller consists of the DMA1 controller and the DMA2 controller, which are transmitted through 7 and 5 channels respectively. Each channel is specifically used to manage requests from one or more peripherals for memory access, and an arbitration server is used to coordinate the priority of each peripherals for DMA transmission requests. Note that DMA2 only exists in large-capacity or interconnected STM32 microcontroller.
2. DMA function Diagram
2.1 request and channel for DMA by STM32 peripherals
Number 1 and number 2 in the request and channel diagram: to transmit data through DMA, The STM32 peripherals must first send the DMA request to the DMA controller, after receiving the DMA request from the peripheral device, the controller sends a response to the peripheral device. After the peripheral device responds and the DMA controller receives the response from the peripheral device, the DMA starts transmission until the transmission is completed.
Why are the complex steps of sending, responding, and receiving responses? The blue frame in the figure shows that the DMA transmission and CPU share the system bus. To enable the DMA transmission, the system bus is idle. In other words, the CPU does not occupy the system bus, therefore, the above response mechanisms are required before the DMA transmission is started. The bottom layer is that the DMA controller and CPU are coordinating 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 channel. Transmit different peripheral requests to the corresponding channel. This is set in software programming.
DMA1 open channels and corresponding requests:
DMA2 open channels and corresponding requests:
Although each channel can receive requests from multiple peripherals, it can only receive one request at a time.
2.2 Arbitration
Number 3 in the ER diagram: when multiple channels of the DMA controller have DMA requests, the ER needs to manage the order of Response Processing. The arbitration server manages DMA requests through software and hardware: The Software refers to the code we write, which is set in the DMA_CCRx (x) Register and has four 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 sequence depends on the channel number. The lower number has a higher priority. In STM32 with DMA2, the response priority of the DMA1 controller is higher than that of dma2.
2.3 configure the DMA Controller
Configuring the DMA controller is nothing more than these registers:
As mentioned above, the DMA data transmission mechanism does not require the CPU to participate, but the DMA controller must work properly and the data must be transmitted correctly. There are three necessary conditions: Source Address, destination address, and data size, for batch data transmission, the data size condition also contains the size and unit of each transmission.
(1) Source Address and Destination Address
There are three directions for DMA data transmission: From peripherals to memory, from memory to peripherals, from memory to memory. BIT [4] DIR of dma_cr is used to configure the data transmission direction:
The value is 0 from the peripherals to the memory, and the value is 1 from the memory to the peripherals. The peripheral address is configured in the DMA_CPAR register, and the memory address is configured in the DMA_CMAR register.
(2) Data size and unit of transmission
Take the serial port to send data to the computer as an example (memory-> peripherals), the Development Board software can send a large amount of data to the computer at a time, the specific number of configuration in DMA_CNDTR:
DMA_CNDTR is valid at a low 16-bit rate and can transmit up to 65535 data records at a 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 BIT of the peripheral data width setting register DMA_CCRx [9: 8] PSIZE is 0:
The data width of the memory is set to the BIT [11: 10] MSIZE of the register DMA_CCRx. The value is also 0:
For DMA data transmission, you also need to set the data sending pointer on the source address and the incremental mode for storing the destination address Data Pointer. The Development Board sends data to the computer through the serial port. If a lot of data is to be sent, the data sending pointer on the memory (Source Address) needs to be added with 1 after each sending, while the serial port data register does not, because there is only one register, the data in the data register is cleared after it is transferred to the computer (even if it is not cleared, it doesn't matter if the data is overwritten directly ). The incremental mode of the peripheral address pointer is configured by PINC of DMA_CCRx, and the memory address pointer is configured by MINC.
(3) transmission ends
The DMA interrupt Status Register DMA_ISR can be used to set the signal for transmission over half, transmission completion, and transmission error display for each DMA channel,
When DMA_CCRx bits 1, 2, and 3 can be set to interrupt when transmission is over half, transmission is completed, and transmission errors occur:
Additionally, bit 0 is used to enable DMA transmission.
Transmission is completed in two modes: one transmission and one loop transmission. One transmission means that the transmission is stopped after one transmission. To re-transmit the data, you need to disable the DMA enabling and reconfigure it before continuing the transmission. Cyclic transmission is the configuration of cyclic transmission after the first transmission is resumed. Set the CIRC in the DMA_CCRx register.
3. Description structure of the DMA function module
The consistent style of the standard library. In the file stm32f10x_dma.h, the struct can be initialized by DMA_InitTypeDef. The DMA_Init () function is defined in stm32f10x_dma.c.
Typedef struct {uint32_t bytes; // peripheral address uint32_t bytes; // storage address uint32_t DMA_DIR; // The number of data transmitted to uint32_t DMA_BufferSize; // The uint32_t bytes; // incremental mode of the peripheral address uint32_t DMA_MemoryInc; // incremental mode of the memory address uint32_t bytes; // The width of the peripheral data uint32_t DMA_MemoryDataSize; // The data width of the memory uint32_t DMA_Mode; // select uint32_t DMA_Priority as the mode; // The channel priority uint32_t DMA_M2M; // memory to memory mode} DMA_InitTypeDef;
(1) DMA_PeripheralBaseAddr: Peripheral address. If the memory is in memory mode, this member is set to the address of one of the memory; otherwise, it is set to the address of the peripheral.
(2) DMA_MemoryBaseAddr: memory address, which is generally set to the first address of the container (array) in the program that stores data.
(3) DMA_DIR: Transmission Direction, which can be set to peripherals to memory and memory to peripherals. Note that there is no storage-to-memory option. When using storage-to-memory, you only need to use one of the memory as a peripheral.
(4) DMA_BufferSize: set the number of data to be transmitted. (5) DMA_PeripheralInc: the incremental mode of the peripheral address. If the value is DMA_PeripheralInc_Enable, the auto-Increment Function of the peripheral address is enabled. Generally, peripherals only have one data register, so this bit is not enabled.
(6) DMA_MemoryInc: If it is configured as the DMA_MemoryInc_Enable table, the memory address auto-increment function is enabled. Generally, the memory is customized, and the region memory stores multiple data, so this bit is generally enabled.
(7) DMA_MemoryDataSize: peripheral data width. Optional 8-bit (in bytes), 16-bit (half-word), 32-bit (word)
(8) DMA_MemoryDataSize: memory data width. Optional 8-bit (in bytes), 16-bit (half-word), 32-bit (word)
(9) DMA_Mode: Transmission Mode Selection, one transmission or one loop Transmission
(10) DMA_Priority: Specifies the channel priority. Optional values are very high, high, medium, and low.
(11) DMA_M2M: memory-to-memory mode
4. Programming common functions 4.1 DMA clock enabling
Function prototype: RCC_AHBPeriphClockCmd (uint32_t RCC_AHBPeriph, FunctionalState NewState) Example: RCC_AHBPeriphClockCmd (RCC_AHBPeriph_DMA1, ENABLE );
4.2 initialize the description structure of the DMA function module
Function prototype: void DMA_Init (optional * DMAy_Channelx, DMA_InitTypeDef * channels) DMAy_Channelx specifies the DMA channel. Example of using the structure described above: DMA_Init (DMAy_Channel1, & channel1 );
4.3 enable peripheral DMA Transmission
Take the DMA sending function as an example:
Function prototype: void USART_DMACmd (USART_TypeDef * USARTx, uint16_t USART_DMAReq, FunctionalState NewState) Example: USART_DMACmd (USART1, USART_DMAReq_Tx, ENABLE );
The DMA transmission of peripherals requires corresponding settings, but memory does not. The DMA_InitTypeDef struct contains DMA_M2M members that need to be enabled.
4.4 enable DMA channel and enable DMA Transmission
Function prototype: void DMA_Cmd (DMA_Channel_TypeDef * DMAy_Channelx, FunctionalState NewState); example: DMA_Cmd (DMAy_Channel1, ENABLE); after enabling, the DMA controller starts to work, start data transmission under DMA control at the right time (CPU does not occupy the bus.
4.5 query the DMA transmission status
During DMA transmission, we can use functions to query the transmission channel status:
Function prototype: FlagStatus DMA_GetFlagStatus (uint32_t DMAy_FLAG) assume that the DMA Channel 4 transmission is completed: DMA_GetFlagStatus (dma1_flag_c4); The returned value is RESET, indicating that the transmission is not completed, and SET.
Function for obtaining the remaining data volume: function prototype: uint16_t DMA_GetCurrDataCounter (DMA_Channel_TypeDef * DMAy_Channelx); example: how much data is not transmitted in DMA Channel 4: Notify (DMAy_Channel4 );
5. programming practices 5.1 DMA transfer-memory-to-memory mode
The hardware platform is the punctual atom MiniSTM32, with two LEDs in red and green respectively.
Program function to copy the built-in FLASH data of STM32 to the built-in SRAM: Define a const static variable as the source data, and use DMA transmission to copy the source data to the target address, if the source and target data are the same, if the green LED lights are the same, otherwise the red LED lights are on.
The core of DMA programming lies in
(1) Enable DMA clock
(2) configure the DMA initialization struct Parameter
(3) Enable DMA to start Data Transmission
(4) wait until the data transmission is completed
The project structure is:
BSP_LED.c:
#include
void LED_Configuration(void){ GPIO_InitTypeDef GPIO_InitTypeStu; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD, ENABLE); //3?ê??ˉPA8í?íìê?3? 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:
#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);}
As mentioned in the previous articles, see BSP_LED.c to configure the 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 32const 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 (peripherals) Mode
// Define DMA_Configuration (const uint32_t * SrcBuf, uint32_t * destBuf, uint32_t BUF_SZ) {region; region (region, ENABLE); region = BUF_SZ; region = region; sequence = (uint32_t) SrcBuf; Sequence = sequence; Sequence = DMA_MemoryInc_Enable; // sequence = DMA_Mode_Normal; Sequence = sequence; // cyclically sent sequence = (uint32_t) destBuf; // (uint32_t) (usart+base + 0x04); region = bandwidth; DMA_Init (DMA1_Channel4, & DMA_InitTypeStu); DMA_Cmd (DMA1_Channel4, ENABLE);} // main. cint main (void) {LED_Configuration (); USART_Configuration (); DMA_Configuration (const uint32_t *) SrcBuf, (uint32_t *) (& USART1-> DR), BUF_SZ ); // (uint32_t *) (usart+base + 0x04) lower (USART1, lower, ENABLE); while (1) {GREEN_LED_ON; RED_LED_OFF; delay_time (); GREEN_LED_OFF; RED_LED_ON; delay_time ();} return 0 ;}
Under the DMA control, the data on the SRAM is constantly sent to the DR register of USART1. In this process, the CPU does not need to be involved, so the LED flashing is also ongoing.
The DMA transmission is actually very simple, and I am a little embarrassed. The key lies in understanding the concept. The use of database functions is still critical, and register operations are basically not needed.