Skip to content

配置分区表 (partitions.csv)

# Name, Type, SubType, Offset, Size, Flags
nvs,      data, nvs,     0x9000,  0x4000,
otadata,  data, ota,     0xd000,  0x2000,
phy_init, data, phy,     0xf000,  0x1000,
factory,  app,  factory, 0x10000, 2M,
ota_0,    app,  ota_0,   ,        2M,
ota_1,    app,  ota_1,   ,        2M,
lvgl_res, data, fat,  ,        4M,      # 资源分区

配置 FATFS 镜像生成 (CMakeLists.txt)

在根目录创建资源文件夹 resources/imagesresources/fonts ,在内部放置图片和字体的资源文件,烧录固件的时候也会将资源烧录进对应的分区,并创建文件目录

cmake
# 在项目根目录的 CMakeLists.txt中添加

# 设置资源目录
set(resource_dir "${CMAKE_CURRENT_SOURCE_DIR}/resources")

# 添加 FATFS 镜像生成规则
fatfs_create_spiflash_image(
    lvgl_res            # 分区名称
    ${resource_dir}     # 资源目录
    FLASH_IN_PROJECT    # 随项目一起烧录
)

实现函数

Details

esp_flash.c

c
#include "esp_flash.h"

#include <inttypes.h>

#include "esp_crc.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "esp_partition.h"
#include "esp_vfs_fat.h"
#include "ff.h"

static const char *TAG = "esp_flash";

// 声明静态回调函数
static void *fs_open(lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode);
static lv_fs_res_t fs_close(lv_fs_drv_t *drv, void *file_p);
static lv_fs_res_t fs_read(lv_fs_drv_t *drv, void *file_p, void *buf,
                           uint32_t btr, uint32_t *br);
static lv_fs_res_t fs_write(lv_fs_drv_t *drv, void *file_p, const void *buf,
                            uint32_t btw, uint32_t *bw);
static lv_fs_res_t fs_seek(lv_fs_drv_t *drv, void *file_p, uint32_t pos,
                           lv_fs_whence_t whence);
static lv_fs_res_t fs_tell(lv_fs_drv_t *drv, void *file_p, uint32_t *pos_p);
static void *fs_dir_open(lv_fs_drv_t *drv, const char *path);
static lv_fs_res_t fs_dir_read(lv_fs_drv_t *drv, void *dir_p, char *fn);
static lv_fs_res_t fs_dir_close(lv_fs_drv_t *drv, void *dir_p);

// 错误码处理
static const char *f_error_msg(FRESULT res);

static wl_handle_t s_wl_handle = WL_INVALID_HANDLE;

void esp_flash_init_hw(void) {
  // 初始化 FATFS 文件系统
  lvgl_res_fs_init();
  // lvgl 文件系统注册
  lvgl_fs_register();
}

/********************************************************************************
 * @brief: 初始化 flash FATFS 文件系统
 * @return {*}
 ********************************************************************************/
esp_err_t lvgl_res_fs_init() {
  ESP_LOGI(TAG, "Initializing FAT filesystem on partition '%s'",
           PARTITION_LABEL);

  // FATFS 挂载配置
  esp_vfs_fat_mount_config_t mount_config = {
      .max_files = 5,
      .format_if_mount_failed = true,
      .allocation_unit_size = CONFIG_WL_SECTOR_SIZE,
      .disk_status_check_enable = false};

  // 用于在 SPI 闪存中初始化 FAT 文件系统并将其注册到 VFS 中
  esp_err_t ret = esp_vfs_fat_spiflash_mount_rw_wl(MOUNT_POINT, PARTITION_LABEL,
                                                   &mount_config, &s_wl_handle);
  if (ret != ESP_OK) {
    ESP_LOGI(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(ret));
    return ret;
  }

  return ESP_OK;
}

/********************************************************************************
 * @brief: 打开文件
 * @param {lv_fs_drv_t} *drv
 * @param {char} *path
 * @param {lv_fs_mode_t} mode
 * @return {*}
 ********************************************************************************/
