STM32F407 HAL库使用 DMA_Normal 模式实现 UART 循环发送(无需中断)
在 STM32 开发中,很多人喜欢使用 DMA 来加速串口发送数据。然而,默认的 DMA 往往配合中断或使用循环模式(DMA_CIRCULAR
)使用。但在某些特定需求下,我们希望:
-
使用
DMA_NORMAL
模式,确保 DMA 每次只发送固定长度数据; -
不启用任何中断,完全通过轮询实现“自动重发”机制;
-
实现串口缓冲区数据循环发送;
本文将以 STM32F407 + USART1 + DMA2_Stream7 为例,展示如何实现这个功能。
一、基本原理
-
DMA_NORMAL
模式下,每次发送完成后 EN 位会自动清除; -
通过轮询
EN
位是否为 0,可以判断是否传输完成; -
判断完成后,手动重新配置 DMA 参数,并重新启动;
-
整个过程不依赖中断,逻辑清晰、稳定可靠。
二、DMA 和串口初始化
void MX_USART1_UART_Init(void)
{huart1.Instance = USART1;huart1.Init.BaudRate = 115200;huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_NONE;huart1.Init.Mode = UART_MODE_TX_RX;huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart1.Init.OverSampling = UART_OVERSAMPLING_16;HAL_UART_Init(&huart1);
}void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{GPIO_InitTypeDef GPIO_InitStruct = { 0 };if(huart->Instance == USART1){/* USER CODE BEGIN USART1_MspInit 0 *//* USER CODE END USART1_MspInit 0 *//* Peripheral clock enable */__HAL_RCC_USART1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/**USART1 GPIO ConfigurationPA9 ------> USART1_TXPA10 ------> USART1_RX*/GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF7_USART1;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/* USART1 DMA Init *//* USART1_RX Init */hdma_usart1_rx.Instance = DMA2_Stream2;hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;hdma_usart1_rx.Init.Mode = DMA_NORMAL;hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;if(HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK){Error_Handler();}__HAL_LINKDMA(huart, hdmarx, hdma_usart1_rx);/* USART1_TX Init */hdma_usart1_tx.Instance = DMA2_Stream7;hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4;hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;hdma_usart1_tx.Init.Mode = DMA_NORMAL;hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;if(HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK){Error_Handler();}__HAL_LINKDMA(huart, hdmatx, hdma_usart1_tx);}}
三、首次启动 DMA
#define TX_LEN 10
uint8_t uart_send_buffer[TX_LEN] = "Hello DMA";void DMA_UART1_SendStart(void)
{__HAL_DMA_DISABLE(&hdma_usart1_tx);while ((hdma_usart1_tx.Instance->CR & DMA_SxCR_EN) != 0);hdma_usart1_tx.Instance->NDTR = TX_LEN;hdma_usart1_tx.Instance->PAR = (uint32_t)&USART1->DR;hdma_usart1_tx.Instance->M0AR = (uint32_t)uart_send_buffer;__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_TCIF3_7);__HAL_DMA_ENABLE(&hdma_usart1_tx);huart1.Instance->CR3 |= USART_CR3_DMAT; // 启用 DMA 发送功能
}
四、主循环中实现自动重发
void main(void)
{while (1){if ((hdma_usart1_tx.Instance->CR & DMA_SxCR_EN) == 0){// 修改内容(模拟数据更新)uart_send_buffer[0]++;// 重启 DMAhdma_usart1_tx.Instance->NDTR = TX_LEN;__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_TCIF3_7);__HAL_DMA_ENABLE(&hdma_usart1_tx);}}
}
五、总结
这种轮询 + DMA_NORMAL 模式非常适合:
-
周期性发送固定格式数据;
-
不想被中断打断主循环的逻辑;
-
希望更强控制发送时机、避免 DMA 无限循环死锁。