一、FAL管理与示例

FAL (Flash Abstraction Layer) Flash 抽象层,是对 Flash 及基于 Flash 的分区进行管理、操作的抽象层,对上层统一了 Flash 及 分区操作的 API ,FAL 框架图如下:

FAL框架图

从上图可以看出FAL抽象层位于SFUD框架的上层,可以将多个Flash硬件(包括片内Flash和片外Flash)统一进行管理,并向上层比如DFS文件系统层提供对底层多个Flash硬件的统一访问接口,方便上层应用对底层硬件的访问操作。

1.1 FAL软件包源码获取

从Github下载的RT-Thread源码中并没有FAL软件包的源码,我们可以通过git工具获取,menuconfig中也配置了FAL软件包的配置项,在配置项内有FAL软件包的下载地址等信息,我们配置启用FAL软件包后即可通过git工具(前提是需要安装并配置后Git工具)自动下载FAL软件包源码到工程目录。

在前篇博客的工程目录stm32l475_dfs_sample中打开env环境,运行menuconfig命令,启用FAL配置界面如下:

启用FAL软件包的配置界面

启用FAL软件包后出现更多选项,我们想使用FAL管理STM32L475片内Flash和W25Q128 Flash,其中W25Q128使用SFUD框架驱动,在启用FAL软件包后,我们可以选择使用SFUD驱动,SFUD设备名默认为norflash0,可以更改设备名为W25Q128,使用SFUD驱动的配置界面如下:

FAL使用SFUD的配置界面

保存配置退出后,在env环境执行pkgs –update命令,会自动从FAL的github仓库获取FAL软件包源码到本地工程目录,如下图所示:

获取FAL软件包

获取到的FAL软件包目录结构如下:

FAL目录结构

1.2 FAL管理

FAL既然是对多个Flash设备进行分区管理的,自然会对Flash设备和分区有相应的数据结构描述。

FAL设备与分区控制块

FAL设备与FAL分区的描述结构体如下:

 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

// projects\stm32l475_dfs_sample\packages\fal-latest\inc\fal_def.h

 

/* FAL flash and partition device name max length */

#ifndef FAL_DEV_NAME_MAX

#define FAL_DEV_NAME_MAX 24

#endif

 

struct fal_flash_dev

{

    char name[FAL_DEV_NAME_MAX];

 

    /* flash device start address and len  */

    uint32_t addr;

    size_t len;

    /* the block size in the flash for erase minimum granularity */

    size_t blk_size;

 

    struct

    {

        int (*init)(void);

        int (*read)(long offset, uint8_t *buf, size_t size);

        int (*write)(long offset, const uint8_t *buf, size_t size);

        int (*erase)(long offset, size_t size);

    } ops;

};

typedef struct fal_flash_dev *fal_flash_dev_t;

 

/**

 * FAL partition

 */

struct fal_partition

{

    uint32_t magic_word;

 

    /* partition name */

    char name[FAL_DEV_NAME_MAX];

    /* flash device name for partition */

    char flash_name[FAL_DEV_NAME_MAX];

 

    /* partition offset address on flash device */

    long offset;

    size_t len;

 

    uint32_t reserved;

};

typedef struct fal_partition *fal_partition_t;

fal_flash_dev结构体除了包含设备名、起始地址、长度、块大小等对flash设备的描述参数,还包括对flash设备的操作函数指针,这些操作函数需要在移植FAL时由下层的驱动实现。

fal_partition结构体则包含分区名、设备名、分区在设备上的偏移地址和长度等,从该结构体定义也可以看出,一个fal_partition分区不能跨flash设备分配。

FAL初始化过程

了解FAL原理,先从FAL组件初始化过程开始:

 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

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal.c

 

/**

 * FAL (Flash Abstraction Layer) initialization.

 * It will initialize all flash device and all flash partition.

 *

 * @return >= 0: partitions total number

 */

int fal_init(void)

{

    extern int fal_flash_init(void);

    extern int fal_partition_init(void);

 

    int result;

 

    /* initialize all flash device on FAL flash table */

    result = fal_flash_init();

 

    if (result < 0) {

        goto __exit;

    }

 

    /* initialize all flash partition on FAL partition table */

    result = fal_partition_init();

 

__exit:

 

    if ((result > 0) && (!init_ok))

    {

        init_ok = 1;

        log_i("RT-Thread Flash Abstraction Layer (V%s) initialize success.", FAL_SW_VERSION);

    }

    else if(result <= 0)

    {

        init_ok = 0;

        log_e("RT-Thread Flash Abstraction Layer (V%s) initialize failed.", FAL_SW_VERSION);

    }

 

    return result;

}
 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

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_flash.c



static const struct fal_flash_dev * const device_table[] = FAL_FLASH_DEV_TABLE;

static const size_t device_table_len = sizeof(device_table) / sizeof(device_table[0]);

static uint8_t init_ok = 0;



/**

* Initialize all flash device on FAL flash table

*

* @return result

*/

int fal_flash_init(void)

{

   size_t i;



   if (init_ok)

   {

       return 0;

   }



   for (i = 0; i < device_table_len; i++)

   {

       assert(device_table[i]->ops.read);

       assert(device_table[i]->ops.write);

       assert(device_table[i]->ops.erase);

       /* init flash device on flash table */

       if (device_table[i]->ops.init)

       {

           device_table[i]->ops.init();

       }

       ......

   }

   init_ok = 1;

   return 0;

}
  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

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_partition.c

 

USED static const struct fal_partition partition_table_def[] SECTION("FalPartTable") = FAL_PART_TABLE;

static const struct fal_partition *partition_table = NULL;

 

/**

 * Initialize all flash partition on FAL partition table

 *

 * @return partitions total number

 */

int fal_partition_init(void)

{

    size_t i;

    const struct fal_flash_dev *flash_dev = NULL;

 

    if (init_ok)

    {

        return partition_table_len;

    }

 

#ifdef FAL_PART_HAS_TABLE_CFG

    partition_table = &partition_table_def[0];

    partition_table_len = sizeof(partition_table_def) / sizeof(partition_table_def[0]);

#else

    /* load partition table from the end address FAL_PART_TABLE_END_OFFSET, error return 0 */

    long part_table_offset = FAL_PART_TABLE_END_OFFSET;

    size_t table_num = 0, table_item_size = 0;

    uint8_t part_table_find_ok = 0;

    uint32_t read_magic_word;

    fal_partition_t new_part = NULL;

 

    flash_dev = fal_flash_device_find(FAL_PART_TABLE_FLASH_DEV_NAME);

    if (flash_dev == NULL)

    {

        log_e("Initialize failed! Flash device (%s) NOT found.", FAL_PART_TABLE_FLASH_DEV_NAME);

        goto _exit;

    }

 

    /* check partition table offset address */

    if (part_table_offset < 0 || part_table_offset >= (long) flash_dev->len)

    {

        log_e("Setting partition table end offset address(%ld) out of flash bound(<%d).", part_table_offset, flash_dev->len);

        goto _exit;

    }

 

    table_item_size = sizeof(struct fal_partition);

    new_part = (fal_partition_t)FAL_MALLOC(table_item_size);

    if (new_part == NULL)

    {

        log_e("Initialize failed! No memory for table buffer.");

        goto _exit;

    }

 

    /* find partition table location */

    {

        uint8_t read_buf[64];

 

        part_table_offset -= sizeof(read_buf);

        while (part_table_offset >= 0)

        {

            if (flash_dev->ops.read(part_table_offset, read_buf, sizeof(read_buf)) > 0)

            {

                /* find magic word in read buf */

                for (i = 0; i < sizeof(read_buf) - sizeof(read_magic_word) + 1; i++)

                {

                    read_magic_word = read_buf[0 + i] + (read_buf[1 + i] << 8) + (read_buf[2 + i] << 16) + (read_buf[3 + i] << 24);

                    if (read_magic_word == ((FAL_PART_MAGIC_WORD_H << 16) + FAL_PART_MAGIC_WORD_L))

                    {

                        part_table_find_ok = 1;

                        part_table_offset += i;

                        log_d("Find the partition table on '%s' offset @0x%08lx.", FAL_PART_TABLE_FLASH_DEV_NAME,

                                part_table_offset);

                        break;

                    }

                }

            }

            else

            {

                /* read failed */

                break;

            }

 

            if (part_table_find_ok)

            {

                break;

            }

            else

            {

                /* calculate next read buf position */

                if (part_table_offset >= (long)sizeof(read_buf))

                {

                    part_table_offset -= sizeof(read_buf);

                    part_table_offset += (sizeof(read_magic_word) - 1);

                }

                else if (part_table_offset != 0)

                {

                    part_table_offset = 0;

                }

                else

                {

                    /* find failed */

                    break;

                }

            }

        }

    }

 

    /* load partition table */

    while (part_table_find_ok)

    {

        memset(new_part, 0x00, table_num);

        if (flash_dev->ops.read(part_table_offset - table_item_size * (table_num), (uint8_t *) new_part,

                table_item_size) < 0)

        {

            log_e("Initialize failed! Flash device (%s) read error!", flash_dev->name);

            table_num = 0;

            break;

        }

 

        if (new_part->magic_word != ((FAL_PART_MAGIC_WORD_H << 16) + FAL_PART_MAGIC_WORD_L))

        {

            break;

        }

 

        partition_table = (fal_partition_t) FAL_REALLOC(partition_table, table_item_size * (table_num + 1));

        if (partition_table == NULL)

        {

            log_e("Initialize failed! No memory for partition table");

            table_num = 0;

            break;

        }

 

        memcpy(partition_table + table_num, new_part, table_item_size);

 

        table_num++;

    };

 

    if (table_num == 0)

    {

        log_e("Partition table NOT found on flash: %s (len: %d) from offset: 0x%08x.", FAL_PART_TABLE_FLASH_DEV_NAME,

                FAL_DEV_NAME_MAX, FAL_PART_TABLE_END_OFFSET);

        goto _exit;

    }

    else

    {

        partition_table_len = table_num;

    }

#endif /* FAL_PART_HAS_TABLE_CFG */

 

    /* check the partition table device exists */

 

    for (i = 0; i < partition_table_len; i++)

    {

        flash_dev = fal_flash_device_find(partition_table[i].flash_name);

        if (flash_dev == NULL)

        {

            log_d("Warning: Do NOT found the flash device(%s).", partition_table[i].flash_name);

            continue;

        }

 

        if (partition_table[i].offset >= (long)flash_dev->len)

        {

            log_e("Initialize failed! Partition(%s) offset address(%ld) out of flash bound(<%d).",

                    partition_table[i].name, partition_table[i].offset, flash_dev->len);

            partition_table_len = 0;

            goto _exit;

        }

    }

 

    init_ok = 1;

 

_exit:

 

#if FAL_DEBUG

    fal_show_part_table();

#endif

 

#ifndef FAL_PART_HAS_TABLE_CFG

    if (new_part)

    {

        FAL_FREE(new_part);

    }

#endif /* !FAL_PART_HAS_TABLE_CFG */

 

    return partition_table_len;

}

FAL组件初始化最重要的是维护两个表:一个是flash设备表;另一个是FAL分区表,两个表的元素分别是前面介绍过的fal_flash_dev结构体地址和fal_partition结构体对象。

fal_flash_dev设备表主要由底层的Flash驱动(包括MCU片内Flash和SFUD驱动的片外Flash)提供,也即FAL移植的重点就是在Flash驱动层向FAL提供fal_flash_dev设备表,每个flash设备提供设备表中的一个元素。

fal_partition分区表由用户事先配置在fal_cfg.h头文件中,FAL向上面的用户层提供的分区访问接口函数操作的内存区间就是从fal_partition分区表获取的,最后对分区的访问还是通过Flash驱动提供的接口函数(fal_flash_dev.ops)实现的。

FAL分区管理接口

FAL主要是进行分区管理的,所以向应用层提供的接口函数主要也是对分区的访问,Flash分区访问接口函数要想访问到Flash硬件设备,最终需要调用Flash驱动向FAL提供的接口函数指针实现,FAL分区访问接口函数声明如下:

  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

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_flash.c

 

/**

 * find flash device by name

 *

 * @param name flash device name

 *

 * @return != NULL: flash device

 *            NULL: not found

 */

const struct fal_flash_dev *fal_flash_device_find(const char *name);

 

 

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_partition.c

 

/**

 * find the partition by name

 *

 * @param name partition name

 *

 * @return != NULL: partition

 *            NULL: not found

 */

const struct fal_partition *fal_partition_find(const char *name);

 

/**

 * get the partition table

 *

 * @param len return the partition table length

 *

 * @return partition table

 */

const struct fal_partition *fal_get_partition_table(size_t *len);

 

/**

 * set partition table temporarily

 * This setting will modify the partition table temporarily, the setting will be lost after restart.

 *

 * @param table partition table

 * @param len partition table length

 */

void fal_set_partition_table_temp(struct fal_partition *table, size_t len);

 

/**

 * read data from partition

 *

 * @param part partition

 * @param addr relative address for partition

 * @param buf read buffer

 * @param size read size

 *

 * @return >= 0: successful read data size

 *           -1: error

 */

int fal_partition_read(const struct fal_partition *part, uint32_t addr, uint8_t *buf, size_t size);

 

/**

 * write data to partition

 *

 * @param part partition

 * @param addr relative address for partition

 * @param buf write buffer

 * @param size write size

 *

 * @return >= 0: successful write data size

 *           -1: error

 */

int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size);

 

/**

 * erase partition data

 *

 * @param part partition

 * @param addr relative address for partition

 * @param size erase size

 *

 * @return >= 0: successful erased data size

 *           -1: error

 */

int fal_partition_erase(const struct fal_partition *part, uint32_t addr, size_t size);

 

/**

 * erase partition all data

 *  * @param part partition

 *  * @return >= 0: successful erased data size

 *           -1: error

 */

int fal_partition_erase_all(const struct fal_partition *part);

FAL分区转设备接口

前篇博客介绍DFS elmfat文件系统时谈到,elmfat文件系统只能挂载到块设备上,FAL管理的分区只是一段连续的flash存储空间,并不是一个块设备。前篇博客将DFS elmfat文件系统挂载到W25Q128 Flash上,实际就是挂载到一个块设备上,SFUD将W25Q128 Flash注册为一个块设备,所以可以顺利挂载。

