目录
一.DMA
通过之前的学习,CPU既要在中断搬运数据,也要处理正常任务的代码。今天来介绍DMA,DMA不仅能更省CPU,同时接收不定长数据 。DMA(Direct Memory Access:直接内存访问)小助手主要作用是可以在寄存器与内存间搬运数据。DMA会在源地址与目标地址之间进行搬运,等全部搬运完,再通过中断提醒我们。
我们需要在串口的接收和发送创建两条DMA通道,就可以让DMA在串口的寄存器与内存变量间搬运数据了

二.CubeMX
1.为USART2分别创建发送与接收的DMA通道

三.普通DMA定长接收发送函数
1.普通DMA发送函数:HAL_UART_Transmit_DMA(huart, pData, Size)
huart:发送的串口指针,这里使用&huart2
pData:发送数据的指针,这里使用receiveData
Size:发送数据的长度:这里使用2
回调函数:
当接收数据半满时,调用回调函数:
HAL_UART_RxHalfCpltCallback(huart)当接收数据全满时,调用回调函数
:HAL_UART_RxCpltCallback(huart)(与中断的回调函数相同)
2.普通DMA接收函数:HAL_UART_Receive_DMA(huart, pData, Size)huart:接收的串口指针,这里使用&huart2
pData:接收数据的指针,这里使用receiveData
Size:接收数据的长度:这里使用2
回调函数:
当发送数据达到一半时,调用回调函数:
HAL_UART_TxHalfCpltCallback(huart)当发送数据全发完时,调用回调函数:
HAL_UART_TxCpltCallback(huart)(与中断的回调函数相同)

cpp
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit_DMA(&huart2,receiveData,2);
//HAL_MAX_DELAY:不设超时时间,直至结束
GPIO_PinState state = GPIO_PIN_SET;//初始化默认高电平
//判断单片机接收的是0还是1,设置高低电平
if(receiveData[1] == '0')
{
state = GPIO_PIN_RESET;
}
//判断单片机接收的是R还是G还是B,输出对应的颜色
if(receiveData[0] == 'R'){
HAL_GPIO_WritePin(LED_RED_GPIO_Port,LED_RED_Pin,state);
}else if(receiveData[0] == 'G')
{
HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin,state);
}else if(receiveData[0] == 'B')
{
HAL_GPIO_WritePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin,state);
}
HAL_UART_Receive_DMA(&huart2,receiveData,2);
}
注意在main函数里开始的串口接收函数也要将最后的IT改为DMA 
RxCpltCallback函数还是由中断触发,只不过不是串口接收中断,而是DMA的接收完成中断。
四.DMA+空闲不定长函数(用来接收不定长数据)
HAL_UARTEx_ReceiveToIdle_DMA(huart, pData, Size);
huart:发送的串口指针,这里使用&huart2
pData:发送数据的指针,这里使用receiveData
Size:一次能接收的最大数据长度(一般为接收数组的长度),这里使用sizeof(receiveData)
回调函数:void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
此回调函数靠串口空闲(Idle)中断,触发条件与接收的字节数无关,只有当RX引脚上无后续数据进入,即串口接收从忙碌转为空闲才会触发。当空闲中断触发时,一帧数据包接收完成,然后再对数据进行分析处理即可。
(1)接收数组大小改为50

(2)将之前的普通定长DMA空闲函数更换为DMA空闲不定长函数:

(3)找回调函数:现在我们不使用之前的RXCpltCallback函数

在打开的文件中找到void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)函数

放进main.c文件中,并写下程序:
注意:使用中断函数一个关键点是确认谁触发了回调函数
cpp
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
//判断进入此回调函数是否是huart2
if(huart == &huart2)
{
HAL_UART_Transmit_DMA(&huart2, receiveData, Size);
//这里函数第三个参数填写RxEventCallback函数的Size入参,来发送与接收相同的字节数
HAL_UARTEx_ReceiveToIdle_DMA(&huart2,receiveData,sizeof(receiveData));
}
}
(4)关闭DMA传输过半中断:
使用DMA模式,除了串口空闲中断,DMA传输过半中断也会触发RxEventCallback回调函数。因此我们需要关闭DMA传输过半中断
__HAL_DMA_DISABLE_IT()
第一个参数为DMA通道的指针地址,也就是USART2的Rx DMA通道
第二个参数为DMA传输过半中断,即DMA_IT_HT(Half Transfer 过半传输)
因此该函数为**__HAL_DMA_DISABLE_IT(&hdma_usart2_rx, DMA_IT_HT)**。当不需要DMA传输过半中断时,将该函数放在 HAL_UARTEx_ReceiveToIdle_DMA后面。
更改后代码如下:


cpp
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include<string.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart2_rx;
DMA_HandleTypeDef hdma_usart2_tx;
/* USER CODE BEGIN PV */
uint8_t receiveData[50];
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit_DMA(&huart2,receiveData,2);
//HAL_MAX_DELAY:不设超时时间,直至结束
GPIO_PinState state = GPIO_PIN_SET;//初始化默认高电平
//判断单片机接收的是0还是1,设置高低电平
if(receiveData[1] == '0')
{
state = GPIO_PIN_RESET;
}
//判断单片机接收的是R还是G还是B,输出对应的颜色
if(receiveData[0] == 'R'){
HAL_GPIO_WritePin(LED_RED_GPIO_Port,LED_RED_Pin,state);
}else if(receiveData[0] == 'G')
{
HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin,state);
}else if(receiveData[0] == 'B')
{
HAL_GPIO_WritePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin,state);
}
HAL_UART_Receive_DMA(&huart2,receiveData,2);
}
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
//判断进入此回调函数是否是huart2
if(huart == &huart2)
{
HAL_UART_Transmit_DMA(&huart2, receiveData, Size);
//这里函数第三个参数填写RxEventCallback函数的Size入参,来发送与接收相同的字节数
HAL_UARTEx_ReceiveToIdle_DMA(&huart2,receiveData,sizeof(receiveData));
//关闭DMA传输过半终端:
__HAL_DMA_DISABLE_IT(&hdma_usart2_rx,DMA_IT_HT);//DMA通道的指针地址;关闭的中断:DMA传输过半中断
}
}
//void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
//{
// if(huart == &huart2)
// {
// HAL_UART_Transmit_DMA(&huart2,receiveData,Size);
//
// HAL_UARTEx_ReceiveToIdle_DMA(&huart2,receiveData,sizeof(receiveData));
// __HAL_DMA_DISABLE_IT(&hdma_usart2_rx,DMA_IT_HT);
// }
//}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, receiveData, sizeof(receiveData));
__HAL_DMA_DISABLE_IT(&hdma_usart2_rx,DMA_IT_HT);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief USART2 Initialization Function
* @param None
* @retval None
*/
static void MX_USART2_UART_Init(void)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART2_Init 2 */
/* USER CODE END USART2_Init 2 */
}
/**
* Enable DMA controller clock
*/
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel6_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);
/* DMA1_Channel7_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, LED_BLUE_Pin|LED_GREEN_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET);
/*Configure GPIO pins : LED_BLUE_Pin LED_GREEN_Pin */
GPIO_InitStruct.Pin = LED_BLUE_Pin|LED_GREEN_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : LED_RED_Pin */
GPIO_InitStruct.Pin = LED_RED_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED_RED_GPIO_Port, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */