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

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字节,但分配失败。尝试增大任务栈大小无效,推测需静态分配帧缓冲区,但不理解动态分配失败的原因。


解决方案

分配失败的核心原因

  1. 内存碎片问题:xPortGetFreeHeapSize()返回的是总可用堆内存,但这些内存可能被分割成多个不连续的小块,没有一块能满足39200字节的连续分配需求。ESP32-S3的内部RAM在创建任务、加载驱动后容易产生碎片。
  2. 内存类型限制: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 14:05:54