BVE 鉄道シミュレーターで自宅運転台を動かそう～液晶ディスプレイとATCメータを動かす～

メトロ02系のモニタ装置を液晶ディスプレイで再現しようの続きとして
BVEで液晶ディスプレイを動かしてみましょう。

https://qiita.com/usashirou/items/b04e8e164561f05c890e

シフトレジスタのプログラムと液晶ディスプレイのプログラムを組み合わせたものとなります。
# プログラム
停車、目的地を読み取るプログラムを追加しています。
* 0~3：速度・電流・ブレーキ
* ４：ドア表示
* ５～：ATC
* 21・22：駅名
* ２３：目的地

２１，２２の駅名番号に応じて表示している駅名を書き換えます。
この際に、前の文字列を上書きする為、背景を黒にしています。
また、ドア表示はパイロットに使っていましたが、液晶ディスプレイにおいて開閉も表示するようにしています。
なお、プログラムの引用元は以下となります。

https://qiita.com/Ninagawa_Izumi/items/6a18249a31a87dad84e2


```
/**
   @file        qiita_6a18249a31a87dad84e2.ino
   @brief       Arduino Sample Code
   @author      Keitetsu
   @date        2021/02/14
   @copyright   Copyright (c) 2021 Keitetsu
   @par         License
                This software is released under the MIT License.
*/

// Font file is stored in SPIFFS
#define FS_NO_GLOBALS
#include <FS.h>

// Graphics and font library
#include <TFT_eSPI.h>
#include <SPI.h>
TFT_eSPI tft = TFT_eSPI();  // Invoke library

const int dac1 = 25;
const int dac2 = 26;
const int dac3 = 32;
const int dac4 = 33;
const int PIN_SER   =  15;
const int PIN_LATCH  =  13;
const int PIN_CLK    = 12;
const int PIN_SCLR    = 14;

char dai1;
char dai2;
char dai3;
char dai4;
char dai5;
char dai5a;

char dai6;
char dai7;
char dai8;
char dai9;
char dai10;
char dai11;
char dai12;
char dai13;
char dai14;
char dai15;
char dai16;
char dai17;
char dai18;
char dai19;
char dai20;
char dai21;
char dai22;
char dai23;
char dai24;
char dai22a;
char dai23a;
char dai24a;

long int di5;
byte b1 = B00000000;
byte b2 = B00000000;
byte b3 = B00000000;

long int dao0;
long int dao1;
long int dao2;
long int dao3;
long int dao4;

#define ELEMENTS_NUM 24 /**< カンマ区切りデータの項目数 */

/**
   @brief       受信済み文字列を格納する配列
*/
static String elements[ELEMENTS_NUM];
static int received_elements_num = 0; /**< 受信済み文字列の数 */

/**
   @brief       セットアップ関数
*/
void setup()
{
  tft.init();
  tft.setRotation(3);
  if (!SPIFFS.begin()) {
    Serial.println("SPIFFS initialisation failed!");
    while (1) yield(); // Stay here twiddling thumbs waiting
  }
  Serial.println("\r\nInitialisation done.");
  tft.fillScreen(TFT_BLACK);

  listFiles(); // Lists the files so you can see what is in the SPIFFS
  // Set "cursor" at top left corner of display (0,0)
  // (cursor will move to next line automatically during printing with 'tft.println'
  //  or stay on the line is there is room for the text with tft.print)
  tft.setCursor(0, 0);
  tft.drawRect(0, 0, 375, 97, TFT_WHITE);

  tft.drawLine(0, 37, 375, 37, TFT_WHITE);
  tft.drawLine(0, 67, 375, 67, TFT_WHITE);
  tft.drawRect(0, 97, 480, 280, TFT_WHITE);
  tft.loadFont("msgothic-36");
  tft.fillRect(375, 0, 480, 97, TFT_YELLOW);
  tft.setTextColor(TFT_BLACK, TFT_YELLOW);
  tft.drawString("状態", 390  , 30);
  tft.fillRect(20, 195, 97, 70, TFT_DARKGREEN);
  tft.fillRect(115, 195, 350, 70, TFT_WHITE);
  tft.fillRect(0, 275, 480, 47, TFT_WHITE);
  tft.fillRect(0, 280, 105, 37, TFT_CYAN);
  tft.fillRect(125, 280, 105, 37, TFT_CYAN);
  tft.fillRect(250, 280, 105, 37, TFT_CYAN);
  tft.fillRect(375, 280, 105, 37, TFT_CYAN);

  tft.loadFont("msgothic-28");
  // Set the font colour to be white with a black background, set text size multiplier to 1
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.drawString("運行 : 21  行先 : ", 10, 7);
  tft.drawString("地点 :     ", 10, 40);
  tft.drawString("終端戸締 :  1 側", 10, 70);
  tft.drawString("車内 :   ", 10, 100);
  tft.drawString(" 1      2      3      4      5      6", 135, 165);
  tft.drawString(" 1側扉 ", 25, 200);
  tft.drawString(" 2側扉 ", 25, 235);
  tft.setTextColor(TFT_BLACK, TFT_WHITE);
  tft.drawString("  閉    閉    閉    閉    閉    閉  ", 120, 200);
  tft.setTextColor(TFT_BLACK, TFT_WHITE);
  tft.drawString("  閉    閉    閉    閉    閉    閉  ", 120, 235);

  pinMode(dac1, OUTPUT);
  pinMode(dac2, OUTPUT);
  pinMode(dac3, OUTPUT);
  pinMode(dac4, OUTPUT);
  pinMode(2, OUTPUT);

  ledcSetup(1, 12800, 8);
  ledcSetup(2, 12800, 8);
  ledcSetup(3, 12800, 8);
  ledcSetup(4, 12800, 8);
  ledcAttachPin(dac1, 1);
  ledcAttachPin(dac2, 2);
  ledcAttachPin(dac3, 3);
  ledcAttachPin(dac4, 4);
  pinMode( PIN_SER, OUTPUT );
  pinMode( PIN_LATCH, OUTPUT );
  pinMode( PIN_CLK, OUTPUT );
  pinMode( PIN_SCLR, OUTPUT );
  delay(100);
  digitalWrite(PIN_SCLR, HIGH);

  Serial.begin(115200);
  Serial.setTimeout(5);        // タイムアウトの時間を100msに変更}
}
/**
   @brief       ループ関数
*/
void loop() {
  String line;              // 受信文字列
  unsigned int beginIndex;  // 要素の開始位置

  // シリアルモニタやProcessingから"AB,C,DEF,12,3,45,A1,2B,-1,+127"のように
  // ELEMENTS_NUM個の文字列の間にカンマを付けて送る
  // 送信側の改行設定は「LFのみ」にすること
  // シリアル通信で1行（改行コードまで）読み込む
  line = Serial.readStringUntil('\n');

  beginIndex = 0;
  for (received_elements_num = 0; received_elements_num < ELEMENTS_NUM; received_elements_num++) {
    // 最後の要素ではない場合
    if (received_elements_num != (ELEMENTS_NUM - 1)) {
      // 要素の開始位置から，カンマの位置を検索する
      unsigned int endIndex;
      endIndex = line.indexOf(',', beginIndex);
      // カンマが見つかった場合
      if (endIndex != -1) {
        // 文字列を切り出して配列に格納する
        elements[received_elements_num] = line.substring(beginIndex, endIndex);
        // 要素の開始位置を更新する
        beginIndex = endIndex + 1;
      }
      // カンマが見つからなかった場合はfor文を中断する
      else {
        break;
      }
    }
    // 最後の要素の場合
    else {
      elements[received_elements_num] = line.substring(beginIndex);
    }
    /*
        // 受信済み文字列の数がELEMENTS_NUM以上の場合
        if (received_elements_num >= ELEMENTS_NUM) {
          // 受信済み文字列をスラッシュ区切りでシリアルモニタに送信する
          for (int i = 0; i < ELEMENTS_NUM; i++) {
            Serial.print(elements[i]);
            Serial.print("/");
          }
          Serial.println();
        }
    */
    dai1 = elements[0].toInt();
    if (dai1 >= 0 && dai1 <= 90) {
      ledcWrite(1, dai1 * 1.45);
    }
    dai2 = elements[1].toInt();
    if (dai2 >= 0 && dai2 <= 900) {
      ledcWrite(4, dai2 * 1.45 / 10);
    }
    dai3 = elements[2].toInt();
    if (dai3 >= 0 && dai3 <= 200) {
      ledcWrite(3, dai3 * 1);
    }
    dai4 = elements[3].toInt();
    if (dai4 >= 0 && dai4 <= 200) {
      ledcWrite(2, dai4 * 1);
    }
    dai5a = elements[4].toInt();
    if ( (dai5a == 1) && (dai5 != dai5a))  {
      tft.setTextColor(TFT_WHITE, TFT_RED);
      tft.drawString("  閉    閉    閉    閉    閉    閉  ", 120, 200);
      dai5 = dai5a;
    }
    if ((dai5a == 0) && (dai5 != dai5a)) {
      tft.setTextColor(TFT_BLACK, TFT_WHITE);
      tft.drawString("  閉    閉    閉    閉    閉    閉  ", 120, 200);
      dai5 = dai5a;
    }
    if (dai5a == 0) {
      b1 |= B00000001;
    }
    dai6 = elements[5].toInt();
    if (dai6 == 1) {
      b1 |= B00010000;
    }
    dai7 = elements[6].toInt();
    if (dai7 == 1) {
      b1 |= B01000000;
    }
    dai8 = elements[7].toInt();
    if (dai8 == 1) {
      b1 |= B10000000;
    }
    dai9 = elements[8].toInt();
    if (dai9 == 1) {
      b1 |= B00100000;
    }
    dai10 = elements[9].toInt();
    if (dai10 == 1) {
      b2 |= B00001000;
    }
    dai11 = elements[10].toInt();
    if (dai11 == 1) {
      b2 |= B00000100;
    }
    dai12 = elements[11].toInt();
    if (dai12 == 1) {
      b2 |= B00000010;
    }
    dai13 = elements[12].toInt();
    if (dai13 == 1) {
      b2 |= B00000001;
    }
    dai14 = elements[13].toInt();
    if (dai14 == 1) {
      b2 |= B10000000;
    }
    dai15 = elements[14].toInt();
    if (dai15 == 1) {
      b2 |= B01000000;
    }
    dai16 = elements[15].toInt();
    if (dai16 == 1) {
      b2 |= B00100000;
    }
    dai17 = elements[16].toInt();
    if (dai17 == 1) {
      b2 |= B00010000;
    }
    dai18 = elements[17].toInt();
    if (dai18 == 1) {
      b3 |= B00001000;
    }
    dai19 = elements[18].toInt();
    if (dai19 == 1) {
      b3 |= B00000100;
    }
    dai20 = elements[19].toInt();
    if (dai20 == 1) {
      b3 |= B00000010;
    }
    dai21 = elements[20].toInt();
    if (dai21 == 1) {
      b3 |= B00000001;
    }
    digitalWrite(PIN_SCLR, HIGH);
    digitalWrite(PIN_LATCH, LOW);
    shiftOut(PIN_SER, PIN_CLK, LSBFIRST, b3);
    shiftOut(PIN_SER, PIN_CLK, LSBFIRST, b2);
    shiftOut(PIN_SER, PIN_CLK, LSBFIRST, b1);
    digitalWrite(PIN_LATCH, HIGH);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    dai22a = elements[21].toInt();
    dai23a = elements[22].toInt();
    dai24a = elements[23].toInt();

    if ((dai22a > 0) && (dai22 != dai22a)) {
      dai22 = dai22a;
      if (dai22 == 29) {
        tft.drawString("   渋谷        ", 100, 40);
      }
      if (dai22 == 28) {
        tft.drawString("  表参道       ", 100, 40);
      }
      if (dai22 == 27) {
        tft.drawString("  外苑前       ", 100, 40);
      }
      if (dai22 == 26) {
        tft.drawString("青山一丁目  ", 100, 40);
      }
      if (dai22 == 25) {
        tft.drawString(" 赤坂見附    ", 100, 40);
      }
      if (dai22 == 24) {
        tft.drawString(" 溜池山王     ", 100, 40);
      }
      if (dai22 == 23) {
        tft.drawString("  虎ノ門       ", 100, 40);
      }
      if (dai22 == 22) {
        tft.drawString("   新橋        ", 100, 40);
      }
      if (dai22 == 21) {
        tft.drawString("   銀座        ", 100, 40);
      }
      if (dai22 == 20) {
        tft.drawString("   京橋        ", 100, 40);
      }
      if (dai22 == 19) {
        tft.drawString("  日本橋       ", 100, 40);
      }
      if (dai22 == 18) {
        tft.drawString("  三越前       ", 100, 40);
      }
      if (dai22 == 17) {
        tft.drawString("   神田        ", 100, 40);
      }
      if (dai22 == 16) {
        tft.drawString("  末広町       ", 100, 40);
      }
      if (dai22 == 15) {
        tft.drawString("上野広小路     ", 100, 40);
      }
      if (dai22 == 14) {
        tft.drawString("   上野        ", 100, 40);
      }
      if (dai22 == 13) {
        tft.drawString("  稲荷町       ", 100, 40);
      }
      if (dai22 == 12) {
        tft.drawString("  田原町       ", 100, 40);
      }
      if (dai22 == 11) {
        tft.drawString("   浅草        ", 100, 40);
      }
    }
    if ((dai23a > 0) && (dai22 != dai23a) ) {
      dai22 = dai23a;
      if (dai22 == 29) {
        tft.drawString("   渋谷        ", 100, 40);
      }
      if (dai22 == 28) {
        tft.drawString("  表参道       ", 100, 40);
      }
      if (dai22 == 27) {
        tft.drawString("  外苑前       ", 100, 40);
      }
      if (dai22 == 26) {
        tft.drawString("青山一丁目     ", 100, 40);
      }
      if (dai22 == 25) {
        tft.drawString(" 赤坂見附      ", 100, 40);
      }
      if (dai22 == 24) {
        tft.drawString(" 溜池山王      ", 100, 40);
      }
      if (dai22 == 23) {
        tft.drawString("  虎ノ門       ", 100, 40);
      }
      if (dai22 == 22) {
        tft.drawString("   新橋        ", 100, 40);
      }
      if (dai22 == 21) {
        tft.drawString("   銀座        ", 100, 40);
      }
      if (dai22 == 20) {
        tft.drawString("   京橋        ", 100, 40);
      }
      if (dai22 == 19) {
        tft.drawString("  日本橋       ", 100, 40);
      }
      if (dai22 == 18) {
        tft.drawString("  三越前       ", 100, 40);
      }
      if (dai22 == 17) {
        tft.drawString("   神田        ", 100, 40);
      }
      if (dai22 == 16) {
        tft.drawString("  末広町       ", 100, 40);
      }
      if (dai22 == 15) {
        tft.drawString("上野広小路   ", 100, 40);
      }
      if (dai22 == 14) {
        tft.drawString("   上野        ", 100, 40);
      }
      if (dai22 == 13) {
        tft.drawString("  稲荷町       ", 100, 40);
      }
      if (dai22 == 12) {
        tft.drawString("  田原町       ", 100, 40);
      }
      if (dai22 == 11) {
        tft.drawString("   浅草        ", 100, 40);
      }
    }

    if ((dai24a > 0) && (dai24 != dai24a) ) {
      dai24 = dai24a;
      if (dai24 == 26) {
        tft.drawString("   渋谷    ", 200, 10);
      }
      if (dai24 == 8) {
        tft.drawString("   浅草    ", 200, 10);
      }
    }
    Serial.println();
    b1 = B00000000;
    b2 = B00000000;
    b3 = B00000000;
  }
}

// -------------------------------------------------------------------------
// List files in ESP8266 or ESP32 SPIFFS memory
// -------------------------------------------------------------------------
void listFiles(void) {
  Serial.println();
  Serial.println("SPIFFS files found:");

#ifdef ESP32
  listDir(SPIFFS, "/", true);
#else
  fs::Dir dir = SPIFFS.openDir("/"); // Root directory
  String  line = "=====================================";

  Serial.println(line);
  Serial.println("  File name               Size");
  Serial.println(line);

  while (dir.next()) {
    String fileName = dir.fileName();
    Serial.print(fileName);
    int spaces = 25 - fileName.length(); // Tabulate nicely
    if (spaces < 0) spaces = 1;
    while (spaces--) Serial.print(" ");
    fs::File f = dir.openFile("r");
    Serial.print(f.size()); Serial.println(" bytes");
    yield();
  }

  Serial.println(line);
#endif
  Serial.println();
  delay(1000);
}

#ifdef ESP32
void listDir(fs::FS & fs, const char * dirname, uint8_t levels) {
  Serial.printf("Listing directory: %s\n", dirname);

  fs::File root = fs.open(dirname);
  if (!root) {
    Serial.println("Failed to open directory");
    return;
  }
  if (!root.isDirectory()) {
    Serial.println("Not a directory");
    return;
  }

  fs::File file = root.openNextFile();
  while (file) {

    if (file.isDirectory()) {
      Serial.print("DIR : ");
      String fileName = file.name();
      Serial.print(fileName);
      if (levels) {
        listDir(fs, file.name(), levels - 1);
      }
    } else {
      String fileName = file.name();
      Serial.print("  " + fileName);
      int spaces = 32 - fileName.length(); // Tabulate nicely
      if (spaces < 1) spaces = 1;
      while (spaces--) Serial.print(" ");
      String fileSize = (String) file.size();
      spaces = 8 - fileSize.length(); // Tabulate nicely
      if (spaces < 1) spaces = 1;
      while (spaces--) Serial.print(" ");
      Serial.println(fileSize + " bytes");
    }

    file = root.openNextFile();
  }
}
#endif
// -------------------------------------------------------------------------

```
# 動いている様子
以上でATCメータ、ORP、電流計、パイロットランプ、液晶を動かすことが出来ました。
これで自宅運転台の完成となります。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">BVE 鉄道シミュレーターで自宅運転台を動かそう<br><br>液晶とパイロットランプの様子 <a href="https://t.co/66dOd5k3z8">pic.twitter.com/66dOd5k3z8</a></p>&mdash; usashirou (@usashirou1) <a href="https://twitter.com/usashirou1/status/1513010803573604357?ref_src=twsrc%5Etfw">April 10, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 参考・引用・著作権表示
© 2020 mackoy
© 三八電車店
© ねぎ味噌BVE公開所
© ED67900-5
© ツナギ図laboratory
© Ninagawa_Izumi
© KeitetsuWorks