基于CMSIS V2队列的STM32 DMA首次中断后无法重启问题
问题分析与修复方案
核心问题拆解
- ISR中使用非线程安全内存分配:标准
malloc不具备FreeRTOS环境下的线程安全性,在中断回调中调用会引发内存管理异常,间接导致DMA重启失败。 - ISR内阻塞式串口发送:
HAL_UART_Transmit是阻塞操作,会大幅延长ISR执行时间,破坏系统实时性,甚至引发系统崩溃。 - DMA重启前未清理硬件状态:首次DMA完成后,未清除UART/DMA的中断标志或检查硬件就绪状态,导致重启失败。
- 任务中内存释放逻辑错误:
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); } } }
初始化补充
- 创建发送队列:
txQueueHandle = xQueueCreate(16, sizeof(char*)); if(txQueueHandle == NULL) { // 队列创建失败,添加错误处理逻辑 }
- 创建串口发送任务:
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
相关产品推荐
相关产品推荐

