简单介绍FATFS

FatFS一般作为一个文件系统,存储介质支持USB,SD卡,NANDFlash等等, 这里主要介绍下如何对接SD卡使用。

实现的功能和效果

由于UI是采用LVGL来实现的,而且大面积用到了贴图和动画效果,占用的存储空间远远超过芯片的存储空间, 所以准备把资源部分单独以BIN格式的文件存放到sd卡上,sd卡的文件系统格式采用FAT32的,这样子程序和资源 就分离开了,程序直接存放到芯片的flash中,图片资源独立存放到sd卡上。

先看SD卡的硬件原理图

SD卡原理图 SD卡原理图 主控MCU 主控原理图

对接底层IO

操作SD卡,所有的操作模块都对应的在DEV_MMC分支里操作。

包含进IO操作相关的头文件

1
#include "SWM341.h"

我这里是SWM341的芯片,如果是其他芯片例如STM32的,则要包含对应的IO操作的头文件进来。

IO初始化

对应位置:fatfs/diskio.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize(BYTE pdrv /* Physical drive nmuber to identify the drive */
) {
	DSTATUS stat;
	int result;

	switch (pdrv) {
	case DEV_RAM:
		//result = RAM_disk_initialize();

		// translate the reslut code here

		return stat;

	case DEV_MMC:
		//result = MMC_disk_initialize();

		// translate the reslut code here
		PORT_Init(PORTM, PIN2, PORTM_PIN2_SD_CLK, 0);
		PORT_Init(PORTM, PIN4, PORTM_PIN4_SD_CMD, 1);
		PORT_Init(PORTM, PIN5, PORTM_PIN5_SD_D0, 1);
		PORT_Init(PORTM, PIN6, PORTM_PIN6_SD_D1, 1);
		PORT_Init(PORTN, PIN0, PORTN_PIN0_SD_D2, 1);
		PORT_Init(PORTN, PIN1, PORTN_PIN1_SD_D3, 1);

		result = SDIO_Init(10000000);
		if (result == SD_RES_OK) {
			stat = RES_OK;

			sd_initialized = 1;
		} else {
			stat = STA_NOINIT;

			sd_initialized = 0;
		}

		return stat;
		return stat;

	case DEV_USB:
		//result = USB_disk_initialize();

		// translate the reslut code here

		return stat;
	}
	return STA_NOINIT;
}

读取IO配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read(BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
) {
	DRESULT res;
	int result;

	switch (pdrv) {
	case DEV_RAM:
		// translate the arguments here

		//result = RAM_disk_read(buff, sector, count);

		// translate the reslut code here

		res = RES_PARERR;

		return res;

	case DEV_MMC:
		// translate the arguments here

		//result = MMC_disk_read(buff, sector, count);

		// translate the reslut code here
		if (count == 1) {
			result = SDIO_BlockRead(sector, (uint32_t*) buff);
		} else {
			result = SDIO_MultiBlockRead(sector, count, (uint32_t*) buff);
		}

		if (result == SD_RES_OK)
			res = RES_OK;
		else
			res = RES_ERROR;

		return res;

	case DEV_USB:
		// translate the arguments here

		//result = USB_disk_read(buff, sector, count);

		// translate the reslut code here
		res = RES_PARERR;

		return res;
	}

	return RES_PARERR;
}

写IO配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if FF_FS_READONLY == 0

DRESULT disk_write(BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
) {
	DRESULT res;
	int result;

	switch (pdrv) {
	case DEV_RAM:
		// translate the arguments here

		//result = RAM_disk_write(buff, sector, count);

		// translate the reslut code here

		return res;

	case DEV_MMC:
		// translate the arguments here

		//result = MMC_disk_write(buff, sector, count);

		// translate the reslut code here
		if (count == 1) {
			result = SDIO_BlockWrite(sector, (uint32_t*) buff);
		} else {
			result = SDIO_MultiBlockWrite(sector, count, (uint32_t*) buff);
		}

		if (result == SD_RES_OK)
			res = RES_OK;
		else
			res = RES_ERROR;

		return res;

	case DEV_USB:
		// translate the arguments here

		//result = USB_disk_write(buff, sector, count);

		// translate the reslut code here

		return res;
	}

	return RES_PARERR;
}

