11.picoのLittleFSサンプル動作 Arduino


11.picoのLittleFSサンプル動作 Arduino

LittleFSを利用してデータをFlashに置くと、電源が切れても前回のデータが残っています。サンプルスケッチを2つ動作させます。

LittleFS

ファイルシステム(FS)はスケッチと同じFlashチップに保存されて、新しいスケッチをプログラミングしてもファイルシステムの内容やEEPROMのデータは変更されません。

Flashメモリのレイアウト

|------------|-------|----|
^            ^       ^
Sketch         File system  EEPROM
LittleFSは、Flashベースのデバイス向けに設計されたファイルシステムで、ファイルシステムの破損に非常に強くなっているそうです。

PicoLittleFSDataUpload拡張機能

PicoLittleFSは、Arduino-IDEのツールで、スケッチのdataディレクトリの内容をファイルシステムにアップロードするためのメニュー項目を追加します。

ツールのダウンロード

必ずインストールが必要です。
https://github.com/earlephilhower/arduino-pico-littlefs-plugin/releases
から
PicoLittleFS-0.2.0.zip
をダウンロード、解凍し
.../Arduino/tools/PicoLittleFS/tool/picolittlefs.jar
となるようにホルダを作り、jarファイルをコピーします。

Arduino-IDEを再起動し、スケッチを開きます。

Arduino-IDE > ツール > Pico LittleFS Data Upload
のメニューが新しく出来ています。

説明
https://arduino-pico.readthedocs.io/en/latest/fs.html

サンプル1について

FSのサイズ設定

Arduino-IDE > ツール > Flash Size: > 2MB(Sketch:1984kB, FS:64kB) を選択
* FSは、0kB,64kB,128kB,256kB,512kB,1MB の6種類あり、スケッチ領域は2MBからその分減ります。

スケッチ1読込み

Arduino-IDE > ファイル > スケッチ例 > (Raspberry Pi Pico用のスケッチ例) LittleFS > FSUpload.ino

アップロード

dataという名前のディレクトリを作成し、その中にある作ったファイルをアップロードします。

Arduino-IDE > スケッチ > スケッチフォルダを表示
すでに、スケッチのdataホルダに
・file1.txt
・PicoLittleFS-0.2.0.zip
が入っていました。file1.txtの内容は、以下です。

Hello!
(空白行)
Welcome to the Raspberry Pi Pico using arduino-pico!
(空白行)
(空白行)
ボードとシリアルポートが選択されていて、シリアルモニタが閉じている事を確認します。(シリアルモニターがシリアルポートを制御している場合、アップロードは失敗します)

Arduino-IDE > ツール > Pico LittleFS Data Upload
(dataホルダのファイル全部がフラッシュファイルへアップロードします)

Serial.write(data);

1byteの数値データ(構文中でのdata)を送信

Serial.write(3);//ASCII文字列の"3"(b110011 1010 1010)ではなく、2進数の3(b0000 0011)が送られます。

Serial.print(文字列);

数値は1桁ずつASCII文字に変換。浮動小数点数は、小数点以下第2位までシリアルポートへ出力(デフォルト)。Byte型データは1文字として送信。文字列はそのまま送信

Serial.print(78) // "78"が出力
Serial.print("Hello world.") // "Hello world."

スケッチ1動作

動作後終了し、Arduino-IDEを再起動し動作させるとシリアルモニタの表示でカウントupしているのがわかります。また、Flashに保存したfile1.txtも表示します。

スケッチ1


//アップロードされたファイルを読取り、スケッチが起動されるたびにカウンターファイルを1増加
//必ずPicoLittleFSDataUpload拡張機能をインストール
// https://github.com/earlephilhower/arduino-pico-littlefs-plugin/releases
//実行前に:1)ツール > フラッシュサイズ >(FS /ファイルシステムのサイズ)を選択
// 2)ツール > Pico LittleFS Data Upload ツールを使用して、
// スケッチdataディレクトリの内容をPicoに転送
#include <LittleFS.h>  // ファイルシステム使用

void setup() {
  Serial.begin(115200); // シリアルモニタ初期化
  delay(5000);          // 5秒待つ
  LittleFS.begin();     // ファイルシステム初期化
  char buff[32];        // cntを格納
  int cnt = 1;          // カウント変数
  File f = LittleFS.open("counts.txt", "r");  // 指定fileを読込モードで開く
  if (f) {                                    // もしあれば
    bzero(buff, 32);                          // (メモリ領域の開始アドレス,ゼロにするバイト数)。
    if (f.read((uint8_t *)buff, 31)) {        // 読込む (文字列,要素数)
      sscanf(buff, "%d", &cnt);               // (解析文字列へのポインタ,解析指定子,変数)
      Serial.printf("%d回実行しました\n", cnt); // 表示
    }
    f.close();                              // ファイルを閉じる
  }
  cnt++;                                    // カウント1加算
  sprintf(buff, "%d\n", cnt);               // cnt変数に変換 (保存文字列,書式,文字)
  f = LittleFS.open("counts.txt", "w");     // 指定fileを書込みモードで開く
  if (f) {                                  // もしあれば
    f.write(buff, strlen(buff));            // 書込む (文字列,要素数)
    f.close();                              // ファイルを閉じる
  }
  Serial.println("---------------");        // 表示
  File i = LittleFS.open("file1.txt", "r"); // 指定fileを読込モードで開く
  if (i) {                                  // もしあれば
    while (i.available()) {                 // 読取れるバイト数(文字数)を取得
      Serial.write(i.read());               // 読込んだ最初の1byteデータ(0-255)を表示
    }
    Serial.println("---------------");      // 表示
    i.close();                              // ファイルを閉じる
  }
}

void loop() {}                              // ループは何もしない
* Flashメモリ(2Mbyte)を、スケッチが3%使用。
RAM(262kbyte)を、グローバル変数が2%使用、ローカル変数で254kbyte使用可能。(1kbyte=1000byteで計算)

サンプル2について

チャンク

大きなデータを分割して制御情報を付加したひとまとまりの断片です。先頭にデータ長やデータの種類、識別子などを付加し、これを連ねてデータ全体を表現します。画像・音声・動画など大きなサイズのバイナリデータの送受信を行なう際によく用いられます。

F( )マクロとPSTR( )マクロ

picoは、Flashが2MBとSRAMが264kBあります。Flashはプログラムの保存先で、実行時には読取りだけが可能です。
SRAMは実行時におけるメモリー領域で、変数が保存され、実行時に読み書きできます。少ないSRAMを消費する要因の1つに文字列があります。数値型であれば1~4バイトで表現できますが、文字は1文字1バイトと、表現内容に比べて必要領域が大きくなります。例えば
Serial.println("Hello World");
これだけで Hello world\0 の12バイト分をSRAMで消費します。
SRAMを節約する方法として、書換えが発生しない文字列はFlashに配置する方法があります。これを実現するのが F()マクロ(定義: WString.h)です。コード内の "Text" を F("Text") とするだけで、この文字列をSRAMからFlashに移動しつつ、Flashから読み取ることができます。
しかし、たいていの関数はSRAM内の値を操作の対象としており、Flashに配置されてしまうと読出しができません。例えば文字列をフォーマッティングできる sprintf() の第二引数には、Fマクロを使うことができません。
**_P 関数に引き渡す文字列はPSTR()マクロを通します。使い方はF()マクロと同じです。F()マクロも内部的にはPSTR()マクロを使っています。

seek

file.seek(offset, mode)
modeに応じて、ファイル内の現在位置を移動します。
mode
SeekSet:ファイルの先頭からのオフセットbyte数
SeekEnd:ファイルの末尾からのオフセットbyte数
SeekCur:現在位置はオフセットbyteだけ移動

位置が正常に設定された場合はtrueを返します。

FSのサイズ設定

Arduino-IDE > ツール > Flash Size: > 2MB(Sketch:1MB, FS:1MkB) を選択
* FSは、0kB,64kB,128kB,256kB,512kB,1MB の6種類あり、スケッチ領域は2MBからその分減ります。

スケッチ2読込み

Arduino-IDE > ファイル > スケッチ例 > (Raspberry Pi Pico用のスケッチ例) LittleFS > SpeedTest.ino

以下の6通りの時間測定です。
256bのチャンクで ①書込み, ②順次読取り, ③逆方向読取り, ④順次読取りで間違い
1bのチャンクで ⑤書込み, ⑥順次読取り

スケッチ2


// ファイルシステムの速度テスト
// Earle F. Philhower、IIIによってパブリックドメインにリリース
// ファイルシステムはテストの開始時にフォーマットします
#include <FS.h>         // 必要?
#include <LittleFS.h>   // ファイルシステム使用
#define TESTFS LittleFS // テストするファイルシステムを選択
//#define TESTFS SDFS   // SDカードのテスト
#define TESTSIZEKB 256  // テストするファイルのサイズ(kB)

const char *rate(unsigned long start, unsigned long stop, unsigned long bytes) {//速度計算
  static char buff[64];                                  // 表示速度格納
  if (stop == start) {                                   // 動作時間0mSなら
    strcpy_P(buff, PSTR("∞ bytes/s"));                  // 速度測定限度以下
  } else {                                               // 動作時間1mS以上なら
    unsigned long delta = stop - start;                  // 動作時間計算
    float r = 1000.0 * (float)bytes / (float)delta;      // 速度計算
    if (r >= 1000000.0) {                                // 1Mbyte以上なら
      sprintf_P(buff, PSTR("%0.2fMB/s"), r / 1000000.0); // 1Mで割る MB/s
    } else if (r >= 1000.0) {                            // 1kbyte以上なら
      sprintf_P(buff, PSTR("%0.2fkB/s"), r / 1000.0);    // 1kで割る kB/s
    } else {                                             // それ以外
      sprintf_P(buff, PSTR("%dbytes/s"), (int)r);        // そのまま変換 B/s
    }
  }
  return buff;  // 開始時間(mS), 終了時間(mS), 処理容量(kbyte)を入力し、速度を返す
}

void DoTest(FS *fs) {                 // テスト実行関数 1回実行
  if (!fs->format()) {                // フォーマット出来ない時
    Serial.println("中止 format NG");  // 表示
    return;                           // 中止
  }
  if (!fs->begin()) {                 // ファイルシステムが初期化出来なければ
    Serial.println("中止 begin NG");  // 表示
    return;                           // 中止
  }
  uint8_t data[256];                  // 転送データ変数
  for (int i = 0; i < 256; i++) {     // i=0から255まで
    data[i] = (uint8_t) i;            // data[0]~data[255]作成
  }

  unsigned long start = millis();           // 開始時間 ボードが実行開始してからのmS
  File f = fs->open("/testwrite.bin", "w"); // 指定fileを書込みモードで開く
  if (!f) {                                 // 開けなければ
    Serial.println("中止 ファイル書込みのために開けません");  // 表示
    return;                                              // 中止
  }
  for (int i = 0; i < TESTSIZEKB; i++) { // i=0から255まで
    for (int j = 0; j < 4; j++) {        // 4回繰り返す 256x4=1024
      f.write(data, 256);                // 書込む (文字列,要素数)
    }
  }
  f.close();                             // ファイルを閉じる
  unsigned long stop = millis();         // 終了時間
  Serial.printf("1)%4.0lumS         256byteのチャンク %dkB 書込み",
                stop - start, TESTSIZEKB);  // 表示

  f = fs->open("/testwrite.bin", "r");           // 指定fileを読込モードで開く
  Serial.printf(" 作成file=%zubyte\n", f.size()); // %zu=size_t型10進符号無し整数
  f.close();                                     // ファイルを閉じる

  // 64kB 1bチャンク 書込み
  start = millis();                 // 開始時間
  f = fs->open("/test1b.bin", "w"); // 指定fileを書込みモードで開く
  for (int i = 0; i < 65536; i++) { // i=0から65535まで 64x1024
    f.write((uint8_t*)&i, 1);       // 書込む (文字列,要素数)
  }
  f.close();                        // ファイルを閉じる
  stop = millis();                  // 終了時間
  Serial.printf("2)%4.0lumS= %s   1bのチャンク  64kB 書込み\n",
                stop - start, rate(start, stop, 65536));  // 表示

  // 64kB 1bチャンク 読み取り
  start = millis();                 // 開始時間
  f = fs->open("/test1b.bin", "r"); // 指定fileを読込モードで開く
  for (int i = 0; i < 65536; i++) { // i=0から65535まで 64x1024=65536
    char c;                         //
    f.read((uint8_t*)&c, 1);        // 読込む (配列のポインタ,要素数)
  }
  f.close();                        // ファイルを閉じる
  stop = millis();                  // 終了時間
  Serial.printf("3)%4.0lumS=%s   1bのチャンク  64kB 読取り 順次\n",
                stop - start, rate(start, stop, 65536));  // 表示


  // 256kB 256bチャンク 読み取り 逆方向
  start = millis();                      // 開始時間
  f = fs->open("/testwrite.bin", "r");   // 指定fileを読込モードで開く
  for (int i = 0; i < TESTSIZEKB; i++) { // i=0から255まで
    for (int j = 0; j < 4; j++) {        // 4回繰り返す 256x4=1024
      if (!f.seek(256 + 256 * j * i, SeekEnd)) {// ファイル末尾からのbyte数 256,256x2,256x3・・・
        Serial.printf("中止 %dをシークできません\n", -256 - 256 * j * i);  // 表示
        return;                                    // 中止
      }
      if (256 != f.read(data, 256)) {              // 読取れなかったら
        Serial.println("中止 256byteを読取れません"); // 表示
        return;                                    // 中止
      }
    }
  }
  f.close();                                       // ファイルを閉じる
  stop = millis();                                 // 終了時間
  Serial.printf("4)%4.0lumS=  %s 256bのチャンク %dkB 読取り 逆方向\n", stop - start,
                rate(start, stop, TESTSIZEKB * 1024), TESTSIZEKB);  // 表示

  //256kB 256bチャンク 読み取り 順次 flashとRAM内間違い
  start = millis();                      // 開始時間
  f = fs->open("/testwrite.bin", "r");   // 指定fileを読込モードで開く
  f.read();                              // 1文字読込む
  for (int i = 0; i < TESTSIZEKB; i++) { // i=0から255まで
    for (int j = 0; j < 4; j++) {        // 4回繰り返す 256x4=1024
      f.read(data + 1, 256);             // 読込む (配列のポインタ,要素数)
    }
  }
  f.close();                             // ファイルを閉じる
  stop = millis();                       // 終了時間
  Serial.printf("5)%4.0lumS=  %s 256bのチャンク %dkB 読取り 順次 間違い\n", stop - start,
                rate(start, stop, TESTSIZEKB * 1024), TESTSIZEKB);  // 表示

  // 256kB 256bチャンク 読み取り
  start = millis();                      // 開始時間
  f = fs->open("/testwrite.bin", "r");   // 指定fileを読込モードで開く
  for (int i = 0; i < TESTSIZEKB; i++) { // i=0から255まで
    for (int j = 0; j < 4; j++) {        // 4回繰り返す 256x4=1024
      f.read(data, 256);                 // 読込む (配列のポインタ,要素数)
    }
  }
  f.close();                             // ファイルを閉じる
  stop = millis();                       // 終了時間
  Serial.printf("6)%4.0lumS=  %s 256bのチャンク %dkB 読取り 順次\n", stop - start,
                rate(start, stop, TESTSIZEKB * 1024), TESTSIZEKB);  // 表示
}

void setup() {
  Serial.begin(115200);        // シリアルモニタ初期化
  delay(5000);                 // 5秒待つ
  Serial.println("テスト開始");  // 表示
  Serial.flush();              // データ送信完まで待つ
  DoTest(&TESTFS);             // テスト実行関数へ ここのみ
  Serial.println("テスト終了");  // 表示
}

void loop() {
  delay(10000);                // 10秒待つ
}
* Flashメモリ(1Mbyte)を、スケッチが7%使用。
RAM(262kbyte)を、グローバル変数が2%使用、ローカル変数で254kbyte使用可能。(1kB=1000Mで計算)