0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

71歳の... ESP32はDOSマシンになるか? 画面の上下スクロールがスムーズにでき、よりそれらしくなりました。

Posted at

 「ESP32-S3」を「DosPC」らしく仕立てようと頑張ってます。今回の改良は次の3点です。ご利用は自己責任で。

1.横80桁、縦24行(800*480ピクセル)の大画面で日本語も見易く
2.USBキーボードからの直接コマンド入力
3.UP/DOWNキーで上下スムーズスクロール可能に

夜明け.jpg
夢の.jpg

環境・構成は以下です。

1.Windows 11 Pro
2.Arduino IDE 2.3.6
3.FREENOVE ESP32-S3 WROOM(Flash 8MB) "Huge APP ..."でコンパイル
 「SD_MMC」ライブラリで、ボード下面の microSD スロット使用
4.4 inch 800*480 Display(NT35510 16bit Parallel 接続)
 「LovyanGFX」ライブラリで表示
5.ELECOM ワイアレスキーボード TK-EDM105T BK 2.4GHz
 「EspUsbHost」ライブラリで接続、使用

長くなるので詳しいことは過去の投稿をご覧ください。

 スクロール動作の確認のため、次の3つのコマンドを実装しています。
ls - microSDカード内のファイルを全て表示
ls フォルダー名 - 指定のフォルダー内を表示
sd - microSDの情報を表示
lsまたはls /などで画面に収まりきらないほど表示させた後に、必要に応じUPキーかDOWNキーを押すとスクロールします。キー一発目は反応しずらいですが(ここは仕様上仕方ないところ)、数回押しているとスクロールし始めます。プログラムとしては、UPキーが押された時のkeycodeの変化を調べ、EspUsbHost.cppの仮想関数を次のように実装しました。

// virtual関数の実装
class MyEspUsbHost : public EspUsbHost {

  void onKeyboard(hid_keyboard_report_t report, hid_keyboard_report_t last_report) {
    if (last_report.keycode[0] == 0 && report.keycode[0] == 82)
      UPbegiTime = millis();  // 計測開始
    if (last_report.keycode[0] == 82 && report.keycode[0] == 0)
      UPkeptTime = millis() - UPbegiTime;  // 経過時間を計算
    if (last_report.keycode[0] == 0 && report.keycode[0] == 81)
      DWbegiTime = millis();  // 計測開始
    if (last_report.keycode[0] == 81 && report.keycode[0] == 0)
      DWkeptTime = millis() - DWbegiTime;  // 経過時間を計算

    Serial.printf("modifier=[0x%02x]->[0x%02x], Key0=[0x%02x]->[0x%02x], Key1=[0x%02x]->[0x%02x], Key2=[0x%02x]->[0x%02x], Key3=[0x%02x]->[0x%02x], Key4=[0x%02x]->[0x%02x], Key5=[0x%02x]->[0x%02x] /n/r",
                  last_report.modifier,
                  report.modifier,
                  last_report.keycode[0],
                  report.keycode[0],
                  last_report.keycode[1],
                  report.keycode[1],
                  last_report.keycode[2],
                  report.keycode[2],
                  last_report.keycode[3],
                  report.keycode[3],
                  last_report.keycode[4],
                  report.keycode[4],
                  last_report.keycode[5],
                  report.keycode[5]);
  }

  void onKeyboardKey(uint8_t ascii, uint8_t keycode, uint8_t modifier) {
    Serial.printf("a:%d ;", ascii);
    Serial.printf("k:%d ;", keycode);
    Serial.printf("m:%d", modifier);
    Serial.println("");

    if (ascii == 10 || ascii == 13) {  // LF, CR
      l_buffer.push_back(">" + cmd);   // stack command string
      ++by;
      //if (CurY < ROWS - 1)
      ++CurY;
      _inputFlg = true;  // break;

    } else if (keycode == 82) {  // [UP]key
      int16_t uptime = UPkeptTime / 200;
      //Serial.println(uptime);
      if (uptime == 0) uptime = 1; // 初回は0となるので
      while (uptime > 0) { // 複数行スクロール可能に
        if (2 + by - ROWS + pastY > 0) {
          pastY--;  // スクロールバック
          ascr();
          set_pri(W - (6 * cellX), (ROWS - 1) * cellY, "l." + (String)(by - 1 + pastY));
        }
        uptime--;
      }
    } else if (keycode == 81) {  // [DOWN]key
      int16_t dwtime = DWkeptTime / 200;
      if (dwtime == 0) dwtime = 1; // 初回は0となるので
      while (dwtime > 0) { // 複数行スクロール可能に
        if (pastY < 0) {
          pastY++;  // スクロールフォワード
          ascr();
          set_pri(W - (6 * cellX), (ROWS - 1) * cellY, "l." + (String)(by - 1 + pastY));
        }
        dwtime--;
      }
    } else {
      if (31 < ascii && ascii < 127) {
        if (pastY < 0) {
          pastY = 0;
          ascr();
        }
        cmd = cmd + (char)ascii;
      } else if (ascii == 8 && cmd.length() > 0) {  // BackSpace
        cmd = cmd.substring(0, cmd.length() - 1);
      }
      Serial.println(">" + cmd);
      set_pri(0, CurY * cellY, ">" + cmd);  // update command string
    }
  }

};