#endif

IO控制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl(BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
) {
	DRESULT res;
	int result;

	switch (pdrv) {
	case DEV_RAM:

		// Process of the command for the RAM drive
		res = RES_PARERR;

	case DEV_MMC:

		// Process of the command for the MMC/SD card
		res = RES_OK;

	case DEV_USB:

		// Process of the command the USB drive

		res = RES_PARERR;
	}

	return RES_PARERR;
}

结合LVGL使用

完整的fatfs/diskio.c 文件

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module SKELETON for FatFs     (C)ChaN, 2019        */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be        */
/* attached to the FatFs via a glue function rather than modifying it.   */
/* This is an example of glue functions to attach various exsisting      */
/* storage control modules to the FatFs module with a defined API.       */
/*-----------------------------------------------------------------------*/

#include "ff.h"			/* Obtains integer types */
#include "diskio.h"		/* Declarations of disk functions */
#include "SWM341.h"
/* Definitions of physical drive number for each drive */
#define DEV_RAM		0	/* Example: Map Ramdisk to physical drive 0 */
#define DEV_MMC		1	/* Example: Map MMC/SD card to physical drive 1 */
#define DEV_USB		2	/* Example: Map USB MSD to physical drive 2 */

/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/
static int sd_initialized = 0;

DSTATUS disk_status(BYTE pdrv /* Physical drive nmuber to identify the drive */
) {
	DSTATUS stat;
	int result;

	switch (pdrv) {
	case DEV_RAM:
		//result = RAM_disk_status();

		// translate the reslut code here
		stat = STA_NOINIT;
		return stat;

	case DEV_MMC:
		//result = MMC_disk_status();

		// translate the reslut code here
		if (sd_initialized)
			stat = RES_OK;
		else
			stat = STA_NOINIT;

		return stat;
		return stat;

	case DEV_USB:
		//result = USB_disk_status();
		stat = STA_NOINIT;
		// translate the reslut code here

		return stat;
	}
	return STA_NOINIT;
}

/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize(BYTE pdrv /* Physical drive nmuber to identify the drive */
) {
	DSTATUS stat;
	int result;

	switch (pdrv) {
	case DEV_RAM:
		//result = RAM_disk_initialize();

		// translate the reslut code here

		return stat;

	case DEV_MMC:
		//result = MMC_disk_initialize();

		// translate the reslut code here
		PORT_Init(PORTM, PIN2, PORTM_PIN2_SD_CLK, 0);
		PORT_Init(PORTM, PIN4, PORTM_PIN4_SD_CMD, 1);
		PORT_Init(PORTM, PIN5, PORTM_PIN5_SD_D0, 1);
		PORT_Init(PORTM, PIN6, PORTM_PIN6_SD_D1, 1);
		PORT_Init(PORTN, PIN0, PORTN_PIN0_SD_D2, 1);
		PORT_Init(PORTN, PIN1, PORTN_PIN1_SD_D3, 1);

		result = SDIO_Init(10000000);
		if (result == SD_RES_OK) {
			stat = RES_OK;

			sd_initialized = 1;
		} else {
			stat = STA_NOINIT;

			sd_initialized = 0;
		}

		return stat;

	case DEV_USB:
		//result = USB_disk_initialize();

		// translate the reslut code here

		return stat;
	}
	return STA_NOINIT;
}

