05.M5PaperS3でBookReaderテスト1


05.M5PaperS3でBookReaderテスト1

説明

M5PaperS3でtxtを表示させるテストです。今回は、SDにあるファイル(スケッチ内で指定)からtxtを読み込み、最初から1頁分を表示します。
1.fontサイズ(32/36), 行間, 画面向き(縦/横), 画面品質はスケッチ内の変数にします。
2.SDから画面1頁以上の文字をbufにコピーします。
3.bufの最初から1Byteずつ何Byte文字か判定し、画面にセットします。
4.1行の文字, 改行から(x,y)を計算して文字を1字ずつ配置していきます。
5.1頁分を配置し終えたら表示します。

UTF-8

SDライブラリはどれもByte単位で操作します。
表示しているUTF-8文字は1~4Byte(初期の定義では6Byteまで)のマルチByte文字です。

UTF-8
https://ja.wikipedia.org/wiki/UTF-8
1byte文字:ASCII(半角数字・アルファベットの大文字・小文字等)
2byte文字:各国のアルファベット
3byte文字:東アジアの諸文字,平仮名,全角半角片仮名,句読点等
4byte文字:古代文字,第3・第4水準漢字の一部

UTF-8の文字コード表
https://orange-factory.com/dnf/utf-8.html

画面表示テスト(NG)

NG.1 固定Byteで表示

改行のみは1画面のByte数が小さく、漢字のみは1画面のByte数が多くなります。ほど良く混ざってい文章はこれが一番楽です。

while (myFile.available()) {  // 読取れるByteがあれば
  buf += char(myFile.read()); // 読取った文字
  fpn += 1;                   // 読取ったByte数
  if (fpn > 480) break;       // 設定Byte読取ったら抜ける
}
display.print(buf);           // 
display.display();            // 表示する

NG.2 設定行まで1文字づつ表示

カーソルyを見ながら1文字ずつ表示さますが、表示速度が読む速度より遅いです。

while (myFile.available()) {        // 読取れるByteがあれば
  if (display.getCursorY() < 500) { // カーソルyが500未満なら
    display.startWrite();           // バッファを効率的に使用
    display.write(myFile.read());   // 読取ったら表示 printではだめ
    display.endWrite();             // バッファを効率的に使用
  } else {                          // 設定したカーソルyになったら
    delay(5000);                    // 5秒待つ
    display.fillScreen(TFT_WHITE);  // 全画面白
    display.setCursor(0, 0);        // カーソル位置
  }
}

マルチByte文字の表示

ここではUTF-8の 2byt文字と4byte文字は無視し、1byt文字(半角英数字)と3byte文字(漢字)についてのみ表示します。さらに3byte文字の半角片仮名(EFBDA7辺り)も使用しないようにします。

UTF-8のマルチByte文字の判別

"【C言語】マルチバイト文字対応strrev関数の作成"のHPより、上位bitの値により、その文字が何Byte文字なのかわかります。

上位1Byte 判定
0xxx xxxx 1Byte文字 (0x00-0x7F)
10xx xxxx マルチByte文字の2Byte目以降 (0x80-0xBF)
110x xxxx 2Byte文字の1Byte目 (0xC0-0xDF)
1110 xxxx 3Byte文字の1Byte目 (0xE0-0xEF)
1111 0xxx 4Byte文字の1Byte目 (0xF0-0xF7)
1111 10xx 5Byte文字の1Byte目 (0xF8-0xFB)
1111 110x 6Byte文字の1Byte目 (0xFC-0xFD)

この範囲を用いてByte文字の判定をします。

SPIFFS vs FATFS

ProdESP32 #4: SPIFFS 上の FatFS
https://productionesp32.com/posts/prodesp4-fatfs-over-spiffs/
では、SPIFFS より FATFS にすべきとあります。

非推奨です...非公式に
SPIFFS リポジトリは、実際には活発に開発されていません。SPIFFS を使用していた一部のライブラリは、リポジトリ自体に公式の非推奨の通知はありませんが、非推奨と宣言しました。いずれにせよ、プロジェクトをフォークして自分で修正したい場合を除き、対処されない可能性が高い未解決の問題がかなりあります。

その他にもいくつか
これらの大規模な取引ブレーカーに加えて、SPIFFSのメモには、本番環境で使用したくない理由がいくつか示されています。これらは、Espressifのドキュメントに明確に文書化されています。
https://docs.espressif.com/projects/esp-idf/en/v5.1.1/esp32/api-reference/storage/spiffs.html

SPIFFSは、
・ディレクトリはサポートされていません
・パーティションスペースの約75%しか使用しません。最大容量に達し始めると、追跡が非常に難しいバグに遭遇します。
・パーティションがいっぱいになると、パフォーマンスは大幅に低下します。
・ファイルを削除すると、ファイルシステムに使用できないセクションが残る可能性があります。これにより、基本的にファイルシステムが断片化され、使用できなくなるポイントに達します。

ネーミングに関する注意
8.3の命名スキームの制限はFatFs実装のデフォルト構成です。menuconfig で CONFIG_FATFS_LONG_FILENAMES オプションを使用して変更できます。ただし、メモリ使用量には影響します。

アクション
すべての新規プロジェクトでは、SPIFFSの代わりにFatFを使用すべきです。

パーテーション

今後を考え、
16M Flash (3MB APP / 9.9MB FATFS)
に設定します。

設定できるパーテーションは、
Arduino-IDE > ツール > Partition Scheme: に
 19種類あります。(32M Flashを除く)
 APP容量順に表示
 ()内の単位はMB

[(APP / FATFS) の場合]
 16M Flash (3 / 9.9)↞これに設定
 16M Flash (2 /12.5)
 No OTA (2 / 2)
 Default 4MB with ffat (1.2 / 1.5)
 No OTA (1 / 3)

[(APP / SPIFFS) の場合]
 ESP SR 16M (3 / 7 / 2.9MB MODEL) ?
 8M with spiffs (3 / 1.5)
 Huge APP (3 No OTA / 1)
 No OTA (2 / 2)
 Minimal SPIFFS (1.9 with OTA / 0.19)
 Minimal (1.3 / 0.7)
 Default 4MB with spiffs (1.2 /1.5)
 No OTA (1 /3)
 Zigbee ZCZR 4MB with spiffs (?)

[?]
 RainMaker 8MB
 RainMaker 4MB
 RainMaker 4MB No OTA
 No FS 4MB (2x2 / 0)
 Custom

* OTA(Over the Air)は、wifiでプログラムを変更できます。

スケッチ


// 05_M5PaperS3_BookReader1.ino
#include <SD.h>                      // SDを使用
File myFile;                         // Fileのインスタンスを作成
#include <epdiy.h>                   // 電子ペーパードライバー
#include <M5GFX.h>                   // M5用グラフィックライブラリ
M5GFX display;                       // M5GFXのインスタンスを作成
#define SD_SPI_CS_PIN GPIO_NUM_47    // SDのpin
#define SD_SPI_SCK_PIN GPIO_NUM_39   // SDのpin
#define SD_SPI_MOSI_PIN GPIO_NUM_38  // SDのpin
#define SD_SPI_MISO_PIN GPIO_NUM_40  // SDのpin
String buf;                          // 表示文章のbuf
int32_t readB = 0;                   // 読込Byte数
int16_t fontS = 32;                  // fontサイズ (32or36)
int16_t gyoukan = 10;                // 行間
bool edpQ = true;                    // (1)高品質or(0)高速
bool gamenTate = true;               // 画面(1)縦or(0)横

void SetFont() {                                  // Font等初期化関数
  display.init();                                 // パネルの初期化
  if (edpQ) {                                     // 画面の品質設定
    display.setEpdMode(epd_mode_t::epd_quality);  // 高品質 (描画遅)
  } else {
    display.setEpdMode(epd_mode_t::epd_fastest);  // 描画速 (低品質)
  }
  if (gamenTate) {           // 画面向き設定
    display.setRotation(0);  // 縦長
  } else {
    display.setRotation(1);  // 横長
  }
  display.fillScreen(TFT_WHITE);                  // 全画面白
  if (fontS == 36) {                              // フォントサイズ設定
    display.setFont(&fonts::lgfxJapanGothic_36);  // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
  } else {
    display.setFont(&fonts::lgfxJapanGothic_32);  // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
  }
  display.setTextColor(TFT_BLACK, TFT_WHITE);  // (字,地)
  display.setTextSize(1);                      // 文字サイズの倍数 1-7
  display.setTextWrap(false, false);           // 自動折返し(x,y)
  display.setCursor(0, 0);                     // カーソル位置
  display.setAutoDisplay(false);               // 即表示しない
}


String setBuf(int32_t byteP) {                // bytePの位置から読込みそのbufを返す
  myFile = SD.open("/SDlib.txt", FILE_READ);  // 読込ファイルを開く
  Serial.println("ファイルを開く");           // 表示
  myFile.seek(byteP);                         // 読取開始のByte位置
  readB = 0;                                  // 読取Byte数リセット
  while (myFile.available()) {                // 読取れるByteがあれば
    buf += char(myFile.read());               // 読取った文字
    readB++;                                  // 読取ったByte数
    if (readB > ((540 / fontS) * (960 / fontS) * 3)) break;
    // 行間0,全3Byte文字として1頁以上読込んだら抜ける
  }
  Serial.println("1頁分以上読込済");   // 表示
  Serial.printf("%dByte\n", readB);  // 読取ったByte数
  //Serial.println(buf);              // 読取った内容
  return buf;
}


int setView(String buf) {        // 1頁セット
  int16_t viewX = 0, viewY = 0;  // 表示x,y位置 リセット
  int16_t i = 0;                 // Byte位置
  int16_t bufL = buf.length();   // bufのByte数 \0は含まず
  do {
    bool CRon = false;  // 改行 リセット

    // 1頁セット終わり
    if ((CRon == true) && (viewY > display.height() - fontS)) break;
    // 改行で行が下まで来たら抜ける

    // 1行セット終わり
    if (viewX > (display.width() / fontS - 1) * fontS) {  // 横が1文字分少なく を超えていたら改行
      CRon = true;                                        // 改行
      viewX = 0;                                          // 画面の左端
      viewY += (fontS + gyoukan);                         // 行間スペースを含めて移動へ
    }

    // 改行
    if (buf.charAt(i) == 0x0D) {   // CR+LF だったら
      CRon = true;                 // 改行
      i += 2;                      // CR+LF Windows等
      viewX = 0;                   // 画面の左端
      viewY += (fontS + gyoukan);  // 行間スペースを含めて移動へ
    }

    // LFは無視
    if (buf.charAt(i) == 0x0A) {   // LFだったら 無いはず
      i += 1;                      // 1Byte進める
      viewX = 0;                   // 画面の左端
      viewY += (fontS + gyoukan);  // 行間スペースを含めて移動へ
    }

    //prit
    display.setCursor(viewX, viewY);           // カーソル位置
    if (buf.charAt(i) < 0x80) {                // 1Byte文字なら
      display.print(buf.substring(i, i + 1));  // 1文字セット
      i += 1;                                  // 1Byte進める
      viewX += fontS / 2;                      // 半角
    } else if (buf.charAt(i) < 0xC0) {         // マルチByteの2Byte目以降
      // ここへは来ないはず
    } else if (buf.charAt(i) < 0xE0) {         // 2Byte文字なら
      display.print(buf.substring(i, i + 2));  // 1文字セット
      i += 2;                                  // 2Byte進める
      viewX += fontS;                          // 全角
    } else if (buf.charAt(i) < 0xF0) {         // 3Byte文字なら
      display.print(buf.substring(i, i + 3));  // 1文字セット
      i += 3;                                  // 3Byte進める
      viewX += fontS;                          // 全角
    } else if (buf.charAt(i) < 0xF8) {         // 4Byte文字なら
      display.print(buf.substring(i, i + 4));  // 1文字セット
      i += 4;                                  // 4Byte進める
      viewX += fontS;                          // 全角
    } else if (buf.charAt(i) < 0xFC) {         // 5Byte文字なら
      display.print(buf.substring(i, i + 5));  // 1文字セット
      i += 5;                                  // 5Byte進める
      viewX += fontS;                          // 全角
    } else if (buf.charAt(i) < 0xFE) {         // 6Byte文字なら
      display.print(buf.substring(i, i + 6));  // 1文字セット
      i += 6;                                  // 6Byte進める
      viewX += fontS;                          // 全角
    } else {
      // ここへは来ないはず
    }
  } while (i < bufL);  // buf終わりまでループ
  return (i);
}

void setup() {
  Serial.begin(115200);  // シリアル通信開始
  while (!Serial) {      // シリアル接続待ち
    ;
  }
  Serial.println("シリアル表示開始");  // シリアル表示
  SPI.begin(SD_SPI_SCK_PIN, SD_SPI_MISO_PIN,
            SD_SPI_MOSI_PIN, SD_SPI_CS_PIN);
  delay(100);                              // 0.1秒待つ
  SetFont();                               // Font等初期化関数へ
  SD.begin(47);                            // SDライブラリとカードを初期化
  Serial.println("SDライブラリの初期化");  // 表示
  int32_t byteP = 0;                       // 最所のByte位置から
  setBuf(byteP);                           // 1頁以上のbuf読込関数へ
  Serial.print(buf);                       // bufの内容
  setView(buf);                            // 1頁分セット関数へ
  display.display();                       // 1頁表示
  myFile.close();                          // ファイルを閉じる
}
void loop() {}
* flash memory(3.1Mbyte)のうち、スケッチが44%使用。RAM(327kbyte)のうち、global変数が6%使用、local変数で306kbyte使用可能。(1000byte=1kbyteで計算)
フォントサイズが、32でも36 でも同じ値です。