スケッチと同じフォルダーに、

`myLovyanGFX.hpp`(Display専用に書いたもの)
`sd_read_write.h`(原本のまま)
`sd_read_write.cpp`(原本のまま)
`EspUsbHost.h`(付け加えたもの。上記過去投稿をご参考に)
`EspUsbHost.cpp`(原本のまま)

を置きコンパイルします。

myLovyanGFX.hpp
#pragma once
#define LGFX_USE_V1
#include <LovyanGFX.hpp>

class LGFX : public lgfx::LGFX_Device {
  lgfx::Panel_NT35510  _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  =  9;//14;                        // RD を接続しているピン番号
    cfg.pin_rs  =  8;//3;                        // RS(D/C)を接続しているピン番号
    cfg.pin_d0  = 18;                        // D0 を接続しているピン番号
    cfg.pin_d1  = 21;// 0;//36;                        // D1 を接続しているピン番号
    cfg.pin_d2  = 17;                        // D2 を接続しているピン番号
    cfg.pin_d3  = 47;//35;//38;//37;                        // D3 を接続しているピン番号
    cfg.pin_d4  = 16;                        // D4 を接続しているピン番号
    cfg.pin_d5  = 45;//36;//39;//38;                        // D5 を接続しているピン番号
    cfg.pin_d6  = 15;                        // D6 を接続しているピン番号s
    cfg.pin_d7  =  0;//37;//40;//39;                        // D7 を接続しているピン番号
    cfg.pin_d8  =  1;//2;                        // D8 を接続しているピン番号
    cfg.pin_d9  =  4;                        // D9 を接続しているピン番号
    cfg.pin_d10 =  2;//42;                        // D10を接続しているピン番号
    cfg.pin_d11 =  5;                        // D11を接続しているピン番号
    cfg.pin_d12 = 42;//41;                        // D12を接続しているピン番号
    cfg.pin_d13 =  6;                        // D13を接続しているピン番号
    cfg.pin_d14 = 41;//40;                        // D14を接続しているピン番号
    cfg.pin_d15 =  7;                        // D15を接続しているピン番号
    _bus_instance.config(cfg);               // 設定値をバスに反映します。
    _panel_instance.setBus(&_bus_instance);  // バスをパネルにセットします。
    {                                        // 表示パネル制御の設定
      auto cfg = _panel_instance.config();   // 表示パネル設定用の構造体を取得します。
      cfg.pin_cs   =  3;//8;           // CSが接続されているピン番号   (-1 = disable)
      cfg.pin_rst  = -1;//9;      // RSTが接続されているピン番号  (-1 = disable)
      cfg.pin_busy = -1;                     // BUSYが接続されているピン番号 (-1 = disable)
      cfg.memory_width = 480;                // ドライバICがサポートしている最大の幅
      cfg.memory_height =800;               // ドライバICがサポートしている最大の高さ
      cfg.panel_width = 480;                 // 実際に表示可能な幅
      cfg.panel_height =800;                // 実際に表示可能な高さ
      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 = false;                  // データ読出しが可能な場合 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);
    }
    setPanel(&_panel_instance);  // 使用するパネルをセットします。
  }
};

スケッチ本体です。

Freenove_DosPC_0.ino
#include "myLovyanGFX.hpp"
#include "EspUsbHost.h"
#include "sd_read_write.h"
#include "SD_MMC.h"
#include <string>
#include <vector>
using namespace std;

// Display Constants
#define W 800
#define H 480
#define cellX 10  //8
#define cellY 20
//#define COLS (int)(W / cellX)
#define ROWS (int)(H / cellY) - 1

#define GREY 0x7BEF
#define BLACK 0x0000
#define WHITE 0xFFFF
#define GREEN 0x03E0
#define MAGENTA 0xF81F
#define CYAN 0x07FF
#define BLUE 0x001F
#define YELLOW 0xFFE0
#define RED 0xF800
#define NAVY 0x0002

// SD_MMC FREENOVE ESP32-S3 WROOM 内蔵SDスロット使用のため
#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.