/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read(BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
) {
	DRESULT res;
	int result;

	switch (pdrv) {
	case DEV_RAM:
		// translate the arguments here

		//result = RAM_disk_read(buff, sector, count);

		// translate the reslut code here

		res = RES_PARERR;

		return res;

	case DEV_MMC:
		// translate the arguments here

		//result = MMC_disk_read(buff, sector, count);

		// translate the reslut code here
		if (count == 1) {
			result = SDIO_BlockRead(sector, (uint32_t*) buff);
		} else {
			result = SDIO_MultiBlockRead(sector, count, (uint32_t*) buff);
		}

		if (result == SD_RES_OK)
			res = RES_OK;
		else
			res = RES_ERROR;

		return res;

	case DEV_USB:
		// translate the arguments here

		//result = USB_disk_read(buff, sector, count);

		// translate the reslut code here
		res = RES_PARERR;

		return res;
	}

	return RES_PARERR;
}

/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if FF_FS_READONLY == 0

DRESULT disk_write(BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
) {
	DRESULT res;
	int result;

	switch (pdrv) {
	case DEV_RAM:
		// translate the arguments here

		//result = RAM_disk_write(buff, sector, count);

		// translate the reslut code here

		return res;

	case DEV_MMC:
		// translate the arguments here

		//result = MMC_disk_write(buff, sector, count);

		// translate the reslut code here
		if (count == 1) {
			result = SDIO_BlockWrite(sector, (uint32_t*) buff);
		} else {
			result = SDIO_MultiBlockWrite(sector, count, (uint32_t*) buff);
		}

		if (result == SD_RES_OK)
			res = RES_OK;
		else
			res = RES_ERROR;

		return res;

	case DEV_USB:
		// translate the arguments here

		//result = USB_disk_write(buff, sector, count);

		// translate the reslut code here

		return res;
	}

	return RES_PARERR;
}

#endif

/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl(BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
) {
	DRESULT res;
	int result;

	switch (pdrv) {
	case DEV_RAM:

		// Process of the command for the RAM drive
		res = RES_PARERR;

	case DEV_MMC:

		// Process of the command for the MMC/SD card
		res = RES_OK;

	case DEV_USB:

		// Process of the command the USB drive

		res = RES_PARERR;
	}

	return RES_PARERR;
}

修改port/lv_port_fs.c文件

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
/**
 * @file lv_port_fs.c
 *
 */

/*Copy this file as "lv_port_fs.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_fs.h"
#include "fatfs/ff.h"
#include "../lvgl/lvgl.h"
#include "stdio.h"
#include <stdio.h>
#define log(fmt, arg...)	printf("[lv_port_fs] "fmt, ##arg)
/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void fs_init(void);
static bool fs_ready(struct _lv_fs_drv_t *drv);
static void* fs_open(struct _lv_fs_drv_t *drv, const char *path,
		lv_fs_mode_t mode);
static lv_fs_res_t fs_close(struct _lv_fs_drv_t *drv, void *file_p);
static lv_fs_res_t fs_read(struct _lv_fs_drv_t *drv, void *file_p, void *buf,
		uint32_t btr, uint32_t *br);
static lv_fs_res_t fs_write(struct _lv_fs_drv_t *drv, void *file_p,
		const void *buf, uint32_t btw, uint32_t *bw);
static lv_fs_res_t fs_seek(struct _lv_fs_drv_t *drv, void *file_p, uint32_t pos,
		lv_fs_whence_t whence);
static lv_fs_res_t fs_tell(struct _lv_fs_drv_t *drv, void *file_p,
		uint32_t *pos_p);
static void* fs_dir_open(struct _lv_fs_drv_t *drv, const char *path);
static lv_fs_res_t fs_dir_read(struct _lv_fs_drv_t *drv, void *rddir_p,
		char *fn);
static lv_fs_res_t fs_dir_close(struct _lv_fs_drv_t *drv, void *rddir_p);

/**********************
 *  STATIC VARIABLES
 **********************/
static FATFS fatfs;
static bool fs_is_ready;
/**********************
 * GLOBAL PROTOTYPES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

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*/
	static lv_fs_drv_t fs_drv;
	lv_fs_drv_init(&fs_drv);

	/*Set up fields...*/
	fs_drv.letter = 'S';
	fs_drv.ready_cb = fs_ready;
	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);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

