Advanced 4: A Minimal Filesystem — LittleFS#

Sample code#

  • Directory: docs/os-on-arduino/code/adv4_fs

uv run pio run -d adv4_fs -t upload
uv run pio device monitor -b 115200

Output starts when you send the single character S.

Source code#

The sketch for this chapter. Click a file name to expand. The code is mirrored automatically from the manuscript repository, so what you see here is always current.

src/main.cpp — 97 lines

Open on GitHub

 1/**
 2 * 応用編 第4章: 最小ファイルシステム — Flash 上に LittleFS を載せる
 3 *
 4 * RA4M1 内蔵の Data Flash (8 KB) を LittleFS のブロックデバイスとして使う。
 5 *
 6 * 起動するたびにカウンタを 1 増やして書き戻すので、電源を入れ直しても
 7 * 値が残っていることで永続化を確認できる。4.2 で説明した電源断耐性
 8 * (LittleFS のコピーオンライト)が効いていることの一番簡単な確かめ方。
 9 *
10 * 注意: Data Flash の書き換え回数は 1 万〜10 万回程度。デモとはいえ
11 * リセットを繰り返しすぎないこと。
12 */
13
14#include <Arduino.h>
15#include "fs.h"
16#include "fs_flash_hal.h"
17
18static void print_err(const char *what, int err)
19{
20    Serial.print(F("[fs] "));
21    Serial.print(what);
22    Serial.print(F(" failed: "));
23    Serial.println(err);
24}
25
26// 起動回数を /boot_count に読み書きする
27static void demo_boot_count(lfs_t *lfs)
28{
29    lfs_file_t f;
30    uint32_t count = 0;
31
32    int err = lfs_file_open(lfs, &f, "/boot_count", LFS_O_RDWR | LFS_O_CREAT);
33    if (err) { print_err("open", err); return; }
34
35    lfs_file_read(lfs, &f, &count, sizeof(count));
36    count++;
37    lfs_file_rewind(lfs, &f);
38    lfs_file_write(lfs, &f, &count, sizeof(count));
39    lfs_file_close(lfs, &f);   // close で初めて Flash へ確定する
40
41    Serial.print(F("boot count = "));
42    Serial.println(count);
43}
44
45// ルートディレクトリの一覧(シェルの ls 相当)
46static void demo_ls(lfs_t *lfs)
47{
48    lfs_dir_t dir;
49    struct lfs_info info;
50
51    int err = lfs_dir_open(lfs, &dir, "/");
52    if (err) { print_err("dir_open", err); return; }
53
54    Serial.println(F("--- / ---"));
55    while (lfs_dir_read(lfs, &dir, &info) > 0) {
56        if (info.type == LFS_TYPE_DIR) {
57            Serial.print(F("  [DIR ] "));
58            Serial.println(info.name);
59        } else {
60            Serial.print(F("  [FILE] "));
61            Serial.print(info.name);
62            Serial.print(F("  "));
63            Serial.print(info.size);
64            Serial.println(F(" B"));
65        }
66    }
67    lfs_dir_close(lfs, &dir);
68}
69
70void setup()
71{
72    Serial.begin(115200);
73    while (!Serial) { }
74    while (Serial.read() != 'S') { delay(1); }
75
76    Serial.println(F("=== Advanced 4: LittleFS on Data Flash ==="));
77    Serial.print(F("data flash: "));
78    Serial.print(FLASH_DATA_SIZE);
79    Serial.print(F(" B / sector "));
80    Serial.print(FLASH_SECTOR_SIZE);
81    Serial.println(F(" B"));
82
83    int err = fs_init();
84    if (err) {
85        print_err("mount", err);
86        while (1) { delay(1000); }
87    }
88    Serial.println(F("mounted."));
89
90    lfs_t *lfs = fs_get();
91    demo_boot_count(lfs);
92    demo_ls(lfs);
93
94    Serial.println(F("done. リセットすると boot count が増えます"));
95}
96
97void loop() { }
include/fs.h — 18 lines

Open on GitHub

 1// fs.h
 2//
 3// 本書 応用編 第4章。LittleFS を Data Flash の上にマウントする薄いラッパ。
 4
 5#pragma once
 6
 7#include "lfs.h"
 8
 9#ifdef __cplusplus
10extern "C" {
11#endif
12
13int    fs_init(void);   // mount。失敗したら format してから再 mount
14lfs_t *fs_get(void);
15
16#ifdef __cplusplus
17}
18#endif
include/fs_flash_hal.h — 19 lines

Open on GitHub

 1// fs_flash_hal.h
 2
 3#ifndef FS_FLASH_HAL_H
 4#define FS_FLASH_HAL_H
 5
 6#include <stdint.h>
 7#include <stddef.h>
 8
 9#define FLASH_DATA_BASE   0x40100000UL  // Data Flash の先頭物理アドレス
10#define FLASH_DATA_SIZE   (8 * 1024)    // 8 KB
11#define FLASH_SECTOR_SIZE 1024          // 1 KB = Data Flash の消去ブロック = LittleFS の block_size
12
13int  flash_hal_init(void);
14int  flash_hal_read (uint32_t offset, void *buf, size_t len);
15int  flash_hal_prog (uint32_t offset, const void *buf, size_t len);
16int  flash_hal_erase(uint32_t offset);   // セクタ消去
17int  flash_hal_sync (void);
18
19#endif
src/fs.cpp — 70 lines

Open on GitHub

 1// fs.c
 2
 3#include <Arduino.h>
 4#include "lfs.h"
 5#include "fs.h"
 6#include "fs_flash_hal.h"
 7
 8static lfs_t        g_lfs;
 9static struct lfs_config g_cfg;
10
11static uint8_t g_read_buf[16];
12static uint8_t g_prog_buf[16];
13static uint8_t g_lookahead_buf[16];
14
15// --- LittleFS から呼ばれる callback ---
16
17static int blk_read(const struct lfs_config *c, lfs_block_t block,
18                    lfs_off_t off, void *buf, lfs_size_t size) {
19    (void)c;
20    return flash_hal_read(block * FLASH_SECTOR_SIZE + off, buf, size);
21}
22
23static int blk_prog(const struct lfs_config *c, lfs_block_t block,
24                    lfs_off_t off, const void *buf, lfs_size_t size) {
25    (void)c;
26    return flash_hal_prog(block * FLASH_SECTOR_SIZE + off, buf, size);
27}
28
29static int blk_erase(const struct lfs_config *c, lfs_block_t block) {
30    (void)c;
31    return flash_hal_erase(block * FLASH_SECTOR_SIZE);
32}
33
34static int blk_sync(const struct lfs_config *c) {
35    (void)c;
36    return flash_hal_sync();
37}
38
39// --- 初期化 ---
40
41int fs_init(void) {
42    flash_hal_init();
43
44    g_cfg.read  = blk_read;
45    g_cfg.prog  = blk_prog;
46    g_cfg.erase = blk_erase;
47    g_cfg.sync  = blk_sync;
48
49    g_cfg.read_size      = 4;
50    g_cfg.prog_size      = 4;
51    g_cfg.block_size     = FLASH_SECTOR_SIZE;        // 1 KB
52    g_cfg.block_count    = FLASH_DATA_SIZE / FLASH_SECTOR_SIZE;  // 8
53    g_cfg.cache_size     = 16;
54    g_cfg.lookahead_size = 16;
55    g_cfg.block_cycles   = 500;
56
57    g_cfg.read_buffer      = g_read_buf;
58    g_cfg.prog_buffer      = g_prog_buf;
59    g_cfg.lookahead_buffer = g_lookahead_buf;
60
61    // 既存 FS を mount。失敗したら新規 format してから再 mount
62    int err = lfs_mount(&g_lfs, &g_cfg);
63    if (err) {
64        lfs_format(&g_lfs, &g_cfg);
65        err = lfs_mount(&g_lfs, &g_cfg);
66    }
67    return err;
68}
69
70lfs_t *fs_get(void) { return &g_lfs; }
src/fs_flash_hal.cpp — 53 lines

Open on GitHub

 1// fs_flash_hal.c
 2// RA4M1 の Data Flash (8KB) を FSP の R_FLASH_LP ドライバ経由で読み書きする。
 3
 4#include <Arduino.h>
 5#include "fs_flash_hal.h"
 6#include "r_flash_lp.h"  // FSP のドライバ
 7
 8static flash_lp_instance_ctrl_t g_ctrl;
 9static flash_cfg_t              g_cfg;
10
11int flash_hal_init(void) {
12    g_cfg.data_flash_bgo  = false;     // foreground (同期) 動作
13    g_cfg.p_callback      = NULL;
14    g_cfg.p_context       = NULL;
15    g_cfg.ipl             = 0;
16    g_cfg.irq             = FSP_INVALID_VECTOR;
17    g_cfg.err_irq         = FSP_INVALID_VECTOR;   // 本文では省略されていた
18    g_cfg.err_ipl         = 0;
19
20    if (R_FLASH_LP_Open(&g_ctrl, &g_cfg) != FSP_SUCCESS) {
21        return -1;
22    }
23    return 0;
24}
25
26int flash_hal_read(uint32_t offset, void *buf, size_t len) {
27    // Data Flash はメモリマップされているので、直接ロードで読める
28    const uint8_t *src = (const uint8_t *)(FLASH_DATA_BASE + offset);
29    uint8_t *dst = (uint8_t *)buf;
30    for (size_t i = 0; i < len; i++) dst[i] = src[i];
31    return 0;
32}
33
34int flash_hal_prog(uint32_t offset, const void *buf, size_t len) {
35    fsp_err_t err = R_FLASH_LP_Write(&g_ctrl,
36                                     (uint32_t)buf,
37                                     FLASH_DATA_BASE + offset,
38                                     len);
39    return (err == FSP_SUCCESS) ? 0 : -1;
40}
41
42int flash_hal_erase(uint32_t offset) {
43    // offset は FLASH_SECTOR_SIZE の倍数であることが前提
44    fsp_err_t err = R_FLASH_LP_Erase(&g_ctrl,
45                                     FLASH_DATA_BASE + offset,
46                                     1);  // 1 セクタ
47    return (err == FSP_SUCCESS) ? 0 : -1;
48}
49
50int flash_hal_sync(void) {
51    // foreground モードでは書き込みが完了してから戻るので何もしなくてよい
52    return 0;
53}

On the board#

../_images/adv4_bootcount.gif

Recorded while pressing RESET six times. boot count goes 9, 10, 11 … with each press. A RAM variable would go back to 0, so this shows it is stored in Data Flash.#

Pitfalls#

(Collected from reader questions — ask via GitHub Issues.)