博主使用的芯片:eps32P4 理论esp32s3一样的
lvgl版本:V9.3
idf版本:V5.5
esp_littlefs版本V1.2
littlefs版本V2.11.1
littlefs介绍
在esp_littlefs的gihub链接:joltwallet/esp_littlefs: ESP-IDF 的 LittleFS 端口 --- joltwallet/esp_littlefs: LittleFS port for ESP-IDF
测试了他的性能和SPIFFS速度的对比,是比他好的,我自己测试也是,所以本文使用的littlefs库

第一步littlefs配置
如同在main同级下创建partitions.csv和littlefs文件夹

littlefs文件夹放存入的文件 比如图片
partitions.csv文件可以参考我的,但是我的是32M,需要改小一点
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 0x4000, # NVS(非易失存储),用于存储系统参数、WiFi 配置等,大小 16 KB
otadata, data, ota, , 0x2000, # OTA 数据区,用于记录当前运行的固件信息,大小 8 KB
phy_init, data, phy, , 0x1000, # PHY 校准数据区,用于保存 WiFi/蓝牙 PHY 参数,大小 4 KB
factory, app, factory, , 4M, # Factory 应用分区,存放出厂/主应用固件,大小 4 MB
littlefs, data, littlefs, , 8M, # LittleFS 文件系统分区,用于存储用户文件,大小 8 MB
在main文件夹下面的CMakeLists.txt里添加,这样littlefs的内容会在下一次编译下载的时候下进去
下载会很慢,下载好后可以把他屏蔽掉
#设置 LittleFS 文件夹路径
set(LITTLEFS_DIR "${CMAKE_CURRENT_LIST_DIR}/../littlefs")
# 生成 LittleFS 分区镜像
idf_build_get_property(idf_target IDF_TARGET)
littlefs_create_partition_image(littlefs "${LITTLEFS_DIR}" FLASH_IN_PROJECT)

测试
可以使用下面代码测试一下,会在串口打印存了什么
#include <stdio.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_littlefs.h"
static const char *TAG = "littlefs_example";
void app_main(void)
{
// 挂载 littlefs
esp_vfs_littlefs_conf_t conf = {
.base_path = "/littlefs", // 挂载路径
.partition_label = "littlefs", // 分区名(要和分区表对应)
.format_if_mount_failed = true,
.read_only = false,
};
esp_err_t ret = esp_vfs_littlefs_register(&conf);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount or format filesystem");
} else if (ret == ESP_ERR_NOT_FOUND) {
ESP_LOGE(TAG, "Failed to find LittleFS partition");
} else {
ESP_LOGE(TAG, "Failed to initialize LittleFS (%s)", esp_err_to_name(ret));
}
return;
}
// 打印文件列表
DIR *dir = opendir("/littlefs");
if (dir == NULL) {
ESP_LOGE(TAG, "Failed to open directory");
return;
}
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
ESP_LOGI(TAG, "Found: %s", ent->d_name);
}
closedir(dir);
// 卸载 littlefs
esp_vfs_littlefs_unregister(conf.partition_label);
ESP_LOGI(TAG, "LittleFS unmounted");
}
第二版移植littlefs文件
本人移植好的的代码,将他全部下载下来放在components
代码解释
esp_littlefs是我下载好的gihub开源开源 ,esp_littlefs版本是V1.2 littlefs版本是V2.11.1
lv_port_fs是我自己写的lvgl文件系统,已经让AI加中文注释了,不得不感慨,现在AI很厉害了,这文件大部分都是他写的,我基本只是调试而已