//LV_FS_RES_OK=0,
//LV_FS_RES_HW_ERR,			/*低级硬件错误*/
//LV_FS_RES_FS_ERR,			/*文件系统结构中的错误*/
//LV_FS_RES_NOT_EX,			/*驱动程序、文件或目录不存在*/
//LV_FS_RES_FULL,			/*磁盘已满*/
//LV_FS_RES_LOCKED,			/*文件已打开*/
//LV_FS_RES_DENIED,			/*访问被拒绝。检查“fs_打开”模式和写保护*/
//LV_FS_RES_BUSY,			/*文件系统现在无法处理它,请稍后再试*/
//LV_FS_RES_TOUT,			/*过程时间路由*/
//LV_FS_RES_NOT_IMP,		/*请求的函数未实现*/
//LV_FS_RES_OUT_OF_MEM,		/*内存不足,无法执行内部操作*/
//LV_FS_RES_INV_PARAM,		/*参数中的参数无效*/
//LV_FS_RES_UNKNOWN,		/*其他未知错误*/
static lv_fs_res_t to_lvfs_res(FRESULT res) {
	switch (res) {
	case FR_OK:
		return LV_FS_RES_OK; /*(0)成功*/
	case FR_DISK_ERR:
		return LV_FS_RES_HW_ERR; /*(1)在低级别磁盘I/O层中发生了一个硬错误*/
	case FR_INT_ERR:
		return LV_FS_RES_FS_ERR; /*(2)断言失败*/
	case FR_NOT_READY:
		return LV_FS_RES_HW_ERR; /*(3)物理驱动器无法工作*/
	case FR_NO_FILE:
		return LV_FS_RES_NOT_EX; /*(4)找不到该文件*/
	case FR_NO_PATH:
		return LV_FS_RES_NOT_EX; /*(5)找不到路径*/
	case FR_INVALID_NAME:
		return LV_FS_RES_INV_PARAM; /*(6)路径名格式无效*/
	case FR_DENIED:
		return LV_FS_RES_DENIED; /*(7)拒绝访问:磁盘以满\使用写模式打开只读文件\删除只读文件...等等*/
	case FR_EXIST:
		return LV_FS_RES_DENIED; /*(8)已经存在同名的文件或目录*/
	case FR_INVALID_OBJECT:
		return LV_FS_RES_INV_PARAM; /*(9)文件/目录对象无效*/
	case FR_WRITE_PROTECTED:
		return LV_FS_RES_DENIED; /*(10)物理驱动器是写保护的*/
	case FR_INVALID_DRIVE:
		return LV_FS_RES_INV_PARAM; /*(11)逻辑驱动器号无效*/
	case FR_NOT_ENABLED:
		return LV_FS_RES_HW_ERR; /*(12)当前卷没有工作区*/
	case FR_NO_FILESYSTEM:
		return LV_FS_RES_HW_ERR; /*(13)没有有效的FAT卷*/
	case FR_MKFS_ABORTED:
		return LV_FS_RES_INV_PARAM; /*(14)f_MKFS()在格式化开始前终止*/
	case FR_TIMEOUT:
		return LV_FS_RES_BUSY; /*(15)无法在定义的时间段内获得访问卷的授权*/
	case FR_LOCKED:
		return LV_FS_RES_LOCKED; /*(16)根据文件共享策略拒绝该操作*/
	case FR_NOT_ENOUGH_CORE:
		return LV_FS_RES_OUT_OF_MEM; /*(17)无法分配LFN工作缓冲区*/
	case FR_TOO_MANY_OPEN_FILES:
		return LV_FS_RES_BUSY; /*(18)打开的文件数目大于 FF_FS_LOCK*/
	case FR_INVALID_PARAMETER:
		return LV_FS_RES_INV_PARAM; /*(19)给定参数无效*/
	}
	return LV_FS_RES_UNKNOWN;
}

