以前、ESP32-S3でDOSコマンドベース風の「ナンチャッテPC」
https://qiita.com/Mobu_Kyoto/items/fa8da97f9ffc481243d1
を作りました。今回は、懐かしい「FD」「sedit」風のファイラー、エディターもどきを作成してみました。もちろん、本家の足元にも及びませんが、500行内に収めることを大前提に、ヘルプ表示機能も付けられ何とか満足なモノが出来ました。既に投稿済みの
https://qiita.com/Mobu_Kyoto/items/bf53d6ece12c533fd5fe
をアップデートしようとも思いましたが、新たな投稿にします。
環境/構成は以下。
1.Windows 11 Pro 24H2
2.Arduino IDE 2.3.6
3.Freenove ESP32-S3 WROOM (8MB Flash,8MB PSRAM) ボード「FNK0085」
付属カメラなしで使用
Arduino IDE上では「ESP32S3 Dev Module」
4.SD関係でエラーが出ることがありますが、ボード内蔵microSDスロットで
"SD_MMC.h"使用であれば問題なし(?)
スケッチと同じフォルダーに"sd_read_write.cpp"、"sd_read_write.h"をコピー
ボードマネージャは最新の"esp32 by Espressif Systems 3.3.2"
5.7"インチ800x480 TFTモジュール、16bitパラレル接続
「LovyanGFX」ライブラリーを使用
電力補充のため外部5V電源接続
6.次のツールオプションを設定してコンパイル
Flash Size: "8MB(64Mb)"
Partition Schem:"Huge APP ...
PSRAM: "OPI PSRAM"
7.キー入力は「Tera Term」を通して行う
キー推しっ放しにも対応してくれる
LovyanGFXで必要なヘッダファイルは次。これをスケッチと同じフォルダーにコピーしておきます。
myLovyanGFX.hpp
#pragma once
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
class LGFX : public lgfx::LGFX_Device {
lgfx::Panel_SSD1963 _panel_instance;
lgfx::Bus_Parallel16 _bus_instance; //16bit Parallelのインスタンス(ESP32s3)
public:
LGFX(void) { // バス制御の設定を行います。
auto cfg = _bus_instance.config(); // バス設定用の構造体を取得します。
// 16ビットパラレルバスの設定
cfg.freq_write = 20000000; // 送信クロック(最大20MHz,80MHzを整数割の値に丸める)
cfg.pin_wr = 46; // WR を接続しているピン番号
cfg.pin_rd = 3; //14; // RD を接続しているピン番号
cfg.pin_rs = 9; //3; // RS(D/C)を接続しているピン番号
cfg.pin_d0 = 21; // D0 を接続しているピン番号
cfg.pin_d1 = 47; // 0;//36; // D1 を接続しているピン番号
cfg.pin_d2 = 45; // D2 を接続しているピン番号
cfg.pin_d3 = 0; //35;//38;//37; // D3 を接続しているピン番号
cfg.pin_d4 = 41; // D4 を接続しているピン番号
cfg.pin_d5 = 42; //36;//39;//38; // D5 を接続しているピン番号
cfg.pin_d6 = 2; // D6 を接続しているピン番号s
cfg.pin_d7 = 1; //37;//40;//39; // D7 を接続しているピン番号
cfg.pin_d8 = 8;//14; //2; // D8 を接続しているピン番号
cfg.pin_d9 = 18;//13; // D9 を接続しているピン番号
cfg.pin_d10= 17;//12; //42; // D10を接続しているピン番号
cfg.pin_d11= 16;//11; // D11を接続しているピン番号
cfg.pin_d12= 15;//10; //41; // D12を接続しているピン番号
cfg.pin_d13= 7;//9; // D13を接続しているピン番号
cfg.pin_d14= 6;//46; //40; // D14を接続しているピン番号
cfg.pin_d15= 5;//3; // D15を接続しているピン番号
_bus_instance.config(cfg); // 設定値をバスに反映します。
_panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。
{ // 表示パネル制御の設定
auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。
cfg.pin_cs = 4; //8; // CSが接続されているピン番号 (-1 = disable)
cfg.pin_rst = -1; //9; // RSTが接続されているピン番号 (-1 = disable)
cfg.pin_busy = -1; // BUSYが接続されているピン番号 (-1 = disable)
cfg.memory_width = 800; // ドライバICがサポートしている最大の幅
cfg.memory_height = 480; // ドライバICがサポートしている最大の高さ
cfg.panel_width = 800; // 実際に表示可能な幅
cfg.panel_height = 480; // 実際に表示可能な高さ
cfg.offset_x = 0; // パネルのX方向オフセット量
cfg.offset_y = 0; // パネルのY方向オフセット量
cfg.offset_rotation = 0; // 回転方向の値のオフセット 0~7 (4~7は上下反転)
cfg.dummy_read_pixel = 8; // ピクセル読出し前のダミーリードのビット数
cfg.dummy_read_bits = 1; // ピクセル以外のデータ読出し前のダミーリードのビット数
cfg.readable = true; // データ読出しが可能な場合 trueに設定
cfg.invert = false; // パネルの明暗が反転してしまう場合 trueに設定
cfg.rgb_order = true; // パネルの赤と青が入れ替わってしまう場合 trueに設定
cfg.dlen_16bit = true; // データ長を16bit単位で送信するパネルの場合trueに設定
cfg.bus_shared = false; // SDカードとバス共有はtrueに設定
_panel_instance.config(cfg);
}
{ // mode and resolution 18bit/24bit 設定
auto cfg = _panel_instance.config_timing_params();
// cfg.data_width = lgfx::Panel_SSD1963::is18bit; // 18bitパネル
cfg.data_width = lgfx::Panel_SSD1963::is18bit_dithering; // 18bitパネル(ディザリング有効)
// cfg.data_width = lgfx::Panel_SSD1963::is18bit_FRC; // 18bitパネル(FRC有効)
// cfg.data_width = lgfx::Panel_SSD1963::is24bit; // 24bitパネル
cfg.pll_clock = 160000000; // pll_clock設定
cfg.refresh_rate = 100; // リフレッシュレート設定
_panel_instance.config_timing_params(cfg);
}
setPanel(&_panel_instance); // 使用するパネルをセットします。
}
};
スケッチ本体です。
07_Fn_Filer_TeTm_2.ino
/*--------------------------
"esp32 by Espressif Systems ver.3.3.2"
HUGE APP"3MB...
PSRAM:OPI PSRAM
最大3145728バイトのフラッシュメモリのうち、スケッチが939671バイト(29%)を使っています。
最大327680バイトのRAMのうち、グローバル変数が34424バイト(10%)を使っていて、ローカル変数で293256バイト使うことができます。
esptool v5.1.0
Chip type: ESP32-S3 (QFN56) (revision v0.2)
Features: Wi-Fi, BT 5 (LE), Dual Core + LP Core, 240MHz, Embedded PSRAM 8MB (AP_3v3)
Crystal frequency: 40MHz
--------------------------*/
#include "sd_read_write.h"
#include "SD_MMC.h"
#include <myLovyanGFX.hpp>
#include <string>
#include <vector>
using namespace std;
// FREENOVE ESP32-S3 WROOM 内蔵SD_MMC スロット使用のため
#define SD_MMC_CMD 38 // MISO. Please do not modify it.
#define SD_MMC_CLK 39 // CLK. Please do not modify it.
#define SD_MMC_D0 40 // MOSI. Please do not modify it.
#define SW 800
#define SH 480
#define _LH 22 // line feed height
#define fnLs 20 // display rows at dir files
#define maxDispLines 20 //18
#define maxTextLines 256 // not over
#define maxFiles 128 // Define maximum number of files to store
LGFX lcd;
LGFX_Sprite spr0(&lcd); // filename Box
LGFX_Sprite spr1(&lcd); // Edit-text area
String fileNames[maxFiles]; // Array to store file names
uint16_t filesCount = 0;
uint16_t chosenFile = 0;
uint8_t fListPage = 0;
uint8_t H_lebel = 0;
String dirName = "";
String fileName = "*.txt, *.c, *.cpp";
String txt[maxTextLines];
String spc[maxTextLines];
uint16_t cuX[maxTextLines];
bool mode = 0; // 0 is edit fileName, 1 is edit content lines
bool openMenu = false; // dir/open files menu
unsigned short modeColor[2] = { TFT_PURPLE, TFT_SILVER };
uint16_t line = 0; // present edit-line
uint16_t lastline = 0; // content lines
uint16_t lb = 0; // scroll base line
std::vector<String> help1 = {
"- When Dir files/dirs -",
"ENTER: Select the file",
"UP : DIR previous file",
"DOWN : DIR next file",
"LEFT : DIR previous page",
"RIGHT: DIR next page",
"ESC : Back"
};
std::vector<String> help2 = {
"- When Editing file -",
"ESC : Back",
"F5 : Save file",
"UP : Previous line",
"DOWN : Next line",
"LEFT : Previous char",
"RIGHT: Next char",
"ENTER: Ins' a line or separate the line",
"DELETE : Delete the char or NL/LF",
"BACKSPACE: Delete prev char or NL/LF"
};
void DispHelp(uint16_t x, uint16_t y, std::vector<String> s) {
lcd.drawRect(50, 0, 247, 24, modeColor[1]);
lcd.setTextColor(TFT_YELLOW);
for (uint8_t i = 0; i < s.size(); i++)
lcd.drawString(s[i], x, y + i * 25);
}
void FKey(String s1, String s2, uint16_t x, uint16_t y) {
lcd.fillRect(x, y, 90, 22, TFT_SILVER);
lcd.setTextColor(TFT_MAGENTA);
lcd.drawString(s1, x + 3, y + 2);
lcd.setTextColor(TFT_BLACK);
lcd.drawString(s2, x + 30, y + 2);
}
void setup(void) {
Serial.begin(115200);
lcd.begin();
lcd.setRotation(0);
lcd.fillScreen(TFT_NAVY);
lcd.fillRect(1, 26, SW - 1, SH - 26, TFT_NAVY);
lcd.setFont(&fonts::efontJA_16_b); // Japanese available monospaced font.
spr0.setFont(&fonts::efontJA_16_b); // 95 chars in a line on 800 wide pixels screen.
spr1.setFont(&fonts::efontJA_16_b);
spr0.setColorDepth(1);
spr0.createSprite(245, 22); // filename editbox
spr1.setColorDepth(3); // 2^3=8 colors pallet. needs 5 colors.
spr1.createSprite(SW - 2, _LH);
spr1.setPaletteColor(2, TFT_LIGHTGRAY); // focused line background
spr1.setPaletteColor(3, TFT_MAGENTA); // cursor color
spr1.setPaletteColor(4, TFT_GREEN); // text color is GREEN
lcd.setTextColor(TFT_YELLOW);
lcd.drawString("FILE:", 14, 3);
FKey("F1", " DIR", 305, 1);
FKey("F2", " NEW", 405, 1);
FKey("F3", "Fi/Ed", 505, 1);
FKey("F4", "SAVE", 605, 1);
FKey("F5", "HELP", 705, 1);
SD_MMC.setPins(SD_MMC_CLK, SD_MMC_CMD, SD_MMC_D0);
if (!SD_MMC.begin("/sdcard", true, true, SDMMC_FREQ_DEFAULT, 5)) {
Serial.println(F("Card Mount Failed."));
return;
} else { // Card Monted Successfully.
uint8_t cardType = SD_MMC.cardType(); // カード情報
String s = "Card Type: ";
if (cardType == CARD_MMC) s += "MMC";
else if (cardType == CARD_SD) s += "SDSC";
else if (cardType == CARD_SDHC) s += "SDHC";
else s += "UNKNOWN";
Serial.println(s);
Serial.printf("Card Size: %d GB\r\n", SD_MMC.cardSize() >> 30); // /(1024 * 1024 * 1024));
}
}
void loop(void) {
lcd.drawRect(50, 0, 247, 24, modeColor[mode]);
lcd.drawRect(1, 26, SW - 2, SH - 26, modeColor[!mode]);
while (Serial.available() == 0) { ; }
uint8_t intC = Serial.read();
String intS;
// FuncKey superiority --------------------------------------------
if (intC < ' ' || intC > '~') { // out of Ascii
intS = (String)intC;
while (Serial.available()) { // get string for like Function keys.
uint8_t k = Serial.read();
intS = intS + (String)k;
}
Serial.printf("%c ; %s\n\r", intC, intS);
if (intS.equals("277980")) { // F1 Dir
H_lebel = 0;
dirName = "";
openMenu = true; // "Dir /"
clsTextArea(dirName);
listupFiles("/"); // store to fileNames[]
dispFiles(fListPage);
}
if (intS.equals("277981")) { // F2 New file
fileName = "*.txt";
clsTextArea(fileName);
openMenu = false;
mode = 0;
}
if (intS.equals("277982")) { // F3 switch edit fileneme/content
mode = !mode; // toggle filename or content
lcd.drawRect(50, 0, 247, 24, modeColor[mode]);
lcd.drawRect(1, 26, SW - 2, SH - 26, modeColor[!mode]);
}
if (intS.equals("27914953126")) { //F5 HELP
lcd.fillRect(1, 26, SW - 1, SH - 26, TFT_NAVY);
DispHelp(40, 60, help1);
DispHelp(400, 60, help2);
}
//if (intS.equals("27914957126")) Serial.println("F8");
//if (intS.equals("27915048126")) Serial.println("F9");
//if (intS.equals("27915049126")) Serial.println("F10");
//if (intS.equals("27915051126")) Serial.println("F11");
//if (intS.equals("27915052126")) Serial.println("F12");
} // FuncKey superiority end ------------------------------------------
if (openMenu) { // File-Choosing mode
if (intS.equals("279168")) { // LEFT previous list page
if (fListPage > 0) {
chosenFile = --fListPage * fnLs;
dispFiles(fListPage);
}
}
if (intS.equals("279167")) { // RIGHT next list page
if (filesCount > fnLs * (fListPage + 1)) {
chosenFile = ++fListPage * fnLs;
dispFiles(fListPage);
}
}
if (intS.equals("27")) { // ESC -> dir upper hierarchy
if (H_lebel > 0) { // not root directory;
H_lebel--;
uint8_t result = dirName.lastIndexOf('/');
if (result > 0) dirName = dirName.substring(0, result);
}
if (H_lebel == 0) dirName = ""; // coased by "H_lebel--""
Serial.printf("%s; lebel=%d", dirName, H_lebel);
fileName = dirName;
listupFiles(dirName);
dispFiles(fListPage);
}
if (intC == 13) { // RETURN -> choose file/folder
fileName = fileNames[chosenFile];
clsTextArea(fileName);
if (fileName.substring(fileName.length() - 1, fileName.length()) == "/") { // isdirectory
dirName += "/" + fileName.substring(0, fileName.length() - 1);
Serial.printf("%s; lebel=%d", dirName, ++H_lebel);
listupFiles(dirName); // list up files in the directory
chosenFile = 0;
fListPage = 0;
dispFiles(fListPage);
} else {
String s;
if (dirName != "") s = dirName + "/" + fileName;
else s = "/" + fileName;
openFile(s); // into opening files.
openMenu = false;
mode = 1;
}
}
if (intS.equals("279166")) { // DOWN at file-choosing
if (chosenFile < filesCount - 1) {
ChosenMark(0);
chosenFile++;
if ((chosenFile % fnLs) == 0) dispFiles(++fListPage);
ChosenMark(1);
dispCurFname(fileNames[chosenFile]);
}
}
if (intS.equals("279165")) { // UP at file-choosing
if (chosenFile > 0) {
ChosenMark(0);
chosenFile--;
if (fListPage > 0 && chosenFile % fnLs == fnLs - 1) dispFiles(--fListPage);
ChosenMark(1);
dispCurFname(fileNames[chosenFile]);
}
}
} else { // "openmenu == false" editing mode
if (intS.equals("27")) { // ESC -> return "dir files"
openMenu = true; // staying file-choosing mode
mode = 0;
clsTextArea(dirName);
listupFiles(dirName);
dispFiles(fListPage);
}
if (mode == 0) { // at editing filename
if ('!' <= intC && intC <= '~') fileName = fileName + String((char)intC);
if (intC == 8) fileName = fileName.substring(0, fileName.length() - 1);
if (intC == 127) fileName = "*.txt/*.c/*.cpp";
dispCurFname(fileName);
} else { // mode == 1 to Editing content lines
if (intS.equals("277983")) saveFile(); // F4 Save file
if (intC == 127) { // DELETE Key
if (cuX[line] < txt[line].length())
txt[line] = txt[line].substring(0, cuX[line]) + txt[line].substring(cuX[line] + 1);
else if (cuX[line] == txt[line].length())
ShiftLines_Up(line);
}
if (intC == 8) { // BACKSPACE Key
if (cuX[line] > 0) { // When any word exists, delete one char.
txt[line] = txt[line].substring(0, cuX[line] - 1) + txt[line].substring(cuX[line]);
spc[line] = spc[line].substring(1);
cuX[line]--;
} else if (cuX[line] == 0 && line > 0) { // delete a line
cuX[line - 1] = txt[line - 1].length();
spc[line - 1] = "|";
for (uint16_t j = 0; j < txt[line - 1].length(); j++) spc[line - 1] = " " + spc[line - 1];
ShiftLines_Up(line - 1);
line--;
}
}
if (intS.equals("279168")) { // LEFT
if (cuX[line] > 0) {
spc[line] = spc[line].substring(1);
cuX[line]--;
} else {
if ((line - lb) > 0) {
drawEditLine(0, line);
line--;
if (lb > 0) Redraw_OverLines(0);
}
}
}
if (intS.equals("279167")) { // RIGHT
if (spc[line].length() <= txt[line].length()) {
spc[line] = " " + spc[line];
cuX[line]++;
} else {
if (line < lastline) {
drawEditLine(0, line);
line++;
spc[line] = "|";
cuX[line] = 0;
if ((line - lb) >= maxDispLines) Redraw_OverLines(1);
}
}
}
if (' ' <= intC && intC <= '~') { // Ascii char
txt[line] = txt[line].substring(0, cuX[line]) + (char)intC + txt[line].substring(cuX[line]);
spc[line] = " " + spc[line];
cuX[line]++;
}
if (intC == 9) { // TAB
txt[line] = txt[line].substring(0, cuX[line]) + " " + txt[line].substring(cuX[line]);
spc[line] = " " + spc[line];
cuX[line] += 2;
}
if (intS.equals("279165")) { // UP at Editing text
if ((line - lb) > 0) {
drawEditLine(0, line);
line--;
if (lb > 0) Redraw_OverLines(0);
}
}
if (intS.equals("279166")) { // DOWN at editing
if (line < lastline) {
drawEditLine(0, line);
line++;
if ((line - lb) >= maxDispLines) Redraw_OverLines(1);
}
}
if (intC == 13) { // RETURN at editing text
for (uint16_t i = lastline; i > line; i--) { // shifting next lines >= line+1
ShiftLine(i + 1, i);
drawEditLine(0, i + 1);
}
lastline++;
if (cuX[line] > 0) { // when "enter" at line middle, separate a line two.
String t = txt[line]; // at line end, new line.
txt[line] = t.substring(0, cuX[line]);
txt[line + 1] = t.substring(cuX[line]);
spc[line] = spc[line].substring(0, cuX[line]) + "|";
spc[line + 1] = "|";
cuX[line + 1] = 0;
} else { // when "enter" at line head, insert a new line.
ShiftLine(line + 1, line);
Init_line(line);
}
drawEditLine(0, line);
line++; // move to next line
if ((line - lb) >= maxDispLines) Redraw_OverLines(1);
}
drawEditLine(1, line);
}
}
}
// Functions -------------------------------------
void Redraw_OverLines(bool pm) {
if (pm == 0) lb--;
else lb++;
for (uint8_t i = 0; i < maxDispLines; i++) drawEditLine(0, i + lb);
}
void ShiftLines_Up(uint16_t ln) {
txt[ln] = txt[ln] + txt[ln + 1];
drawEditLine(0, ln);
for (uint16_t i = ln + 1; i < lastline; i++) {
ShiftLine(i, i + 1);
drawEditLine(0, i);
}
Init_line(lastline);
spr1.fillSprite(0);
spr1.pushSprite(1, 35 + ((lastline - lb) * _LH));
lastline--;
}
void ShiftLine(uint16_t a, uint16_t b) {
txt[a] = txt[b];
spc[a] = spc[b];
cuX[a] = cuX[b];
}
void Init_line(uint16_t i) {
txt[i] = "";
spc[i] = "|";
cuX[i] = 0;
}
void clsTextArea(String fn) {
dispCurFname(fn);
lcd.fillRect(1, 27, SW - 1, SH - 27, TFT_BLACK);
line = 0;
lastline = 0;
lb = 0;
for (uint16_t i = 0; i < maxTextLines; i++) Init_line(i);
}
void listupFiles(String dirName) {
if (dirName.equals("") || dirName.equals("/")) dirName = "/";
File root = SD_MMC.open(dirName);
filesCount = 0;
if (!root.isDirectory()) {
return;
}
File file = root.openNextFile();
while (file) { // Store the file name in the array
if (filesCount < maxFiles) {
String fn = file.name();
fn.toLowerCase();
if (file.isDirectory()) fn = fn + "/";
fileNames[filesCount] = fn;
filesCount++; // Increment file counter
}
file = root.openNextFile(); // Move to the next file
}
}
void saveFile() {
String ff = "/" + fileName;
File file = SD_MMC.open(ff, FILE_WRITE); // Open file in write mode
if (file) { // Write
for (uint8_t i = 0; i <= lastline; i++) file.println(txt[i]);
file.close();
Serial.println(F("File save successfully."));
}
}
void openFile(String s) {
Serial.println("Trying read " + s);
File fp = SD_MMC.open(s, FILE_READ);
if (strstr(s.c_str(), ".bmp")) {
lcd.drawBmp(&fp, 1, 26); // if available
} else if (strstr(s.c_str(), ".png")) {
lcd.drawPng(&fp, 1, 26);
} else if (strstr(s.c_str(), ".jpg")) {
lcd.drawJpg(&fp, 1, 26);
} else {
clsTextArea(s);
lastline = 0;
while (fp.available()) {
uint8_t c = fp.read();
if (c == 10) lastline++; // Increment the line count
if (lastline == maxTextLines - 1) break;
if (c == 9) txt[lastline] += "\t"; // Horizontal Tab
if (c >= 32 && c <= 126) txt[lastline] += (char)c;
}
fp.close(); // Close the file
for (uint16_t l = 0; l <= lastline; l++) { // make background spc
if (l == maxTextLines - 1) break;
cuX[l] = txt[l].length();
spc[l] = "|";
for (uint16_t j = 0; j < txt[l].length(); j++) spc[l] = " " + spc[l];
drawEditLine((l == 0) ? 1 : 0, l);
}
line = 0;
Serial.println(F("File read successfully."));
}
}
void dispFiles(uint8_t page) {
lcd.fillRect(1, 26, SW - 1, SH - 26, TFT_NAVY);
lcd.setTextColor(TFT_YELLOW);
for (uint8_t i = 0; i < fnLs; i++) {
if (i + page * fnLs < filesCount) {
char s[16];
sprintf(s, "%2d", i + page * fnLs);
lcd.drawString((String)s + ":" + fileNames[i + page * fnLs], 30, 34 + i * _LH);
}
}
ChosenMark(1);
dispCurFname(fileNames[chosenFile]);
}
void ChosenMark(bool c) {
if (c == 0) lcd.fillRect(15, 38 + (chosenFile % fnLs) * _LH, 10, 10, TFT_NAVY);
else lcd.fillRect(15, 38 + (chosenFile % fnLs) * _LH, 10, 10, TFT_YELLOW);
}
void drawEditLine(bool _d, uint16_t ln) {
if (ln < 0) return;
if (_d) { // == 1 then draw cursor
spr1.fillSprite(1); //フォーカス行の背景は少し明るく
spr1.setTextColor(3);
spr1.drawString(spc[ln], 33, 2); // "|"を先に表示
} else spr1.fillSprite(0);
String n = (ln + 1 < 10) ? "00" : ((ln + 1 < 100) ? "0" : "");
spr1.setTextColor(2);
spr1.drawString(n + String(ln + 1) + ">", 1, 2); // 行番号
spr1.setTextColor(4);
spr1.drawString(txt[ln], 33, 2);
spr1.pushSprite(1, 35 + (ln - lb) * _LH);
}
void dispCurFname(String fn) {
spr0.fillSprite(0);
spr0.setTextColor(1);
spr0.drawString(fn, 2, 3);
spr0.pushSprite(51, 1);
}
最後まで見ていただきありがとうございました。