static void *fs_open(lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode) {
  // ESP_LOGW(TAG, "OPEN -------------------------------------------");
  LV_UNUSED(drv);
  uint8_t flags = 0;

  if (mode == LV_FS_MODE_WR)
    flags = FA_WRITE | FA_OPEN_ALWAYS;
  else if (mode == LV_FS_MODE_RD)
    flags = FA_READ;
  else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD))
    flags = FA_READ | FA_WRITE | FA_OPEN_ALWAYS;

  FIL *f = lv_mem_alloc(sizeof(FIL));
  if (f == NULL) return NULL;

  FRESULT res = f_open(f, path, flags);
  if (res == FR_OK) {
    return f;
  } else {
    lv_mem_free(f);
    return NULL;
  }
}

/********************************************************************************
 * @brief: 关闭文件
 * @param {lv_fs_drv_t} *drv
 * @param {void} *file_p
 * @return {*}
 ********************************************************************************/
static lv_fs_res_t fs_close(lv_fs_drv_t *drv, void *file_p) {
  LV_UNUSED(drv);
  f_close(file_p);
  lv_mem_free(file_p);
  return LV_FS_RES_OK;
}

/********************************************************************************
 * @brief: 读取文件
 * @param {lv_fs_drv_t} *drv
 * @param {void} *file_p
 * @param {void} *buf
 * @param {uint32_t} btr
 * @param {uint32_t} *br
 * @return {*}
 ********************************************************************************/
static lv_fs_res_t fs_read(lv_fs_drv_t *drv, void *file_p, void *buf,
                           uint32_t btr, uint32_t *br) {
  LV_UNUSED(drv);
  FRESULT res = f_read(file_p, buf, btr, (UINT *)br);
  if (res == FR_OK)
    return LV_FS_RES_OK;
  else
    return LV_FS_RES_UNKNOWN;
}


 /********************************************************************************
  * @brief: 写入文件
  * @param {lv_fs_drv_t} *drv
  * @param {void} *file_p
  * @param {void} *buf
  * @param {uint32_t} btw
  * @param {uint32_t} *bw
  * @return {*}
  ********************************************************************************/
static lv_fs_res_t fs_write(lv_fs_drv_t *drv, void *file_p, const void *buf,
                            uint32_t btw, uint32_t *bw) {
  LV_UNUSED(drv);
  FRESULT res = f_write(file_p, buf, btw, (UINT *)bw);
  if (res == FR_OK)
    return LV_FS_RES_OK;
  else
    return LV_FS_RES_UNKNOWN;
}

/********************************************************************************
 * @brief: 定位文件指针
 * @param {lv_fs_drv_t} *drv
 * @param {void} *file_p
 * @param {uint32_t} pos
 * @param {lv_fs_whence_t} whence
 * @return {*}
 ********************************************************************************/
static lv_fs_res_t fs_seek(lv_fs_drv_t *drv, void *file_p, uint32_t pos,
                           lv_fs_whence_t whence) {
  LV_UNUSED(drv);
  switch (whence) {
    case LV_FS_SEEK_SET:
      f_lseek(file_p, pos);
      break;
    case LV_FS_SEEK_CUR:
      f_lseek(file_p, f_tell((FIL *)file_p) + pos);
      break;
    case LV_FS_SEEK_END:
      f_lseek(file_p, f_size((FIL *)file_p) + pos);
      break;
    default:
      break;
  }
  return LV_FS_RES_OK;
}

/********************************************************************************
 * @brief: 获取文件指针位置
 * @param {lv_fs_drv_t} *drv
 * @param {void} *file_p
 * @param {uint32_t} *pos_p
 * @return {*}
 ********************************************************************************/
static lv_fs_res_t fs_tell(lv_fs_drv_t *drv, void *file_p, uint32_t *pos_p) {
  LV_UNUSED(drv);
  *pos_p = f_tell((FIL *)file_p);
  return LV_FS_RES_OK;
}

/********************************************************************************
 * @brief: 打开目录
 * @param {lv_fs_drv_t} *drv
 * @param {char} *path
 * @return {*}
 ********************************************************************************/
static void *fs_dir_open(lv_fs_drv_t *drv, const char *path) {
  LV_UNUSED(drv);
  FF_DIR *d = lv_mem_alloc(sizeof(FF_DIR));
  if (d == NULL) return NULL;

  FRESULT res = f_opendir(d, path);
  if (res != FR_OK) {
    lv_mem_free(d);
    d = NULL;
  }
  return d;
}

/********************************************************************************
 * @brief: 读取目录
 * @param {lv_fs_drv_t} *drv
 * @param {void} *dir_p
 * @param {char} *fn
 * @return {*}
 ********************************************************************************/