/*Initialize your Storage device and File system.*/
static void fs_init(void) {
	/*E.g. for FatFS initialize the SD card and FatFS itself*/
	FRESULT res;
	res = f_mount(&fatfs, "sd:", 1);
	if (res != FR_OK) {
		fs_is_ready = false;
		log("f_mount failed...\n");
		return;
	}

	log("f_mount success...\n");
	fs_is_ready = true;
	return;
}

/***********************************************************************************
 *	函数: 			fs_ready
 *	功能:			LVGL 会通过此函数判断文件系统是否就绪
 *	参数:
 *		drv:		上下文, 我的理解, 便于取 letter、user_data 成员变量
 *	返回值:
 *		true:		已就绪
 *		false:		未就绪
 ***********************************************************************************/
static bool fs_ready(struct _lv_fs_drv_t *drv) {
	FRESULT res;
	if (false == fs_is_ready) {
		res = f_mount(&fatfs, "sd:", 1);
		if (res != FR_OK) {
			fs_is_ready = false;
			log("f_mount failed...\n");
			return fs_is_ready;
		}

		if (false == fs_is_ready) {
			log("f_mount failed...\n");
		}
	}
	return fs_is_ready;
}

/***********************************************************************************
 *	函数: 			fs_open
 *	功能:			LVGL 会通过此函数打开指定的文件
 *	参数:
 *		drv:		上下文, 我的理解, 便于取 letter、user_data 成员变量
 *		path:		要打开的文件路径
 *		mode:		打开模式 LV_FS_MODE_WR\LV_FS_MODE_RD
 *	返回值:
 *		>0:			返回打开文件的句柄,后续操作皆以此句柄为核心(void * file_p)
 *		NULL:		打开文件失败
 ***********************************************************************************/
static void* fs_open(struct _lv_fs_drv_t *drv, const char *path,
		lv_fs_mode_t mode) {
	FRESULT fr_res;
	unsigned char f_mode = 0x00;
	char *path_buf = NULL;
	uint16_t path_len = strlen(path);

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

	// 根据传入的letter判断是什么存储设备
	switch (drv->letter) {
	case 'S':       // SD card
		path_buf = (char*) lv_mem_alloc(sizeof(char) * (path_len + 4));
		log("origin path:%s",path);
		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的函数 */

	FIL *fil = (FIL*) lv_mem_alloc(sizeof(FIL));
	if (NULL == fil) {
		log("fs_open: lv_mem_alloc failed %d Byte...\n", sizeof(FIL));
		return NULL;
	}

	/* 文件操作方法,将FatFS的转换成LVGL的操作方法 */
	if (mode == LV_FS_MODE_WR) {
		f_mode = FA_OPEN_ALWAYS | FA_WRITE;
	} else if (mode == LV_FS_MODE_RD) {
		f_mode = FA_OPEN_EXISTING | FA_READ;
	} else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) {
		f_mode = FA_WRITE | FA_READ;
	}

	if ((fr_res = f_open(fil, path_buf, f_mode)) != FR_OK) {
		//释放内存
		lv_mem_free(fil);
		log("f_open failed:[0x%.2X][%d][%s]...\n", f_mode, fr_res, path_buf);
		return NULL;
	}
	//释放内存
	lv_mem_free(path_buf);
	return (void*) fil;
}

/***********************************************************************************
 *	函数: 			fs_close
 *	功能:			LVGL 会通过此函数关闭指定的文件
 *	参数:
 *		drv:		上下文, 我的理解, 便于取 letter、user_data 成员变量
 *		file_p: 	文件对应的句柄, 由 fs_open() 成功时取得
 *	返回值:			参考 lv_fs_res_t
 ***********************************************************************************/
static lv_fs_res_t fs_close(struct _lv_fs_drv_t *drv, void *file_p) {
	if (file_p) {
		f_close((FIL*) file_p);
		lv_mem_free(file_p);
		return LV_FS_RES_OK;
	}

	return LV_FS_RES_INV_PARAM;
}

