You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Waveshare ESP32-S3 7寸LCD:SD卡与LVGL共存故障代码修改需求

问题解决:Waveshare ESP32-S3 7寸LCD套件SD卡与LVGL共存异常修复

问题现象

  • LVGL UI(显示+触摸)单独运行正常
  • SD卡读写单独运行正常
  • 同时使用时出现以下异常:
    • 屏幕闪烁后变绿
    • SD卡初始化失败
    • 设备偶尔重启或死机
    • LVGL日志报错:lv_inv_area: detected modifying dirty areas in render

已尝试无效方案

  • SD卡初始化期间调用lv_disp_suspend()暂停显示
  • 将SD卡切换至SPI3_HOST
  • 禁用GT911触摸以移除I2C依赖
  • 确认上拉电阻存在
  • 确保LVGL完全初始化后再初始化SD卡

开发环境

  • ESP32-S3(Waveshare板载,16MB Flash、8MB PSRAM)
  • ESP-IDF v5.5.1
  • LVGL v8.x
  • 显示屏:RGB接口
  • GT911触摸:I2C连接
  • SD卡:SPI连接

核心问题分析

核心冲突来自SPI总线操作与LVGL渲染的资源竞争:SD卡SPI读写会占用CPU或DMA资源,打断LVGL的渲染流程,导致脏区域修改异常;同时RGB屏的高速刷新也会干扰SPI总线稳定性。此外,原测试函数卸载SPI总线的行为会导致后续资源冲突。

针对性修改方案

1. 隔离SD卡操作与LVGL渲染时序

在SD卡初始化、读写的全流程中,锁定LVGL任务调度,避免渲染线程打断SPI操作:

// SD卡初始化前后添加锁
lv_lock();
esp_err_t ret = waveshare_sd_card_init();
lv_unlock();

同时在读写函数中添加锁:

static esp_err_t s_example_write_file(const char *path, char *data)
{
    lv_lock();
    // 文件操作逻辑...
    lv_unlock();
    return ESP_OK;
}

2. 调整SD卡SPI配置降低干扰

  • 降低SPI时钟频率,避免高速信号干扰RGB屏:
// 在waveshare_sd_card_init中添加
host.max_freq_khz = 10000; // 从默认20MHz降至10MHz
  • 禁用SPI DMA,改用CPU轮询避免资源竞争:
// 初始化SPI总线时修改DMA参数
ret = spi_bus_initialize(host.slot, &bus_cfg, SPI_DMA_DISABLED);

3. 保留SPI总线避免重复初始化问题

原测试函数末尾的spi_bus_free(host.slot)会释放SPI总线,导致后续资源冲突,直接注释该行:

// 注释掉此行:spi_bus_free(host.slot);

4. 优化LVGL渲染配置

  • 开启异步渲染分离任务:
// LVGL初始化时添加
lv_disp_set_async_mode(disp, true);
  • 调整刷新周期避免时序重叠:
lv_timer_set_period(lv_disp_get_refresh_timer(disp), 30); // 设置为30ms刷新一次

5. 确保I2C总线线程安全

依赖GT911的I2C初始化时,添加互斥锁避免I2C操作冲突:

// 定义全局互斥锁
static SemaphoreHandle_t i2c_mutex;

// 初始化时创建锁
i2c_mutex = xSemaphoreCreateMutex();

// 操作I2C时加锁
xSemaphoreTake(i2c_mutex, portMAX_DELAY);
// I2C写操作逻辑...
xSemaphoreGive(i2c_mutex);

修改后的完整SD卡代码

#include "sd_card.h"
#include "lvgl.h"
#include "freertos/semphr.h"

static const char *TAG = "example";

sdmmc_card_t *card;
const char mount_point[] = MOUNT_POINT;
static SemaphoreHandle_t i2c_mutex;

sdmmc_host_t host = SDSPI_HOST_DEFAULT();

static esp_err_t s_example_write_file(const char *path, char *data)
{
    lv_lock();
    ESP_LOGW(TAG, "Opening file %s", path);
    FILE *f = fopen(path, "w");
    if (f == NULL)
    {
        ESP_LOGW(TAG, "Failed to open file for writing");
        lv_unlock();
        return ESP_FAIL;
    }
    fprintf(f, data);
    fclose(f);
    ESP_LOGW(TAG, "File written");
    lv_unlock();
    return ESP_OK;
}

static esp_err_t s_example_read_file(const char *path)
{
    lv_lock();
    ESP_LOGW(TAG, "Reading file %s", path);
    FILE *f = fopen(path, "r");
    if (f == NULL)
    {
        ESP_LOGW(TAG, "Failed to open file for reading");
        lv_unlock();
        return ESP_FAIL;
    }
    char line[EXAMPLE_MAX_CHAR_SIZE];
    fgets(line, sizeof(line), f);
    fclose(f);

    char *pos = strchr(line, '\n');
    if (pos)
    {
        *pos = '\0';
    }
    ESP_LOGW(TAG, "Read from file: '%s'", line);
    lv_unlock();
    return ESP_OK;
}