/**
* @file lv_port_fs.c
* @brief LVGL v9 文件系统接口 (ESP32 + LittleFS)
*
* 本文件实现了 LVGL 文件系统接口的移植层,使用 ESP32 的 LittleFS 作为底层存储。
* 支持文件和目录的打开、读写、寻址、关闭等操作。
*/
#include "lv_port_fs.h"
#include "lvgl.h"
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include "esp_timer.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_littlefs.h"
#define TAG "lv_fs"
typedef FILE * file_t; // 文件句柄类型
typedef DIR * dir_t; // 目录句柄类型
/* forward declaration */
static void fs_init(void); // 文件系统初始化函数
/* LVGL FS 回调函数声明 */
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 * rddir_p, char * fn, uint32_t fn_len);
static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * rddir_p);
/**
* @brief 初始化 LVGL 文件系统并注册驱动
*
* @inputs 无
* @outputs 无
*/
void lv_port_fs_init(void)
{
fs_init(); // 初始化 LittleFS
static lv_fs_drv_t fs_drv;
lv_fs_drv_init(&fs_drv); // 初始化 LVGL 文件系统驱动结构体
fs_drv.letter = 'S'; // 设置 LVGL 中访问的盘符,如 S:/path/to/file
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_open_cb = fs_dir_open;
fs_drv.dir_read_cb = fs_dir_read;
fs_drv.dir_close_cb = fs_dir_close;
lv_fs_drv_register(&fs_drv); // 注册驱动到 LVGL
}
/**
* @brief LittleFS 初始化
*
* @inputs 无
* @outputs 无
*/
static void fs_init(void)
{
esp_vfs_littlefs_conf_t conf = {
.base_path = LV_FS_PATH, // 挂载点
.partition_label = NULL, // 使用默认分区
.format_if_mount_failed = true, // 挂载失败时格式化
.dont_mount = false // 自动挂载
};
esp_err_t ret = esp_vfs_littlefs_register(&conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to mount LittleFS (%s)", esp_err_to_name(ret));
return;
}
size_t total = 0, used = 0;
ret = esp_littlefs_info(conf.partition_label, &total, &used);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "LittleFS Partition size: total: %d, used: %d", total, used);
}
}
/* 文件操作实现 */
/**
* @brief 打开文件
*
* @inputs
* - drv: LVGL 文件系统驱动指针
* - path: 文件路径,支持 LVGL 风格盘符(如 S:/file.txt)
* - mode: 打开模式 (LV_FS_MODE_RD 或 LV_FS_MODE_WR)
* @outputs
* - 返回 FILE* 文件句柄,失败返回 NULL
*/
static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode)
{
LV_UNUSED(drv);
const char *p = path;
if (p[1] == ':') p += 2; // 去掉 LVGL 的盘符,如 "S:"
char filepath[256];
if (*p == '/') {
snprintf(filepath, sizeof(filepath), LV_FS_PATH "%s", p);
} else {
snprintf(filepath, sizeof(filepath), LV_FS_PATH "/%s", p);
}
const char *mode_str = (mode & LV_FS_MODE_WR) ? "wb" : "rb"; // 写模式或读模式
ESP_LOGI(TAG, "open %s (%s)", filepath, mode_str);
FILE *f = fopen(filepath, mode_str);
if (!f) {
ESP_LOGE(TAG, "Failed to open %s (errno=%d)", filepath, errno);
}
return f;
}
/**
* @brief 关闭文件
*
* @inputs
* - drv: LVGL 文件系统驱动指针
* - file_p: 文件句柄
* @outputs
* - 返回 LV_FS_RES_OK 或 LV_FS_RES_UNKNOWN
*/
static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p)
{
LV_UNUSED(drv);
if (!file_p) return LV_FS_RES_UNKNOWN;
fclose((FILE *)file_p);
return LV_FS_RES_OK;
}
/**
* @brief 读取文件
*
* @inputs
* - drv: LVGL 文件系统驱动指针
* - file_p: 文件句柄
* - buf: 数据缓冲区
* - btr: 期望读取字节数
* @outputs
* - br: 实际读取字节数
* - 返回 LV_FS_RES_OK 或 LV_FS_RES_UNKNOWN
*/
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);
if (!file_p) return LV_FS_RES_UNKNOWN;
size_t n = fread(buf, 1, btr, (FILE *)file_p);
*br = (uint32_t)n;
if (n == 0) {
if (feof((FILE *)file_p)) return LV_FS_RES_OK; // 文件结尾
if (ferror((FILE *)file_p)) return LV_FS_RES_UNKNOWN; // 读取错误
}
return LV_FS_RES_OK;
}
/**
* @brief 写入文件
*
* @inputs
* - drv: LVGL 文件系统驱动指针
* - file_p: 文件句柄
* - buf: 数据缓冲区
* - btw: 期望写入字节数
* @outputs
* - bw: 实际写入字节数
* - 返回 LV_FS_RES_OK 或 LV_FS_RES_UNKNOWN
*/
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);
if (!file_p) return LV_FS_RES_UNKNOWN;
size_t n = fwrite(buf, 1, btw, (FILE *)file_p);
*bw = (uint32_t)n;
if (n < btw && ferror((FILE *)file_p)) return LV_FS_RES_UNKNOWN;
return LV_FS_RES_OK;
}
/**
* @brief 文件定位
*
* @inputs
* - drv: LVGL 文件系统驱动指针
* - file_p: 文件句柄
* - pos: 相对位置或绝对位置
* - whence: 定位方式 (LV_FS_SEEK_SET/CUR/END)
* @outputs
* - 返回 LV_FS_RES_OK 或 LV_FS_RES_UNKNOWN
*/
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);
if (!file_p) return LV_FS_RES_UNKNOWN;
int origin = SEEK_SET;
switch (whence) {
case LV_FS_SEEK_SET: origin = SEEK_SET; break;
case LV_FS_SEEK_CUR: origin = SEEK_CUR; break;
case LV_FS_SEEK_END: origin = SEEK_END; break;
}
return fseek((FILE *)file_p, pos, origin) == 0 ? LV_FS_RES_OK : LV_FS_RES_UNKNOWN;
}
/**
* @brief 获取文件当前位置
*
* @inputs
* - drv: LVGL 文件系统驱动指针
* - file_p: 文件句柄
* @outputs
* - pos_p: 当前文件位置
* - 返回 LV_FS_RES_OK 或 LV_FS_RES_UNKNOWN
*/
static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p)
{
LV_UNUSED(drv);
if (!file_p || !pos_p) return LV_FS_RES_UNKNOWN;
long pos = ftell((FILE *)file_p);
if (pos < 0) return LV_FS_RES_UNKNOWN;
*pos_p = (uint32_t)pos;
return LV_FS_RES_OK;
}
/* 目录操作实现 */
/**
* @brief 打开目录
*
* @inputs
* - drv: LVGL 文件系统驱动指针
* - path: 目录路径
* @outputs
* - 返回 DIR* 目录句柄,失败返回 NULL
*/
static void * fs_dir_open(lv_fs_drv_t * drv, const char * path)
{
LV_UNUSED(drv);
char dirpath[256];
if (path[0] == '/') snprintf(dirpath, sizeof(dirpath), LV_FS_PATH "%s", path);
else snprintf(dirpath, sizeof(dirpath), LV_FS_PATH "/%s", path);
return opendir(dirpath);
}
/**
* @brief 读取目录
*
* @inputs
* - drv: LVGL 文件系统驱动指针
* - rddir_p: 目录句柄
* - fn_len: 文件名缓冲区长度
* @outputs
* - fn: 读取到的文件名,目录读完返回空字符串
* - 返回 LV_FS_RES_OK 或 LV_FS_RES_UNKNOWN
*/
static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * rddir_p,
char * fn, uint32_t fn_len)
{
LV_UNUSED(drv);
if (!rddir_p) return LV_FS_RES_UNKNOWN;
struct dirent *entry = readdir((DIR *)rddir_p);
if (!entry) {
fn[0] = '\0'; // LVGL 要求读完返回空字符串
return LV_FS_RES_OK;
}
strncpy(fn, entry->d_name, fn_len - 1);
fn[fn_len - 1] = '\0';
return LV_FS_RES_OK;
}
/**
* @brief 关闭目录
*
* @inputs
* - drv: LVGL 文件系统驱动指针
* - rddir_p: 目录句柄
* @outputs
* - 返回 LV_FS_RES_OK 或 LV_FS_RES_UNKNOWN
*/
static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * rddir_p)
{
LV_UNUSED(drv);
if (!rddir_p) return LV_FS_RES_UNKNOWN;
closedir((DIR *)rddir_p);
return LV_FS_RES_OK;
}
第三步使用方法
在lvgl初始化后初始化就行,记得调用头文件和组件
lv_port_fs_init();
//后面调用文件类似下面
//**gif初始化***//
lv_gif_set_src(gif, "S:/xxx.gif");
ESP32+LittleFS运行LVGL V9
1648

被折叠的 条评论
为什么被折叠?



