硬汉嵌入式论坛

 找回密码
 立即注册
查看: 129|回复: 6
收起左侧

[UART] 使用STM32H723实现串口DMA+空闲中断,极限测试会串口卡死

[复制链接]

9

主题

59

回帖

86

积分

初级会员

积分
86
发表于 昨天 11:08 | 显示全部楼层 |阅读模式
目前使用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();
    }
}

回复

使用道具 举报

0

主题

45

回帖

45

积分

新手上路

积分
45
发表于 昨天 11:34 | 显示全部楼层
把空闲中断换掉换成超时中断试一试
回复

使用道具 举报

4

主题

131

回帖

143

积分

初级会员

积分
143
发表于 昨天 11:53 | 显示全部楼层
115200发送70个字节理论上就需要超过6ms,10ms还需要应答。电脑10ms串口是分辨不出中断的。建议使用示波器或专用设备测试
回复

使用道具 举报

28

主题

299

回帖

383

积分

高级会员

积分
383
QQ
发表于 昨天 14:17 | 显示全部楼层
如何判断是串口卡死了?

我看到有两个可优化的点
1. 错误回调只是重启了接收 DMA 通道,没有重启 UART 外设。可以置标志,在主程序里做外设完全重新初始化。
2. 用了乒乓 buffer 没必要用循环模式,增加接收 DMA 通道结束的处理。如果通道结束说明原 buufer 收满了,切 buffer 重开 DMA 接收,原 buffer 做处理。
回复

使用道具 举报

28

主题

299

回帖

383

积分

高级会员

积分
383
QQ
发表于 昨天 14:17 | 显示全部楼层
如何判断是串口卡死了?

我看到有两个可优化的点
1. 错误回调只是重启了接收 DMA 通道,没有重启 UART 外设。可以置标志,在主程序里做外设完全重新初始化。
2. 用了乒乓 buffer 没必要用循环模式,增加接收 DMA 通道结束的处理。如果通道结束说明原 buufer 收满了,切 buffer 重开 DMA 接收,原 buffer 做处理。
回复

使用道具 举报

7

主题

49

回帖

70

积分

初级会员

积分
70
发表于 昨天 14:22 | 显示全部楼层
HAL库会自动打开ORE和FE这两个错误标志,你的错误之后的回调是有问题的,仅仅只是重启DMA接收。事实上一直在执行ORE和FE的中断。
回复

使用道具 举报

7

主题

404

回帖

425

积分

高级会员

积分
425
发表于 昨天 17:28 | 显示全部楼层
什么叫卡死?不懂的人可以说这两个字,专业搞单片机的人不要说这两个字。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2026-3-27 05:22 , Processed in 0.315447 second(s), 24 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表