目前使用STM32H723这款芯片实现串口功能,原来裸机环境下采用的是串口+队列方式,目前需要将代码移植到FREERTOS中,为了防止任务被频繁中断,所以想采用DMA方式降低CPU负载。采用的串口1,串口配置波特率115200-8-N-1,根据理论计算115200波特率情况下,10ms可传输115.2字节数据,但是实际使用过程中,通过串口调试助手固定70字节发送频率10ms。串口过一会儿卡死了,但是整个程序还是正常运行。目前测试就是串口调试助手发送数据,单片机接收到立马返回。请问有专门做过类似功能的朋友可以看看串口DMA+空闲中断需要如何设计。提供一些指导。
1、串口配置如下:
[C] 纯文本查看 复制代码 /* USER CODE BEGIN Header */
/**
******************************************************************************
* @file usart.c
* @brief This file provides code for the configuration
* of the USART instances.
******************************************************************************
* @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 "usart.h"
/* USER CODE BEGIN 0 */
#include <string.h>
#include <stdint.h>
#include "FreeRTOS.h"
#include "task.h"
// ==================== 硬件句柄 ====================
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;
// ==================== 接收 Ping-Pong 双缓冲 ====================
#define RX_BUF_SIZE 256
static uint8_t rx_buf[2][RX_BUF_SIZE]; // 双缓冲
static volatile uint8_t rx_writing = 0; // DMA 正在写的缓冲 index
static volatile uint8_t rx_reading = 1; // 主循环正在读的缓冲 index
static volatile uint16_t rx_len = 0; // 当前帧长度
static volatile uint8_t rx_data_ready = 0; // 数据就绪标志
// ==================== 发送队列(环形缓冲)====================
#define TX_QUEUE_SIZE 8
#define TX_BUF_SIZE 256
typedef struct {
uint8_t data[TX_BUF_SIZE];
uint16_t len;
} TXItem;
static TXItem tx_queue[TX_QUEUE_SIZE];
static volatile uint8_t tx_head = 0; // 写指针(放数据)
static volatile uint8_t tx_tail = 0; // 读指针(DMA 发送)
static volatile uint8_t tx_busy = 0;
// ==================== 状态统计(调试用)====================
volatile uint32_t uart_rx_cnt = 0; // 累计收包数
volatile uint32_t uart_tx_cnt = 0; // 累计发包数
volatile uint32_t uart_err = 0; // 错误计数
volatile uint32_t uart_last_tick = 0;
// ==================== 接收数据处理回调 ====================
static void (*uart1_rx_callback)(uint8_t *data, uint16_t len) = NULL;
/* USER CODE END 0 */
/* USART1 init function */
void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
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;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
// ---- 启动 DMA 接收(循环模式,双缓冲)----
HAL_UART_Receive_DMA(&huart1, rx_buf[rx_writing], RX_BUF_SIZE);
// ---- 使能空闲中断 ----
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
// 清除空闲标志
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
/* USER CODE END USART1_Init 2 */
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/** Initializes the peripherals clock
*/
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART1;
PeriphClkInitStruct.Usart16ClockSelection = RCC_USART16910CLKSOURCE_D2PCLK2;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> 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 = DMA1_Stream0;
hdma_usart1_rx.Init.Request = DMA_REQUEST_USART1_RX;
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_CIRCULAR; // 循环模式,持续接收
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH;
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
/* USART1_TX Init */
hdma_usart1_tx.Instance = DMA1_Stream1;
hdma_usart1_tx.Init.Request = DMA_REQUEST_USART1_TX;
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_HIGH;
hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* DMA interrupt Init */
HAL_NVIC_SetPriority(DMA1_Stream0_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn);
HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
}
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspDeInit 0 */
/* USER CODE END USART1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_USART1_CLK_DISABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
/* USART1 DMA DeInit */
HAL_DMA_DeInit(uartHandle->hdmarx);
HAL_DMA_DeInit(uartHandle->hdmatx);
/* USART1 interrupt Deinit */
HAL_NVIC_DisableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspDeInit 1 */
/* USER CODE END USART1_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/**
* @brief UART1 发送数据(DMA方式,支持队列)
* @param data: 数据指针
* @param len: 数据长度
* @retval 0: 成功, 1: 队列满
*/
uint8_t UART1_SendData(uint8_t *data, uint16_t len)
{
if (len == 0 || len > TX_BUF_SIZE) return 1;
uint8_t next_head = (tx_head + 1) % TX_QUEUE_SIZE;
if (next_head == tx_tail) {
// 队列满
return 1;
}
// 复制数据到发送队列
memcpy(tx_queue[tx_head].data, data, len);
tx_queue[tx_head].len = len;
tx_head = next_head;
// 如果DMA空闲,立即启动发送
if (!tx_busy) {
tx_busy = 1;
HAL_UART_Transmit_DMA(&huart1, tx_queue[tx_tail].data, tx_queue[tx_tail].len);
}
return 0;
}
/**
* @brief UART1 发送数据(阻塞方式)
* @param data: 数据指针
* @param len: 数据长度
* @param timeout: 超时时间
* @retval HAL status
*/
HAL_StatusTypeDef UART1_SendData_Blocking(uint8_t *data, uint16_t len, uint32_t timeout)
{
return HAL_UART_Transmit(&huart1, data, len, timeout);
}
/**
* @brief 获取接收到的数据
* @param buffer: 数据缓冲区
* @param max_len: 最大读取长度
* @retval 实际读取的字节数
*/
uint16_t UART1_GetReceivedData(uint8_t *buffer, uint16_t max_len)
{
if (!rx_data_ready || rx_len == 0) return 0;
uint16_t copy_len = (rx_len > max_len) ? max_len : rx_len;
memcpy(buffer, rx_buf[rx_reading], copy_len);
rx_data_ready = 0;
return copy_len;
}
/**
* @brief 检查是否有新数据
* @retval 1: 有新数据, 0: 无新数据
*/
uint8_t UART1_DataAvailable(void)
{
return rx_data_ready;
}
/**
* @brief 获取接收缓冲区指针(直接访问,零拷贝)
* @retval 接收缓冲区指针
*/
uint8_t* UART1_GetRxBuffer(void)
{
if (rx_data_ready) {
return rx_buf[rx_reading];
}
return NULL;
}
/**
* @brief 获取接收数据长度
* @retval 数据长度
*/
uint16_t UART1_GetRxLen(void)
{
if (rx_data_ready) {
return rx_len;
}
return 0;
}
/**
* @brief 清除接收标志
*/
void UART1_ClearRxFlag(void)
{
rx_data_ready = 0;
}
/**
* @brief 注册接收回调函数
* @param callback: 回调函数指针
*/
void UART1_RegisterRxCallback(void (*callback)(uint8_t *data, uint16_t len))
{
uart1_rx_callback = callback;
}
/**
* @brief 获取接收统计
* @retval 接收包数
*/
uint32_t UART1_GetRxCount(void)
{
return uart_rx_cnt;
}
/**
* @brief 获取发送统计
* @retval 发送包数
*/
uint32_t UART1_GetTxCount(void)
{
return uart_tx_cnt;
}
/**
* @brief DMA发送完成回调
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
uart_tx_cnt++;
tx_tail = (tx_tail + 1) % TX_QUEUE_SIZE;
// 检查是否还有数据要发送
if (tx_tail != tx_head) {
HAL_UART_Transmit_DMA(&huart1, tx_queue[tx_tail].data, tx_queue[tx_tail].len);
} else {
tx_busy = 0;
}
}
}
/**
* @brief 处理空闲中断(应在USART1_IRQHandler中调用)
* @note 当接收到数据后,总线空闲时触发
*/
void UART1_HandleIdleIRQ(void)
{
// 检查空闲标志
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
// DMA 剩余计数 → 已接收字节数
uint16_t remaining = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
uint16_t received = RX_BUF_SIZE - remaining;
if (received > 0 && received <= RX_BUF_SIZE)
{
// 停止当前DMA传输
HAL_UART_DMAStop(&huart1);
// 保存接收信息
rx_len = received;
rx_reading = rx_writing;
rx_writing = 1 - rx_writing;
rx_data_ready = 1;
uart_rx_cnt++;
uart_last_tick = HAL_GetTick();
// 如果有回调函数,直接调用
if (uart1_rx_callback != NULL) {
uart1_rx_callback(rx_buf[rx_reading], rx_len);
rx_data_ready = 0; // 回调中已处理,清除标志
}
// 重启DMA接收
HAL_UART_Receive_DMA(&huart1, rx_buf[rx_writing], RX_BUF_SIZE);
}
}
}
/**
* @brief UART错误回调
*/
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
uart_err++;
// 错误恢复:重新启动DMA接收
HAL_UART_DMAStop(&huart1);
HAL_UART_Receive_DMA(&huart1, rx_buf[rx_writing], RX_BUF_SIZE);
}
}
/* USER CODE END 1 */
2、测试demo
[C] 纯文本查看 复制代码 void UART1_Demo_Process(void)
{
static uint32_t last_tx_tick = 0;
static uint32_t last_stat_tick = 0;
uint32_t current_tick = HAL_GetTick();
// 检查是否有新数据到达
if (UART1_DataAvailable()) {
uint16_t rx_len = UART1_GetRxLen();
uint8_t *rx_data = UART1_GetRxBuffer();
if (rx_data != NULL && rx_len > 0) {
test_rx_count++;
// 检查是否是命令(单字节)
if (rx_len == 1) {
ProcessCommand(rx_data[0]);
}
// 根据模式处理数据
switch (demo_mode) {
case UART_DEMO_ECHO_MODE:
// 回传模式:立即回传收到的数据
UART1_SendData(rx_data, rx_len);
test_tx_count++;
break;
case UART_DEMO_LOOPBACK_TEST:
// 环回测试:验证数据一致性
if (rx_len == TEST_PACKET_SIZE) {
if (VerifyEchoData(test_tx_buffer, rx_data, rx_len) != 0) {
test_error_count++;
}
}
break;
case UART_DEMO_PERIODIC_TX:
// 周期发送模式:仅统计接收
// 上位机应该回传我们发送的数据
break;
default:
break;
}
UART1_ClearRxFlag();
}
}
// 周期发送模式:每10ms发送一包数据
if (demo_mode == UART_DEMO_PERIODIC_TX) {
if (current_tick - last_tx_tick >= TEST_INTERVAL_MS) {
last_tx_tick = current_tick;
// 发送测试数据包
if (UART1_SendData(test_tx_buffer, TEST_PACKET_SIZE) == 0) {
test_tx_count++;
}
}
}
// 定期打印统计信息
if (current_tick - last_stat_tick >= TEST_STAT_INTERVAL_MS) {
last_stat_tick = current_tick;
// PrintStatistics();
}
}
|