有时候我们想在一个Flash设备上分出多个空间分别用于不同的用途,比如FAL框架图中展示的,一部分空间用于挂载文件系统,一部分空间用于存储非易失配置参数,另一部分空间用于存储OTA文件。这就需要把一个flash物理设备转换为多个逻辑设备,FAL便提供了将flash分区转换为BLK/MTD/Char设备的功能。

FAL分区转换BLK/MTD/Char设备的过程有很大的类似性,这里以FAL分区转BLK块设备为例,说明其工作原理。首先看FAL块设备的数据结构描述:

  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

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_rtt.c

 

struct fal_blk_device

{

    struct rt_device                parent;

    struct rt_device_blk_geometry   geometry;

    const struct fal_partition     *fal_part;

};

接着看FAL块设备的创建与注册过程:



// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_rtt.c

 

/**

 * create RT-Thread block device by specified partition

 *

 * @param parition_name partition name

 *

 * @return != NULL: created block device

 *            NULL: created failed

 */

struct rt_device *fal_blk_device_create(const char *parition_name)

{

    struct fal_blk_device *blk_dev;

    const struct fal_partition *fal_part = fal_partition_find(parition_name);

    const struct fal_flash_dev *fal_flash = NULL;

 

    if (!fal_part)

    {

        log_e("Error: the partition name (%s) is not found.", parition_name);

        return NULL;

    }

 

    if ((fal_flash = fal_flash_device_find(fal_part->flash_name)) == NULL)

    {

        log_e("Error: the flash device name (%s) is not found.", fal_part->flash_name);

        return NULL;

    }

 

    blk_dev = (struct fal_blk_device*) rt_malloc(sizeof(struct fal_blk_device));

    if (blk_dev)

    {

        blk_dev->fal_part = fal_part;

        blk_dev->geometry.bytes_per_sector = fal_flash->blk_size;

        blk_dev->geometry.block_size = fal_flash->blk_size;

        blk_dev->geometry.sector_count = fal_part->len / fal_flash->blk_size;

 

        /* register device */

        blk_dev->parent.type = RT_Device_Class_Block;

 

#ifdef RT_USING_DEVICE_OPS

        blk_dev->parent.ops  = &blk_dev_ops;

#else

        blk_dev->parent.init = NULL;

        blk_dev->parent.open = NULL;

        blk_dev->parent.close = NULL;

        blk_dev->parent.read = blk_dev_read;

        blk_dev->parent.write = blk_dev_write;

        blk_dev->parent.control = blk_dev_control;

#endif

 

        /* no private */

        blk_dev->parent.user_data = RT_NULL;

 

        log_i("The FAL block device (%s) created successfully", fal_part->name);

        rt_device_register(RT_DEVICE(blk_dev), fal_part->name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STANDALONE);

    }

    else

    {

        log_e("Error: no memory for create FAL block device");

    }

 

    return RT_DEVICE(blk_dev);

}

 

#ifdef RT_USING_DEVICE_OPS

const static struct rt_device_ops blk_dev_ops =

{

    RT_NULL,

    RT_NULL,

    RT_NULL,

    blk_dev_read,

    blk_dev_write,

    blk_dev_control

};

#endif

FAL块设备的创建跟 I / O设备模型框架中设备的创建注册过程类似,主要是还是向 I / O设备模型框架注册一个块设备及其接口函数,使该设备可以通过 I / O设备管理接口访问。

FAL创建块设备向I / O设备管理层注册的访问接口函数最终调用的是FAL分区访问接口函数,下面以块设备写入接口函数实现过程为例进行说明:

 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

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_rtt.c

 

static rt_size_t blk_dev_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size)

{

    int ret = 0;

    struct fal_blk_device *part;

    rt_off_t phy_pos;

    rt_size_t phy_size;

 

    part = (struct fal_blk_device*) dev;

    assert(part != RT_NULL);

 

    /* change the block device's logic address to physical address */

    phy_pos = pos * part->geometry.bytes_per_sector;

    phy_size = size * part->geometry.bytes_per_sector;

 

    ret = fal_partition_erase(part->fal_part, phy_pos, phy_size);

 

    if (ret == (int) phy_size)

    {

        ret = fal_partition_write(part->fal_part, phy_pos, buffer, phy_size);

    }

 

    if (ret != (int) phy_size)

    {

        ret = 0;

    }

    else

    {

        ret = size;

    }

 

    return ret;

}

 
 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

// projects\stm32l475_dfs_sample\packages\fal-latest\src\fal_partition.c

 

int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size)

{

    int ret = 0;

    const struct fal_flash_dev *flash_dev = NULL;

 

    assert(part);

    assert(buf);

 

    if (addr + size > part->len)

    {

        log_e("Partition write error! Partition address out of bound.");

        return -1;

    }

 

    flash_dev = fal_flash_device_find(part->flash_name);

    if (flash_dev == NULL)

    {

        log_e("Partition write error!  Don't found flash device(%s) of the partition(%s).", part->flash_name, part->name);

        return -1;

    }

 

    ret = flash_dev->ops.write(part->offset + addr, buf, size);

    if (ret < 0)

    {

        log_e("Partition write error! Flash device(%s) write error!", part->flash_name);

    }

 

    return ret;

}

1.3 FAL移植

前面介绍了FAL移植的关键是向其提供fal_flash_dev设备表,也相当于flash驱动层向FAL抽象层提供该flash设备的参数及访问接口函数。

考虑到packages下面的软件版本后续可能会升级覆盖,我们不在\packages\fal-latest目录下直接进行移植修改,而是在packages目录外新建一个文件夹ports专门保存软件包的移植文件信息。

新建与packages软件包同级的移植文件目录ports,将packages\fal-latest\samples\porting目录下的fal_cfg.h与fal_flash_sfud_port.c文件复制一份到ports\fal目录下,将packages\fal-latest\SConscript复制一份到ports\fal目录下,将packages\SConscript复制一份到ports目录下,复制文件后的目录结构如下图所示:

新建移植文件目录ports

由于不再使用packages\fal-latest\samples\porting目录下的移植文件,可以将packages\fal-latest\SConscript文件中如下的代码删除:

1
2
3
4
5
6
7
8

// projects\stm32l475_dfs_sample\packages\fal-latest\SConscript

/* Delete the code below */

if GetDepend(['FAL_USING_SFUD_PORT']):

    src += Glob('samples\porting\fal_flash_sfud_port.c')

由于ports\fal目录及下面的文件名有变化,所以需要修改编译脚本ports\fal\SConscript,主要是修改文件目录及文件名,修改后的编译脚本如下:

 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

// projects\stm32l475_dfs_sample\ports\fal\SConscript

 

from building import *

import rtconfig

 

cwd     = GetCurrentDir()

src     = []

CPPPATH = [cwd]

LOCAL_CCFLAGS = ''

 

if GetDepend(['FAL_USING_SFUD_PORT']):

    src += Glob('fal_flash_sfud_port.c')

 

if rtconfig.CROSS_TOOL == 'gcc':

    LOCAL_CCFLAGS += ' -std=c99'