File fp;
const String _rt = "/";

void ascr();
void set_pri(uint16_t x, uint16_t y, String s);

// For Disolay lines
static LGFX t;
std::vector<String> l_buffer;
String cmd;
volatile bool _inputFlg = false;

int8_t CurY = 0;
int16_t by = 0;
int16_t pastY = 0;  // スクロールのため

// USB Keyboard
int16_t UPbegiTime;  // UP/DOWN スクロール継続のため
int16_t UPkeptTime;
int16_t DWbegiTime;
int16_t DWkeptTime;

// virtual関数の実装
class MyEspUsbHost : public EspUsbHost {

  void onKeyboard(hid_keyboard_report_t report, hid_keyboard_report_t last_report) {
    if (last_report.keycode[0] == 0 && report.keycode[0] == 82)
      UPbegiTime = millis();  // 計測開始
    if (last_report.keycode[0] == 82 && report.keycode[0] == 0)
      UPkeptTime = millis() - UPbegiTime;  // 経過時間を計算
    if (last_report.keycode[0] == 0 && report.keycode[0] == 81)
      DWbegiTime = millis();  // 計測開始
    if (last_report.keycode[0] == 81 && report.keycode[0] == 0)
      DWkeptTime = millis() - DWbegiTime;  // 経過時間を計算

    Serial.printf("modifier=[0x%02x]->[0x%02x], Key0=[0x%02x]->[0x%02x], Key1=[0x%02x]->[0x%02x], Key2=[0x%02x]->[0x%02x], Key3=[0x%02x]->[0x%02x], Key4=[0x%02x]->[0x%02x], Key5=[0x%02x]->[0x%02x] /n/r",
                  last_report.modifier,
                  report.modifier,
                  last_report.keycode[0],
                  report.keycode[0],
                  last_report.keycode[1],
                  report.keycode[1],
                  last_report.keycode[2],
                  report.keycode[2],
                  last_report.keycode[3],
                  report.keycode[3],
                  last_report.keycode[4],
                  report.keycode[4],
                  last_report.keycode[5],
                  report.keycode[5]);
  }

  void onKeyboardKey(uint8_t ascii, uint8_t keycode, uint8_t modifier) {
    Serial.printf("a:%d ;", ascii);
    Serial.printf("k:%d ;", keycode);
    Serial.printf("m:%d", modifier);
    Serial.println("");

    if (ascii == 10 || ascii == 13) {  // LF, CR
      l_buffer.push_back(">" + cmd);   // stack command string
      ++by;
      //if (CurY < ROWS - 1)
      ++CurY;
      _inputFlg = true;  // break;

    } else if (keycode == 82) {  // [UP]key
      int16_t uptime = UPkeptTime / 200;
      //Serial.println(uptime);
      if (uptime == 0) uptime = 1; // 初回は0となるので
      while (uptime > 0) { // 複数行スクロール可能に
        if (2 + by - ROWS + pastY > 0) {
          pastY--;  // スクロールバック
          ascr();
          set_pri(W - (6 * cellX), (ROWS - 1) * cellY, "l." + (String)(by - 1 + pastY));
        }
        uptime--;
      }
    } else if (keycode == 81) {  // [DOWN]key
      int16_t dwtime = DWkeptTime / 200;
      if (dwtime == 0) dwtime = 1; // 初回は0となるので
      while (dwtime > 0) { // 複数行スクロール可能に
        if (pastY < 0) {
          pastY++;  // スクロールフォワード
          ascr();
          set_pri(W - (6 * cellX), (ROWS - 1) * cellY, "l." + (String)(by - 1 + pastY));
        }
        dwtime--;
      }
    } else {
      if (31 < ascii && ascii < 127) {
        if (pastY < 0) {
          pastY = 0;
          ascr();
        }
        cmd = cmd + (char)ascii;
      } else if (ascii == 8 && cmd.length() > 0) {  // BackSpace
        cmd = cmd.substring(0, cmd.length() - 1);
      }
      Serial.println(">" + cmd);
      set_pri(0, CurY * cellY, ">" + cmd);  // update command string
    }
  }

};

MyEspUsbHost usbHost;

void GetKey(void* args) {
  while (1) {
    delay(1);
    usbHost.task();
  }
}

// ls
int pn;
void ls_0(File fp) {
  String s;
  while (File file = fp.openNextFile()) {
    // Loop through all the files in the root directory
    if (file.isDirectory()) {
      s = "/" + (String)file.name() + "/";
      Serial.print(s);
    } else {
      s = (String)file.name() + " : size=" + (String)file.size() + "b";
      Serial.print(s);
    }
    tString(s);
    file.close();  // Close the file
  }
  Serial.println("--- Found Dirs/Files.");
  tString("--- Found Dirs/Files.");
}