/***********************************************************************************
 *	函数: 			fs_close
 *	功能:			LVGL 会通过此函数读取指定的文件
 *	参数:
 *		drv:		上下文, 我的理解, 便于取 letter、user_data 成员变量
 *		file_p: 	文件对应的句柄, 由 fs_open() 成功时取得
 *		buf:		存储文件数据的缓冲区
 *		btr:		存储文件数据的缓冲区大小
 *		br:			实际读取文件的数据长度
 *	返回值:			参考 lv_fs_res_t
 ***********************************************************************************/
static lv_fs_res_t fs_read(struct _lv_fs_drv_t *drv, void *file_p, void *buf,
		uint32_t btr, uint32_t *br) {
	FRESULT fr_res = FR_OK;

	if ((fr_res = f_read((FIL*) file_p, buf, btr, br)) != FR_OK) {
		log("f_read failed:[%d]...\n", fr_res);
	} else {
		log("f_read success:btr:[%d] br:[%d]...\n", btr, *br);
	}
	return to_lvfs_res(fr_res);
}

/***********************************************************************************
 *	函数: 			fs_write
 *	功能:			LVGL 会通过此函数写入指定的文件
 *	参数:
 *		drv:		上下文, 我的理解, 便于取 letter、user_data 成员变量
 *		file_p: 	文件对应的句柄, 由 fs_open() 成功时取得
 *		buf:		存储要写入文件的数据
 *		btw:		写于文件数据的长度
 *		bw:			实际写入文件的数据长度
 *	返回值:			参考 lv_fs_res_t
 ***********************************************************************************/
static lv_fs_res_t fs_write(struct _lv_fs_drv_t *drv, void *file_p,
		const void *buf, uint32_t btw, uint32_t *bw) {
	FRESULT fr_res = f_write((FIL*) file_p, buf, btw, bw);

	if (FR_OK == fr_res && *bw < btw) {
		log("fs_write failed disk full: %d < %d...\n", *bw, btw);
		return LV_FS_RES_FULL;
	}

	if (FR_OK != fr_res) {
		log("f_write failed:[%d]...\n", fr_res);
	}

	return to_lvfs_res(fr_res);
}

/***********************************************************************************
 *	函数: 			fs_seek
 *	功能:			LVGL 会通过此函数调整文件指针的偏移位置
 *	参数:
 *		drv:		上下文, 我的理解, 便于取 letter、user_data 成员变量
 *		file_p: 	文件对应的句柄, 由 fs_open() 成功时取得
 *		pos:		文件位置
 *		whence:		指定以什么方式调整: LV_FS_SEEK_SET(起始)、 LV_FS_SEEK_CUR(当前)、 LV_FS_SEEK_END(结束)
 *	返回值:			参考 lv_fs_res_t
 ***********************************************************************************/
static lv_fs_res_t fs_seek(struct _lv_fs_drv_t *drv, void *file_p, uint32_t pos,
		lv_fs_whence_t whence) {
	switch (whence) {
	case LV_FS_SEEK_SET:
		return to_lvfs_res(f_lseek((FIL*) file_p, pos));
	case LV_FS_SEEK_CUR:
		return to_lvfs_res(f_lseek((FIL*) file_p, f_tell((FIL *)file_p) + pos));
	case LV_FS_SEEK_END:
		return to_lvfs_res(f_lseek((FIL*) file_p, f_size((FIL *)file_p) + pos));
	}
	return LV_FS_RES_INV_PARAM;
}

/***********************************************************************************
 *	函数: 			fs_tell
 *	功能:			LVGL 会通过此函数获取当前文件指针的位置
 *	参数:
 *		drv:		上下文, 我的理解, 便于取 letter、user_data 成员变量
 *		file_p: 	文件对应的句柄, 由 fs_open() 成功时取得
 *		pos:		通过此参数返回文件指针的位置
 *	返回值:			参考 lv_fs_res_t
 ***********************************************************************************/
