メトロ02系のモニタ装置を液晶ディスプレイで再現しよう

東京メトロ02系の運転席の横にあるモニタ装置液晶ディスプレイを作ってみましょう。
使用した液晶ディスプレイは3.5インチの解像度320x480、SPI接続のものを選択しました。
タッチパネル機能がありますが、今回は使用していません。
# 画面イメージ
最初に画面イメージを作成します。
ポンチ絵でも構いませんが作る事をお勧めします。
（扉の開閉の文字は、本来開いたら __開__ が正しいと思いますが閉としています）
![02系TFT.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/973276c2-b92a-d442-7883-34c827029a02.png)
# 構成
今回は、メインマイコンとしてESP32を使用します。
液晶ディスプレイを動かすライブラリには __TFT_eSPI__ を使用します。

https://github.com/Bodmer/TFT_eSPI
# 配線
3.5インチTFT液晶ディスプレイの仕様は以下のようです。

http://www.lcdwiki.com/3.5inch_SPI_Module_ILI9488_SKU:MSP3520

今回使用する3.5インチ液晶ディスプレイに搭載しているディスプレイドライバILI948xはタッチパネルを使用する際にMISOを接続しないようにしてください。
映像が映らなくなります。
ピンは以下につなげています。
* MOSI23
* MISO 19
* SCLK 18
* CS 5 
* DC 17
* RST なし
# ライブラリの導入
Arduinoに __TFT_eSPI__ のライブラリを追加します。
 __スケッチ⇒ライブラリをインクルード⇒ライブラリを管理⇒TFT_eSPI__ をインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/2f375243-07fd-6408-99f3-88a5db279587.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/7f10e1f1-3542-513a-527f-a7baffcecf48.png)
 __User_Setup.h__ のピン設定を変更します。

```
//#define ILI9486_DRIVER
//#define ILI9488_DRIVER     // WARNING: Do not connect ILI9488 display SDO to MISO if other devices share the SPI bus (TFT SDO does NOT tristate when CS is high)

//#define TFT_MISO 19
//#define TFT_MOSI 23
//#define TFT_SCLK 18
//#define TFT_CS   15  // Chip select control pin
//#define TFT_DC    2  // Data Command control pin
//#define TFT_RST   4  // Reset pin (could connect to RST pin)
//#define TFT_RST  -1  // Set TFT_RST to -1 if display RESET is connected to ESP32 board RST
```
を以下に変更しました。
ピン設定は各自の環境に合わせてください。
リセットピンは使用しないので __-1__ とします。
```
#define ILI9486_DRIVER
//#define ILI9488_DRIVER     // WARNING: Do not connect ILI9488 display SDO to MISO if other devices share the SPI bus (TFT SDO does NOT tristate when CS is high)

#define TFT_MISO 19
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS   5  // Chip select control pin
#define TFT_DC   17  // Data Command control pin
//#define TFT_RST   4  // Reset pin (could connect to RST pin)
#define TFT_RST  -1  // Set TFT_RST to -1 if display RESET is connected to ESP32 board RST
```
# 動作確認
最初に、キチンと動くか確認をするためにサンプルプログラムを動かしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/26353686-8ce5-a37b-4c08-1740bba3599f.png)
# プログラム
サンプルプログラムが動いたら画面イメージを元に画面を作って行きましょう。
線や丸、四角を開始位置、終了位置、色を設定します。
```
 線：tft.drawLine(x, y, width, hight, Color);
 四角(塗りつぶし無し）：tft.drawRect(x, y, width, hight, Color);
 四角(塗りつぶしあり）： tft.fillRect(x, y, width, hight, Color);
```
# 日本語フォントファイルの準備
この段階で、例えば以下を画面出力すると日本語の文字が文字化けします。

```
    tft.loadFont("使用フォント名");
    tft.setTextColor(Color);
    tft.drawString("表示内容", x, y);
```
ここからは __Watako__ さんの __M5Stackで好きなフォントを使う__ を参考にフォントファイルを作ります。

https://watako-lab.com/2018/10/31/m5_font/

最初に __Processing__ をダウンロードします。

https://processing.org/download

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/80b00e5d-8a88-9b04-92da-15053319cfde.png)
__ファイル⇒スケッチ例⇒TFT_eSPI⇒Tools⇒Create_Smooth_Font⇒Create_font__ を開き
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/9dbd610e-7028-b8d1-0243-f035fced62b7.png)
プログラムを丸ごと __Processing__ に貼り付け実行します。
![スクリーンショット 2022-04-14 115327.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/6ac34697-d28a-9cb4-e2ac-2de6560e37ae.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/550dee5f-f60e-a798-cc41-9222120e798b.png)
実行後パソコンに搭載しているフォントが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/b16f52eb-0282-a0b5-48b2-4d8610774c22.png)
# Unicodeの準備
今回のプログラムではUnicodeの番号を指定する必要があります。
使用する日本語を羅列し、変換します。