elif rtconfig.CROSS_TOOL == 'keil':

    LOCAL_CCFLAGS += ' --c99'

 

group = DefineGroup('fal', src, depend = ['PKG_USING_FAL'], CPPPATH = CPPPATH, LOCAL_CCFLAGS = LOCAL_CCFLAGS)

 

Return('group')

完成上面的修改后,我们新建的移植文件目录ports下的移植文件就可以通过scons命令编译进工程内了,下面开始修改ports\fal目录下的移植文件。

FAL SFUD(W25Q128 Flash)移植

我们想使用FAL管理STM32L475片内Flash和W25Q128片外Flash,W25Q128 Flash的驱动由SFUD框架提供。FAL提供了SFUD的移植示例文件\fal-latest\samples\porting\fal_flash_sfud_port.c,该文件已经被复制到ports\fal目录下,我们可以直接使用该文件。

FAL提供的SFUD移植示例中,SFUD框架向FAL提供的fal_flash_dev设备表项如下:

 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

// projects\stm32l475_dfs_sample\ports\fal\fal_flash_sfud_port.c

 

#ifndef FAL_USING_NOR_FLASH_DEV_NAME

#define FAL_USING_NOR_FLASH_DEV_NAME             "norflash0"

#endif

 

static sfud_flash_t sfud_dev = NULL;

struct fal_flash_dev nor_flash0 = {FAL_USING_NOR_FLASH_DEV_NAME, 0, 8 * 1024 * 1024, 4096, {init, read, write, erase}};

 

 

// projects\stm32l475_dfs_sample\rtconfig.h

 

/* system packages */

#define PKG_USING_FAL

#define FAL_DEBUG_CONFIG

#define FAL_DEBUG 1

#define FAL_PART_HAS_TABLE_CFG

#define FAL_USING_SFUD_PORT

#define FAL_USING_NOR_FLASH_DEV_NAME "W25Q128"

#define PKG_USING_FAL_LATEST_VERSION

#define PKG_FAL_VER_NUM 0x99999

在menuconfig FAL配置项中我们使用了SFUD并且将设备名修改为了W25Q128,保存配置后在rtconfig.h中定义宏FAL_USING_NOR_FLASH_DEV_NAME的值为"W25Q128",在fal_flash_sfud_port.c文件中是通过条件宏定义的,也即优先使用外界定义的FAL_USING_NOR_FLASH_DEV_NAME,这里我们不需要修改。

SFUD提供的fal_flash_dev对象nor_flash0参数中,flash大小只有8M字节,我们可以修改为W25Q128的16M字节,也可以不修改,因为在调用初始化接口函数init后,会从flash设备读取正确的参数更新到nor_flash0表项中,我们在使用FAL组件前都需要调用FAL初始化函数fal_init,其内调用flash设备初始化函数fal_flash_init,最后会调用注册到fal_flash_dev设备表项中的初始化函数device_table[i]->ops.init,所以nor_flash0表项参数会在FAL初始化时被更新。

这里我们既然已经知道W25Q128 Flash的参数,便顺手把参数修改如下:

1
2
3
4
5
6

// projects\stm32l475_dfs_sample\ports\fal\fal_flash_sfud_port.c

 

struct fal_flash_dev nor_flash0 = {FAL_USING_NOR_FLASH_DEV_NAME, 0, 16 * 1024 * 1024, 4096, {init, read, write, erase}};

SFUD向FAL注册的接口函数实际调用的是SFUD框架层的接口函数,调用过程如下:

  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

// projects\stm32l475_dfs_sample\ports\fal\fal_flash_sfud_port.c

 

static int init(void)

{

 

#ifdef RT_USING_SFUD

    /* RT-Thread RTOS platform */

    sfud_dev = rt_sfud_flash_find_by_dev_name(FAL_USING_NOR_FLASH_DEV_NAME);

#else

    /* bare metal platform */

    extern sfud_flash sfud_norflash0;

    sfud_dev = &sfud_norflash0;

#endif

 

    if (NULL == sfud_dev)

    {

        return -1;

    }

 

    /* update the flash chip information */

    nor_flash0.blk_size = sfud_dev->chip.erase_gran;

    nor_flash0.len = sfud_dev->chip.capacity;

 

    return 0;

}

 

static int read(long offset, uint8_t *buf, size_t size)

{

    assert(sfud_dev);

    assert(sfud_dev->init_ok);

    sfud_read(sfud_dev, nor_flash0.addr + offset, size, buf);

 

    return size;

}

 

static int write(long offset, const uint8_t *buf, size_t size)

{

    assert(sfud_dev);

    assert(sfud_dev->init_ok);

    if (sfud_write(sfud_dev, nor_flash0.addr + offset, size, buf) != SFUD_SUCCESS)

    {

        return -1;

    }

 

    return size;

}

 

static int erase(long offset, size_t size)

{

    assert(sfud_dev);

    assert(sfud_dev->init_ok);

    if (sfud_erase(sfud_dev, nor_flash0.addr + offset, size) != SFUD_SUCCESS)

    {

        return -1;

    }

 

    return size;

}

FAL MCU Flash移植

STM32L475片内Flash驱动,RT-Thread已经在libraries\HAL_Drivers \drv_flash\drv_flash_l4.c目录下提供了,同时还通过条件宏提供了向FAL注册fal_flash_dev设备表项的代码:

 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

// projects\stm32l475_dfs_sample\board\board.h

 

#define STM32_FLASH_START_ADRESS       ((uint32_t)0x08000000)

#define STM32_FLASH_SIZE               (512 * 1024)

#define STM32_FLASH_END_ADDRESS        ((uint32_t)(STM32_FLASH_START_ADRESS + STM32_FLASH_SIZE))

 

 

// libraries\HAL_Drivers\drv_flash\drv_flash_l4.c

 

const struct fal_flash_dev stm32_onchip_flash = { "onchip_flash", STM32_FLASH_START_ADRESS, STM32_FLASH_SIZE, 2048, {NULL, fal_flash_read, fal_flash_write, fal_flash_erase} };

 

static int fal_flash_read(long offset, rt_uint8_t *buf, size_t size)

{

    return stm32_flash_read(stm32_onchip_flash.addr + offset, buf, size);

}

 

static int fal_flash_write(long offset, const rt_uint8_t *buf, size_t size)

{

    return stm32_flash_write(stm32_onchip_flash.addr + offset, buf, size);

}

 

static int fal_flash_erase(long offset, size_t size)

{

    return stm32_flash_erase(stm32_onchip_flash.addr + offset, size);

}

STM32L475向FAL提供的fal_flash_dev设备对象stm32_onchip_flash包含了STM32L475片内Flash的参数及其访问接口函数,Flash参数在工程目录的board.h头文件中定义,Flash访问接口函数则在驱动文件drv_flash_l4.c中提供,接口函数最终调用的是STM32L4 HAL库函数,这里就不展开介绍其过程了。

如果想使用STM32L475片内Flash驱动,需要定义相应的宏,这里通过在工程目录Kconfig文件中增加menuconfig配置来实现,新增的配置如下:

 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

// projects\stm32l475_dfs_sample\board\Kconfig

menu "Hardware Drivers Config"

 

