SPI/QSPI 接口屏幕相关问题总结

[English]

本文档主要总结了 SPI/QSPI 接口屏幕开发过程中常见的问题,并给出了相应的解决方法。

相关文档

SPI/QSPI 接口屏幕开发详解

相关示例

SPI 接口屏幕驱动示例

QSPI 接口屏幕驱动示例(有屏上 RAM)

QSPI 接口屏幕驱动示例(无屏上 RAM)

引脚接线

引脚连接建议:

  • TE(Tearing Effect):若平台有 PSRAM 且需要防撕裂,可选择接入;否则可省略。

  • CS(片选):强烈建议保留,不推荐省略。

  • Reset:若引脚资源充足,建议连接至可控 IO;否则可拉高并通过软件复位。

  • SPI 接口屏幕至少占用 4 个 IO(SCLK、MOSI、MISO 以及 CS 或 DC/RS 等控制信号)。

SPI 双屏方案

SPI 双屏异显(即双屏显示不同内容)与同显(即双屏显示相同内容)可通过控制屏幕 CS 线来实现。性能参考:ESP32-S3 可支持双屏分辨率 360 × 360,异显帧率约 20 fps。

SRAM 节省

一、前提说明

  • ESP-IDF 的 SPI 驱动暂不具备通过 GDMA 直接发送 PSRAM 数据的能力

  • 当使用 esp_lcd_panel_io_spi 刷屏且颜色数据位于 PSRAM,驱动会在 spi_master.c::setup_priv_desc() 检测 DMA 不可达后:

    1. 在内部 SRAM (带 MALLOC_CAP_DMA)重新分配临时缓冲区;

    2. 将颜色数据 memcpy 到该缓冲区;

    3. 再由 DMA 从 SRAM 发送数据至 LCD。

    该「搬运 → 复制 → DMA」过程会反复发生,额外消耗 SRAM。

二、问题现象

  • 系统同时运行 Wi-Fi / BLE 等高 SRAM 占用模块时,临时缓冲区抢占 SRAM,可能导致:

    • 其他组件申请内存失败;

    • 刷屏过程报 color trans fail;

    • LVGL 任务 WDT(看门狗)复位。

三、原因分析

  • PSRAM → SRAM 的临时拷贝是 spi_master 驱动的 回退机制,确保 DMA 无法直达外部 RAM 时仍能工作。

  • 内部 SRAM 既存放任务栈、FreeRTOS 对象、Wi-Fi 缓存,又承载 SPI 刷屏临时缓冲区,容易形成争用。

  • 临时缓冲区大小约为 max_transfer_sz × 已排队事务数,其中事务数由 trans_queue_depth 决定。

四、规避 / 优化方案

  1. 通过配置减少 SRAM 占用

    • 减小 spi_bus_config_t.max_transfer_sz:一次 DMA 搬运字节数越小,单次临时缓冲区越小。

      建议行刷模式下设置为 LCD_H_RES × 行数 × 2,行数控制在 20~60

    • 降低 esp_lcd_panel_io_spi_config_t.trans_queue_depth:排队事务越少,同时存在的缓冲副本越少。非并发刷屏场景下,设为 2~4 足矣。

    示例配置代码:

    ESP_LOGI(TAG, "Initialize SPI bus");
    spi_bus_config_t buscfg = {
        .sclk_io_num = EXAMPLE_PIN_NUM_SCLK,
        .mosi_io_num = EXAMPLE_PIN_NUM_MOSI,
        .miso_io_num = EXAMPLE_PIN_NUM_MISO,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = EXAMPLE_LCD_H_RES * 20 * sizeof(uint16_t),
    };
    ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
    
    ESP_LOGI(TAG, "Install panel IO");
    esp_lcd_panel_io_handle_t io_handle = NULL;
    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = EXAMPLE_PIN_NUM_LCD_DC,
        .cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS,
        .pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
        .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
        .lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
        .spi_mode = 0,
        .trans_queue_depth = 2,
    };
    
  2. 合理规划 Wi-Fi 与图形任务栈 / 缓存

  3. 运行时监控 SRAM 水位

  4. 在使用 LVGL 时,关闭 LV_ATTRIBUTE_FAST_MEM_USE_IRAM 可以节省很多内存。(该配置默认情况下未开启)

ESP32-P4 SPI 时钟速率无法拉高

  • 问题现象:当时钟速率拉高到 20 MHz 以上时,报错 invalid sclk speed

  • 解决方案:参考 patch

SPI 总线锁 spi_bus_lock 在多核场景下存在并发竞态

建议: 用户控制 SPI 中断和 LVGL 任务固定运行在同一颗 CPU 核心上。

#include "driver/spi_master.h"

#define SPI_HOST   SPI2_HOST          // 以 SPI2 为例
#define LCD_CORE   APP_CPU_NUM        // 目标核(1:APP_CPU,0:PRO_CPU)

static void lcd_bus_init(void)
{
    spi_bus_config_t buscfg = {
        .mosi_io_num = GPIO_NUM_7,
        .miso_io_num = GPIO_NUM_2,
        .sclk_io_num = GPIO_NUM_6,
        .max_transfer_sz = 4096,
        .isr_cpu_id = LCD_CORE,       // ★ 指定 SPI 中断注册到哪颗核
    };

    ESP_ERROR_CHECK(spi_bus_initialize(SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
static void lvgl_task(void *arg)
{
    for (;;) {
        lv_timer_handler();
        vTaskDelay(pdMS_TO_TICKS(5));
    }
}

void app_main(void)
{
    lcd_bus_init();   // 必须先初始化总线

    xTaskCreatePinnedToCore(lvgl_task,
                            "lvgl",
                            8192,
                            NULL,
                            5,
                            NULL,
                            LCD_CORE);   // ★ 与 .isr_cpu_id 相同
}