https://tech-unlimited.com/escape-unicode.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/bfe198a7-dae3-ab48-6327-0854da7f04de.png)
生成されたUnicodeをProcessingの該当箇所を

    static final int[] specificUnicodes = {
以下へ貼り付けます。
この際に

    \u4eca\u65e5
となっているものは

    0x4eca
    0x65e5
と書き換えます。

```
int fontNumber = 258; // << Use [Number] in brackets from the fonts listed.

int  fontSize = 28;

static final int[] specificUnicodes = {
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/95199c69-ec1e-f4a2-dba8-7230f4b8e735.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/f820b2ea-626a-2013-9ac5-c9585d73cfa7.png)

以上を必要なフォントごとに実行すると下記のようなフォントファイルが出来上がっていると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/93cd72b6-ad8a-5574-114b-6d3877045c61.png)
# フォントファイルのアップロード
上記で作成したフォントファイルを __Arduinoプログラムフォルダ__ 内に __dataフォルダ__ を作成しにコピーします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/9d69d6e7-b6d8-1f2f-e529-88da1f684358.png)
次に __Arduino ESP32 filesystem uploader__ をダウンロードします。

https://github.com/me-no-dev/arduino-esp32fs-plugin/tree/master/src

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/d5f8776f-174f-ead1-789a-a0447e7ea405.png)
ダウンロードした __ESP32FS.java__ を __Userフォルダ⇒Arduino⇒Toolsフォルダ__ にコピーします。
 __ESP32 Sketch Data Upload__ が表示されていると思います。
 __ESP32 Sketch Data Upload__ をクリックしファイルをアップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285344/437d894e-5e72-6f5f-ccc2-3c715ce47670.png)
以上でフォントファイルがアップロードされました。
なお、フォントの色を以下で指定しています。

    tft.setTextColor(文字色,背景色);
最後に、プログラムをコンパイルして実行し、動作確認をしましょう。

```
/*
  Sketch to demonstrate using the print class with smooth fonts

  Sketch is writtent for a 240 x 320 display

  Load the font file into SPIFFS first by using the Arduino IDE
  Sketch Data Upload menu option. Font files must be stored in the
  sketch data folder (Ctrl+k to view).
  https://github.com/esp8266/arduino-esp8266fs-plugin
  https://github.com/me-no-dev/arduino-esp32fs-plugin

  New font files in the .vlw format can be created using the Processing
  sketch in the library Tools folder. The Processing sketch can convert
  TrueType fonts in *.ttf or *.otf files.

  Note: SPIFFS does not accept an underscore _ in filenames!

  The library supports 16 bit unicode characters:
  https://en.wikipedia.org/wiki/Unicode_font

  The characters supported are in the in the Basic Multilingal Plane:
  https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane

  Make sure all the display driver and pin connenctions are correct by
  editing the User_Setup.h file in the TFT_eSPI library folder.

  #########################################################################
  ###### DON'T FORGET TO UPDATE THE User_Setup.h FILE IN THE LIBRARY ######
  #########################################################################
*/

// 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

// -------------------------------------------------------------------------
// Setup
// -------------------------------------------------------------------------
void setup(void) {
  Serial.begin(115200); // Used for messages

  tft.init();
  tft.setRotation(1);

  if (!SPIFFS.begin()) {
    Serial.println("SPIFFS initialisation failed!");
    while (1) yield(); // Stay here twiddling thumbs waiting
  }
  Serial.println("\r\nInitialisation done.");

  listFiles(); // Lists the files so you can see what is in the SPIFFS

}

// -------------------------------------------------------------------------
// Main loop
// -------------------------------------------------------------------------
void loop() {

  int xpos =  0;
  int ypos = 50;


  // Wrap test at right and bottom of screen
  tft.setTextWrap(true, true);

  // Name of font file (library adds leading / and .vlw)
  String fileName = "msgothic-28";

  // Font and background colour, background colour is used for anti-alias blending
  tft.setTextColor(TFT_WHITE, TFT_BLACK);

  // Load the font
  tft.loadFont(fileName);

  // Display all characters of the font
  tft.showFont(2000);

  // 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, 95, TFT_WHITE);

  tft.drawLine(0, 35, 375, 35, TFT_WHITE);
  tft.drawLine(0, 65, 375, 65, TFT_WHITE);
  tft.drawRect(0, 95, 480, 280, TFT_WHITE);
  tft.loadFont("msgothic-36");
  tft.fillRect(375, 0, 480, 95, TFT_YELLOW);
  tft.setTextColor(TFT_BLACK, TFT_YELLOW);
  tft.drawString("状態", 390, 30);
  tft.fillRect(20, 195, 95, 70, TFT_DARKGREEN);
  tft.fillRect(115, 195, 350, 70, TFT_WHITE);
  tft.fillRect(0, 275, 480, 45, TFT_WHITE);
  tft.fillRect(0, 280, 105, 35, TFT_CYAN);
  tft.fillRect(125, 280, 105, 35, TFT_CYAN);
  tft.fillRect(250, 280, 105, 35, TFT_CYAN);
  tft.fillRect(375, 280, 105, 35, 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, 37);
  tft.drawString("終端戸締 :  1 側", 10, 67);
  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_WHITE, TFT_RED);
  tft.drawString("  閉    閉    閉    閉    閉    閉  ", 120, 200);
  tft.setTextColor(TFT_BLACK, TFT_WHITE);
  tft.drawString("  閉    閉    閉    閉    閉    閉  ", 120, 235);
  delay(10000);
}


// -------------------------------------------------------------------------
// 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
// -------------------------------------------------------------------------
```
# 画面が表示されない・再起動する
フォントファイルをキチンとアップロードできていないと液晶が表示されないだけでなくESP32がリブートを繰り返します。
# 調整
文字や線などの配置を確認しながら進めていきます。
特に文字が抜けていて文字化けすることなどあるので注意しましょう。
# BVE連携する
以上で、液晶画面にモニタ装置の情報が表示できたと思います。
それでは最後に、BVEと連携させてみましょう。

https://qiita.com/usashirou/items/571a834cac50e128c881