You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于CMSIS V2队列的STM32 DMA首次中断后无法重启问题

问题分析与修复方案

核心问题拆解

  1. ISR中使用非线程安全内存分配:标准malloc不具备FreeRTOS环境下的线程安全性,在中断回调中调用会引发内存管理异常,间接导致DMA重启失败。
  2. ISR内阻塞式串口发送:HAL_UART_Transmit是阻塞操作,会大幅延长ISR执行时间,破坏系统实时性,甚至引发系统崩溃。
  3. DMA重启前未清理硬件状态:首次DMA完成后,未清除UART/DMA的中断标志或检查硬件就绪状态,导致重启失败。
  4. 任务中内存释放逻辑错误:vPortFree(receivedData)放在队列接收判断外部,极端情况下会释放野指针,引发内存错误。

修复后代码实现

UART中断回调函数修改

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if(huart->Instance == UART5)
    {
        remote_cmd_t *remote_cmd = (remote_cmd_t *)pvPortMalloc(sizeof(remote_cmd_t));
        if(remote_cmd != NULL)
        {
            // 拷贝实际接收的数据长度,而非固定值
            remote_cmd->Count = 1000;
            remote_cmd->Size = Size;
            memcpy(remote_cmd->Payload, RemoteRxBuf, Size);

            // 发送数据到队列,失败则释放内存
            BaseType_t xHigherPriorityTaskWoken = pdFALSE;
            if(xQueueSendFromISR(remoteQueueHandle, &remote_cmd, &xHigherPriorityTaskWoken) != pdPASS)
            {
                vPortFree(remote_cmd);
            }
            portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
        }

        // 重启DMA接收前清理硬件状态
        __HAL_UART_CLEAR_FLAG(&huart5, UART_FLAG_IDLE);
        __HAL_DMA_CLEAR_FLAG(&hdma_uart5_rx, DMA_FLAG_TCIF5_4); // 根据实际DMA通道调整标志

        // 若UART忙,先终止当前接收再重启
        if(huart->gState != HAL_UART_STATE_READY)
        {
            HAL_UART_AbortReceive_DMA(&huart5);
        }

        // 重启Idle模式DMA接收,禁用半传输中断(建议初始化时配置,此处保留兼容)
        HAL_UARTEx_ReceiveToIdle_DMA(&huart5, RemoteRxBuf, RemoteRxBuf_SIZE);
        __HAL_DMA_DISABLE_IT(&hdma_uart5_rx, DMA_IT_HT);
    }
}

队列处理任务与新增串口发送任务

// 全局发送队列,需提前初始化
QueueHandle_t txQueueHandle;

void StartRemoteCmdTask(void *argument)
{
    char *buffer;
    remote_cmd_t *receivedData;

    while(1)
    {
        if(xQueueReceive(remoteQueueHandle, &receivedData, portMAX_DELAY) == pdPASS)
        {
            // 发送提示消息到发送队列
            char *tip_msg = pvPortMalloc(strlen("Received data from ISR\r\n") + 1);
            if(tip_msg != NULL)
            {
                strcpy(tip_msg, "Received data from ISR\r\n");
                xQueueSend(txQueueHandle, &tip_msg, portMAX_DELAY);
            }

            // 格式化接收数据并发送到队列
            buffer = pvPortMalloc(3 * receivedData->Size + receivedData->Size + 3);
            if(buffer != NULL)
            {
                char *ptr = buffer;
                for (uint8_t i = 0; i < receivedData->Size; i++)
                {
                    ptr += sprintf(ptr, "%02X ", receivedData->Payload[i]);
                }
                strcat(ptr, "\r\n");
                xQueueSend(txQueueHandle, &buffer, portMAX_DELAY);
            }

            // 释放接收的结构体内存
            vPortFree(receivedData);
        }

        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

// 串口发送专用任务,处理所有阻塞式发送操作
void StartUartTxTask(void *argument)
{
    char *tx_data;
    while(1)
    {
        if(xQueueReceive(txQueueHandle, &tx_data, portMAX_DELAY) == pdPASS)
        {
            HAL_UART_Transmit(&hlpuart1, (uint8_t *)tx_data, strlen(tx_data), HAL_MAX_DELAY);
            vPortFree(tx_data);
        }
    }
}

初始化补充

  1. 创建发送队列:
txQueueHandle = xQueueCreate(16, sizeof(char*));
if(txQueueHandle == NULL)
{
    // 队列创建失败,添加错误处理逻辑
}
  1. 创建串口发送任务:
osThreadAttr_t uartTxTaskAttrs = {
    .name = "UartTxTask",
    .stack_size = 128 * 4,
    .priority = (osPriority_t) osPriorityNormal,
};
osThreadNew(StartUartTxTask, NULL, &uartTxTaskAttrs);

关键注意事项

  • ISR极简原则:中断回调只做数据拷贝、队列发送、DMA重启等最短操作,禁止任何阻塞或耗时逻辑。
  • 内存安全:全程使用FreeRTOS提供的pvPortMalloc/vPortFree替代标准库内存函数,分配后必须检查返回值。
  • 硬件状态清理:DMA重启前必须清除UART和DMA的中断标志,确保硬件处于就绪状态,避免残留状态导致启动失败。

内容的提问来源于stack exchange,提问作者fossil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 07:08:16