config SOC_STM32L475VE

    bool

    select SOC_SERIES_STM32L4

    default y

......

menu "On-chip Peripheral Drivers"

	......

    config BSP_USING_ON_CHIP_FLASH

        bool "Enable on-chip FLASH"

        default n

    ......

为何增加宏BSP_USING_ON_CHIP_FLASH的配置项呢?主要是从工程编译管理文件libraries\HAL_Drivers\SConscript中查得的,看驱动文件drv_flash/drv_flash_l4.c的编译依赖宏是BSP_USING_ON_CHIP_FLASH与SOC_SERIES_STM32L4,后者在RT-Thread CPU架构与BSP移植过程时已经定义,所以这里只需要在Kconfig文件中增加宏BSP_USING_ON_CHIP_FLASH的配置选项即可。

在Kconfig文件中新增宏BSP_USING_ON_CHIP_FLASH配置后保存,在工程目录env环境输入menuconfig开启刚才新增的配置选项,如下图所示:

片内Flash启用配置

STM32L475片内Flash到FAL的移植到这里就完成了。

FAL分区表配置

在分区表配置文件ports\fal\fal_cfg.h中主要修改fal_flash_dev设备对象名,Flash设备名NOR_FLASH_DEV_NAME,FAL分区表FAL_PART_TABLE的定义等内容。

我们将onchip_flash分为两个分区,将W25Q128 Flash分为五个分区,同时修改我们在底层flash驱动中提供的fal_flash_dev设备对象名和Flash设备名,修改后的FAL分区配置文件代码如下:

 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

// projects\stm32l475_dfs_sample\ports\fal\fal_cfg.h

......

#ifndef FAL_USING_NOR_FLASH_DEV_NAME

#define NOR_FLASH_DEV_NAME             "norflash0"

#else

#define NOR_FLASH_DEV_NAME              FAL_USING_NOR_FLASH_DEV_NAME

#endif

 

/* ===================== Flash device Configuration ========================= */

extern const struct fal_flash_dev stm32_onchip_flash;

extern struct fal_flash_dev nor_flash0;

 

/* flash device table */

#define FAL_FLASH_DEV_TABLE                                          \

{                                                                    \

    &stm32_onchip_flash,                                             \

    &nor_flash0,                                                     \

}

/* ====================== Partition Configuration ========================== */

#ifdef FAL_PART_HAS_TABLE_CFG

/* partition table */

#define FAL_PART_TABLE                                                                                                  \

{                                                                                                                       \

    {FAL_PART_MAGIC_WROD,        "app",     "onchip_flash",                                    0,       384 * 1024, 0}, \

    {FAL_PART_MAGIC_WROD,      "param",     "onchip_flash",                           384 * 1024,       128 * 1024, 0}, \

    {FAL_PART_MAGIC_WROD,  "easyflash", NOR_FLASH_DEV_NAME,                                    0,       512 * 1024, 0}, \

    {FAL_PART_MAGIC_WROD,   "download", NOR_FLASH_DEV_NAME,                           512 * 1024,      1024 * 1024, 0}, \

    {FAL_PART_MAGIC_WROD, "wifi_image", NOR_FLASH_DEV_NAME,                  (512 + 1024) * 1024,       512 * 1024, 0}, \

    {FAL_PART_MAGIC_WROD,       "font", NOR_FLASH_DEV_NAME,            (512 + 1024 + 512) * 1024,  7 * 1024 * 1024, 0}, \

    {FAL_PART_MAGIC_WROD, "filesystem", NOR_FLASH_DEV_NAME, (512 + 1024 + 512 + 7 * 1024) * 1024,  7 * 1024 * 1024, 0}, \

}

#endif /* FAL_PART_HAS_TABLE_CFG */

到这里FAL移植就完成了,在使用FAL前需要对其进行初始化,也即调用调用函数fal_init,下面用一个示例程序验证FAL软件包移植是否成功。

1.4 FAL使用示例

我们主要想通过示例验证FAL移植是否有问题,并熟悉FAL向上层提供的接口函数的使用,所以本示例先初始化FAL组件,然后对特定分区进行擦除、读取、写入等访问操作,同时根据分区名获取fal_partition分区参数及fal_flash_dev设备参数等信息。

在projects\stm32l475_dfs_sample\applications目录下新建fal_sample.c文件,按照上面的实现目标在fal_sample.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

// projects\stm32l475_dfs_sample\applications\fal_sample.c

 

#include "rtthread.h"

#include "rtdevice.h"

#include "board.h"

#include "fal.h"

 

 

#define BUF_SIZE 1024

 

static int fal_test(const char *partiton_name)

{

    int ret;

    int i, j, len;

    uint8_t buf[BUF_SIZE];

    const struct fal_flash_dev *flash_dev = RT_NULL;

    const struct fal_partition *partition = RT_NULL;

 

    if (!partiton_name)

    {

        rt_kprintf("Input param partition name is null!\n");

        return -1;

    }

 

    partition = fal_partition_find(partiton_name);

    if (partition == RT_NULL)

    {

        rt_kprintf("Find partition (%s) failed!\n", partiton_name);

        ret = -1;

        return ret;

    }

 

    flash_dev = fal_flash_device_find(partition->flash_name);

    if (flash_dev == RT_NULL)

    {

        rt_kprintf("Find flash device (%s) failed!\n", partition->flash_name);

        ret = -1;

        return ret;

    }

 

    rt_kprintf("Flash device : %s   "

               "Flash size : %dK   \n"

               "Partition : %s   "

               "Partition size: %dK\n", 

                partition->flash_name, 

                flash_dev->len/1024,

                partition->name,

                partition->len/1024);

 

    /* erase all partition */

    ret = fal_partition_erase_all(partition);

    if (ret < 0)

    {

        rt_kprintf("Partition (%s) erase failed!\n", partition->name);

        ret = -1;

        return ret;

    }

    rt_kprintf("Erase (%s) partition finish!\n", partiton_name);

 

    /* read the specified partition and check data */

    for (i = 0; i < partition->len;)

    {

        rt_memset(buf, 0x00, BUF_SIZE);

 

        len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);

 

        ret = fal_partition_read(partition, i, buf, len);

        if (ret < 0)

        {

            rt_kprintf("Partition (%s) read failed!\n", partition->name);

            ret = -1;

            return ret;

        }

 

        for(j = 0; j < len; j++)

        {

            if (buf[j] != 0xFF)

            {

                rt_kprintf("The erase operation did not really succeed!\n");

                ret = -1;

                return ret;

            }

        }

        i += len;

    }

 

    /* write 0x00 to the specified partition */

    for (i = 0; i < partition->len;)

    {

        rt_memset(buf, 0x00, BUF_SIZE);

 

        len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);

 

        ret = fal_partition_write(partition, i, buf, len);

        if (ret < 0)

        {

            rt_kprintf("Partition (%s) write failed!\n", partition->name);

            ret = -1;

            return ret;

        }

 

        i += len;

    }

    rt_kprintf("Write (%s) partition finish! Write size %d(%dK).\n", partiton_name, i, i/1024);

 

    /* read the specified partition and check data */

    for (i = 0; i < partition->len;)

    {

        rt_memset(buf, 0xFF, BUF_SIZE);

 

        len = (partition->len - i) > BUF_SIZE ? BUF_SIZE : (partition->len - i);

 

        ret = fal_partition_read(partition, i, buf, len);

        if (ret < 0)

        {

            rt_kprintf("Partition (%s) read failed!\n", partition->name);

            ret = -1;

            return ret;

        }

 

        for(j = 0; j < len; j++)

        {

            if (buf[j] != 0x00)

            {

                rt_kprintf("The write operation did not really succeed!\n");

                ret = -1;

                return ret;

            }

        }

 

        i += len;

    }

 

    ret = 0;

    return ret;

}

 