static lv_fs_res_t fs_dir_read(lv_fs_drv_t *drv, void *dir_p, char *fn) {
  LV_UNUSED(drv);
  FRESULT res;
  FILINFO fno;
  fn[0] = '\0';

  do {
    res = f_readdir(dir_p, &fno);
    if (res != FR_OK) return LV_FS_RES_UNKNOWN;

    if (fno.fattrib & AM_DIR) {
      fn[0] = '/';
      strcpy(&fn[1], fno.fname);
    } else
      strcpy(fn, fno.fname);

  } while (strcmp(fn, "/.") == 0 || strcmp(fn, "/..") == 0);

  return LV_FS_RES_OK;
}

/********************************************************************************
 * @brief: 关闭目录
 * @param {lv_fs_drv_t} *drv
 * @param {void} *dir_p
 * @return {*}
 ********************************************************************************/
static lv_fs_res_t fs_dir_close(lv_fs_drv_t *drv, void *dir_p) {
  LV_UNUSED(drv);
  f_closedir(dir_p);
  lv_mem_free(dir_p);
  return LV_FS_RES_OK;
}

/********************************************************************************
 * @brief: 注册 LVGL 文件系统
 * @return {*}
 ********************************************************************************/
void lvgl_fs_register() {
  static lv_fs_drv_t fs_drv; /*A driver descriptor*/
  lv_fs_drv_init(&fs_drv);

  /*Set up fields...*/
  fs_drv.letter = LVGL_DRIVE_LETTER;
  fs_drv.cache_size = 0;

  fs_drv.open_cb = fs_open;
  fs_drv.close_cb = fs_close;
  fs_drv.read_cb = fs_read;
  fs_drv.write_cb = fs_write;
  fs_drv.seek_cb = fs_seek;
  fs_drv.tell_cb = fs_tell;

  fs_drv.dir_close_cb = fs_dir_close;
  fs_drv.dir_open_cb = fs_dir_open;
  fs_drv.dir_read_cb = fs_dir_read;

  lv_fs_drv_register(&fs_drv);
}

/********************************************************************************
 * @brief: 检查文件是否存在
 * @param {char} *path
 * @return {*}
 ********************************************************************************/
bool file_exists(const char *path) {
  FILINFO fno;
  FRESULT res = f_stat(path, &fno);

  if (res == FR_OK) {
    ESP_LOGI(TAG, "文件存在: %s (大小: %lu 字节)", path, fno.fsize);
    if (fno.fsize < 1024) {
      ESP_LOGI(TAG, "图片文件过小: %s (大小: %lu 字节)", path, fno.fsize);
    }
    return true;
  } else {
    ESP_LOGW(TAG, "文件不存在: %s (错误: %s)", path, f_error_msg(res));
    return false;
  }
}

/********************************************************************************
 * @brief: 错误码转文本函数
 * @param {FRESULT} res
 * @return {*}
 ********************************************************************************/
static const char *f_error_msg(FRESULT res) {
  switch (res) {
    case FR_OK:
      return "成功";
    case FR_DISK_ERR:
      return "磁盘错误";
    case FR_INT_ERR:
      return "内部错误";
    case FR_NOT_READY:
      return "设备未就绪";
    case FR_NO_FILE:
      return "文件不存在";
    case FR_NO_PATH:
      return "路径不存在";
    case FR_INVALID_NAME:
      return "无效文件名";
    case FR_DENIED:
      return "访问被拒绝";
    case FR_EXIST:
      return "文件/目录已存在";
    case FR_INVALID_OBJECT:
      return "无效对象";
    case FR_WRITE_PROTECTED:
      return "写保护";
    case FR_INVALID_DRIVE:
      return "无效驱动器";
    case FR_NOT_ENABLED:
      return "文件系统未启用";
    case FR_NO_FILESYSTEM:
      return "无有效文件系统";
    case FR_MKFS_ABORTED:
      return "格式化中止";
    case FR_TIMEOUT:
      return "超时";
    case FR_LOCKED:
      return "文件被锁定";
    case FR_NOT_ENOUGH_CORE:
      return "内存不足";
    case FR_TOO_MANY_OPEN_FILES:
      return "打开文件过多";
    case FR_INVALID_PARAMETER:
      return "无效参数";
    default:
      return "未知错误";
  }
}

