LVGL 文件系統(tǒng)移植
文件系統(tǒng)介紹
在 LVGL 里可以將圖像以文件的形式存儲(chǔ)在 SPI Flash 或 SD 卡上噪叙,但是必須存儲(chǔ)在 LVGL 的文件系統(tǒng)里阳液,因此實(shí)踐移植了 LVGL 文件系統(tǒng)。
LVGL 的文件系統(tǒng)可以適配任何其他的文件系統(tǒng)咖为,只要實(shí)現(xiàn)相應(yīng)的回調(diào)函數(shù)即可秕狰。該文件系統(tǒng)有一個(gè)驅(qū)動(dòng)字符(drive letter)用于識(shí)別不同設(shè)備,例如 SD 卡可以與驅(qū)動(dòng)字符 'S' 相關(guān)聯(lián)躁染,這樣可以通過路徑 "S:/path/to/file.txt"
來訪問一個(gè)文件鸣哀。
添加驅(qū)動(dòng)
為了添加文件系統(tǒng)的驅(qū)動(dòng),需要初始化一個(gè) lv_fs_drv_t
的結(jié)構(gòu)體吞彤,類似下列示例代碼:
lv_fs_drv_t drv;
lv_fs_drv_init(&drv); /*Basic initialization*/
drv.letter = 'S'; /*An uppercase letter to identify the drive */
drv.file_size = sizeof(my_file_object); /*Size required to store a file object*/
drv.rddir_size = sizeof(my_dir_object); /*Size required to store a directory object (used by dir_open/close/read)*/
drv.ready_cb = my_ready_cb; /*Callback to tell if the drive is ready to use */
drv.open_cb = my_open_cb; /*Callback to open a file */
drv.close_cb = my_close_cb; /*Callback to close a file */
drv.read_cb = my_read_cb; /*Callback to read a file */
drv.write_cb = my_write_cb; /*Callback to write a file */
drv.seek_cb = my_seek_cb; /*Callback to seek in a file (Move cursor) */
drv.tell_cb = my_tell_cb; /*Callback to tell the cursor position */
drv.trunc_cb = my_trunc_cb; /*Callback to delete a file */
drv.size_cb = my_size_cb; /*Callback to tell a file's size */
drv.rename_cb = my_rename_cb; /*Callback to rename a file */
drv.dir_open_cb = my_dir_open_cb; /*Callback to open directory to read its content */
drv.dir_read_cb = my_dir_read_cb; /*Callback to read a directory's content */
drv.dir_close_cb = my_dir_close_cb; /*Callback to close a directory */
drv.free_space_cb = my_free_space_cb; /*Callback to tell free space on the drive */
drv.user_data = my_user_data; /*Any custom data if required*/
lv_fs_drv_register(&drv); /*Finally register the drive*/
上述的任何一個(gè)回調(diào)函數(shù)都可以為 NULL 表明該操作不支持我衬。
舉例說明一些這些回調(diào)函數(shù)是如何被使用的叹放,如果我們執(zhí)行操作:
lv_fs_open(&file, "S:/folder/file.txt", LV_FS_MODE_WR);
LVGL 會(huì)依次
- 檢查已經(jīng)注冊(cè)的驅(qū)動(dòng)其驅(qū)動(dòng)字節(jié)是否為
's'
。 - 檢查
open_cb
回調(diào)函數(shù)是否被實(shí)現(xiàn)(不為 NULL)挠羔。 - 調(diào)用
open_cb
井仰,其中 path 參數(shù)為"folder/file.txt"
。
使用示例
以下代碼演示了如何讀取文件:
lv_fs_file_t f;
lv_fs_res_t res;
res = lv_fs_open(&f, "S:folder/file.txt", LV_FS_MODE_RD);
if(res != LV_FS_RES_OK) my_error_handling();
uint32_t read_num;
uint8_t buf[8];
res = lv_fs_read(&f, buf, 8, &read_num);
if(res != LV_FS_RES_OK || read_num != 8) my_error_handling();
lv_fs_close(&f);
lv_fs_open
里的 mode 參數(shù)也可以是 LV_FS_MODE_WR
或 LV_FS_MODE_WR | LV_FS_MODE_RD
破加。
下面的例子展示了如何讀取一個(gè)目錄里的內(nèi)容俱恶。由驅(qū)動(dòng)程序決定如何標(biāo)記目錄,類似 Unix 風(fēng)格范舀,最好在目錄名稱前面插入 '/'
表明根目錄合是。
lv_fs_dir_t dir;
lv_fs_res_t res;
res = lv_fs_dir_open(&dir, "S:/folder");
if(res != LV_FS_RES_OK) my_error_handling();
char fn[256];
while(1) {
res = lv_fs_dir_read(&dir, fn);
if(res != LV_FS_RES_OK) {
my_error_handling();
break;
}
/*fn is empty, if not more files to read*/
if(strlen(fn) == 0) {
break;
}
printf("%s\n", fn);
}
lv_fs_dir_close(&dir);
圖像所使用的驅(qū)動(dòng)程序
LVGL 的 image 對(duì)象可以從文件里打開(另一種方式是直接存儲(chǔ)在 Flash)。
為了初始化圖像锭环,需要以下回調(diào)函數(shù)的支持:
- open_cb
- close_cb
- read_cb
- seek_cb
- tell_cb
移植
因?yàn)橐浦策@個(gè)文件系統(tǒng)主要是為了存儲(chǔ)并打開圖像文件聪全,因此沒必要完全實(shí)現(xiàn),只需實(shí)現(xiàn) open, close, read, seek, tell 這五個(gè)回調(diào)函數(shù)即可辅辩。
本次移植之前需要在 RT-Thread 里成功移植了 SD 卡难礼,并掛載了虛擬文件系統(tǒng) DFS,可以參考:RT-Thread Studio驅(qū)動(dòng)SD卡 汽久。
RT-Thread 的 DFS 實(shí)現(xiàn)了 POSIX 接口鹤竭,因此可以使用 POSIX 的 open, close, read, lseek 這些操作文件的 API踊餐,大大簡(jiǎn)化了移植 LVGL 文件系統(tǒng)的事件景醇。
這些接口的使用可以參考 RT-Thread 文檔 。
在 port 目錄下有一個(gè)官方提供的移植模板文件 lv_port_fs_template.c
吝岭,這里面提供了移植的步驟三痰,以及需要實(shí)現(xiàn)的部分。
類型定義
模板文件里相關(guān)內(nèi)容如下:
/**********************
* TYPEDEFS
**********************/
/* Create a type to store the required data about your file.
* If you are using a File System library
* it already should have a File type.
* For example FatFS has `FIL`. In this case use `typedef FIL file_t`*/
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;
根據(jù)提示窜管,file_t 是一個(gè)類似句柄的類型散劫,就比如 FatFS 里的 FIL 以及 POSIX 里的 int 。而且我們目前只需要操作文件幕帆,不需要操作目錄获搏,因此上述內(nèi)容就可以更改為:
typedef int file_t;
函數(shù)申明
其次是函數(shù)聲明的部分:
/**********************
* STATIC PROTOTYPES
**********************/
static void fs_init(void);
static lv_fs_res_t fs_open (lv_fs_drv_t * drv, void * file_p, 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);
static lv_fs_res_t fs_size (lv_fs_drv_t * drv, void * file_p, uint32_t * size_p);
static lv_fs_res_t fs_tell (lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p);
static lv_fs_res_t fs_remove (lv_fs_drv_t * drv, const char *path);
static lv_fs_res_t fs_trunc (lv_fs_drv_t * drv, void * file_p);
static lv_fs_res_t fs_rename (lv_fs_drv_t * drv, const char * oldname, const char * newname);
static lv_fs_res_t fs_free (lv_fs_drv_t * drv, uint32_t * total_p, uint32_t * free_p);
static lv_fs_res_t fs_dir_open (lv_fs_drv_t * drv, void * rddir_p, const char *path);
static lv_fs_res_t fs_dir_read (lv_fs_drv_t * drv, void * rddir_p, char *fn);
static lv_fs_res_t fs_dir_close (lv_fs_drv_t * drv, void * rddir_p);
較老版本的移植模板文件這些函數(shù)可能沒有第一個(gè)參數(shù) lv_fs_drv_t * drv
,需要自己添加失乾。
刪除不必要的接口常熙,只保留我們需要的部分,如下所示:
static void fs_init(void);
static lv_fs_res_t fs_open (lv_fs_drv_t * drv, void * file_p, 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_seek (lv_fs_drv_t * drv, void * file_p, uint32_t pos);
static lv_fs_res_t fs_tell (lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p);
初始化函數(shù)
void lv_port_fs_init(void)
{
/*----------------------------------------------------
* Initialize your storage device and File System
* -------------------------------------------------*/
fs_init();
/*---------------------------------------------------
* Register the file system interface in LittlevGL
*--------------------------------------------------*/
/* Add a simple drive to open images */
lv_fs_drv_t fs_drv; /*A driver descriptor*/
memset(&fs_drv, 0, sizeof(lv_fs_drv_t)); /*Initialization*/
/*Set up fields...*/
fs_drv.file_size = sizeof(file_t);
fs_drv.letter = 'P';
fs_drv.open = fs_open;
fs_drv.close = fs_close;
fs_drv.read = fs_read;
fs_drv.write = fs_write;
fs_drv.seek = fs_seek;
fs_drv.tell = fs_tell;
fs_drv.free = fs_free;
fs_drv.size = fs_size;
fs_drv.remove = fs_remove;
fs_drv.rename = fs_rename;
fs_drv.trunc = fs_trunc;
fs_drv.rddir_size = sizeof(dir_t);
fs_drv.dir_close = fs_dir_close;
fs_drv.dir_open = fs_dir_open;
fs_drv.dir_read = fs_dir_read;
lv_fs_add_drv(&fs_drv);
}
初始化函數(shù)這里會(huì)注冊(cè)設(shè)備碱茁,設(shè)備描述符 fs_drv
經(jīng)過默認(rèn)初始化后所有的回調(diào)函數(shù)都為 NULL裸卫,因此沒有回調(diào)函數(shù)的成員可以不用管。修改成如下內(nèi)容:
int lv_port_fs_init(void)
{
/*----------------------------------------------------
* Initialize your storage device and File System
* -------------------------------------------------*/
fs_init();
/*---------------------------------------------------
* Register the file system interface in LittlevGL
*--------------------------------------------------*/
/* Add a simple drive to open images */
lv_fs_drv_t fs_drv; /*A driver descriptor*/
lv_fs_drv_init(&fs_drv); /*Basic initialization*/
/*Set up fields...*/
fs_drv.file_size = sizeof(file_t);
fs_drv.letter = 'S';
fs_drv.open_cb = fs_open;
fs_drv.close_cb = fs_close;
fs_drv.read_cb = fs_read;
fs_drv.seek_cb = fs_seek;
fs_drv.tell_cb = fs_tell;
lv_fs_drv_register(&fs_drv);
return RT_EOK;
}
這個(gè)初始化函數(shù)需要在 LVGL 初始化之后再調(diào)用纽竣。
回調(diào)接口實(shí)現(xiàn)
fs_init
該函數(shù)本來是為了用戶初始化 SD 卡或 Flash墓贿,但我們的 SD 卡或 Flash 在外部已經(jīng)初始化好了茧泪,因此這里默認(rèn)不管即可。
fs_open
在這個(gè)接口里我們需要按照 mode 模式打開文件聋袋,由于我們只需要讀文件队伟,因此只實(shí)現(xiàn) LV_FS_MODE_RD 的部分即可,模板文件內(nèi)容如下:
/**
* 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;
if(mode == LV_FS_MODE_WR)
{
/*Open a file for write*/
/* Add your code here*/
}
else if(mode == LV_FS_MODE_RD)
{
/*Open a file for read*/
/* Add your code here*/
}
else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD))
{
/*Open a file for read and write*/
/* Add your code here*/
}
return res;
}
函數(shù)參數(shù) file_p
就是之前定義的 file_t
類型的指針幽勒,在實(shí)際移植過程中需要用相應(yīng)的句柄去填充 file_p
所指向的內(nèi)容缰泡,因?yàn)楹竺鎸?shí)現(xiàn)其他接口時(shí)都是通過 file_p
這個(gè)參數(shù)來傳遞句柄。這里我填充了 POSIX 的 int 類型句柄:
int fd;
*(file_t *)file_p = fd;
一般 SD 卡或 Flash 都是掛載在 '/'
目錄下的代嗤,為了與其他內(nèi)容區(qū)分開棘钞,我這里將 LVGL 打開文件的起始地址導(dǎo)向了 '/lvgl/'
目錄下,也就是說 lv_fs_open(&f, "S:/file.txt", LV_FS_MODE_RD);
實(shí)際是調(diào)用了 open("/lvgl/file.txt", O_RDONLY)
這個(gè)函數(shù)干毅。將 LVGL 的默認(rèn)目錄定義為 '/lvgl/'
宜猜,也可以理解為將 LVGL 的文件系統(tǒng)掛載到 '/lvgl/'
目錄下了。
移植后內(nèi)容如下:
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;
rt_size_t len = rt_strlen(path);
RT_ASSERT(len < DFS_PATH_MAX);
char posix_path[DFS_PATH_MAX] = "/lvgl/";
const char *lvgl_dir_path = path;
strncat(posix_path, lvgl_dir_path, DFS_PATH_MAX-6);
int fd;
if(mode == LV_FS_MODE_RD)
{
/*Open a file for read*/
if ((fd = open(posix_path, O_RDONLY)) > 0) {
*(file_t *)file_p = fd;
res = LV_FS_RES_OK; // open success
} else {
res = LV_FS_RES_NOT_EX; // open fail
}
}
return res;
}
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)
{
lv_fs_res_t res = LV_FS_RES_NOT_IMP;
/* Add your code here*/
return res;
}
這個(gè)接口需要關(guān)閉打開的文件硝逢,因此直接調(diào)用 close 即可姨拥,用 int fd = *(file_t *)file_p
將 file_p
強(qiáng)制轉(zhuǎn)換為 POSIX 的 int 型句柄,移植如下:
static lv_fs_res_t fs_close (lv_fs_drv_t * drv, void * file_p)
{
lv_fs_res_t res = LV_FS_RES_UNKNOWN;
int fd = *(file_t *)file_p;
if (close(fd) == 0)
res = LV_FS_RES_OK; // close success
return res;
}
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)
{
lv_fs_res_t res = LV_FS_RES_NOT_IMP;
/* Add your code here*/
return res;
}
需要從文件里讀出最大 btr 個(gè)字節(jié)到緩沖區(qū) buf 里渠鸽,實(shí)際讀取的字節(jié)通過 br 返回叫乌。移植如下:
static lv_fs_res_t fs_read (lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br)
{
lv_fs_res_t res = LV_FS_RES_UNKNOWN;
int fd = *(file_t *)file_p;
int read_bytes = read(fd, buf, btr);
if (read_bytes >= 0) {
*br = read_bytes;
res = LV_FS_RES_OK;
}
return res;
}
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)
{
lv_fs_res_t res = LV_FS_RES_NOT_IMP;
/* Add your code here*/
return res;
}
該函數(shù)將文件的讀寫流移動(dòng)到距文件開始的 pos 個(gè)字節(jié)處。很容易用 lseek 實(shí)現(xiàn):
static lv_fs_res_t fs_seek (lv_fs_drv_t * drv, void * file_p, uint32_t pos)
{
lv_fs_res_t res = LV_FS_RES_UNKNOWN;
int fd = *(file_t *)file_p;
if (lseek(fd, pos, SEEK_SET) >= 0)
res = LV_FS_RES_OK;
return res;
}
fs_tell
模板示例如下:
/**
* Give the position of the read write pointer
* @param drv pointer to a driver where this function belongs
* @param file_p pointer to a file_t variable.
* @param pos_p pointer to to store the result
* @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_tell (lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p)
{
lv_fs_res_t res = LV_FS_RES_NOT_IMP;
/* Add your code here*/
return res;
}
該函數(shù)需要得到文件當(dāng)前的讀寫流的位置徽缚,也可以用 lseek 來實(shí)現(xiàn):
static lv_fs_res_t fs_tell (lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p)
{
lv_fs_res_t res = LV_FS_RES_UNKNOWN;
int fd = *(file_t *)file_p;
off_t pos = lseek(fd, 0, SEEK_CUR);
if (pos >= 0) {
*pos_p = pos;
res = LV_FS_RES_OK;
}
return res;
}
測(cè)試
如何檢驗(yàn)我們是否移植成功呢憨奸?需要測(cè)試一下了。
我使用的測(cè)試代碼如下凿试,用到了上述所有回調(diào)接口排宰,初始化完成之后就可以調(diào)用進(jìn)行測(cè)試了。
注意:此時(shí)文件系統(tǒng)里需有 /lvgl/tmp.txt
這個(gè)文件那婉。
#include "lvgl/lvgl.h"
long lv_tell(lv_fs_file_t *fd)
{
uint32_t pos = 0;
lv_fs_tell(fd, &pos);
rt_kprintf("\tcur pos is: %d\n", pos);
return pos;
}
void lvgl_fs_test(void)
{
char rbuf[30] = {0};
uint32_t rsize = 0;
lv_fs_file_t fd;
lv_fs_res_t res;
res = lv_fs_open(&fd, "S:/tmp.txt", LV_FS_MODE_RD);
if (res != LV_FS_RES_OK) {
rt_kprintf("open S:/tmp.txt ERROR\n");
return ;
}
lv_tell(&fd);
lv_fs_seek(&fd, 3);
lv_tell(&fd);
res = lv_fs_read(&fd, rbuf, 100, &rsize);
if (res != LV_FS_RES_OK) {
rt_kprintf("read ERROR\n");
return ;
}
lv_tell(&fd);
rt_kprintf("READ(%d): %s",rsize , rbuf);
lv_fs_close(&fd);
}
可以進(jìn)行如下操作得到這個(gè)文件:
然后復(fù)位開發(fā)板板甘,可以看到如下現(xiàn)象:
此時(shí)說明文件系統(tǒng)移植成功。
完整的移植文件可以在我的 github 里找到详炬。