esp_err_t waveshare_sd_card_init()
{
    esp_err_t ret;
    host.slot = SPI3_HOST;
    host.max_freq_khz = 10000; // 降低SPI时钟至10MHz

    // 初始化I2C互斥锁
    if (!i2c_mutex) {
        i2c_mutex = xSemaphoreCreateMutex();
    }

    // 控制CH422G(需启用时取消注释)
    xSemaphoreTake(i2c_mutex, portMAX_DELAY);
    uint8_t write_buf = 0x01;
    i2c_master_write_to_device(I2C_MASTER_NUM, 0x24, &write_buf, 1, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
    write_buf = 0x0A;
    i2c_master_write_to_device(I2C_MASTER_NUM, 0x38, &write_buf, 1, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
    xSemaphoreGive(i2c_mutex);

    esp_vfs_fat_sdmmc_mount_config_t mount_config = {
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
        .format_if_mount_failed = true,
#else
        .format_if_mount_failed = false,
#endif
        .max_files = 5,
        .allocation_unit_size = 16 * 1024
    };

    ESP_LOGW(TAG, "Initializing SD card");

    spi_bus_config_t bus_cfg = {
        .mosi_io_num = PIN_NUM_MOSI,
        .miso_io_num = PIN_NUM_MISO,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 4000,
    };
    // 禁用DMA避免资源冲突
    ret = spi_bus_initialize(host.slot, &bus_cfg, SPI_DMA_DISABLED);
    if (ret != ESP_OK)
    {
        ESP_LOGW(TAG, "Failed to initialize bus.");
        return ESP_FAIL;
    }

    sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
    slot_config.gpio_cs = PIN_NUM_CS;
    slot_config.host_id = host.slot;

    ESP_LOGW(TAG, "Mounting filesystem");
    lv_lock(); // 初始化期间锁定LVGL
    ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);
    lv_unlock();

    if (ret != ESP_OK)
    {
        if (ret == ESP_FAIL)
        {
            ESP_LOGW(TAG, "Failed to mount filesystem. If you want the card to be formatted, set the CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
        }
        else
        {
            ESP_LOGW(TAG, "Failed to initialize the card (%s). Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
        }
        return ESP_FAIL;
    }

    ESP_LOGW(TAG, "Filesystem mounted");
    return ESP_OK;
}

esp_err_t waveshare_sd_card_test()
{
    esp_err_t ret;

    lv_lock();
    sdmmc_card_print_info(stdout, card);
    lv_unlock();

    const char *file_hello = MOUNT_POINT "/hello.txt";
    char data[EXAMPLE_MAX_CHAR_SIZE];
    snprintf(data, EXAMPLE_MAX_CHAR_SIZE, "%s %s!\n", "Hello", card->cid.name);
    ret = s_example_write_file(file_hello, data);
    if (ret != ESP_OK)
    {
        return ESP_FAIL;
    }

    const char *file_foo = MOUNT_POINT "/foo.txt";

    struct stat st;
    if (stat(file_foo, &st) == 0)
    {
        unlink(file_foo);
    }

    ESP_LOGW(TAG, "Renaming file %s to %s", file_hello, file_foo);
    lv_lock();
    if (rename(file_hello, file_foo) != 0)
    {
        ESP_LOGW(TAG, "Rename failed");
        lv_unlock();
        return ESP_FAIL;
    }
    lv_unlock();

    ret = s_example_read_file(file_foo);
    if (ret != ESP_OK)
    {
        return ESP_FAIL;
    }

#ifdef CONFIG_EXAMPLE_FORMAT_SD_CARD
    lv_lock();
    ret = esp_vfs_fat_sdcard_format(mount_point, card);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to format FATFS (%s)", esp_err_to_name(ret));
        lv_unlock();
        return ret;
    }

    if (stat(file_foo, &st) == 0) {
        ESP_LOGI(TAG, "file still exists");
        lv_unlock();
        return ESP_FAIL;
    } else {
        ESP_LOGI(TAG, "file doesn't exist, formatting done");
    }
    lv_unlock();
#endif

    const char *file_nihao = MOUNT_POINT "/nihao.txt";
    memset(data, 0, EXAMPLE_MAX_CHAR_SIZE);
    snprintf(data, EXAMPLE_MAX_CHAR_SIZE, "%s %s!\n", "Nihao", card->cid.name);
    ret = s_example_write_file(file_nihao, data);
    if (ret != ESP_OK)
    {
        return ESP_FAIL;
    }

    ret = s_example_read_file(file_nihao);
    if (ret != ESP_OK)
    {
        return ESP_FAIL;
    }

    // 只卸载文件系统,保留SPI总线
    lv_lock();
    esp_vfs_fat_sdcard_unmount(mount_point, card);
    lv_unlock();
    ESP_LOGW(TAG, "Card unmounted");

    // 注释掉SPI总线释放,避免后续冲突
    // spi_bus_free(host.slot);
    return ESP_OK;
}

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

火山引擎 最新活动