/********************************************************************************
 * @brief: 保存图片到文件系统
 * @return {*}
 ********************************************************************************/
esp_err_t save_image_to_fs(const char *filename, const uint8_t *data,
                           size_t size) {
  char path[64];
  snprintf(path, sizeof(path), "%s/images/%s", MOUNT_POINT, filename);

  FILE *f = fopen(path, "wb");
  if (!f) {
    // ESP_LOGI(TAG, "Failed to open file: %s", path);
    return ESP_FAIL;
  }

  size_t written = fwrite(data, 1, size, f);
  fclose(f);

  if (written != size) {
    // ESP_LOGI(TAG, "Incomplete write: %u/%u bytes", written, size);
    return ESP_FAIL;
  }

  // ESP_LOGI(TAG, "Image saved to: %s (%u bytes)", path, size);
  return ESP_OK;
}

/********************************************************************************
 * @brief: 从文件系统读取图片
 * @return {*}
 ********************************************************************************/
esp_err_t read_image_from_fs(const char *filename, uint8_t *buffer,
                             size_t buffer_size, size_t *out_size) {
  char path[64];
  snprintf(path, sizeof(path), "%s/images/%s", MOUNT_POINT, filename);

  FILE *f = fopen(path, "rb");
  if (!f) {
    // ESP_LOGI(TAG, "Failed to open file: %s", path);
    return ESP_FAIL;
  }

  fseek(f, 0, SEEK_END);
  long file_size = ftell(f);
  fseek(f, 0, SEEK_SET);

  if (file_size > buffer_size) {
    fclose(f);
    // ESP_LOGI(TAG, "Buffer too small: %u < %ld", buffer_size, file_size);
    return ESP_FAIL;
  }

  size_t read = fread(buffer, 1, file_size, f);
  fclose(f);

  if (read != (size_t)file_size) {
    // ESP_LOGI(TAG, "Read incomplete: %u/%ld bytes", read, file_size);
    return ESP_FAIL;
  }

  *out_size = file_size;
  // ESP_LOGI(TAG, "Read %u bytes from %s", read, path);
  return ESP_OK;
}


/********************************************************************************
 * @brief: 从文件读取图片到 PSRAM 并显示
 * @param {char} *image_path
 * @return {*}
 ********************************************************************************/