TaskHandle_t thp;

void setup(void) {
  Serial.begin(115200);
  Serial.println("DOS PC Console Start");

  t.init();
  delay(1000);
  t.setRotation(1);
  t.setColorDepth(16);
  t.startWrite();  // ここでstartWrite()することでSpeedUp ?
  t.setBaseColor(NAVY);
  t.clear();
  t.setFreeFont(&fonts::lgfxJapanGothic_20);  //&lgfx::fonts::Font2);
  t.setTextSize(1);
  t.setTextWrap(false, false);
  t.setTextColor(TFT_WHITE, NAVY);

  SD_MMC.setPins(SD_MMC_CLK, SD_MMC_CMD, SD_MMC_D0);
  if (!SD_MMC.begin("/SD", true, true, SDMMC_FREQ_DEFAULT, 5)) {
    Serial.println("Card Mount Failed");
    return;
  }

  usbHost.begin();
  usbHost.setHIDLocal(HID_LOCAL_Japan_Katakana);
  xTaskCreateUniversal(GetKey, "Get Key", 4096, NULL, 3, &thp, CONFIG_ARDUINO_RUNNING_CORE);
}

void loop(void) {
  cmd = "";
  Serial.println(">");
  set_pri(0, CurY * cellY, ">");
  set_pri(W - (6 * cellX), CurY * cellY, "l." + (String)by);
  _inputFlg = false;
  while (_inputFlg == false) { ; }
  //pri_both(">"+cmd);

  if (cmd.substring(0, 2) == "sd") {  // SD information
    SDMMC_Info();

  } else if (cmd.substring(0, 2) == "ls") {  // SD information
    if (cmd.length() == 2) {                 // list up all dirs/files
      File root = SD_MMC.open(_rt);
      pn = 0;
      printDirectory(root, 0);
    } else {
      String s, fn;
      if (cmd.length() == 4 && cmd.substring(3, 1) == _rt) fn = _rt;
      else fn = _rt + cmd.substring(3);  // "ls dir/file"
      if (SD_MMC.exists(fn)) {
        s = fn + " exists.";
        pri_both(s);
        File fp = SD_MMC.open(fn);
        if (fp.isDirectory()) ls_0(fp);  //そのdir内のみを表示
      } else {
        s = fn + " not exists.";
        pri_both(s);
      }
    }

  } else {
    pri_both(cmd + " ?");
  }
}

// パラメータを切り出す
int split(String data, char delimiter, String* dst, int num) {
  int index = 0;
  int len = data.length();
  for (int i = 0; i < len; i++) {
    char tmp = data.charAt(i);
    if (tmp == delimiter) {
      index++;
      if (index > num - 1) return -1;
    } else {
      dst[index] += tmp;
    }
  }
  return index + 1;
}

void set_pri(uint16_t x, uint16_t y, String s) {
  t.fillRect(x, y, W - x - 2, cellY, NAVY);
  t.setCursor(x, y);
  t.print(s);
}

void pri_both(String s) {
  Serial.println(s);
  tString(s);
}

void tString(String s) {
  l_buffer.push_back(s);
  set_pri(0, CurY * cellY, s);
  ++by;
  //if (CurY < ROWS - 1)
  ++CurY;
  ascr();
}

void ascr() {  // TFT下端に達したらスクロール
  if (by >= ROWS) {
    for (int y = 0; y < ROWS; y++) {
      set_pri(0, y * cellY, l_buffer[2 + by - ROWS + pastY + y]);
    }
    CurY = ROWS - pastY;
  }
}

void SDMMC_Info() {
  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";
  pri_both(s);

  uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024 * 1024);
  s = "Card Size: " + (String)(cardSize) + "GB";
  pri_both(s);
}

void printDirectory(File rt, int numTabs) {
  while (File file = rt.openNextFile()) {
    Serial.printf(" #%d=", ++pn);
    for (uint8_t i = 0; i < numTabs; i++) Serial.print('\t');

    if (file.isDirectory()) {
      Serial.println((String)file.name() + "/");
      tString(" #" + (String)pn + "=" + (String)file.name() + '/');
      printDirectory(file, numTabs + 1);
    } else {  // files have sizes, directories do not
      Serial.printf("%s\t\t%d\n\r", file.name(), file.size());
      tString(" #" + (String)pn + "=" + (String)file.name() + " _ " + (String)file.size());
    }
    file.close();
  }
}

 全くの手探りでここまでたどり着きました。最後まで見ていただきありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?