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