static void fal_sample(void)

{

    /* 1- init */

    fal_init();

 

    if (fal_test("param") == 0)

    {

        rt_kprintf("Fal partition (%s) test success!\n", "param");

    }

    else

    {

        rt_kprintf("Fal partition (%s) test failed!\n", "param");

    }

 

    if (fal_test("download") == 0)

    {

        rt_kprintf("Fal partition (%s) test success!\n", "download");

    }

    else

    {

        rt_kprintf("Fal partition (%s) test failed!\n", "download");

    }

}

 

MSH_CMD_EXPORT(fal_sample, fal sample);

本示例程序是在前篇博客DFS文件系统管理与devfs/elmfat示例工程的基础上新增的,由于之前已经编写的源文件applications\dfs_sample.c中包含了QSPI设备绑定和SFUD Flash探测函数的自动调用,所以在本示例工程applications\fal_sample.c中并没有进行QSPI与SFUD flash初始化和注册工作,如果applications/dfs_sample.c源文件不存在,则需要在applications\fal_sample.c中完成QSPI与SFUD flash初始化及注册工作。

在工程目录启动env环境并执行scons –target=mdk5命令,编译生成MDK5工程文件,打开project.uvprojx编译无错误(由于部分RT-Thread组件不支持Clang编译器,把template.uvprojx工程默认编译器设置为ARM Compiler V5了),将程序烧录到STM32L475潘多拉开发板中,运行结果如下:

FAL组件示例工程运行结果

在finsh环境下运行fal_sample结果正常,输出到串口的不仅有FAL分区表信息,还有在特定分区中擦除、写入、读取数据的测试结果信息。

FAL为便于用户调试,也提供了finsh命令fal,包括fal probe / read / write / erase / bench等命令,命令使用示例如下:

finsh fal命令使用示例

本示例工程源码下载地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_dfs_sample

二、DFS挂载到FAL分区示例

在前篇博客DFS文件系统管理与devfs/elmfat示例中我们将DFS框架中的elmfat文件系统挂载到了SFUD驱动的W25Q128块设备上,这里增加FAL flash抽象层,我们将elmfat文件系统挂载到W25Q128 flash设备的filesystem分区上,由于FAL管理的filesystem分区不是块设备,需要先使用FAL分区转BLK设备接口函数将filesystem分区转换为块设备,然后再将DFS elmfat文件系统挂载到filesystem块设备上。

挂载DFS elmfat文件系统的示例程序主要还是使用前篇博客elmfat_sample函数中的代码,只是在前面增加了fal初始化和将分区filesystem创建为块设备的代码。按照该目标在fal_sample.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

// projects\stm32l475_dfs_sample\applications\fal_sample.c

 

#include "dfs_posix.h"

 

#define FS_PARTITION_NAME  "filesystem"

 

static void fal_elmfat_sample(void)

{

    int fd, size;

    struct statfs elm_stat;

    struct fal_blk_device *blk_dev;

    char str[] = "elmfat mount to W25Q flash.", buf[80];

 

    /* fal init */

    fal_init();

 

    /* create block device */

    blk_dev = (struct fal_blk_device *)fal_blk_device_create(FS_PARTITION_NAME);

    if(blk_dev == RT_NULL)

        rt_kprintf("Can't create a block device on '%s' partition.\n", FS_PARTITION_NAME);

    else

        rt_kprintf("Create a block device on the %s partition of flash successful.\n", FS_PARTITION_NAME);

 

    /* make a elmfat format filesystem */

    if(dfs_mkfs("elm", FS_PARTITION_NAME) == 0)

        rt_kprintf("make elmfat filesystem success.\n");

 

    /* mount elmfat file system to FS_PARTITION_NAME */

    if(dfs_mount(FS_PARTITION_NAME, "/", "elm", 0, 0) == 0)

        rt_kprintf("elmfat filesystem mount success.\n");

 

    /* Get elmfat file system statistics */

    if(statfs("/", &elm_stat) == 0)

        rt_kprintf("elmfat filesystem block size: %d, total blocks: %d, free blocks: %d.\n", 

                    elm_stat.f_bsize, elm_stat.f_blocks, elm_stat.f_bfree);

 

    if(mkdir("/user", 0x777) == 0)

        rt_kprintf("make a directory: '/user'.\n");

 

    rt_kprintf("Write string '%s' to /user/test.txt.\n", str);

 

    /* Open the file in create and read-write mode, create the file if it does not exist*/

    fd = open("/user/test.txt", O_WRONLY | O_CREAT);

    if (fd >= 0)

    {

        if(write(fd, str, sizeof(str)) == sizeof(str))

            rt_kprintf("Write data done.\n");

 

        close(fd);   

    }

 

    /* Open file in read-only mode */

    fd = open("/user/test.txt", O_RDONLY);

    if (fd >= 0)

    {

        size = read(fd, buf, sizeof(buf));

 

        close(fd);

 

        if(size == sizeof(str))

            rt_kprintf("Read data from file test.txt(size: %d): %s \n", size, buf);

    }

}

MSH_CMD_EXPORT_ALIAS(fal_elmfat_sample, fal_elmfat,fal elmfat sample);

使用MDK5编译无报错,将程序烧录到STM32L475开发板中,程序运行结果如下:

在FAL上移植DFS elmfat文件系统示例运行结果

本示例工程源码下载地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_dfs_sample

三、Easyflash移植到FAL分区示例

我们在使用linux或者windows系统时都有专门配置环境变量的地方,在使用RT-Thread时也有要保存类似环境变量这种键值关系的空间,特别是对于蓝牙/WIFI等射频类通信需要保存的配置项还不少。有些配置是在系统运行过程中产生的,自然不能保存到代码区,由于这些配置在下次开机时仍需调用,也不能保存到SRAM内存区,只能保存到非易失性存储区NVM(non-volatile memory)。

在系统运行过程中产生的配置项需要保存到NVM非易失性存储区,也即ROM / Flash中,当然也可以使用DFS文件系统保存到某个配置文件中,但在文件中保存/获取配置项的值并没有那么便利,RT-Thread提供了一个easyflash软件包提供了专门的键值对(Key-Value)管理接口,可以让用户很方便的在NVM中通过接口函数保存/获取配置项,而不需要关心该配置项的存储位置。

3.1 Easyflash软件包源码获取

在stm32l475_dfs_sample工程目录打开env执行menuconfig命令,在RT-Thread online packages –> tools packages –> easyflash中启用该软件包,选择lastest最新版本。

easyflash支持ENV环境变量、IAP在线升级、LOG日志保存等功能,这里我们只使用ENV环境变量功能,所以另外两个保持默认的未选中状态,easyflash配置界面如下:

easyflash启用配置界面

保存配置,在env环境中执行pkgs –update命令,自动从github获取easyflash软件包到本地,获取结果如下:

easyflash软件包下载结果

下载的easyflash软件包的目录结构如下:

easyflash软件包目录结构N

在packages\EasyFlash-latest\docs\zh目录下有详细的说明文档,包括api接口介绍、easyflash设计与实现原理、移植过程说明等,说明文档比较详细,这里就不过多赘述了,只简单介绍其初始化过程与移植过程。

3.2 easyflash环境变量管理

先看下easyflash管理环境变量ENV的数据结构描述:

 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

// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\inc\easyflash.h

 

typedef struct _ef_env {

    char *key;

    void *value;

    size_t value_len;

} ef_env, *ef_env_t;

 

 

// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\ports\ef_fal_port.c

 

/* default ENV set for user */

static const ef_env default_env_set[] = {

        {"iap_need_copy_app", "0"},

        {"iap_need_crc32_check", "0"},

        {"iap_copy_app_size", "0"},

        {"stop_in_bootloader", "0"},

};

 

用户设置的环境变量都保存在default_env_set[]表中,用户可以在编写代码时事先在该表中配置一部分环境变量,也可以在使用过程中通过接口函数往里面新增、删除、修改、获取环境变量,easyflash移植有SFUD和FAL两种方式,SFUD是直接在某个Flash上使用easyflash,FAL则是在某个分区上使用easyflash,我们只需要将环境变量保存在一段较小的flash分区中,因此使用FAL移植接口文件ef_fal_port.c。

接下来看easyflash软件包初始化过程:

  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

// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\src\easyflash.c

 

/**

 * EasyFlash system initialize.

 *

 * @return result

 */

EfErrCode easyflash_init(void) {

    extern EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size);

    extern EfErrCode ef_env_init(ef_env const *default_env, size_t default_env_size);

    extern EfErrCode ef_iap_init(void);

    extern EfErrCode ef_log_init(void);

 

    size_t default_env_set_size = 0;

    const ef_env *default_env_set;

    EfErrCode result = EF_NO_ERR;

 

    result = ef_port_init(&default_env_set, &default_env_set_size);

 

#ifdef EF_USING_ENV

    if (result == EF_NO_ERR) {

        result = ef_env_init(default_env_set, default_env_set_size);

    }

#endif

 

#ifdef EF_USING_IAP

    if (result == EF_NO_ERR) {

        result = ef_iap_init();

    }

#endif

 

#ifdef EF_USING_LOG

    if (result == EF_NO_ERR) {

        result = ef_log_init();

    }

#endif

 

    if (result == EF_NO_ERR) {

        EF_INFO("EasyFlash V%s is initialize success.\n", EF_SW_VERSION);

    } else {

        EF_INFO("EasyFlash V%s is initialize fail.\n", EF_SW_VERSION);

    }

    EF_INFO("You can get the latest version on https://github.com/armink/EasyFlash .\n");

 

    return result;

}

 

 

// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\ports\ef_fal_port.c

 

/**

 * Flash port for hardware initialize.

 *

 * @param default_env default ENV set for user

 * @param default_env_size default ENV size

 *

 * @return result

 */

EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size) {

    EfErrCode result = EF_NO_ERR;

 

    *default_env = default_env_set;

    *default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]);

    rt_sem_init(&env_cache_lock, "env lock", 1, RT_IPC_FLAG_PRIO);

 

    part = fal_partition_find(FAL_EF_PART_NAME);

    EF_ASSERT(part);

 

    return result;

}

 

 

// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\src\ef_env.c

 

/**

 * Flash ENV initialize.

 *

 * @param default_env default ENV set for user

 * @param default_env_size default ENV set size

 *

 * @return result

 */

EfErrCode ef_env_init(ef_env const *default_env, size_t default_env_size) {

    EfErrCode result = EF_NO_ERR;

 

#ifdef EF_ENV_USING_CACHE

    size_t i;

#endif

 

    EF_ASSERT(default_env);

    EF_ASSERT(ENV_AREA_SIZE);

    /* must be aligned with erase_min_size */

    EF_ASSERT(ENV_AREA_SIZE % EF_ERASE_MIN_SIZE == 0);

    /* sector number must be greater than or equal to 2 */

    EF_ASSERT(SECTOR_NUM >= 2);

    /* must be aligned with write granularity */

    EF_ASSERT((EF_STR_ENV_VALUE_MAX_SIZE * 8) % EF_WRITE_GRAN == 0);

 

    if (init_ok) {

        return EF_NO_ERR;

    }

 

#ifdef EF_ENV_USING_CACHE

    for (i = 0; i < EF_SECTOR_CACHE_TABLE_SIZE; i++) {

        sector_cache_table[i].addr = FAILED_ADDR;

    }

    for (i = 0; i < EF_ENV_CACHE_TABLE_SIZE; i++) {

        env_cache_table[i].addr = FAILED_ADDR;

    }

#endif /* EF_ENV_USING_CACHE */

 

    env_start_addr = EF_START_ADDR;

    default_env_set = default_env;

    default_env_set_size = default_env_size;

 

    EF_DEBUG("ENV start address is 0x%08X, size is %d bytes.\n", EF_START_ADDR, ENV_AREA_SIZE);

 

    result = ef_load_env();

 

#ifdef EF_ENV_AUTO_UPDATE

    if (result == EF_NO_ERR) {

        env_auto_update();

    }

#endif

 

    if (result == EF_NO_ERR) {

        init_ok = true;

    }

 

    return result;

}

easyflash初始化实际上主要是对环境变量表 default_env_set的相关处理,先是从移植文件ef_fal_port.c中获取default_env_set的首地址与元素个数,包括获取easyflash所使用FAL分区的结构体对象指针;然后配置ENV管理中需要使用的全局变量,最后将default_env_set表中配置的环境变量加载到SRAM内存中去,方便用户程序对环境变量的使用与配置。

接下来看easyflash环境变量管理的常用接口函数声明:

 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

// projects\stm32l475_dfs_sample\packages\EasyFlash-latest\ports\ef_fal_port.c

 

#ifdef EF_USING_ENV

/* only supported on ef_env.c */

size_t ef_get_env_blob(const char *key, void *value_buf, size_t buf_len, size_t *saved_value_len);

EfErrCode ef_set_env_blob(const char *key, const void *value_buf, size_t buf_len);

 

/* ef_env.c, ef_env_legacy_wl.c and ef_env_legacy.c */

EfErrCode ef_load_env(void);

void ef_print_env(void);

char *ef_get_env(const char *key);

EfErrCode ef_set_env(const char *key, const char *value);

EfErrCode ef_del_env(const char *key);

EfErrCode ef_save_env(void);

EfErrCode ef_env_set_default(void);

size_t ef_get_env_write_bytes(void);

EfErrCode ef_set_and_save_env(const char *key, const char *value);

EfErrCode ef_del_and_save_env(const char *key);

#endif

更多接口函数说明可参阅EasyFlash-latest\docs\zh\api.md文档或源码。

3.3 easyflash移植