static lv_fs_res_t fs_tell(struct _lv_fs_drv_t *drv, void *file_p,
		uint32_t *pos_p) {
	return to_lvfs_res(f_tell((FIL* )file_p));
}

/***********************************************************************************
 *	函数: 			fs_dir_open
 *	功能:			LVGL 会通过此函数打开当前目录
 *	参数:
 *		drv:		上下文, 我的理解, 便于取 letter、user_data 成员变量
 *		path: 	 	要打开的目录路径
 *	返回值:
 *		>0:			返回打开目录的句柄,后续操作皆以此句柄为核心(void * rddir_p)
 *		NULL:		打开目录失败
 ***********************************************************************************/
static void* fs_dir_open(struct _lv_fs_drv_t *drv, const char *path) {
	FRESULT fr_res = FR_OK;

	DIR *dir = (DIR*) lv_mem_alloc(sizeof(DIR));
	if (NULL == dir) {
		log("f_opendir: lv_mem_alloc %d byte failed...\n", sizeof(DIR));
		return NULL;
	}

	if ((fr_res = f_opendir(dir, path)) != FR_OK) {
		lv_mem_free(dir);
		log("f_opendir failed:[%d][%s]...\n", fr_res, path);
		return NULL;
	}

	return (void*) dir;
}

/***********************************************************************************
 *	函数: 			fs_dir_read
 *	功能:			LVGL 会通过此函数获取当前目录的条目(即该目录下有哪些文件夹和文件)
 *	参数:
 *		drv:		上下文, 我的理解, 便于取 letter、user_data 成员变量
 *		rddir_p: 	目录对应的句柄, 由 fs_dir_open() 成功时取得
 *		fn:			通过此参数返回目录内容, 一次只返回一个条目(如有十个文件, 只返回一个)
 *	返回值:			参考 lv_fs_res_t
 *	备注说明:
 *					1. 没有条目或读取完条目则返回空文本内容到 fn (即 \0)
 *					2. LGVL 对上述似乎并没有明确要求, 并且参数也显得不合理(缺少缓冲区长度信息)
 *					3. 使用时务必注意 fn 越界问题, fn 缓冲区大小必须大于 FILINFO->fname+1 大小
 ***********************************************************************************/
static lv_fs_res_t fs_dir_read(struct _lv_fs_drv_t *drv, void *rddir_p,
		char *fn) {
	FRESULT fr_res = FR_OK;
	FILINFO fno;

	fr_res = f_readdir((DIR*) rddir_p, &fno);
	if (fr_res != FR_OK) {
		log("f_readdir failed:[%d]...\n", fr_res);
		return to_lvfs_res(fr_res);
	}

	if ('\0' == fno.fname[0]) {
		fn[0] = '\0';
		return LV_FS_RES_OK;
	}

	if (fno.fattrib & AM_DIR) {
		fn[0] = '/';
		fn++;
	}

	// 注意, 这里会存在内存溢出的风险, LVGL 的接口并未提供缓冲区长度信息
	strcpy(fn, fno.fname); // strncpy(fn, fno.fname, fn_size);

	return LV_FS_RES_OK;
}

/***********************************************************************************
 *	函数: 			fs_dir_close
 *	功能:			LVGL 会通过此函数关闭指定目录
 *	参数:
 *		drv:		上下文, 我的理解, 便于取 letter、user_data 成员变量
 *		rddir_p: 	目录对应的句柄, 由 fs_dir_open() 成功时取得
 *	返回值:			参考 lv_fs_res_t
 ***********************************************************************************/
static lv_fs_res_t fs_dir_close(struct _lv_fs_drv_t *drv, void *rddir_p) {
	if (rddir_p) {
		f_closedir((DIR*) rddir_p);
		lv_mem_free(rddir_p);
		return LV_FS_RES_OK;
	}

	return LV_FS_RES_INV_PARAM;
}

#else /*Enable this file at the top*/

/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif