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); } } }
问题根源分析
- 全局指针复用导致内容覆盖:
data6是全局唯一的指针,每次中断收到换行后,发送的都是同一个指针地址到队列。后续中断接收新字符时,会直接覆盖data6指向的内存内容,当任务最终读取时,原数据已经被新数据覆盖甚至清空。 - 非法内存释放:任务中调用
vPortFree(msg)释放的是全局分配的data6内存,释放后该内存被FreeRTOS堆管理器回收,后续中断再往data6写入属于非法访问,必然导致内容异常。 - 缓冲区溢出风险:
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
相关产品推荐
相关产品推荐