为了方便后面easyflash软件包的升级,以免我们的配置信息被覆盖,依然采用FAL移植时的处理方式,在新建的ports文件夹下再新建EasyFlash文件夹用于保存其移植文件,同时把packages\EasyFlash-latest\ports\ef_fal_port.c复制一份到ports\EasyFlash\ef_fal_port.c中,把packages\EasyFlash-latest\SConscript复制一份到ports\EasyFlash\SConscript中,移植文件配置目录如下:

easyflash移植文件目录

在packages\EasyFlash-latest\SConscript文件中并没有添加packages\EasyFlash-latest\ports目录下的源码文件,我们也不使用该目录下的移植文件,所以该编译配置文件不需要修改。

打开ports\EasyFlash\SConscript并修改源文件与头文件的目录配置,修改后的编译配置脚本代码如下:

 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

// projects\stm32l475_dfs_sample\ports\EasyFlash\SConscript

 

from building import *

 

# get current directory

cwd     = GetCurrentDir()

# The set of source files associated with this SConscript file.

src     = []

src     += Glob('*.c')

 

path    = [cwd]

 

group = DefineGroup('EasyFlash', src, depend = ['PKG_USING_EASYFLASH'], CPPPATH = path)

 

Return('group')

接下来看移植文件ports\EasyFlash\ef_fal_port.c的修改,前面介绍的ef_port_init文件读取环境变量配置表default_env_set的信息,并通过FAL分区名获取分区对象指针,所以在ef_fal_port.c文件中需要修改环境变量配置表default_env_set和要存储的FAL分区名FAL_EF_PART_NAME。

在前面介绍FAL时,专门在W25Q128 flash上配置了一个名为easyflash的分区用于存储easyflash软件包管理的数据。环境变量我们先只设置一个开机次数,修改后的配置表default_env_set与分区名FAL_EF_PART_NAME如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

// projects\stm32l475_dfs_sample\ports\EasyFlash\ef_fal_port.c

 

/* EasyFlash partition name on FAL partition table */

#define FAL_EF_PART_NAME               "easyflash"

 

/* default ENV set for user */

static const ef_env default_env_set[] = {

        {"boot_times", "0"}

};

easyflash要在FAL分区上保存/读取环境变量,需要实现访问FAL分区的函数,从与下面FAL抽象层的交互接口可以更熟悉移植过程,easyflash访问FAL分区的函数实现如下:

  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

// projects\stm32l475_dfs_sample\ports\EasyFlash\ef_fal_port.c

 

static const struct fal_partition *part = NULL;

 

/**

 * Flash port for hardware initialize.

 *

 * @param default_env default ENV set for user

 * @param default_env_size default ENV size

 *

 * @return result

 */

EfErrCode ef_port_init(ef_env const **default_env, size_t *default_env_size) {

    EfErrCode result = EF_NO_ERR;

 

    *default_env = default_env_set;

    *default_env_size = sizeof(default_env_set) / sizeof(default_env_set[0]);

    rt_sem_init(&env_cache_lock, "env lock", 1, RT_IPC_FLAG_PRIO);

 

    part = fal_partition_find(FAL_EF_PART_NAME);

    EF_ASSERT(part);

 

    return result;

}

 

/**

 * Read data from flash.

 * @note This operation's units is word.

 *

 * @param addr flash address

 * @param buf buffer to store read data

 * @param size read bytes size

 *

 * @return result

 */

EfErrCode ef_port_read(uint32_t addr, uint32_t *buf, size_t size) {

    EfErrCode result = EF_NO_ERR;

 

    fal_partition_read(part, addr, (uint8_t *)buf, size);

 

    return result;

}

 

/**

 * Erase data on flash.

 * @note This operation is irreversible.

 * @note This operation's units is different which on many chips.

 *

 * @param addr flash address

 * @param size erase bytes size

 *

 * @return result

 */

EfErrCode ef_port_erase(uint32_t addr, size_t size) {

    EfErrCode result = EF_NO_ERR;

 

    /* make sure the start address is a multiple of FLASH_ERASE_MIN_SIZE */

    EF_ASSERT(addr % EF_ERASE_MIN_SIZE == 0);

 

    if (fal_partition_erase(part, addr, size) < 0)

    {

        result = EF_ERASE_ERR;

    }

 

    return result;

}

/**

 * Write data to flash.

 * @note This operation's units is word.

 * @note This operation must after erase. @see flash_erase.

 *

 * @param addr flash address

 * @param buf the write data buffer

 * @param size write bytes size

 *

 * @return result

 */

EfErrCode ef_port_write(uint32_t addr, const uint32_t *buf, size_t size) {

    EfErrCode result = EF_NO_ERR;

 

    if (fal_partition_write(part, addr, (uint8_t *)buf, size) < 0)

    {

        result = EF_WRITE_ERR;

    }

 

    return result;

}

到这里easyflash组件的移植完成了,下面用一个示例程序验证移植是否成功。

3.4 easyflash使用示例

在使用easyflash组件前需要先调用easyflash_init进行初始化,在示例程序中我们获取环境变量boot_times的值,然后对其执行加一操作,串口打印当前boot_times的值后,再把新的环境变量值设置给变量名boot_times,最后将该环境变量的新值保存到FAL指定分区中。

需要注意的是获取的环境变量值为字符串格式,如果要对其进行数字运算,需要先将该值转换为数字类型,同样保存该环境变量时也是以字符串形式设置的,通过easyflash提供的接口函数参数格式也可以看出来。

按照上面的任务目标,在fal_sample.c文件中新增easyflash示例代码如下:

 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

// projects\stm32l475_dfs_sample\applications\fal_sample.c

 

#include "easyflash.h"

#include <stdlib.h>

 

static void easyflash_sample(void)

{

    /* fal init */

    fal_init();

 

    /* easyflash init */

    if(easyflash_init() == EF_NO_ERR)

    {

        uint32_t i_boot_times = NULL;

        char *c_old_boot_times, c_new_boot_times[11] = {0};

 

        /* get the boot count number from Env */

        c_old_boot_times = ef_get_env("boot_times");

        /* get the boot count number failed */

        if (c_old_boot_times == RT_NULL)

            c_old_boot_times[0] = '0';

 

        i_boot_times = atol(c_old_boot_times);

        /* boot count +1 */

        i_boot_times ++;

        rt_kprintf("===============================================\n");

        rt_kprintf("The system now boot %d times\n", i_boot_times);

        rt_kprintf("===============================================\n");

        /* interger to string */

        sprintf(c_new_boot_times, "%d", i_boot_times);

        /* set and store the boot count number to Env */

        ef_set_env("boot_times", c_new_boot_times);

        ef_save_env();

    }

}

MSH_CMD_EXPORT(easyflash_sample, easyflash sample);

在工程目录打开env执行scons –target=mdk5命令,自动编译生成MDK5工程文件,打开project.uvprojx,编译无报错,将程序烧录到STM32L475开发板中,运行结果如下:

easyflash示例运行结果

easyflash为方便调试,也提供了finsh命令printenv / resetenv / setenv / saveenv / getvalue等,这些命令的使用示例如下:

easyflash finsh命令使用示例

本示例工程源码下载地址:

https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_dfs_sample