08_M5PaperS3でTxtReader
08_M5PaperS3でTxtReader
説明
M5PaperS3でテキストリーダーを作成しました。SD内のtxtファイルの中身を表示します。UFT-8のマルチバイト文字に対応していますが、表示フォントは、lgfxJapanGothic_32 横幅32px か、
lgfxJapanGothic_36 横幅36px です。
これは、常用漢字+αで4639文字。半角カナ、→の矢印、①等はありません。日本語学習サイトなどの、レベル別漢字一覧リストだそうです。
https://lang-ship.com/blog/work/lovyangfx-3-ja-font/
文字リスト
https://lang-ship.com/blog/files/LovyanGFX/japan.html
インストールVerの制限
ボードマネージャ・esp32 by Espressif Systems は v3.0.7以下の事
ライブラリ
・M5Unified by M5Stack は v0.2.2以上の事
・M5GFX by M5Stack は v0.2.2以上の事
ボードの設定
Arduino-IDE > ツール にて・ボード : "ESP32S3 Dev Module"
・ポート : "COM□"
・USB CDC On Boot: “Enabled”
・Flash Size: “16MB(128Mb)” ← 32MBでない
・Partition Scheme: "16M Flash (3MB APP / 9.9MB FATFS)" ← Defaultでは容量不足
・PSRAM: “OPI PSRAM” ← M5GFXで必要
使い方
SDのファイルは、・ルートにあるファイル
・8.3形式のファイル
・9ファイル以下
の制約があります。
起動するとメニュー画面が開きます。
・品質・速度 : 高品質 / 高速
(タッチで変更)
・画面向き : 縦向き / 横向き
(タッチで変更)
・フォントサイズ : 32px / 36px
(タッチで変更)
・行間 : 5px / 10px / 15px
(タッチで変更)
・ファイル名
(タッチでメニュー画面を終了し、ファイルの中身を表示)
・戻る
(タッチでメニュー画面を終了し、ファイル1を表示)
・次の頁 : 画面右側タッチ
・前の頁 : 画面左側タッチ
スケッチ
// 08_M5PaperS3_BookReader.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
int16_t fontS = 32; // fontサイズ (32or36)
int16_t gyoukan = 10; // 行間px
bool edpQ = false; // (true)高品質or(false)高速
bool gamenTate = true; // 画面(true)縦or(false)横
int32_t pageN, pageB[1000]; // 頁番号,頁最初のByte値
lgfx::touch_point_t tp[1]; // タッチポイント
int16_t menuDY = 70; // メニュー画面刻みpx
String fName, fList[9][2]; // ファイル名,ファイルリスト[n][fName,fByte]
void SetFont() { // Font等初期化関数
display.init(); // パネルの初期化
display.startWrite(); // バッファを効率的に使用開始
display.setEpdMode(epd_mode_t::epd_fastest); // 描画速 (低品質)
//display.setEpdMode(epd_mode_t::epd_quality);// 高品質 (描画遅)
display.setRotation(0); // 縦長
//display.setRotation(1);// 横長
display.setFont(&fonts::lgfxJapanGothic_32); // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
//display.setFont(&fonts::lgfxJapanGothic_36);// 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
display.fillScreen(TFT_WHITE); // 全画面白
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("/" + 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) { // 1頁表示関数
int16_t viewX = 0, viewY = 0; // 表示x,y位置 リセット
int16_t i = 0; // Byte位置 リセット
int16_t bufL = buf.length(); // bufの全Byte数 \0は含まず
display.clear(); // パネルをクリア
while (i < bufL) { // buf終わりまでループ
bool CRon = false; // 改行 リセット
// -------------------------------- 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); // 行間スペースを含めて移動へ
}
// -------------------------------- 1頁セット終わり
if ((viewY + gyoukan + fontS > display.height())
&& (CRon == true)) break; // 行が下まで来て改行なら抜ける
// -------------------------------- 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 {
// ここへは来ないはず
}
}
display.display(); // セットしたのを表示
return (i); // 処理Byte数を返す
}
void printMenu() {
display.setRotation(0); // 縦長
display.setFont(&fonts::lgfxJapanGothic_32); // 固定幅ゴシック体 8,12,16,20,24,28,32,36,40
display.setEpdMode(epd_mode_t::epd_fastest); // 描画速 (低品質)
display.fillScreen(TFT_WHITE); // 全画面白
display.setTextColor(TFT_BLACK, TFT_WHITE); // (字,地)
while (true) {
// -------------------------------- メニュー1行目セット
int16_t menuX = 0, menuY = 20; // カーソル変数
display.setCursor(menuX, menuY); // カーソル位置
if (edpQ == true) { // 高品質画面なら
display.print(" 品質・速度 : "); // 表示をセット
display.setTextColor(TFT_WHITE, TFT_BLACK); // (字,地)
display.print("高品質"); // 表示をセット
display.setTextColor(TFT_BLACK, TFT_WHITE); // (字,地)
display.print(" 高速"); // 表示をセット
} else { // 高速画面なら
display.print(" 品質・速度 : 高品質 "); // 表示をセット
display.setTextColor(TFT_WHITE, TFT_BLACK); // (字,地)
display.print("高速"); // 表示をセット
display.setTextColor(TFT_BLACK, TFT_WHITE); // (字,地)
}
// -------------------------------- メニュー2行目セット
menuY += menuDY; // 次の行へ
display.setCursor(menuX, menuY); // カーソル位置
if (gamenTate == true) { // 縦画面なら
display.print(" 画面向き : "); // 表示をセット
display.setTextColor(TFT_WHITE, TFT_BLACK); // (字,地)
display.print("縦向き"); // 表示をセット
display.setTextColor(TFT_BLACK, TFT_WHITE); // (字,地)
display.print(" 横向き"); // 表示をセット
} else { // 横画面なら
display.print(" 画面向き : 縦向き "); // 表示をセット
display.setTextColor(TFT_WHITE, TFT_BLACK); // (字,地)
display.print("横向き"); // 表示をセット
display.setTextColor(TFT_BLACK, TFT_WHITE); // (字,地)
}
// -------------------------------- メニュー3行目セット
menuY += menuDY; // 次の行へ
display.setCursor(menuX, menuY); // カーソル位置
if (fontS == 32) { // fontサイズが32pxなら
display.print(" Font Size : "); // 表示をセット
display.setTextColor(TFT_WHITE, TFT_BLACK); // (字,地)
display.print("32px"); // 表示をセット
display.setTextColor(TFT_BLACK, TFT_WHITE); // (字,地)
display.print(" 36px"); // 表示をセット
} else { // fontサイズが36pxなら
display.print(" Font Size : 32px "); // 表示をセット
display.setTextColor(TFT_WHITE, TFT_BLACK); // (字,地)
display.print("36px"); // 表示をセット
display.setTextColor(TFT_BLACK, TFT_WHITE); // (字,地)
}
// -------------------------------- メニュー4行目セット
menuY += menuDY; // 次の行へ
display.setCursor(menuX, menuY); // カーソル位置
if (gyoukan == 5) { // 行間が5pxなら
display.print(" 行間(px) : "); // 行間px
display.setTextColor(TFT_WHITE, TFT_BLACK); // (字,地)
display.print("05"); // 表示をセット
display.setTextColor(TFT_BLACK, TFT_WHITE); // (字,地)
display.print(" 10 15"); // 表示をセット
} else if (gyoukan == 10) { // 行間が10pxなら
display.print(" 行間(px) : 05 "); // 表示をセット
display.setTextColor(TFT_WHITE, TFT_BLACK); // (字,地)
display.print("10"); // 表示をセット
display.setTextColor(TFT_BLACK, TFT_WHITE); // (字,地)
display.print(" 15"); // 表示をセット
} else { // 行間が15pxなら
display.print(" 行間(px) : 05 10 "); // 表示をセット
display.setTextColor(TFT_WHITE, TFT_BLACK); // (字,地)
display.print("15"); // 表示をセット
display.setTextColor(TFT_BLACK, TFT_WHITE); // (字,地)
}
// -------------------------------- ファイル名セット
menuY += menuDY; // 次の行へ
for (int8_t i = 0; i < 9; i++) { // 9ファイルまで表示
display.setCursor(menuX, menuY); // カーソル位置
display.printf("%3d ", i + 1); // ファイル番号セット
fName = fList[i][0] // ファイル名
+ " "; // 9文字空白追加
fName = fName.substring(0, 12); // 頭から12文字
display.print(fName); // ファイル名セット
display.print(fList[i][1]); // ファイルサイズセット
menuY += menuDY; // 次の行へ
}
// -------------------------------- 罫線セット 960x540
for (int8_t i = 1; i < 5; i++) { // 水平線上4本
display.drawFastHLine(0, menuDY * i, 540, TFT_BLACK);
}
for (int8_t i = 5; i < 13; i++) { // 水平線下8本
display.drawFastHLine(0, menuDY * i, 400, TFT_BLACK);
}
display.drawFastVLine(335, 0, 210, TFT_BLACK); // 垂直線上3行
display.drawFastVLine(285, 210, 70, TFT_BLACK); // 4行目左
display.drawFastVLine(420, 210, 70, TFT_BLACK); // 4行目右
display.drawFastVLine(400, 280, 680, TFT_BLACK); // 垂直線下ファイル行
// -------------------------------- 文字セット
display.setCursor(450, 500); // カーソル位置
display.print("戻"); // 表示をセット
display.setCursor(450, 600); // カーソル位置
display.print("る"); // 表示をセット
display.display(); // セットを表示
// -------------------------------- タッチ判定
bool drawed = false; // 描画変数=描画していない
if (display.getTouchRaw(tp, 1)) { // タッチ情報を取得
drawed = true; // 描画した
if (tp[0].y < 70) { // 1行目なら
if (tp[0].x < 335) { // 左側なら
edpQ = 1; // 高品質
} else { // 右側なら
edpQ = 0; // 高速
}
} else if (tp[0].y < 140) { // 2行目なら
if (tp[0].x < 335) { // 左側なら
gamenTate = 1; // 縦向き
} else { // 右側なら
gamenTate = 0; // 横向き
}
} else if (tp[0].y < 210) { // 3行目なら
if (tp[0].x < 335) { // 左側なら
fontS = 32; // 32px
} else { // 右側なら
fontS = 36; // 36px
}
} else if (tp[0].y < 280) { // 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行目以下の右なら
fName = fList[0][0]; // file1
break; // 戻る
} else if (tp[0].y < 350) { // 5行目のfile1なら
fName = fList[0][0]; // file1
break; // 戻る
} else if (tp[0].y < 420) { // 6行目のfile2なら
fName = fList[1][0]; // file2
break; // 戻る
} else if (tp[0].y < 490) { // 7行目のfile3なら
fName = fList[2][0]; // file3
Serial.println(fName);
break; // 戻る
} else if (tp[0].y < 560) { // 8行目のfile4なら
fName = fList[3][0]; // file4
break; // 戻る
} else if (tp[0].y < 630) { // 9行目のfile5なら
fName = fList[4][0]; // file5
break; // 戻る
} else if (tp[0].y < 700) { // 10行目のfile6なら
fName = fList[5][0]; // file6
break; // 戻る
} else if (tp[0].y < 770) { // 11行目のfile7なら
fName = fList[6][0]; // file7
break; // 戻る
} else if (tp[0].y < 840) { // 12行目のfile8なら
fName = fList[7][0]; // file8
break; // 戻る
} else { // 13行目のfile9なら
fName = fList[8][0]; // file9
break; // 戻る
}
} else if (drawed) { // もし描画していれば
drawed = false; // 描画変数を戻す
}
display.display();// セットを表示
vTaskDelay(1); // 1tickの間タスクをブロック
}
}
void setup() {
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ライブラリとカードを初期化できなければ
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()); // ファイル名を取得
if (entry.size() < 1000) { // ファイルサイズが1kB未満なら
sprintf(buf2, " %5dB\n", entry.size()); // bufにファイルサイズ(bytes)を
} else { // 1kB以上なら
sprintf(buf2, " %4dkB\n", entry.size() / 1000); // Bufにファイルサイズ(kB)を
}
fList[i][1] = String(buf2); // ファイルサイズ(B or kB単位)
i++; // 次のファイル番号
}
entry.close(); // ファイルを閉じる
}
// -------------------------------- メニュー画面
fName = fList[0][0]; // 何も入力しなければfile1
printMenu(); // メニュー表示関数
// -------------------------------- メニュー内容で設定
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); // 横長
}
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
}
// -------------------------------- 文章表示画面
pageN = 0; // 頁番号0
pageB[pageN] = 0; // 頁番号の最初のバイト値は0
buf = setBuf(pageB[pageN]); // その頁のbufを得る関数へ
pageB[pageN + 1] = setView(buf); // 戻り値(Byte値)は次の頁の開始Byte値
}
void loop() {
bool drawed = false; // 描画変数=描画していない
if (display.getTouch(tp, 1)) { // タッチ情報を取得
drawed = true; // 描画した
if (tp[0].x > 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 if (drawed) { // もし描画していれば
drawed = false; // 描画変数を戻す
}
vTaskDelay(1); // 1tickの間タスクをブロック
}
* flash memory(3.1Mbyte)のうち、スケッチが44%使用。RAM(327kbyte)のうち、global変数が7%使用、local変数で302kbyte使用可能。(1000byte=1kbyteで計算)