T-Display-S3-long FreeRTOS剩余堆内存充足但分配失败排查
问题
正在为T-Display-S3-long开发程序,已下载AXS15231B.cpp库并完成静态图片显示测试。目标是通过FreeRTOS任务处理UI渲染,当前测试程序仅启动任务并尝试在屏幕绘制四个矩形。配置为使用DMA绘制时,AXS15231B库会尝试分配内存存储矩形像素,再通过DMA->QSPI推送至显示屏,但该内存分配失败。由于启用了内存与堆损坏检测,程序直接重启。已增大绘图任务及app_main函数的栈大小,并添加日志查看分配前的可用堆内存,结果显示内存充足。
相关代码
app_main.cpp
static uint16_t col_back = 0x07ba; static uint16_t col1 = 0x07ba; static uint16_t col2 = 0xfc60; static uint16_t col3 = 0xd800; static uint16_t col4 = 0x16e0; void test_lcd(void *arg) { xSemaphoreTake(sync_lcd_task, portMAX_DELAY); pinMode(TFT_BL, OUTPUT); digitalWrite(TFT_BL, HIGH); ESP_LOGI(TFT_TAG, "Display Initialised"); axs15231_init(); lcd_setRotation(1); while(1) { digitalWrite(TFT_BL, HIGH); lcd_fill(10 , 20, 150, 160, col1); lcd_fill(170, 20, 310, 160, col2); lcd_fill(330, 20, 470, 160, col3); lcd_fill(490, 20, 630, 160, col4); vTaskDelay(10000); } } void app_main(void) { ESP_LOGI("main", "Free Heap size %d", xPortGetFreeHeapSize()); //Allow other core to finish initialization vTaskDelay(pdMS_TO_TICKS(100)); //Create semaphores to synchronize sync_wifi_prov_task = xSemaphoreCreateBinary(); sync_stats_task = xSemaphoreCreateBinary(); sync_lcd_task = xSemaphoreCreateBinary(); xTaskCreatePinnedToCore(provision_wifi, WIFI_PROV_TASK_NAME, 4096, NULL, WIFI_PROV_PRIO, NULL, 0); ESP_LOGI("main", "Free Heap size %d", xPortGetFreeHeapSize()); xTaskCreatePinnedToCore(stats_task, STATS_TASK_NAME, 4096, NULL, STATS_TASK_PRIO, NULL, tskNO_AFFINITY); ESP_LOGI("main", "Free Heap size %d", xPortGetFreeHeapSize()); xTaskCreatePinnedToCore(test_lcd, "lcd_test", 220000, NULL, 4, NULL, 1); ESP_LOGI("main", "Free Heap size %d", xPortGetFreeHeapSize()); xSemaphoreGive(sync_lcd_task); xSemaphoreGive(sync_wifi_prov_task); xSemaphoreGive(sync_stats_task); }
修改后的AXS15231B.cpp相关函数
static const char *TFT_TAG = "display"; void lcd_fill(uint16_t xsta, uint16_t ysta, uint16_t xend, uint16_t yend, uint16_t color) { uint16_t w = xend - xsta; uint16_t h = yend - ysta; uint32_t size = w * h * 2; ESP_LOGI(TFT_TAG, "Free Heap size %d", xPortGetFreeHeapSize()); ESP_LOGI(TFT_TAG, "Trying to allocate %lu bytes", size); uint16_t *color_p = (uint16_t *)heap_caps_malloc(size, MALLOC_CAP_INTERNAL); if (color_p == NULL) { ESP_LOGE(TFT_TAG, "Failed to allocate memory (%lu bytes)", size); return; } int i = 0; for(i = 0; i < w * h ; i+=1) { color_p[i] = color; } lcd_PushColors(xsta, ysta, w, h, color_p); free(color_p); }
运行日志
任务分配阶段(app_main): I (454) main: Free Heap size 296752 I (558) main: Free Heap size 291928 I (558) main: Free Heap size 287440 I (565) main: Free Heap size 67048 ... 内存分配阶段(AXS15231B): ... I (1377) display: Free Heap size 44024 I (1378) display: Trying to allocate 39200 bytes Mem alloc fail. size 0x00009920 caps 0x00000800 Backtrace: 0x40375c72:0x3fce7c40 0x4037e47d:0x3fce7c60 0x403765dd:0x3fce7c80 0x40376632:0x3fce7ce0 0x40376d15:0x3fce7d00 0x40376e3d:0x3fce7d50 0x4200c238:0x3fce7d70 0x4200ba76:0x3fce7da0 0x403811de:0x3fce7dc0 0x40375c72: panic_abort at C:/Users/~/esp/esp-idf/components/esp_system/panic.c:452 0x4037e47d: esp_system_abort at C:/Users/~/esp/esp-idf/components/esp_system/port/esp_system_chip.c:84 0x403765dd: heap_caps_alloc_failed at C:/Users/~/esp/esp-idf/components/heap/heap_caps.c:96 0x40376632: heap_caps_malloc at C:/Users/~/esp/esp-idf/components/heap/heap_caps.c:201 0x40376d15: trace_malloc at C:/Users/~/esp/esp-idf/components/heap/include/heap_trace.inc:95 0x40376e3d: __wrap_heap_caps_malloc at C:/Users/~/esp/esp-idf/components/heap/include/heap_trace.inc:184 0x4200c238: lcd_fill(unsigned short, unsigned short, unsigned short, unsigned short, unsigned short) at ###/main/AXS15231B.cpp:284 0x4200ba76: test_lcd(void*) at ###/main/app_main.cpp:48 (discriminator 1) 0x403811de: vPortTaskWrapper at C:/Users/~/esp/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:162
日志显示可用堆内存44024字节,需分配39200字节,但分配失败。尝试增大任务栈大小无效,推测需静态分配帧缓冲区,但不理解动态分配失败的原因。
解决方案
分配失败的核心原因
- 内存碎片问题:
xPortGetFreeHeapSize()返回的是总可用堆内存,但这些内存可能被分割成多个不连续的小块,没有一块能满足39200字节的连续分配需求。ESP32-S3的内部RAM在创建任务、加载驱动后容易产生碎片。 - 内存类型限制:
heap_caps_malloc指定了MALLOC_CAP_INTERNAL,要求从内部RAM分配,但内部RAM可能已经被WiFi、FreeRTOS任务栈等组件占用了大部分连续空间,剩余的连续块不足以容纳39200字节。
具体解决办法
方法1:调整内存分配的能力标志
放宽内存类型限制,允许系统自动选择合适的内存区域,或者改用外部SPIRAM(如果硬件支持):
// 方案A:允许从任意可用内存分配 uint16_t *color_p = (uint16_t *)heap_caps_malloc(size, MALLOC_CAP_DEFAULT); // 方案B:如果硬件带SPIRAM,优先使用(需确认DMA支持SPIRAM访问) uint16_t *color_p = (uint16_t *)heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
方法2:静态预分配缓冲区
在全局或静态区域预分配足够大的缓冲区,彻底避免动态分配的碎片问题:
// 在文件全局范围定义静态缓冲区(按最大需求设置,比如40000字节,按DMA要求4字节对齐) static uint8_t lcd_buffer[40000] __attribute__((aligned(4))); void lcd_fill(uint16_t xsta, uint16_t ysta, uint16_t xend, uint16_t yend, uint16_t color) { uint16_t w = xend - xsta; uint16_t h = yend - ysta; uint32_t size = w * h * 2; if (size > sizeof(lcd_buffer)) { ESP_LOGE(TFT_TAG, "Buffer size insufficient"); return; } uint16_t *color_p = (uint16_t *)lcd_buffer; for(int i = 0; i < w * h ; i++) { color_p[i] = color; } lcd_PushColors(xsta, ysta, w, h, color_p); // 静态缓冲区无需free }
方法3:优化内存分配策略
- 复用缓冲区:多次调用
lcd_fill时复用同一个缓冲区,避免重复分配释放。 - 启用内存碎片整理:在ESP-IDF的menuconfig中开启
CONFIG_HEAP_TREE_DEBUG或CONFIG_HEAP_FILL_DEBUG,辅助分析碎片情况,也可尝试开启内存压缩功能。 - 调整任务栈:检查其他任务的栈大小是否过大,释放不必要的内部RAM空间。
方法4:修改库的绘制逻辑
利用AXS15231B的硬件填充功能,跳过MCU端的像素缓冲区生成:查看LCD控制器 datasheet,修改lcd_fill函数直接发送填充命令和颜色值,让LCD硬件自动完成区域填充,无需分配内存存储像素数据。
内容的提问来源于stack exchange,提问作者chrisake
相关产品推荐
相关产品推荐

