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

FreeRTOS队列无法正确获取数据问题求助及代码排查

FreeRTOS队列传递指针后内容为空的问题排查与修复

问题描述

使用FreeRTOS队列传递UART接收的字符串指针,发现从队列取出指针后,指向的内容为空。相关代码如下:

任务与队列初始化

status = xTaskCreate(print_task, "print_task", 250, NULL, 2, &handle_print_task);
configASSERT(status == pdPASS);
queue_print = xQueueCreate(20, sizeof(uint8_t *));
data6 = pvPortMalloc(sizeof(uint8_t) * 50);

其中uint8_t *data6; // global为全局堆内存指针。

UART中断回调(接收数据并发送到队列)

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART6)
  {
    if (count_size6 < 100)
    {
      data6[count_size6] = data_byte6;
      count_size6++;
      if (data_byte6 == '\n')
      {
        data6[count_size6] = '\0';
        xQueueSendFromISR(queue_print, &data6, NULL);
        count_size6 = 0;
        // memset(data6,0,sizeof(&data6));
      }
    }
    HAL_UART_Receive_IT(&huart6, &data_byte6, 1);
  }
}

任务接收队列数据

//this is the task handler
void print_task(void *param)
{
    UBaseType_t free_spaces;
    uint8_t arr[10];
    uint8_t *msg;
    while (1)
    {
        if (xQueueReceive(queue_print, &msg, portMAX_DELAY) == pdPASS)
        {
            free_spaces = uxQueueSpacesAvailable(queue_print);
            sprintf((char *)arr, "Left: %u\n", (unsigned int)free_spaces);
            HAL_UART_Transmit(&huart2, arr, 10, 1000);
            HAL_UART_Transmit(&huart2, msg, 50, HAL_MAX_DELAY);
            vPortFree(msg);
        }
    }
}

问题根源分析

  1. 全局指针复用导致内容覆盖:data6是全局唯一的指针,每次中断收到换行后,发送的都是同一个指针地址到队列。后续中断接收新字符时,会直接覆盖data6指向的内存内容,当任务最终读取时,原数据已经被新数据覆盖甚至清空。
  2. 非法内存释放:任务中调用vPortFree(msg)释放的是全局分配的data6内存,释放后该内存被FreeRTOS堆管理器回收,后续中断再往data6写入属于非法访问,必然导致内容异常。
  3. 缓冲区溢出风险:count_size6的判断上限是100,但data6只分配了50字节的内存,写入超过50字节会直接溢出,破坏堆结构或其他内存区域。

修复方案

核心思路:为每一条完整的UART消息分配独立内存,避免全局缓冲区的复用冲突,保证每条消息的生命周期独立。

1. 初始化代码调整

确保全局变量正确初始化,同时保留全局缓冲区作为临时接收用:

#include <string.h> // 引入字符串操作函数

uint8_t *data6; // 全局临时接收缓冲区
uint32_t count_size6 = 0; // 必须初始化,避免初始值未知
QueueHandle_t queue_print;
TaskHandle_t handle_print_task;

// 初始化部分
void init_task_queue(void)
{
    BaseType_t status = xTaskCreate(print_task, "print_task", 250, NULL, 2, &handle_print_task);
    configASSERT(status == pdPASS);
    
    queue_print = xQueueCreate(20, sizeof(uint8_t *));
    configASSERT(queue_print != NULL);
    
    data6 = pvPortMalloc(sizeof(uint8_t) * 50); // 临时缓冲区,大小匹配最大预期消息长度
    configASSERT(data6 != NULL);
}

2. 中断回调修改(为每条消息分配独立内存)

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART6)
  {
    // 限制缓冲区写入不超过分配的50字节,留1位给字符串结束符
    if (count_size6 < 49)
    {
      data6[count_size6] = data_byte6;
      count_size6++;
      
      if (data_byte6 == '\n')
      {
        data6[count_size6] = '\0'; // 补全字符串结束符
        
        // 为当前消息分配独立内存,按实际长度分配节省空间
        uint8_t *msg_buffer = pvPortMalloc(strlen((char*)data6) + 1);
        if (msg_buffer != NULL)
        {
          strcpy((char*)msg_buffer, (char*)data6); // 复制临时缓冲区的内容到新内存
          
          BaseType_t task_woken = pdFALSE;
          xQueueSendFromISR(queue_print, &msg_buffer, &task_woken);
          portYIELD_FROM_ISR(task_woken); // 若唤醒更高优先级任务,立即切换
        }
        
        count_size6 = 0; // 重置计数器,准备接收下一条消息
      }
    }
    else
    {
      // 缓冲区满,强制重置避免溢出
      count_size6 = 0;
    }
    
    // 重新开启UART中断接收
    HAL_UART_Receive_IT(&huart6, &data_byte6, 1);
  }
}

3. 任务代码调整(正确处理消息内存)

void print_task(void *param)
{
    UBaseType_t free_spaces;
    uint8_t arr[20]; // 扩大缓冲区,避免格式化字符串溢出
    uint8_t *msg;
    
    while (1)
    {
        if (xQueueReceive(queue_print, &msg, portMAX_DELAY) == pdPASS)
        {
            free_spaces = uxQueueSpacesAvailable(queue_print);
            // 格式化队列剩余空间信息,用strlen获取实际长度发送
            sprintf((char *)arr, "Left: %u\n", (unsigned int)free_spaces);
            HAL_UART_Transmit(&huart2, arr, strlen((char*)arr), 1000);
            
            // 发送消息实际内容,按字符串长度发送而非固定50字节
            HAL_UART_Transmit(&huart2, msg, strlen((char*)msg), HAL_MAX_DELAY);
            
            vPortFree(msg); // 释放当前消息的独立内存,无全局冲突
        }
    }
}

额外注意事项

  • 确保FreeRTOS堆大小足够,避免pvPortMalloc返回NULL,可通过configTOTAL_HEAP_SIZE调整。
  • 若消息长度固定,可直接分配固定大小内存(如50字节),省去strlen计算,提升中断处理效率。
  • 全局临时缓冲区data6不需要在任务中释放,它会持续被复用接收新字符。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 00:47:06