Overview
DMA is commonly used in embedded real-time task handling. Using DMA for UART packet transmission can significantly reduce CPU processing time and prevent excessive CPU resource use, which is especially advantageous when UART sends and receives large volumes of packets or high-frequency commands.
What is DMA
DMA: Direct Memory Access. In simple terms, it is a controller that allows RAM to exchange data directly with peripherals without CPU intervention. DMA enables hardware devices operating at different speeds to communicate without relying on the CPU for a large interrupt load. Otherwise, the CPU would need to copy each segment of data to buffers and write them back, during which it cannot perform other work.
Advantages of DMA
The role of DMA in a system can be compared to an employee in a company, while the CPU is the owner. If the owner needs to send a package to another city, they can simply give an instruction to the employee and do not have to handle the detailed steps. Similarly, for UART transmission the CPU only needs to perform simple setup to hand a data buffer to the DMA for direct transmission. When transmission completes, a completion interrupt notifies the CPU.
If the CPU handled UART transmission directly, it would continuously arbitrate transmission and occupy a large amount of CPU resources. In RTOS environments with many tasks, using DMA for UART transmission is convenient and efficient. The advantage is also apparent in bare-metal systems. HAL libraries or equivalent on other platforms can achieve the same result.
Example: DMA Send Configuration
The following example uses an STM32 F4 MCU and the standard peripheral library to illustrate the configuration.
/* COMM communication */
#define COMM_COM USART2
#define COMM_COM_CLK RCC_APB1Periph_USART2
#define COMM_COM_TX_GPIO_CLK RCC_AHB1Periph_GPIOD // UART TX
#define COMM_COM_TX_PIN GPIO_Pin_5
#define COMM_COM_TX_GPIO_PORT GPIOD
#define COMM_COM_TX_SOURCE GPIO_PinSource5
#define COMM_COM_TX_AF GPIO_AF_USART2
#define COMM_COM_RX_GPIO_CLK RCC_AHB1Periph_GPIOD // UART RX
#define COMM_COM_RX_PIN GPIO_Pin_6
#define COMM_COM_RX_GPIO_PORT GPIOD
#define COMM_COM_RX_SOURCE GPIO_PinSource6
#define COMM_COM_RX_AF GPIO_AF_USART2
#define COMM_COM_IRQn USART2_IRQn
#define COMM_COM_Priority 9 // Priority
#define COMM_COM_BaudRate 115200 // Baud rate
#define COMM_COM_IRQHandler USART2_IRQHandler // Interrupt handler interface (see stm32f4xx_it.c)
USART Configuration
void USART_COMM_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* Clock configuration */
RCC_AHB1PeriphClockCmd(COMM_COM_TX_GPIO_CLK | COMM_COM_RX_GPIO_CLK, ENABLE);
if((USART1 == COMM_COM) || (USART6 == COMM_COM))
RCC_APB2PeriphClockCmd(COMM_COM_CLK, ENABLE);
else
RCC_APB1PeriphClockCmd(COMM_COM_CLK, ENABLE);
/* Alternate function configuration */
GPIO_PinAFConfig(COMM_COM_TX_GPIO_PORT, COMM_COM_TX_SOURCE, COMM_COM_TX_AF);
GPIO_PinAFConfig(COMM_COM_RX_GPIO_PORT, COMM_COM_RX_SOURCE, COMM_COM_RX_AF);
/* Pin configuration */
GPIO_InitStructure.GPIO_Pin = COMM_COM_TX_PIN; // USART Tx
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // Alternate function mode
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(COMM_COM_TX_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = COMM_COM_RX_PIN; // USART Rx
GPIO_Init(COMM_COM_RX_GPIO_PORT, &GPIO_InitStructure);
/* NVIC configuration */
NVIC_InitStructure.NVIC_IRQChannel = COMM_COM_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = COMM_COM_Priority;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* USART configuration */
USART_InitStructure.USART_BaudRate = COMM_COM_BaudRate; // Baud rate
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // Word length
USART_InitStructure.USART_StopBits = USART_StopBits_1; // Stop bits
USART_InitStructure.USART_Parity = USART_Parity_No ; // Parity
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // Rx and Tx
USART_Init(COMM_COM, &USART_InitStructure);
USART_ClearFlag(COMM_COM, USART_FLAG_RXNE | USART_FLAG_TC);
USART_ITConfig(COMM_COM, USART_IT_RXNE, ENABLE); // Enable RX interrupt
USART_DMACmd(COMM_COM, USART_DMAReq_Tx, ENABLE); // Enable DMA for USART Tx
USART_Cmd(COMM_COM, ENABLE); // Enable USART
}
DMA Configuration
void USART_COMM_DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable clock */
RCC_AHB1PeriphClockCmd(COMM_DMA_CLK, ENABLE);
/* NVIC configuration */
NVIC_InitStructure.NVIC_IRQChannel = COMM_TX_DMA_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = COMM_TX_DMA_Priority;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* DMA configuration */
DMA_DeInit(COMM_TX_DMA_STREAM);
DMA_InitStructure.DMA_Channel = COMM_TX_DMA_CHANNEL; // DMA channel
DMA_InitStructure.DMA_PeripheralBaseAddr = COMM_DR_ADDRESS; // Peripheral address
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)0; // Memory address (to be passed as parameter)
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; // Transfer direction
DMA_InitStructure.DMA_BufferSize = 0; // Transfer length (to be passed as parameter)
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // Peripheral increment
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // Memory increment
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // Data width
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // Circular mode
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // Priority
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(COMM_TX_DMA_STREAM, &DMA_InitStructure);
DMA_ClearFlag(COMM_TX_DMA_STREAM, COMM_TX_DMA_FLAG_TCIF);
DMA_ITConfig(COMM_TX_DMA_STREAM, DMA_IT_TC, ENABLE); // Enable DMA transfer complete interrupt
DMA_Cmd(COMM_TX_DMA_STREAM, DISABLE); // Initialize disabled
}
DMA Send UART Packet
void COMM_SendBufByDMA(uint8_t *Buf, uint16_t Length)
{
DMA_Cmd(COMM_TX_DMA_STREAM, DISABLE); // Disable DMA
// Memory address
DMA_MemoryTargetConfig(COMM_TX_DMA_STREAM, (uint32_t)Buf, DMA_Memory_0);
DMA_SetCurrDataCounter(COMM_TX_DMA_STREAM, Length); // Set DMA transfer length
DMA_Cmd(COMM_TX_DMA_STREAM, ENABLE); // Enable DMA
}
Careful readers will notice the send function is simple. This example uses the STM32F4 series; other chips are similar in principle. The HAL layer can also be used. Regarding the DMA transfer-complete interrupt: in RTOS environments, transmission is often part of a task that waits for a completion signal (the DMA transfer-complete interrupt) before proceeding.