lv_img_dsc_t *load_image_from_psram(const char *image_path) {
  // 1. 构建完整文件路径
  char full_path[128];
  snprintf(full_path, sizeof(full_path), "%s%s", MOUNT_POINT, image_path);
  ESP_LOGI(TAG, "加载图片: %s", full_path);

  // 2. 打开文件
  FILE *f = fopen(full_path, "rb");
  if (!f) {
    ESP_LOGE(TAG, "文件打开失败: %s", full_path);
    lv_obj_t *label = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(label, "文件打开失败:\n%s", image_path);
    lv_obj_set_style_text_color(label, lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_center(label);
    return NULL;
  }

  // 3. 获取文件大小
  fseek(f, 0, SEEK_END);
  size_t file_size = ftell(f);
  fseek(f, 0, SEEK_SET);
  ESP_LOGI(TAG, "文件大小: %d 字节", file_size);

  // 4. 在 PSRAM 中分配内存
  uint8_t *img_data = heap_caps_malloc(file_size, MALLOC_CAP_SPIRAM);
  if (!img_data) {
    ESP_LOGE(TAG, "PSRAM 分配失败: %d 字节", file_size);
    fclose(f);

    lv_obj_t *label = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(label, "内存不足:\n%d 字节", file_size);
    lv_obj_set_style_text_color(label, lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_center(label);
    return NULL;
  }

  // 5. 读取文件内容到 PSRAM
  size_t read_size = fread(img_data, 1, file_size, f);
  fclose(f);

  if (read_size != file_size) {
    ESP_LOGE(TAG, "读取不完整: %d/%d 字节", read_size, file_size);
    heap_caps_free(img_data);

    lv_obj_t *label = lv_label_create(lv_scr_act());
    lv_label_set_text_fmt(label, "读取失败:\n%d/%d 字节", read_size, file_size);
    lv_obj_set_style_text_color(label, lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_center(label);
    return NULL;
  }
  ESP_LOGI(TAG, "成功读取到 PSRAM: %p", img_data);

  // 6. 解析图片头信息
  lv_img_header_t header;
  if (lv_img_decoder_get_info(img_data, &header) != LV_RES_OK) {
    ESP_LOGE(TAG, "图片头解析失败");
    heap_caps_free(img_data);

    lv_obj_t *label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "图片格式不支持");
    lv_obj_set_style_text_color(label, lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_center(label);
    return NULL;
  }
  ESP_LOGI(TAG, "图片信息: %dx%d, 格式: %d", header.w, header.h, header.cf);

  // 7. 创建图像描述符
  lv_img_dsc_t *img_dsc =
      heap_caps_malloc(sizeof(lv_img_dsc_t), MALLOC_CAP_DEFAULT);
  if (!img_dsc) {
    ESP_LOGE(TAG, "描述符分配失败");
    heap_caps_free(img_data);
    return NULL;
  }

  img_dsc->header = header;
  img_dsc->data_size = file_size;
  img_dsc->data = img_data;

  return img_dsc;
}

esp_flash.h

c
#ifndef ESP_FLASH_H
#define ESP_FLASH_H

#include "esp_err.h"
#include "lvgl.h"

// 配置区块大小 (4KB对齐)
#define CONFIG_BLOCK_SIZE 4096
#define CONFIG_MAGIC 0x55AA55AA

// 定义 LVGL 资源分区标签
#define PARTITION_LABEL "lvgl_res"
// 定义文件系统挂载点
#define MOUNT_POINT "/fat"
// 定义 LVGL 文件系统驱动器字母
#define LVGL_DRIVE_LETTER 'S'

void esp_flash_init_hw(void);

esp_err_t lvgl_res_fs_init();

void lvgl_fs_register();

void create_res_dir();

void display_fs_info(void);

bool file_exists(const char* path);

void list_entire_filesystem();

void format_file_size(uint32_t size, char* buffer, size_t buffer_size);

void list_files_recursive(const char* path, int depth);

lv_img_dsc_t *load_image_to_cache(const char *filename);

lv_obj_t *create_image_obj(lv_obj_t *parent, const char *filename);

esp_err_t update_image_resource(const char *filename, const uint8_t *data,
                                size_t size);

esp_err_t save_image_to_fs(const char *filename, const uint8_t *data,
                           size_t size);

esp_err_t read_image_from_fs(const char *filename, uint8_t *buffer,
                             size_t buffer_size, size_t *out_size);

lv_img_dsc_t *find_image_in_cache(const char *filename);

void free_image_cache(const char *filename);

void print_resource_usage();

void preload_com_res();


#endif

文件系统信息打印显示

Details
c
/********************************************************************************
 * @brief: 显示文件系统信息
 * @return {*}
 ********************************************************************************/
void display_fs_info(void) {
  FATFS *fs;
  DWORD fre_clust;
  FRESULT res = f_getfree(MOUNT_POINT, &fre_clust, &fs);
  if (res == FR_OK) {
    DWORD total_sectors = (fs->n_fatent - 2) * fs->csize;
    DWORD free_sectors = fre_clust * fs->csize;
    ESP_LOGI(TAG, "分区信息: 总空间=%luKB, 可用空间=%luKB",
             total_sectors * fs->ssize / 1024, free_sectors * fs->ssize / 1024);
  } else {
    ESP_LOGE(TAG, "获取分区信息失败: %s", f_error_msg(res));
  }
}

/********************************************************************************
 * @brief: 递归遍历目录并打印文件信息
 * @param {char} *path
 * @param {int} depth
 * @return {*}
 ********************************************************************************/
void list_files_recursive(const char *path, int depth) {
  FF_DIR dir;
  FILINFO fno;
  FRESULT res;
  char full_path[256];

  // 打开目录
  res = f_opendir(&dir, path);
  if (res != FR_OK) {
    ESP_LOGE(TAG, "无法打开目录: %s (%s)", path, f_error_msg(res));
    return;
  }

  // 打印当前目录信息
  ESP_LOGI(TAG, "%*s[DIR] %s", depth * 2, "", path);

  // 遍历目录中的所有条目
  while (1) {
    res = f_readdir(&dir, &fno);
    if (res != FR_OK || fno.fname[0] == 0) {
      break;  // 错误或没有更多文件
    }

    // 跳过特殊目录项
    if (strcmp(fno.fname, ".") == 0 || strcmp(fno.fname, "..") == 0) {
      continue;
    }

    // 构建完整路径
    snprintf(full_path, sizeof(full_path), "%s/%s", path, fno.fname);

    // 处理目录
    if (fno.fattrib & AM_DIR) {
      // 递归遍历子目录
      list_files_recursive(full_path, depth + 1);
    }
    // 处理文件
    else {
      // 打印文件信息
      char size_str[16];
      format_file_size(fno.fsize, size_str, sizeof(size_str));

      ESP_LOGI(TAG, "%*s[FILE] %-40s %10s", (depth + 1) * 2, "", fno.fname,
               size_str);
    }
  }

  // 关闭目录
  f_closedir(&dir);
}

/********************************************************************************
 * @brief: 格式化文件大小为易读的格式
 * @param {uint32_t} size
 * @param {char} *buffer
 * @param {size_t} buffer_size
 * @return {*}
 ********************************************************************************/
void format_file_size(uint32_t size, char *buffer, size_t buffer_size) {
  const char *units[] = {"B", "KB", "MB", "GB"};
  double size_val = size;
  int unit_index = 0;

  while (size_val >= 1024 && unit_index < 3) {
    size_val /= 1024;
    unit_index++;
  }

  if (size_val < 10 && unit_index > 0) {
    snprintf(buffer, buffer_size, "%.2f %s", size_val, units[unit_index]);
  } else {
    snprintf(buffer, buffer_size, "%.1f %s", size_val, units[unit_index]);
  }
}

/********************************************************************************
 * @brief: 完整遍历文件系统
 * @return {*}
 ********************************************************************************/
void list_entire_filesystem() {
  ESP_LOGI(TAG, "==============================================");
  ESP_LOGI(TAG, "文件系统内容:");
  ESP_LOGI(TAG, "==============================================");

  // 获取分区信息
  FATFS *fs;
  DWORD fre_clust;
  FRESULT res = f_getfree("", &fre_clust, &fs);

  if (res == FR_OK) {
    DWORD total_sectors = (fs->n_fatent - 2) * fs->csize;
    DWORD free_sectors = fre_clust * fs->csize;

    char total_str[16], free_str[16];
    // 格式化数据大小显示
    format_file_size(total_sectors * fs->ssize, total_str, sizeof(total_str));
    format_file_size(free_sectors * fs->ssize, free_str, sizeof(free_str));
    // 显示分区大小和可用空间
    ESP_LOGI(TAG, "分区大小: %s, 可用空间: %s", total_str, free_str);
    ESP_LOGI(TAG, "----------------------------------------------");
  }

  // 从根目录开始递归遍历
  list_files_recursive("/", 0);

  ESP_LOGI(TAG, "==============================================");
}

读取和显示图片字体

c
// 创建并配置图像对象
lv_obj_t *img = lv_img_create(lv_scr_act());
// 可以手动读取后显示
// lv_img_dsc_t *img_file = load_image_from_psram("/IMAGES/1.bin");
// 直接用 lvgl 的文件系统接口,读取后显示
lv_img_set_src(img, "S:/IMAGES/1.bin");
lv_obj_center(img);

const lv_font_t *font = lv_font_load("S:/FONTS/P_128.BIN");
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "33");
lv_obj_center(label);

lv_obj_set_style_text_font(label, font, LV_PART_MAIN);

如果遇到回调函数不调用的问题

首先在 lv_conf.h 中打开

c
#define LV_USE_FS_FATFS 1
#if LV_USE_FS_FATFS
    #define LV_FS_FATFS_LETTER 'S'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
    #define LV_FS_FATFS_CACHE_SIZE 0    /*>0 to cache this number of bytes in lv_fs_read()*/
#endif

lvgl/env_support/cmake/esp.cmake 文件中引用 fatfs 的组件

c
idf_component_register(SRCS ${SOURCES} ${EXAMPLE_SOURCES} ${DEMO_SOURCES}
      INCLUDE_DIRS ${LVGL_ROOT_DIR} ${LVGL_ROOT_DIR}/src ${LVGL_ROOT_DIR}/../
                   ${LVGL_ROOT_DIR}/examples ${LVGL_ROOT_DIR}/demos
      REQUIRES 
      	esp_timer
        fatfs
 )

这样 lvgl 会内部实现文件读取的接口,只需要自己初始化 fatfs 就可以

Released under the GPL License.