当前位置: 首页 > 工具软件 > LittlevGL > 使用案例 >

LittleVGL (LVGL)干货入门教程三之LVGL的文件系统(fs)API对接。

全丰
2023-12-01

LittleVGL (LVGL)干货入门教程三之LVGL的文件系统(fs)API对接。


前言:

阅读前,请确保你拥有以下条件:

  • 你的项目已经完成“FatFS”的移植(例如你可以用FatFS进行SD卡的文件读写等)。
  • 你已经完成“显示API”的移植。

LVGL有三大种需要对接的API

  1. 显示API(教程一已实现, 链接:LittleVGL (LVGL)入门教程一之移植到stm32芯片
  2. 输入设备API(教程二已实现,链接:LittleVGL (LVGL)干货入门教程二之LVGL的输入设备(indev)API对接。
  3. 文件系统API(如FatFS等,这篇文章实现)

这篇文章讲“文件系统API”的移植,默认你已经移植好了“显示API”。

重要) 编译LVGL至少需要c99标准



一、为什么要为LVGL移植FatFS?

在笔者看来,LVGL中文件系统API是非常重要的东西,因为往往我们需要进行复杂的UI设计,你可以选择直接用LVGL内置的基本元素来进行设计,然而更明智的做法是从外部存储设备读取自绘的UI(比如不同的icons有不同的设计,例如“计算器UI”的图标明显和“设置UI”的图标不一样)。从外部存储设备读取自绘的UI的方法,可以设计出更美观优秀的交互界面。而你在LVGL中不可能直接使用FatFS的函数(会造成API的混乱,你的代码会非常难看,用一种说法就是:代码被污染了)。


二、什么地方用得上LVGL的文件系统API?

  • 你可以像使用FatFS一样使用LVGL的文件系统API
  • 在LVGL使用外部存储设备的资源时会被自动调用(此文第四章讲)。

三、lv_port_fs更改(重要

(一)使能fs的port文件

  1. 在lv_port_fs文件中把“#if 0”改为“#if 1”,c文件和h文件都要改。
  2. 在lv_port_fs.h中添加声明void lv_port_fs_init(void);

(二)移植fs的port文件(重要

在这个部分,我们需要干的事情会比较多。lvgl在port文件中给我们提供了很多文件系统的API,但其实重要的只有以下几个:

  • fs_open(打开文件)
  • fs_close(关闭文件)
  • fs_read(读取文件)
  • fs_write(写入文件)
  • fs_seek(定位)

但是要注意的一点是,我们在使用时并不会直接调用这些函数,而是调用更高层的以“lv_fs_”为前缀的一系列函数。

以下的代码会比较长,我建议大家根据目录进行阅读。

(1)type的对接

我们打开lv_port_fs.c文件,根据注释找到“TYPEDEFS”。

// 这是原本的typedef,我们可以发现这个typedef几乎没有任何意义,只是个模板而已
// 你不改的话,也能编译,但程序跑起来,十有八九会奔溃
typedef struct {
    /*Add the data you need to store about a file*/
    uint32_t dummy1;
    uint32_t dummy2;
}file_t;

/*Similarly to `file_t` create a type for directory reading too */
typedef struct {
    /*Add the data you need to store about directory reading*/
    uint32_t dummy1;
    uint32_t dummy2;
}dir_t;


// 我们根据FatFS的变量类型对上面的代码进行更改
// 这里的更改比较灵活,你可以像上面一样定义一个你自己的结构体类型,里面放你的数据
// 当然FIL类型和DIR类型必须有

/*
 * 第一种
 */
typedef FIL file_t;		// 把FIL类型定义成file_t
typedef DIR dir_t; 		// 把DIR类型定义成dir_t

/*
 * 第二种,比较灵活,但效率明显会低,初始化会比较麻烦
 */
typedef struct {
    FIL file;
    uint32_t user_data;
} file_t;

typedef struct {
    DIR dir;
    uint32_t user_data;
}dir_t;

以上的修改必须完成,不然无法使用。

(2)初始化函数

我们打开lv_port_fs.c文件,根据注释找到“GLOBAL FUNCTIONS”。里面开头有两个函数:

  1. lv_port_fs_init (初始化你的设备,并将对应的接口注册到LVGL里)
  2. fs_init (初始化你的设备)

参照下面改法或自行修改:

void lv_port_fs_init(void)
{
    /*----------------------------------------------------
     * Initialize your storage device and File System
     * -------------------------------------------------*/
    
    /* 这个函数实现在下面,里头是空的,由你自己实现 */
    fs_init();

    /*---------------------------------------------------
     * Register the file system interface  in LVGL
     *--------------------------------------------------*/

    /* Add a simple drive to open images */
    
    lv_fs_drv_t fs_drv;
	/* 上面这里默认只有一个fs_drv,如果我有多个存储设备的话,我们可以这样 */
	// lv_fs_drv_t fs_drv[FF_VOLUMES]; // FF_VOLUMES 定义在fatfs的ffconf.h文件中

    lv_fs_drv_init(&fs_drv);

    /*Set up fields...*/
    // ↓ 这里的file_t的size会被LVGL使用文件系统时调用进行内存分配,所以你上面一定要改好
    fs_drv.file_size = sizeof(file_t);	
    // ↓ 这里letter是比较重要的一个东西,比如调用SD卡时,我们直接“S:/xxx/xxx”即可
    // 如果我还用SPI Flash的话,这里的letter也可以写成F,避免和S冲突
    fs_drv.letter = 'S';
    /* 下面就是各个接口的注册 */
    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.free_space_cb = fs_free;		// 获取设备剩余空间和总空间
    fs_drv.size_cb = fs_size;			// 获取文件大小
    fs_drv.remove_cb = fs_remove;		// 删除文件
    fs_drv.rename_cb = fs_rename;		// 重命名
    fs_drv.trunc_cb = fs_trunc;			// 截取文件
	/*这个预处理是我写的,你可以使用LV_USE_USER_DATA宏打开这个使用(在lv_conf.h中)*/
	/* 后面我会讲这个东西怎么用,这里了解一下这个东西即可 */
#if LV_USE_USER_DATA == 1
	fs_drv.user_data = xxx;
#endif
	
	/* 这里和上面差不多,这个是文件夹的操作句柄,不过LVGL貌似没什么地方会用到这个 */
    fs_drv.rddir_size = sizeof(dir_t);
    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);
}

/* Initialize your Storage device and File system. */
static void fs_init(void)
{
    /*E.g. for FatFS initalize the SD card and FatFS itself*/
    /*You code here*/
    
    /* 这里是文件系统和你存储设备初始化的地方,你所有的设备最好别在别的地方初始化了 */
	/* 到这里都会进行初始化和挂载 */
    FATFS * fs = (FATFS*)lv_mem_alloc(sizeof(FATFS));
	FRESULT fres = FR_NOT_READY;

	/* 你设备的初始化 */
	fatfs_dev_init();

	/* 挂载设备 */
  	if (fres != FR_OK) {
  		/* 这里的驱动号我直接使用字符串“SD:”,在ffconf.h文件中的FF_VOLUME_STRS定义 */
    	fres = f_mount(fs, "SD:", 1);
    	if (fres != FR_OK) {
      		DEBUG_PRINT("SD Card mounted error. (%d)\n", fres );
    	} else
	    	DEBUG_PRINT("SD Card mounted successfully.\n\n");
  	}

	lv_mem_free(fs);
}

(3)fs_open函数

参照下面改法或自行修改:

/**
 * Open a file
 * @param drv pointer to a driver where this function belongs
 * @param file_p pointer to a file_t variable
 * @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt)
 * @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR
 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
 */
static lv_fs_res_t fs_open (
	lv_fs_drv_t * drv, 
	void * file_p, 
	const char * path, 
	lv_fs_mode_t mode
)
{
    lv_fs_res_t res = LV_FS_RES_NOT_IMP;

	/************************************************
   	* 在这里,我们需要先进行设备的判断,因为我们实际上
   	* 可能不只有一个设备,而且LVGL里使用letter(即单个字符)
   	* 作为驱动号
   	* 而在FatFS里使用VOLUME_STR(字符串)或者一个字节的pdrv
   	* 驱动号,使用前要进行转换。
   	* 
   	* 有一种方便的方法,就是上面提到的user_data,我们可以把
   	* VOLUME_STR提前装入user_data,那我们的程序进行判断时
   	* 耦合度会更低,在更多设备时,这种方法会更方便
 	 *************************************************/

	char *path_buf = NULL;
  	uint8_t opt_mode = 0;
	uint16_t path_len = strlen(path);
	
  	// 根据传入的letter判断是什么存储设备
  	switch (drv->letter) {
  	case 'S':       // SD card
  		path_buf = (char *)lv_mem_alloc(sizeof(char) * (path_len + 4));
    	sprintf(path_buf, "SD:/%s", path);
    	break;
  	case 'F':       // SPI FALSH
  		path_buf = (char *)lv_mem_alloc(sizeof(char) * (path_len + 6));
    	sprintf(path_buf, "SPIF:/%s", path);
    	break;
  	default:
    	printf("No drive %c\n", drv->letter);
    	return LV_FS_RES_NOT_EX;
  	}
  	
	/* 文件操作方法,将FatFS的转换成LVGL的操作方法 */
    if(mode == LV_FS_MODE_WR) {
    	opt_mode = FA_OPEN_ALWAYS | FA_WRITE;
    } else if(mode == LV_FS_MODE_RD) {
    	opt_mode = FA_OPEN_EXISTING | FA_READ;
    } else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) {
		opt_mode = FA_WRITE | FA_READ;
    }

	/* 调用FatFs的函数 */
  	FRESULT fres = f_open((FIL*)file_p, path_buf, opt_mode);
	if (fres != FR_OK) {
    	printf("f_open error (%d)\n", fres);
    	res = LV_FS_RES_NOT_IMP;
  	} else
    	res = LV_FS_RES_OK;

	lv_mem_free(path_buf);

    return res;
}

(4)fs_close函数

参照下面改法或自行修改:

/**
 * Close an opened file
 * @param drv pointer to a driver where this function belongs
 * @param file_p pointer to a file_t variable. (opened with lv_ufs_open)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv_fs_res_t enum
 */
static lv_fs_res_t fs_close (lv_fs_drv_t * drv, void * file_p)
{
    if (f_close((FIL*)file_p) != FR_OK)
    	return LV_FS_RES_NOT_IMP;
  	else
    	return LV_FS_RES_OK;
}
(5)fs_read函数

参照下面改法或自行修改:

/**
 * Read data from an opened file
 * @param drv pointer to a driver where this function belongs
 * @param file_p pointer to a file_t variable.
 * @param buf pointer to a memory block where to store the read data
 * @param btr number of Bytes To Read
 * @param br the real number of read bytes (Byte Read)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv_fs_res_t enum
 */
 static lv_fs_res_t fs_read(
 	lv_fs_drv_t* drv, 
 	void* file_p, 
 	void* buf, 
 	uint32_t btr, 
 	uint32_t* br
 )
{
	FRESULT fres = f_read((FIL*)file_p, buf, btr, br);
  	if (fres != FR_OK) {
    	printf("f_read error (%d)\n", fres);
    	return LV_FS_RES_NOT_IMP;
  	} else 
    	return LV_FS_RES_OK;
}

(6)fs_write函数

参照下面改法或自行修改:

/**
 * Write into a file
 * @param drv pointer to a driver where this function belongs
 * @param file_p pointer to a file_t variable
 * @param buf pointer to a buffer with the bytes to write
 * @param btr Bytes To Write
 * @param br the number of real written bytes (Bytes Written). NULL if unused.
 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
 */
static lv_fs_res_t fs_write(
	lv_fs_drv_t* drv, 
	void* file_p, 
	const void* buf, 
	uint32_t btw, 
	uint32_t* bw
)
{
    /* Add your code here*/
  	FRESULT fres = f_write((FIL*)file_p, buf, btw, bw);
  	if (fres != FR_OK) {
    	printf("f_read error (%d)\n", fres);
    	return LV_FS_RES_NOT_IMP;
  	} else
    	return LV_FS_RES_OK;
}

(7)fs_seek函数

参照下面改法或自行修改:

/**
 * Set the read write pointer. Also expand the file size if necessary.
 * @param drv pointer to a driver where this function belongs
 * @param file_p pointer to a file_t variable. (opened with lv_ufs_open )
 * @param pos the new position of read write pointer
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv_fs_res_t enum
 */
static lv_fs_res_t fs_seek(lv_fs_drv_t* drv, void* file_p, uint32_t pos)
{
    /* Add your code here*/
  	FRESULT fres = f_lseek((FIL*)file_p, pos);
 	 if (fres != FR_OK) {
    	printf("f_lseek error (%d)\n", fres);
    	return LV_FS_RES_NOT_IMP;
  	} else
    	return LV_FS_RES_OK;
}

(三)lv_port_fs修改结束

那么到这里,你完成了最基本也是必须的API对接,剩下的API你可以不管,除非你用的上。


四、LVGL如何使用外部资源?

在LVGL中,我们会调用外部存储设备的图像资源,我们使用一个小例程来展示如何使用外部资源。
LVGL的启动方法参考我的文章:LittleVGL (LVGL)入门教程一之移植到stm32芯片

这里涉及一个问题,即如何将图片格式(如JPG、PNG、GIF等)转换成LVGL可以识别的格式(内部数组、外部bin文件)的问题。有以下几种方法:

  1. 使用解码器(自己写或者使用LVGL官方GitHub提供的解码器)
  2. 使用官网的在线图片转换(常用)
  3. 使用第三方大牛的离线转换工具(常用)

工具的使用等不在此文讲,后面出文章讲。

#include <lvgl.h>

#define LVGL_TICK 	5

/**
 * 在LVGL启动运行后,会自动读取图片文件,你只要设置图片路径即可
 */
static void my_lvgl_test(void)
{
	/* 创建img btn */
	lv_obj_t * imgbtn = lv_imgbtn_create( lv_scr_act(), NULL );
	/* 设置imgbtn在被按下时显示所调用的图片 */
  	lv_imgbtn_set_src( imgbtn, LV_BTN_STATE_PRESSED, "S:/icon_press.bin" );
  	/* 设置imgbtn在被释放时显示所调用的图片 */
  	lv_imgbtn_set_src( imgbtn, LV_BTN_STATE_RELEASED, "S:/icon_release.bin" );
  	/* 居中对齐 */
  	lv_obj_align( imgbtn, NULL, LV_ALIGN_CENTER, 0, 0 );
}

static void my_lvgl_fs_test(void) 
{
	lv_fs_file_t lv_file;
  	lv_fs_res_t  lv_res;

  	lv_res = lv_fs_open( &lv_file, "S:/xxx/xxx", LV_FS_MODE_RD );
  	if ( lv_res != LV_FS_RES_OK ) {
    	printf( "LVGL FS open error. (%d)\n", lv_res );
  	} else 
  		printf( "LVGL FS open Ok\n" );
	lv_fs_close(&lv_file);
}

static void lvgl_init( void ) 
{
    lv_init();
    lv_port_disp_init();        // 显示器初始化
    lv_port_indev_init();       // 输入设备初始化
    lv_port_fs_init();          // 文件系统设备初始化
}

int main(void)
{
	lvgl_init();

	my_lvgl_fs_test();

	my_lvgl_test();

	while(1) {
		// 先调用 lv_tick_inc 再调用 lv_task_handler
		lv_tick_inc(LVGL_TICK);
		lv_task_handler();
		delay_ms(LVGL_TICK);
	}
}


五、启动LVGL

参考我的第一篇文章即可:LittleVGL (LVGL)干货入门教程一之移植到stm32芯片


本篇完


其他:

LittleVGL (LVGL)干货入门教程一之移植到stm32芯片

LittleVGL (LVGL)干货入门教程二之LVGL的输入设备(indev)API对接。

LittleVGL (LVGL)干货入门教程三之LVGL的文件系统(fs)API对接。

LittleVGL (LVGL)干货入门教程四之制作和使用中文汉字字库


个人Github页:https://github.com/Trisuborn


下一篇

LittleVGL (LVGL)干货入门教程四之制作和使用中文汉字字库

 类似资料: