09_M5PaperS3でTxtReader2


09_M5PaperS3でTxtReader2

説明

前回(08_M5PaperS3でTxtReader)の修正・追加版です。

表示文字

SD内のtxtファイルの中身を表示します。UFT-8のマルチバイト文字に対応していますが、表示フォントは、
lgfxJapanGothic_32 横幅32px か、
lgfxJapanGothic_36 横幅36px
なので、常用漢字+αで4639文字。半角カナ、→の矢印、①等はありません。詳しくは、前回を参照してください。

ファイル名

SDライブラリは、8.3形式かと思いまいたが、そのままで日本語もロングネームも読めました。
SDのルートにある50ファイルまでを表示します。前のファイル頁は"File▲"、次のファイル頁は"File▼"をタッチし、9ファイルごとに表示します。ファイル名をタッチするとファイル内容を表示します。

時刻・バッテリー容量

起動画面の1行目に表示します。両数値は随時更新しています。

メニュー2

1.時刻設定:設定後RTCに保存します。
2.スマホシャッター:スマホのカメラアプリのシャッターを動作させます。

作成した関数

・String setBuf(int32_t byteP)
 bytePの位置から文章を1頁読込み、その文字列bufを返します。
 printFirst()とloop()から呼ばれ、1頁以上読み込んだらbufを返します。
・int setView(String buf)
 1頁分を表示セットします。
 printFirst()とloop()から呼ばれ、表示後、処理Byte数を返します。
・printFirst()
 メニュー内容を設定し1頁目を表示します。
 setup()とloop()から呼ばれ、メニュー内を設定し1頁目を表示し、戻ります。
・printMenu()
 起動画面(メニュー)を表示します。
 setup()とloop()から呼ばれ、表示後、ファイル名かmenu2をタッチしたら戻ります。
・printMenu2()
 メニュー2を表示します。
 setup()から呼ばれ、各関数へ移動します。メニュー2から戻るには再起動します。
・setTime()
 時刻を手動設定します。
 メニュー2から呼ばれ、時刻を手動設定し、"RTC保存"後、戻るには再起動します。
・shutter()
 スマホシャッター
 メニュー2から呼ばれ、画面タッチで動作し、終了は再起動します。
・SetFont()
 Font等初期化します。
 setup()から呼ばれ、設定後戻ります。

* 再起動は横のボタンを押します。

各Ver

Arduino IDE v2.3.4

ボードマネージャ
・esp32 by Espressif Systems は v3.0.7以下の事。v3.0.7
・M5Stack by M5Stack official v2.1.1→v2.1.3
 M5Stsck内にM5PaperS3が出てきました
ライブラリ
・M5Unified by M5Stack は v0.2.2以上の事
 v0.2.5にすると動作しなくなったのでv0.2.2へ戻しました。
・M5GFX by M5Stack は v0.2.2以上の事
 v0.2.6にすると動作しなくなったのでv0.2.3へ戻しました。

"06.ATOM Liteでスマホの写真シャッター"を参照し、"ESP32-BLE-Keyboard"をインストールします。

ボードの設定

Arduino-IDE > ツールにて
・ボード > M5Stack > M5PaperS3
・ポート : "COM□"
・その他はDefault

使い方

SDのファイル

・ルートにある事
・日本語・ロングネーム可
・50ファイル以下(変数より)
の制約があります。
表示は、ファイル名の拡張子も含め、全角9.5文字(半角19文字)まで ファイルサイズの表示は、999kB以下(表示桁数より)です。
起動するとメニュー画面が開きます。

メニュー画面

・品質・速度 : 高品質 / 高速 (タッチで変更)
・画面向き : 縦向き / 横向き (タッチで変更)
・フォントサイズ : 32px / 36px (タッチで変更)
・行間 : 5px / 10px / 15px (タッチで変更)
・ファイル名
 "File▲"/"File▼"は前/次のファイル名を表示します。
 タッチでメニュー画面を終了し、ファイルの中身を表示します。

ファイル名の表示

例 SDのルートに以下の13ファイルがある場合
・C++ 独学入門サイト【C言語からステップアップするカリキュラム】.txt
・SDライブラリの使用方法.txt
・M5Canvasについて.txt
・M5GFX API.txt
・M5Unified API.txt
・M5Unifiedへの移植ポイント.txt
・Open-Meteo.txt
・室内快適湿度40~60%.txt
・time.txt
・シンフリーサーバー残量.txt
・すぐやる事.txt
・Xfreeの終了.txt
・FatFsブログ.txt

文章表示

ファイル名選択後、文章を表示します。
・1行目にファイル名、設定値(速度・品質/文字サイズ/行間)、頁番号を表示します。
・次の頁に移動は、画面右側タッチ
・前の頁に移動は、画面左側タッチ
ただし、画面下側20%の部分のタッチは無視します。
(親指の付け根で触ってしまうので)

時刻合わせ(メニュー2)

年月日時分を設定し、"RTC保存"をタッチしたタイミングでRTCに書込みます。書込み後は再起動します。中断は保存せず再起動します。秒は30秒に固定です。

スマホシャッター(メニュー2)

スマホとBluetoothの接続をします。
1.M5PaperS3のスマホシャッターを起動
2.スマホ > 設定 > 接続設定 > 新しいデバイスとペア設定 > BLE-Shutter1 > "ペア設定する"
3.スマホに"HD Camera"をインストール
(既存のカメラではシャッターが動作しなかったので)
4.スマホのカメラ起動
5.M5PaperS3の画面をタッチでシャッターが動作します。
6.終了は再起動します。

次回

追加するかもしれない項目
・しおり
・設定頁までジャンプ
・レート変換電卓
・スワイプ
・世界天気予報

スケッチ


  // 09_M5PaperS3_BookReader2.ino  960x540
  // 数分放置で画面が黒くなるのはリフレッシュ必要 画面黒→画面白で時間延ばす
  #include <SD.h>                           // SDを使用
  File myFile;                              // Fileのインスタンスを作成
  #include <epdiy.h>                        // 電子ペーパードライバー
  #include <M5Unified.h>                    // M5共通ライブラリ
  #include <BleKeyboard.h>                  // BLEキーボードを使用
  BleKeyboard bleKeyboard("BLE-Shutter1");  // Bluetooth名を設定
  
  #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
  int16_t fontS = 32;                  // 初期fontサイズ (32/36)
  int16_t gyoukan = 10;                // 初期行間px (5/10/15)
  bool edpQ = false;                   // (true)高品質 / (false)高速
  bool gamenTate = true;               // 画面(true)縦 / (false)横
  int32_t pageN, pageB[1000];          // 頁番号,頁最初のByte値
  int32_t goPageN;                     // ジャンプ先頁番号
  lgfx::touch_point_t tp[1];           // タッチポイント
  int16_t menuDY = 70;                 // メニュー画面y刻みpx
  String fName, fList[50][2];          // ファイルリスト[n][(0)fName,(1)fByte]
  int8_t fPage = 0;                    // ファイル名選択頁(9ファイル/頁)
  bool noPrint = false;                // setViewでprintしないの義(する)
  //bool txtEnd;                       // 文章終わり
  bool goMenu2 = false;  // 最初はメニュー2へ行かない
  
  int16_t yy = 2025, mm = 02, dd = 28, hhh = 23, mmm = 59;  // 年月日時分の初期値
  static constexpr const char* const wd[7] = { "日", "月", "火", "水", "木", "金", "土" };
  
  void SetFont() {                                   // Font等初期化関数
    M5.Display.startWrite();                         // バッファを効率的に使用開始
    M5.Display.setEpdMode(epd_mode_t::epd_fastest);  // 描画速 (低品質)
    //M5.Display.setEpdMode(epd_mode_t::epd_quality);// 高品質 (描画遅)
    M5.Display.setRotation(0);  // 縦長
    //M5.Display.setRotation(1);// 横長
    M5.Display.setFont(&fonts::lgfxJapanGothic_32);  // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
    //M5.Display.setFont(&fonts::lgfxJapanGothic_36);// 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
    M5.Display.fillScreen(TFT_WHITE);               // 全画面白
    M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);  // (字,地)
    M5.Display.setTextSize(1);                      // 文字サイズの倍数 1-7
    M5.Display.setTextWrap(false, false);           // 自動折返し(x,y)
    M5.Display.setCursor(0, 0);                     // カーソル位置
    M5.Display.setAutoDisplay(false);               // 即表示しない
  }
  
  String setBuf(int32_t byteP) {               // bytePの位置から読込みそのbufを返す
    myFile = SD.open("/" + fName, FILE_READ);  // 読込ファイルを開く
    myFile.seek(byteP);                        // 読取開始のByte位置
    int32_t i = 0;                             // 読取Byte数リセット
    buf = "";                                  // bufリセット
    while (myFile.available()) {               // 読取れるByteがあれば
      buf += char(myFile.read());              // 読取文字
      i++;                                     // 読取Byte数1加算
      if (i > ((540 / fontS) * (960 / fontS) * 3)) break;
      // 行間0,全3Byte文字として1頁以上読込んだら抜ける
    }
    myFile.close();  // ファイルを閉じる
    return buf;      // 1頁以上の文字を返す
  }
  
  int setView(String buf) {              // 表示セット関数
    int16_t tabX = 2;                    // 左余白
    int16_t viewX = tabX, viewY = 0;     // 表示x,y位置 リセット
    int16_t i = 0;                       // Byte位置 リセット
    int16_t bufL = buf.length();         // bufの全Byte数 \0は含まず
    if (noPrint == false) {              // printするなら
      M5.Display.fillScreen(TFT_BLACK);  // 全画面黒 しないと段々黒くなる
      // -------------------------------- セットを表示
      M5.Display.display();              // セットを表示
      vTaskDelay(1);                     // 1tickの間タスクをブロック
      M5.Display.fillScreen(TFT_WHITE);  // 全画面白
      M5.Display.display();              // セットを表示
      vTaskDelay(1);                     // 1tickの間タスクをブロック
    }
    // -------------------------------- 1行目のタイトルセット
    M5.Display.setCursor(viewX, 1);     // カーソル位置
    if (noPrint == false) {             // printするなら
      M5.Display.print(fName);          // ファイル名セット
      if (edpQ) {                       // 高品質画面なら
        M5.Display.print(" (高品質/");  // 表示セット
      } else {                          // 高速画面なら
        M5.Display.print(" (高速/");    // 表示セット
      }
      M5.Display.printf("%d/", fontS);        // フォントサイズpxセット
      M5.Display.printf("%d)", gyoukan);      // 行間pxセット
      M5.Display.setCursor(440, 1);           // カーソル位置
      M5.Display.printf("%4d頁", pageN + 1);  // 頁番号セット
    }
    viewY += fontS;                                                           // 次の行へ
    if (noPrint == false) {                                                   // printするなら
      M5.Display.drawFastHLine(0, viewY - 1, M5.Display.width(), TFT_BLACK);  // 水平線セット
    }
    while (i < bufL) {    // buf終わりまでループ
      bool CRon = false;  // 改行 リセット
      // -------------------------------- 1行セット終わり
      if (viewX > (M5.Display.width() / fontS - 1) * fontS) {  // 横が1文字分少なく を超えていたら改行
        CRon = true;                                           // 改行
        viewX = tabX;                                          // 画面の左端
        viewY += (fontS + gyoukan);                            // 行間スペースを含めて移動へ
      }
      // -------------------------------- 改行
      if (buf.charAt(i) == 0x0D) {   // CR+LF だったら
        CRon = true;                 // 改行
        i += 2;                      // CR+LF Windows等
        viewX = tabX;                // 画面の左端
        viewY += (fontS + gyoukan);  // 行間スペースを含めて移動へ
      }
      // -------------------------------- LFは無視
      if (buf.charAt(i) == 0x0A) {   // LFだったら 無いはず
        i += 1;                      // 1Byte進める
        viewX = tabX;                // 画面の左端
        viewY += (fontS + gyoukan);  // 行間スペースを含めて移動へ
      }
      // -------------------------------- 1頁セット終わり
      if ((viewY + gyoukan + fontS > M5.Display.height())
          && (CRon == true)) break;  // 行が下まで来て改行なら抜ける
      // -------------------------------- prit
      M5.Display.setCursor(viewX, viewY);           // カーソル位置
      if (buf.charAt(i) < 0x80) {                   // 1Byte文字なら
        M5.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文字なら
        M5.Display.print(buf.substring(i, i + 2));  // 1文字セット
        i += 2;                                     // 2Byte進める
        viewX += fontS;                             // 全角
      } else if (buf.charAt(i) < 0xF0) {            // 3Byte文字なら
        M5.Display.print(buf.substring(i, i + 3));  // 1文字セット
        i += 3;                                     // 3Byte進める
        viewX += fontS;                             // 全角
      } else if (buf.charAt(i) < 0xF8) {            // 4Byte文字なら
        M5.Display.print(buf.substring(i, i + 4));  // 1文字セット
        i += 4;                                     // 4Byte進める
        viewX += fontS;                             // 全角
      } else if (buf.charAt(i) < 0xFC) {            // 5Byte文字なら
        M5.Display.print(buf.substring(i, i + 5));  // 1文字セット
        i += 5;                                     // 5Byte進める
        viewX += fontS;                             // 全角
      } else if (buf.charAt(i) < 0xFE) {            // 6Byte文字なら
        M5.Display.print(buf.substring(i, i + 6));  // 1文字セット
        i += 6;                                     // 6Byte進める
        viewX += fontS;                             // 全角
      } else {
        // ここへは来ないはず
      }
    }
    if (noPrint == false) {  // printするなら
      // -------------------------------- セットを表示
      M5.Display.display();  // セットしたのを表示
      vTaskDelay(1);         // 1tickの間タスクをブロック
    }
    return (i);  // 処理Byte数を返す
  }
  
  void printMenu() {                                 // 起動画面表示
    M5.Display.setRotation(0);                       // 縦長
    M5.Display.setFont(&fonts::lgfxJapanGothic_32);  // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
    M5.Display.setEpdMode(epd_mode_t::epd_fastest);  // 描画速 (低品質)
    M5.Display.fillScreen(TFT_WHITE);                // 全画面白
    M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);   // (字,地)
    goPageN = 0;                                     // ジャンプ先頁番号=1頁目
    int16_t battLevMae = 0;
    while (true) {  // ファイル名をタッチしたら抜ける
      // -------------------------------- メニュー1行目セット h50
      M5.Display.fillRect(0, 0, 540, 50, TFT_LIGHTGREY);  // 塗りつぶし
      M5.Display.setCursor(5, (50 - 32) / 2);             // カーソル位置
      auto dt = M5.Rtc.getDateTime();                     // RTC時間取得
      M5.Display.printf("%04d/%02d/%02d(%s)%02d:%02d",
                        dt.date.year, dt.date.month, dt.date.date,
                        wd[dt.date.weekDay], dt.time.hours, dt.time.minutes);
      int16_t battLev = M5.Power.getBatteryLevel();  // batt残量を取得 0-100
      if (abs(battLev - battLevMae) < 2) {           // ±1%のちらつきは無視する
        battLev = battLevMae;                        // 前のまま
      }
      M5.Display.setCursor(540 - 16 * 4 - 15, (50 - 32) / 2);  // カーソル位置
      M5.Display.printf("%3d%%", battLev);                     // batt残量を画面セット
      battLevMae = battLev;                                    // 今回のを前の%とする
      // -------------------------------- メニュー2行目セット
      int16_t menuX = 0, menuY = 70;                    // カーソル変数
      M5.Display.setCursor(menuX, menuY);               // カーソル位置
      if (edpQ == true) {                               // 高品質画面なら
        M5.Display.print(" 品質・速度 : ");              // 表示をセット
        M5.Display.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
        M5.Display.print("高品質");                     // 表示をセット
        M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);  // (字,地)
        M5.Display.print("  高速");                   // 表示をセット
      } else {                                          // 高速画面なら
        M5.Display.print(" 品質・速度 : 高品質  ");    // 表示をセット
        M5.Display.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
        M5.Display.print("高速");                       // 表示をセット
        M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);  // (字,地)
      }
      // -------------------------------- メニュー3行目セット
      menuY += menuDY;                                  // 次の行へ
      M5.Display.setCursor(menuX, menuY);               // カーソル位置
      if (gamenTate == true) {                          // 縦画面なら
        M5.Display.print(" 画面向き  : ");              // 表示をセット
        M5.Display.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
        M5.Display.print("縦向き");                     // 表示をセット
        M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);  // (字,地)
        M5.Display.print("  横向き");                 // 表示をセット
      } else {                                          // 横画面なら
        M5.Display.print(" 画面向き  : 縦向き  ");    // 表示をセット
        M5.Display.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
        M5.Display.print("横向き");                     // 表示をセット
        M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);  // (字,地)
      }
      // -------------------------------- メニュー4行目セット
      menuY += menuDY;                                  // 次の行へ
      M5.Display.setCursor(menuX, menuY);               // カーソル位置
      if (fontS == 32) {                                // fontサイズが32pxなら
        M5.Display.print(" Font Size : ");              // 表示をセット
        M5.Display.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
        M5.Display.print("32px");                     // 表示をセット
        M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);  // (字,地)
        M5.Display.print("  36px");                 // 表示をセット
      } else {                                          // fontサイズが36pxなら
        M5.Display.print(" Font Size : 32px  ");    // 表示をセット
        M5.Display.setTextColor(TFT_WHITE, TFT_BLACK);  // (字,地)
        M5.Display.print("36px");                     // 表示をセット
        M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);  // (字,地)
      }
      // -------------------------------- メニュー5行目セット
      menuY += menuDY;                                     // 次の行へ
      M5.Display.setCursor(menuX, menuY);                  // カーソル位置
      if (gyoukan == 5) {                                  // 行間が5pxなら
        M5.Display.print(" 行間(px) : ");                  // 行間px
        M5.Display.setTextColor(TFT_WHITE, TFT_BLACK);     // (字,地)
        M5.Display.print("05");                          // 表示をセット
        M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);     // (字,地)
        M5.Display.print("  10  15");              // 表示をセット
      } else if (gyoukan == 10) {                          // 行間が10pxなら
        M5.Display.print(" 行間(px) : 05  ");          // 表示をセット
        M5.Display.setTextColor(TFT_WHITE, TFT_BLACK);     // (字,地)
        M5.Display.print("10");                          // 表示をセット
        M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);     // (字,地)
        M5.Display.print("  15");                      // 表示をセット
      } else {                                             // 行間が15pxなら
        M5.Display.print(" 行間(px) : 05  10  ");  // 表示をセット
        M5.Display.setTextColor(TFT_WHITE, TFT_BLACK);     // (字,地)
        M5.Display.print("15");                          // 表示をセット
        M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);     // (字,地)
      }
      // -------------------------------- ファイル名セット
      menuY += menuDY;                       // 次の行へ
      for (int8_t i = 0; i < 9; i++) {       // 9ファイルまで表示
        M5.Display.setCursor(menuX, menuY);  // カーソル位置
        //M5.Display.printf("%3d ", i + 1);    // ファイル番号セット
        fName = " " + fList[fPage * 9 + i][0]       // ファイル名
                + "                    ";           // 20文字空白追加
        M5.Display.print(fName);                    // ファイル名セット
        M5.Display.setCursor(menuX + 330, menuY);   // カーソル位置
        M5.Display.print("                    ");   // 20文字消す
        M5.Display.setCursor(menuX + 330, menuY);   // カーソル位置
        M5.Display.print(fList[fPage * 9 + i][1]);  // ファイルサイズセット
        menuY += menuDY;                            // 次の行へ
      }
      // -------------------------------- 罫線セット 960x540
      for (int8_t i = 1; i < 5; i++) {  // 水平線上4本
        M5.Display.drawFastHLine(0, menuDY * i + 50, 540, TFT_BLACK);
      }
      for (int8_t i = 5; i < 13; i++) {  // 水平線下8本
        M5.Display.drawFastHLine(0, menuDY * i + 50, 540, TFT_BLACK);
      }
      M5.Display.drawFastHLine(0, menuDY * 13 + 50, 400, TFT_BLACK);  // 水平線一番下
      M5.Display.drawFastVLine(335, 50, 210, TFT_BLACK);              // 垂直線上3行
      M5.Display.drawFastVLine(285, 260, 70, TFT_BLACK);              // 4行目左
      M5.Display.drawFastVLine(420, 260, 70, TFT_BLACK);              // 4行目右
      M5.Display.drawFastVLine(400, 330, menuDY * 13, TFT_BLACK);     // 垂直線下ファイル行
  
      M5.Display.drawRoundRect(3, 53, 534, menuDY * 4 - 6, 30, TFT_BLACK);                     // 上の丸四角
      M5.Display.drawRoundRect(3, menuDY * 4 + 4 + 50, 394, menuDY * 9 - 6, 30, TFT_BLACK);    // 下左の丸四角
      M5.Display.drawRoundRect(404, menuDY * 5 + 4 + 50, 134, menuDY * 6 - 6, 30, TFT_BLACK);  // 下右の丸四角
      // -------------------------------- 文字セット
      menuY = 300 + 50;                          // 次の行へ
      M5.Display.setCursor(470 - 6 * 8, menuY);  // カーソル位置
      M5.Display.print("File▲");                 // 表示をセット
      menuY += menuDY;                           // 次の行へ
      M5.Display.setCursor(470 - 3 * 8, menuY);  // カーソル位置
      //M5.Display.print("+10");                   // 表示をセット
      menuY += menuDY;                           // 次の行へ
      M5.Display.setCursor(470 - 2 * 8, menuY);  // カーソル位置
      //M5.Display.print("+5");                    // 表示をセット
      menuY += menuDY;                           // 次の行へ
      M5.Display.setCursor(470 - 2 * 8, menuY);  // カーソル位置
      //M5.Display.print("+1");                    // 表示をセット
      menuY += menuDY;                   // 次の行へ
      M5.Display.setCursor(430, menuY);  // カーソル位置
      //M5.Display.printf("%4d頁", goPageN + 1);   // 表示をセット
      menuY += menuDY;                           // 次の行へ
      M5.Display.setCursor(470 - 2 * 8, menuY);  // カーソル位置
      //M5.Display.print("-1");                    // 表示をセット
      menuY += menuDY;                           // 次の行へ
      M5.Display.setCursor(470 - 3 * 8, menuY);  // カーソル位置
      //M5.Display.print("-10");                   // 表示をセット
      menuY += menuDY;                           // 次の行へ
      M5.Display.setCursor(470 - 6 * 8, menuY);  // カーソル位置
      M5.Display.print("File▼");                 // 表示をセット
      menuY += menuDY;                           // 次の行へ
      M5.Display.setCursor(470 - 6 * 8, menuY);  // カーソル位置
      M5.Display.print("menu 2");                // 表示をセット
  
      // -------------------------------- セットを表示
      M5.Display.display();  // セットを表示
      // -------------------------------- タッチ判定
      bool drawed = false;  // 描画変数=描画していない
      goMenu2 = false;
      if (M5.Display.getTouchRaw(tp, 1)) {  // タッチ情報を取得
        drawed = true;                      // 描画した
        if (tp[0].y < menuDY * 1 + 50) {    // 1行目なら
          if (tp[0].x < 335) {              // 左側なら
            edpQ = 1;                       // 高品質
          } else {                          // 右側なら
            edpQ = 0;                       // 高速
          }
        } else if (tp[0].y < menuDY * 2 + 50) {  // 2行目なら
          if (tp[0].x < 335) {                   // 左側なら
            gamenTate = 1;                       // 縦向き
          } else {                               // 右側なら
            gamenTate = 0;                       // 横向き
          }
        } else if (tp[0].y < menuDY * 3 + 50) {  // 3行目なら
          if (tp[0].x < 335) {                   // 左側なら
            fontS = 32;                          // 32px
          } else {                               // 右側なら
            fontS = 36;                          // 36px
          }
        } else if (tp[0].y < menuDY * 4 + 50) {  // 4行目なら
          if (tp[0].x < 285) {                   // 左側なら
            gyoukan = 5;                         // 行間5px
          } else if (tp[0].x < 420) {            // 中央なら
            gyoukan = 10;                        // 行間10px
          } else {                               // 右側なら
            gyoukan = 15;                        // 行間15px
          }
        } else if (tp[0].x > 400) {                 // 5行目以下の右なら
          if (tp[0].y < menuDY * 5 + 50) {          // 5行目なら
            fPage -= 1;                             // ファイル名表示頁-1
            if (fPage < 0) fPage = 0;               // 最低0
          } else if (tp[0].y < menuDY * 6 + 50) {   // 6行目なら
            goPageN += 10;                          // +10
          } else if (tp[0].y < menuDY * 7 + 50) {   // 7行目なら
            goPageN += 5;                           // +5
          } else if (tp[0].y < menuDY * 8 + 50) {   // 8行目なら
            goPageN++;                              // +1
          } else if (tp[0].y < menuDY * 9 + 50) {   // 9行目なら
                                                    // 何もしない
          } else if (tp[0].y < menuDY * 10 + 50) {  // 10行目なら
            goPageN--;                              // -1
          } else if (tp[0].y < menuDY * 11 + 50) {  // 11行目なら
            goPageN -= 10;                          // -10
          } else if (tp[0].y < menuDY * 12 + 50) {  // 12行目なら
            fPage += 1;                             // ファイル名表示頁+1
          } else {
            goMenu2 = true;
            break;
          }
          if (goPageN < 0) goPageN = 0;           // 最低0頁とする
          if (goPageN > 999) goPageN = 999;       // 最高999頁とする
        } else if (tp[0].y < menuDY * 5 + 50) {   // 5行目のfile1なら
          fName = fList[fPage * 9 + 0][0];        // file1,10,19,28,37,...
          break;                                  // 戻る
        } else if (tp[0].y < menuDY * 6 + 50) {   // 6行目のfile2なら
          fName = fList[fPage * 9 + 1][0];        // file2
          break;                                  // 戻る
        } else if (tp[0].y < menuDY * 7 + 50) {   // 7行目のfile3なら
          fName = fList[fPage * 9 + 2][0];        // file3
          break;                                  // 戻る
        } else if (tp[0].y < menuDY * 8 + 50) {   // 8行目のfile4なら
          fName = fList[fPage * 9 + 3][0];        // file4
          break;                                  // 戻る
        } else if (tp[0].y < menuDY * 9 + 50) {   // 9行目のfile5なら
          fName = fList[fPage * 9 + 4][0];        // file5
          break;                                  // 戻る
        } else if (tp[0].y < menuDY * 10 + 50) {  // 10行目のfile6なら
          fName = fList[fPage * 9 + 5][0];        // file6
          break;                                  // 戻る
        } else if (tp[0].y < menuDY * 11 + 50) {  // 11行目のfile7なら
          fName = fList[fPage * 9 + 6][0];        // file7
          break;                                  // 戻る
        } else if (tp[0].y < menuDY * 12 + 50) {  // 12行目のfile8なら
          fName = fList[fPage * 9 + 7][0];        // file8
          break;                                  // 戻る
        } else {                                  // 13行目のfile9なら
          fName = fList[fPage * 9 + 8][0];        // file9
          break;                                  // 戻る
        }
      } else if (drawed) {  // もし描画していれば
        drawed = false;     // 描画変数を戻す
      }
      // -------------------------------- セットを表示
      M5.Display.display();  // セットを表示
      vTaskDelay(1);         // 1tickの間タスクをブロック
    }
  }
  
  void printMenu2() {                                // メニュー2表示
    M5.Display.setRotation(0);                       // 縦長
    M5.Display.setFont(&fonts::lgfxJapanGothic_32);  // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
    M5.Display.setEpdMode(epd_mode_t::epd_fastest);  // 描画速 (低品質)
    M5.Display.fillScreen(TFT_BLACK);                // 全画面黒 しないと段々黒くなる
    // -------------------------------- セットを表示
    M5.Display.display();                           // セットを表示
    vTaskDelay(1);                                  // 1tickの間タスクをブロック
    M5.Display.fillScreen(TFT_WHITE);               // 全画面白
    M5.Display.display();                           // セットを表示
    vTaskDelay(1);                                  // 1tickの間タスクをブロック
    M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);  // (字,地)
    while (true) {                                  // タッチしたら抜ける
      // -------------------------------- 罫線
      for (int8_t i = 2; i < 13; i++) {  // 水平線11本
        M5.Display.drawFastHLine(0, menuDY * i, 540, TFT_BLACK);
      }
      // -------------------------------- 塗りつぶし
      M5.Display.fillRect(0, 0, 540, menuDY, TFT_LIGHTGREY);
      M5.Display.fillRect(0, menuDY * 13, 540, 960 - menuDY * 13, TFT_LIGHTGREY);
      // -------------------------------- 文字セット
      int16_t menuX = 0, menuY = 20;                     // カーソル変数
      M5.Display.setCursor((540 - 16 * 14) / 2, menuY);  // カーソル位置
      M5.Display.print(" [ menu 2 ] ");                // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      M5.Display.setCursor(menuX, menuY);                // カーソル位置
      M5.Display.print("  1.時刻手動合わせ");            // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      M5.Display.setCursor(menuX, menuY);                // カーソル位置
      M5.Display.print("  2.スマホシャッター");          // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      M5.Display.setCursor(menuX, menuY);                // カーソル位置
      M5.Display.print("  3.世界天気予報(予定)");        // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      M5.Display.setCursor(menuX, menuY);                // カーソル位置
      M5.Display.print("  4.");                          // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      M5.Display.setCursor(menuX, menuY);                // カーソル位置
      M5.Display.print("  5.");                          // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      M5.Display.setCursor(menuX, menuY);                // カーソル位置
      M5.Display.print("  6.");                          // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      M5.Display.setCursor(menuX, menuY);                // カーソル位置
      M5.Display.print("  7.");                          // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      M5.Display.setCursor(menuX, menuY);                // カーソル位置
      M5.Display.print("  8.");                          // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      M5.Display.setCursor(menuX, menuY);                // カーソル位置
      M5.Display.print("  9.");                          // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      M5.Display.setCursor(menuX, menuY);                // カーソル位置
      M5.Display.print(" 10.");                          // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      M5.Display.setCursor(menuX, menuY);                // カーソル位置
      M5.Display.print(" 11.");                          // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      M5.Display.setCursor(menuX, menuY);                // カーソル位置
      M5.Display.print(" 12.");                          // 表示をセット
      // -------------------------------- セットを表示
      M5.Display.display();
      // -------------------------------- タッチ判定
      if (M5.Display.getTouchRaw(tp, 1)) {  // タッチ情報を取得
        if (tp[0].y < menuDY * 2) {         // 2行目なら
          setTime();                        // (1)時刻合わせ 手動
          break;
        } else if (tp[0].y < menuDY * 3) {  // 3行目なら
          shutter();                        // (2)スマホシャッター
          break;
        } else if (tp[0].y < menuDY * 4) {  // 4行目なら
          // 3.何もしない
        } else if (tp[0].y < menuDY * 5) {  // 5行目なら
          // 4.何もしない
        } else if (tp[0].y < menuDY * 6) {  // 6行目なら
          // 5.何もしない
        } else if (tp[0].y < menuDY * 7) {  // 7行目なら
          // 6.何もしない
        } else if (tp[0].y < menuDY * 8) {  // 8行目なら
          // 7.何もしない
        } else if (tp[0].y < menuDY * 9) {  // 9行目なら
          // 8.何もしない
        } else if (tp[0].y < menuDY * 10) {  // 10行目なら
          // 9.何もしない
        } else if (tp[0].y < menuDY * 11) {  // 11行目なら
          // 10.何もしない
        } else if (tp[0].y < menuDY * 12) {  // 12行目なら
          // 11.何もしない
        } else if (tp[0].y < menuDY * 13) {  // 13行目なら
          // 12.何もしない
        } else {  // 14行目なら
          // 何もしない
        }
      }
    }
  }
  
  void setTime() {                                   // メニュー3 時刻手動設定
    M5.Display.setRotation(0);                       // 縦長
    M5.Display.setFont(&fonts::lgfxJapanGothic_32);  // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
    M5.Display.setEpdMode(epd_mode_t::epd_fastest);  // 描画速 (低品質)
    M5.Display.fillScreen(TFT_BLACK);                // 全画面黒 しないと段々黒くなる
    // -------------------------------- セットを表示
    M5.Display.display();                           // セットを表示
    vTaskDelay(1);                                  // 1tickの間タスクをブロック
    M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);  // (字,地)
    auto dt = M5.Rtc.getDateTime();
    yy = dt.date.year;
    mm = dt.date.month;
    dd = dt.date.date;
    hhh = dt.time.hours;
    mmm = dt.time.minutes;
    while (true) {  // タッチしたら抜ける
      // -------------------------------- メニュー3 枠セット
      M5.Display.fillRoundRect(10, 10, 530, 50, 5, TFT_LIGHTGREY);               // 丸四角1行分
      int16_t x0 = 3, y0 = -3, r = 20;                                           // x,y,r設定
      int16_t x2 = 540 / 2 + x0, w = 540 / 2 - x0 * 2, h = menuDY * 3 - y0 * 2;  // 式
      for (int8_t i = 2; i < 11; i += 4) {
        M5.Display.fillRoundRect(x0, menuDY * i + y0, w, h, r, TFT_LIGHTGREY);  // 丸四角3行分
        M5.Display.fillRoundRect(x2, menuDY * i + y0, w, h, r, TFT_LIGHTGREY);  // 丸四角3行分
      }
      x0 = 10, y0 = 10, r = 16;                                          // x,y,r設定
      int16_t x1 = 540 / 4 + x0, x3 = 540 / 4 * 3 + x0;                  // 式
      x2 = 540 / 2 + x0, w = 540 / 4 - x0 * 2, h = menuDY * 1 - y0 * 2;  // 式
      for (int8_t i = 3; i < 9; i += 4) {
        M5.Display.fillRoundRect(x0, menuDY * i + y0, w, h, r, TFT_WHITE);        // 丸四角1行分
        M5.Display.fillRoundRect(x1, menuDY * i + y0, w, h, r, TFT_WHITE);        // 丸四角1行分
        M5.Display.fillRoundRect(x2, menuDY * i + y0, w, h, r, TFT_WHITE);        // 丸四角1行分
        M5.Display.fillRoundRect(x3, menuDY * i + y0, w, h, r, TFT_WHITE);        // 丸四角1行分
        M5.Display.fillRoundRect(x0, menuDY * (i + 1) + y0, w, h, r, TFT_WHITE);  // 丸四角1行分
        M5.Display.fillRoundRect(x1, menuDY * (i + 1) + y0, w, h, r, TFT_WHITE);  // 丸四角1行分
        M5.Display.fillRoundRect(x2, menuDY * (i + 1) + y0, w, h, r, TFT_WHITE);  // 丸四角1行分
        M5.Display.fillRoundRect(x3, menuDY * (i + 1) + y0, w, h, r, TFT_WHITE);  // 丸四角1行分
      }
      M5.Display.fillRoundRect(x0, menuDY * 11 + y0, w, h, r, TFT_WHITE);  // 丸四角1行分
      M5.Display.fillRoundRect(x1, menuDY * 11 + y0, w, h, r, TFT_WHITE);  // 丸四角1行分
      M5.Display.fillRoundRect(x0, menuDY * 12 + y0, w, h, r, TFT_WHITE);  // 丸四角1行分
      M5.Display.fillRoundRect(x1, menuDY * 12 + y0, w, h, r, TFT_WHITE);  // 丸四角1行分
      // -------------------------------- メニュー3 文字セット
      int16_t menuX04 = (540 / 2 - 16 * 4) / 2, menuX06 = (540 / 2 - 16 * 6) / 2;
      int16_t menuX24 = 540 / 2 + menuX04, menuX26 = 540 / 2 + menuX06;
      int16_t menuX12 = (540 / 4 - 16 * 2) / 2;
      int16_t menuX13 = (540 / 4 - 16 * 3) / 2, menuX22 = 540 / 4 * 1 + menuX12;  // 4列時2列目2文字,3文字のカーソル位置
      int16_t menuX33 = 540 / 4 * 2 + menuX13, menuX42 = 540 / 4 * 3 + menuX12;
      int16_t menuY = 20;                                // カーソル変数
      M5.Display.setCursor((540 - 16 * 20) / 2, menuY);  // カーソル位置
      M5.Display.print("[ 時刻合わせ 手動 ]");          // 表示をセット
      menuY += menuDY;                                   // 次の行へ
      menuY += menuDY;
      M5.Display.setCursor(menuX06, menuY);  // カーソル位置
      M5.Display.printf("%04d年", yy);       // 表示をセット
      M5.Display.setCursor(menuX24, menuY);  // カーソル位置
      M5.Display.printf("%02d月", mm);       // 表示をセット
      menuY += menuDY;                       // 次の行へ
      M5.Display.setCursor(menuX12, menuY);  // カーソル位置
      M5.Display.print("+2");                // 表示をセット
      M5.Display.setCursor(menuX22, menuY);  // カーソル位置
      M5.Display.print("+1");                // 表示をセット
      M5.Display.setCursor(menuX33, menuY);  // カーソル位置
      M5.Display.print("+10");               // 表示をセット
      M5.Display.setCursor(menuX42, menuY);  // カーソル位置
      M5.Display.print("+1");                // 表示をセット
      menuY += menuDY;                       // 次の行へ
      M5.Display.setCursor(menuX12, menuY);  // カーソル位置
      M5.Display.print("-2");                // 表示をセット
      M5.Display.setCursor(menuX22, menuY);  // カーソル位置
      M5.Display.print("-1");                // 表示をセット
      M5.Display.setCursor(menuX33, menuY);  // カーソル位置
      M5.Display.print("-10");               // 表示をセット
      M5.Display.setCursor(menuX42, menuY);  // カーソル位置
      M5.Display.print("-1");                // 表示をセット
      menuY += menuDY;                       // 次の行へ
      menuY += menuDY;                       // 次の行へ
      M5.Display.setCursor(menuX04, menuY);  // カーソル位置
      M5.Display.printf("%02d日", dd);       // 表示をセット
      M5.Display.setCursor(menuX24, menuY);  // カーソル位置
      M5.Display.printf("%02d時", hhh);      // 表示をセット
      menuY += menuDY;                       // 次の行へ
      M5.Display.setCursor(menuX13, menuY);  // カーソル位置
      M5.Display.print("+10");               // 表示をセット
      M5.Display.setCursor(menuX22, menuY);  // カーソル位置
      M5.Display.print("+1");                // 表示をセット
      M5.Display.setCursor(menuX33, menuY);  // カーソル位置
      M5.Display.print("+10");               // 表示をセット
      M5.Display.setCursor(menuX42, menuY);  // カーソル位置
      M5.Display.print("+1");                // 表示をセット
      menuY += menuDY;                       // 次の行へ
      M5.Display.setCursor(menuX13, menuY);  // カーソル位置
      M5.Display.print("-10");               // 表示をセット
      M5.Display.setCursor(menuX22, menuY);  // カーソル位置
      M5.Display.print("-1");                // 表示をセット
      M5.Display.setCursor(menuX33, menuY);  // カーソル位置
      M5.Display.print("-10");               // 表示をセット
      M5.Display.setCursor(menuX42, menuY);  // カーソル位置
      M5.Display.print("-1");                // 表示をセット
      menuY += menuDY;                       // 次の行へ
      menuY += menuDY;                       // 次の行へ
      M5.Display.setCursor(menuX04, menuY);  // カーソル位置
      M5.Display.printf("%02d分", mmm);      // 表示をセット
      menuY += menuDY;                       // 次の行へ
      M5.Display.setCursor(menuX13, menuY);  // カーソル位置
      M5.Display.print("+10");               // 表示をセット
      M5.Display.setCursor(menuX22, menuY);  // カーソル位置
      M5.Display.print("+1");                // 表示をセット
      M5.Display.setCursor(menuX26, menuY);  // カーソル位置
      M5.Display.print("RTC保存");           // 表示をセット
      menuY += menuDY;                       // 次の行へ
      M5.Display.setCursor(menuX13, menuY);  // カーソル位置
      M5.Display.print("-10");               // 表示をセット
      M5.Display.setCursor(menuX22, menuY);  // カーソル位置
      M5.Display.print("-1");                // 表示をセット
      menuY += menuDY;                       // 次の行へ
      M5.Display.setCursor(15, menuY);        // カーソル位置
  
      M5.Display.print(" 保存で表示=現在時刻となります。");  // 表示をセット
      // -------------------------------- セットを表示
      M5.Display.display();
      // -------------------------------- タッチ判定
      bool drawed = false;                  // 描画変数=描画していない
      if (M5.Display.getTouchRaw(tp, 1)) {  // タッチ情報を取得
        drawed = true;                      // 描画した
        if (tp[0].y < menuDY * 4) {         // 4行目なら
          if (tp[0].x < 135 * 1) {          // 1列目なら
            yy += 2;                        // 年+2
          } else if (tp[0].x < 135 * 2) {   // 2列目なら
            yy++;                           // 年+1
          } else if (tp[0].x < 135 * 3) {   // 3列目なら
            mm += 10;                       // 月+10
          } else {                          // 4列目なら
            mm++;                           // 月+1
          }
          if (mm > 12) mm = 1;              // 12月の次は1月
        } else if (tp[0].y < menuDY * 6) {  // 5行目なら
          if (tp[0].x < 135 * 1) {          // 1列目なら
            yy -= 2;                        // 年-2
          } else if (tp[0].x < 135 * 2) {   // 2列目なら
            yy--;                           // 年-1
          } else if (tp[0].x < 135 * 3) {   // 3列目なら
            mm -= 10;                       // 月-10
          } else {                          // 4列目なら
            mm--;                           // 月-1
          }
          if (yy < 2025) yy = 2025;         // 2025年以降
          if (mm < 1) mm = 12;              // 1月の次は12月
        } else if (tp[0].y < menuDY * 8) {  // 8行目なら
          if (tp[0].x < 135 * 1) {          // 1列目なら
            dd += 10;                       // 日+10
          } else if (tp[0].x < 135 * 2) {   // 2列目なら
            dd++;                           // 日+1
          } else if (tp[0].x < 135 * 3) {   // 3列目なら
            hhh += 10;                      // 時+10
          } else {                          // 4列目なら
            hhh++;                          // 時+1
          }
          if (dd > 31) dd = 1;               // 31日の次は1日
          if (hhh > 23) hhh = 0;             // 23時の次は0時
        } else if (tp[0].y < menuDY * 10) {  // 9行目なら
          if (tp[0].x < 135 * 1) {           // 1列目なら
            dd -= 10;                        // 日-10
          } else if (tp[0].x < 135 * 2) {    // 2列目なら
            dd--;                            // 日-1
          } else if (tp[0].x < 135 * 3) {    // 3列目なら
            hhh -= 10;                       // 時-10
          } else {                           // 4列目なら
            hhh--;                           // 時-1
          }
          if (dd < 1) dd = 31;           // 1日の次は31日
          if (hhh < 0) hhh = 23;         // 0時の次は23時
        } else if (tp[0].x > 135 * 2) {  // 11行以降の右側
          M5.Rtc.setDateTime({ { yy, mm, dd }, { hhh, mmm, 30 } });
          //break;                             // menu 2へ
        } else if (tp[0].y < menuDY * 12) {  // 12行目なら
          if (tp[0].x < 135 * 1) {           // 1列目なら
            mmm += 10;                       // 分+10
          } else {                           // 2列目なら
            mmm++;                           // 分+1
          }
          if (mmm > 59) mmm = 0;    // 59分の次は0分
        } else {                    // 13行目なら
          if (tp[0].x < 135 * 1) {  // 1列目なら
            mmm -= 10;              // 分-10
          } else {                  // 2列目なら
            mmm--;                  // 分-1
          }
          if (mmm < 0) hhh = 59;  // 0分の次は59分
        }
        // -------------------------------- セットを表示
        M5.Display.display();
        vTaskDelay(1);  // 1tickの間タスクをブロック
      }
    }
  }
  
  void shutter() {                                             // スマホシャッター
    bool connectTruePrint = false, connectFalsePrint = false;  // 表示してないか
    M5.Display.setRotation(0);                                 // 縦長
    M5.Display.setFont(&fonts::lgfxJapanGothic_32);            // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
    M5.Display.setEpdMode(epd_mode_t::epd_fastest);            // 描画速 (低品質)
    M5.Display.fillScreen(TFT_BLACK);                          // 全画面黒 しないと段々黒くなる
    // -------------------------------- セットを表示
    M5.Display.display();              // セットを表示
    vTaskDelay(1);                     // 1tickの間タスクをブロック
    M5.Display.fillScreen(TFT_WHITE);  // 全画面黒 しないと段々黒くなる
    // -------------------------------- セットを表示
    M5.Display.display();                           // セットを表示
    vTaskDelay(1);                                  // 1tickの間タスクをブロック
    M5.Display.setCursor(0, 0);                     // カーソル位置
    M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);  // (字,地)
    bleKeyboard.begin();                            // Bluetoothキーボード開始
  #if defined(USE_NIMBLE)
    BLEDevice::setSecurityAuth(true, true, true);
  #else
    BLESecurity* pSecurity = new BLESecurity();
    pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);  // S3では必要
  #endif                                                    // USE_NIMBLE
    M5.Display.println("スケッチ開始 画面touchでshutter");  // セット
    M5.Display.display();                                   // セットを表示
    while (1) {
      if (bleKeyboard.isConnected()) {    // Bluetoothキーボードが接続されていれば
        Serial.println("接続中");         // シリアルモニターに表示
        if (connectTruePrint == false) {  // 接続中が未表示なら
          M5.Display.println("接続中");   // セット
          M5.Display.display();           // セットを表示
        }
        connectTruePrint = true;                   // 接続中は表示済
        connectFalsePrint = false;                 // 未接続は未表示
        if (M5.Display.getTouchRaw(tp, 1)) {       // タッチ情報を取得
          M5.Display.println("シャッター");        // セット
          M5.Display.display();                    // セットを表示
          bleKeyboard.write(KEY_MEDIA_VOLUME_UP);  // 音量upキーを送信
          delay(500);                              // 0.5秒待つ
        }
      } else {
        if (connectFalsePrint == false) {               // 未接続が未表示なら
          M5.Display.println("未接続 (BLE-Shutter1)");  // セット
          M5.Display.display();                         // セットを表示
        }
        Serial.println("未接続");  // シリアルモニターに表示
        connectFalsePrint = true;  // 未接続は表示済
        connectTruePrint = false;  // 接続中は未表示
      }
      delay(200);  // 0.2秒待つ
    }
  }
  
  void printFirst() {  // メニュー内容を設定し1頁目を表示
    // -------------------------------- メニュー内容で設定
    if (edpQ) {                                        // 画面の品質設定
      M5.Display.setEpdMode(epd_mode_t::epd_quality);  // 高品質 (描画遅)
    } else {
      M5.Display.setEpdMode(epd_mode_t::epd_fastest);  // 描画速 (低品質)
    }
    if (gamenTate) {              // 画面向き設定
      M5.Display.setRotation(0);  // 縦長
    } else {
      M5.Display.setRotation(1);  // 横長
    }
    if (fontS == 36) {                                 // フォントサイズ設定
      M5.Display.setFont(&fonts::lgfxJapanGothic_36);  // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
    } else {
      M5.Display.setFont(&fonts::lgfxJapanGothic_32);  // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
    }
    // -------------------------------- 文章表示画面
    pageN = 0;                                         // 頁番号0
    pageB[pageN] = 0;                                  // 頁番号の最初のバイト値は0
    if (goPageN == 0) {                                // メニューで1頁にセットなら
      buf = setBuf(pageB[pageN]);                      // その頁のbufを得る関数へ
      noPrint = false;                                 // printする
      pageB[pageN + 1] = setView(buf);                 // 戻り値(Byte値)は次の頁の開始Byte値
    } else {                                           // 飛び先が1(2頁)以上なら
      for (pageN = 0; pageN < goPageN - 1; pageN++) {  // 表示無しで計算
        buf = setBuf(pageB[pageN]);                    // その頁のbufを得る関数へ
        noPrint = true;                                // printしない
        pageB[pageN + 1] = pageB[pageN] + setView(buf);
        // 1頁表示し、(開始+処理)のByte数を次頁の開始Byte数にする
      }
      pageN++;                     // 次の頁
      buf = setBuf(pageB[pageN]);  // その頁のbufを得る関数へ
      noPrint = false;             // printする
      pageB[pageN + 1] = pageB[pageN] + setView(buf);
      // 1頁表示し、(開始+処理)のByte数を次頁の開始Byte数にする
    }
  }
  
  void setup() {
    auto cfg = M5.config();  // 設定用の構造体を代入
    M5.begin(cfg);           // 設定した値でデバイスを開始
    Serial.begin(115200);    // シリアル通信開始
    SPI.begin(SD_SPI_SCK_PIN, SD_SPI_MISO_PIN,
              SD_SPI_MOSI_PIN, SD_SPI_CS_PIN);
    delay(100);                                       // 0.1秒待つ
    SetFont();                                        // Font等初期化関数へ
    if (!SD.begin(47)) {                              // SDライブラリとカードを初期化できなければ
      M5.Display.println("失敗 SDライブラリ初期化");  // 表示
      while (true)                                    // 無限ループ
        ;
    }
    // -------------------------------- ファイル名取得
    myFile = SD.open("/");                   // ルートのファイルを開く
    char buf2[40];                           // 作業用
    int8_t i = 0;                            // ファイル数
    while (true) {                           // breakになるまで無限ループ
      File entry = myFile.openNextFile();    // ディレクトリ内の次のファイルまたはホルダを取得
      if (!entry) break;                     // 次のファイルまたはフォルダがないなら抜ける
      if (!entry.isDirectory()) {            // ホルダでなければ
        fList[i][0] = String(entry.name());  // ファイル名を取得
        Serial.println(fList[i][0]);
        if (entry.size() < 1000) {                       // ファイルサイズが1kB未満なら
          sprintf(buf2, "%4d\n", entry.size());          // bufにファイルサイズ(bytes)を
        } else {                                         // 1kB以上なら
          sprintf(buf2, "%3dk\n", entry.size() / 1000);  // Bufにファイルサイズ(kB)を
        }
        fList[i][1] = String(buf2);  // ファイルサイズ(B or kB単位)
        i++;                         // 次のファイル番号
      }
      entry.close();  // ファイルを閉じる
    }
    printMenu();  // メニュー表示関数
    if (goMenu2) printMenu2();
    printFirst();  // メニュー内容を設定し1頁目を表示
  }
  
  void loop() {
    bool drawed = false;                          // 描画変数=描画していない
    if (M5.Display.getTouch(tp, 1)) {             // タッチ情報を取得
      if (tp[0].y < M5.Display.height() * 0.8) {  // 画面下2割の部分のタッチは無視
        drawed = true;                            // 描画した
        if (tp[0].x > M5.Display.width() / 2) {   // 次頁タッチなら
          pageN++;                                // 頁番号1加算
          buf = setBuf(pageB[pageN]);             // その頁のbufを得る関数へ
          pageB[pageN + 1] = pageB[pageN] + setView(buf);
          // 1頁表示し、(開始+処理)のByte数を次頁の開始Byte数にする
        } else if (pageN > 0) {        // 前頁タッチで頁番号が1以上なら
          pageN--;                     // 頁番号1減算
          buf = setBuf(pageB[pageN]);  // その頁のbufを得る関数へ
          setView(buf);                // 1頁表示関数へ
        } else {                       // 頁番号1で前頁タッチなら
          printMenu();                 // メニュー表示関数へ
          printFirst();                // メニュー内容を設定し1頁目を表示
        }
      } else if (drawed) {  // もし描画していれば
        drawed = false;     // 描画変数を戻す
      }
      vTaskDelay(1);  // 1tickの間タスクをブロック
    }
  }
* flash memory(3.1Mbyte)のうち、スケッチが64%使用。RAM(327kbyte)のうち、global変数が16%使用、local変数で274kbyte使用可能。(1000byte=1kbyteで計算)