70歳... ESP32-Wrover-Camの画像をTFT-LCDモジュールにリアルタイムで表示してみた

　前回の<a href="https://qiita.com/Mobu_Kyoto/items/270e97a9a44c94cd07f5">ESP32-CAMで撮影した画像をSDカードに保存</a>の続きです。ウエブページ上ではなく、且つSDカードへの保存後でもなく、TFT-LCDに直接表示出来たので報告します。

 ![ESP32Cam.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3189926/5e0106a1-54f1-c67b-41ee-ea646179e782.jpeg)

GPIO32ピンをフリーにしている間、この子たちは出来る限りの最高速で撮影と表示を繰り返します。GNDに落とすとループは止まります。
　次のサイトを参考にさせていただきました。

https://aloseed.com/it/esp32-wrover-cam/

この中の「映像を液晶ディスプレイに表示する(HSPI使用例)」を特に利用させていただきました。ただし、スケッチがそのままではうまく動作しなかったのでいくつかの変更を行いました。そうこうするうち、直接TFT-LCDに表示できるのではと思いやってみたところ出来ました。構成は、マイクロSDカードへの保存がうまくいかず省いた結果以下のように。

```
１．Arduino IDE 2.3.2
２．Freenove ESP32-Wrover カメラ付きボード（FNK0060）
３．TFT LCD ディスプレイモジュール （ILI9488 SPI接続 480x320）
４．TFT LCD用ライブラリは"LovyanGFX"
５．カメラ部はアマゾン購入の「Diystudio製160°広角レンズ」に交換、ケーブル長75mm（前回既に交換済み）
```
以下にコードを示します。

```cpp:myLovyanGFX.hpp
#pragma once

#define LGFX_USE_V1

#include <LovyanGFX.hpp>

// ESP32でLovyanGFXを独自設定で利用する場合の設定例

/*
このファイルを複製し、新しい名前を付けて、環境に合わせて設定内容を変更してください。
作成したファイルをユーザープログラムからincludeすることで利用可能になります。

複製したファイルはライブラリのlgfx_userフォルダに置いて利用しても構いませんが、
その場合はライブラリのアップデート時に削除される可能性があるのでご注意ください。

安全に運用したい場合はバックアップを作成しておくか、ユーザープロジェクトのフォルダに置いてください。
//*/


/// 独自の設定を行うクラスを、LGFX_Deviceから派生して作成します。
class LGFX : public lgfx::LGFX_Device
{
/*
 クラス名は"LGFX"から別の名前に変更しても構いません。
 AUTODETECTと併用する場合は"LGFX"は使用されているため、LGFX以外の名前に変更してください。
 また、複数枚のパネルを同時使用する場合もそれぞれに異なる名前を付けてください。
 ※ クラス名を変更する場合はコンストラクタの名前も併せて同じ名前に変更が必要です。

 名前の付け方は自由に決めて構いませんが、設定が増えた場合を想定し、
 例えばESP32 DevKit-CでSPI接続のILI9341の設定を行った場合、
  LGFX_DevKitC_SPI_ILI9341
 のような名前にし、ファイル名とクラス名を一致させておくことで、利用時に迷いにくくなります。
//*/

lgfx::Panel_ILI9488   _panel_instance;
lgfx::Bus_SPI         _bus_instance;   // SPIバスのインスタンス
lgfx::Touch_XPT2046   _touch_instance;

public:
  // コンストラクタを作成し、ここで各種設定を行います。
  // クラス名を変更した場合はコンストラクタも同じ名前を指定してください。
  LGFX(void)
  {
    { // バス制御の設定を行います。
      auto cfg = _bus_instance.config();    // バス設定用の構造体を取得します。

// SPIバスの設定
      cfg.spi_host = VSPI_HOST;     // 使用するSPIを選択  ESP32-S2,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
      // ※ ESP-IDFバージョンアップに伴い、VSPI_HOST , HSPI_HOSTの記述は非推奨になるため、エラーが出る場合は代わりにSPI2_HOST , SPI3_HOSTを使用してください。
      cfg.spi_mode = 0;             // SPI通信モードを設定 (0 ~ 3)
      cfg.freq_write = 40000000;    // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
      cfg.freq_read  = 16000000;    // 受信時のSPIクロック
      cfg.spi_3wire  = true;        // 受信をMOSIピンで行う場合はtrueを設定
      cfg.use_lock   = true;        // トランザクションロックを使用する場合はtrueを設定
      cfg.dma_channel = SPI_DMA_CH_AUTO; // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設定)
      // ※ ESP-IDFバージョンアップに伴い、DMAチャンネルはSPI_DMA_CH_AUTO(自動設定)が推奨になりました。1ch,2chの指定は非推奨になります。
      cfg.pin_sclk = 14;            // SPIのSCLKピン番号を設定
      cfg.pin_mosi = 13;            // SPIのMOSIピン番号を設定
      cfg.pin_miso = 12;            // SPIのMISOピン番号を設定 (-1 = disable)
      cfg.pin_dc   =  0;            // SPIのD/Cピン番号を設定  (-1 = disable)
     // SDカードと共通のSPIバスを使う場合、MISOは省略せず必ず設定してください。
      _bus_instance.config(cfg);    // 設定値をバスに反映します。
      _panel_instance.setBus(&_bus_instance);      // バスをパネルにセットします。
    }

    { // 表示パネル制御の設定を行います。
      auto cfg = _panel_instance.config();    // 表示パネル設定用の構造体を取得します。

      cfg.pin_cs           =    15;  // CSが接続されているピン番号   (-1 = disable)
      cfg.pin_rst          =    2;  // RSTが接続されているピン番号  (-1 = disable)
      cfg.pin_busy         =    -1;  // BUSYが接続されているピン番号 (-1 = disable)

      // ※ 以下の設定値はパネル毎に一般的な初期値が設定されていますので、不明な項目はコメントアウトして試してみてください。

      cfg.panel_width      =   320;  // 実際に表示可能な幅
      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        = false;  // パネルの赤と青が入れ替わってしまう場合 trueに設定
      cfg.dlen_16bit       = false;  // 16bitパラレルやSPIでデータ長を16bit単位で送信するパネルの場合 trueに設定
      cfg.bus_shared       =  true;  // SDカードとバスを共有している場合 trueに設定(drawJpgFile等でバス制御を行います)

// 以下はST7735やILI9163のようにピクセル数が可変のドライバで表示がずれる場合にのみ設定してください。
//    cfg.memory_width     =   240;  // ドライバICがサポートしている最大の幅
//    cfg.memory_height    =   320;  // ドライバICがサポートしている最大の高さ

      _panel_instance.config(cfg);
    }

/*
    { // タッチスクリーン制御の設定を行います。（必要なければ削除）
      auto cfg = _touch_instance.config();

      cfg.x_min      = 0;    // タッチスクリーンから得られる最小のX値(生の値)
      cfg.x_max      = 319;  // タッチスクリーンから得られる最大のX値(生の値)
      cfg.y_min      = 0;    // タッチスクリーンから得られる最小のY値(生の値)
      cfg.y_max      = 479;  // タッチスクリーンから得られる最大のY値(生の値)
      cfg.pin_int    = -1;   // INTが接続されているピン番号
      cfg.bus_shared = true; // 画面と共通のバスを使用している場合 trueを設定
      cfg.offset_rotation = 0;// 表示とタッチの向きのが一致しない場合の調整 0~7の値で設定

// SPI接続の場合
      cfg.spi_host = VSPI_HOST;// 使用するSPIを選択 (HSPI_HOST or VSPI_HOST)
      cfg.freq = 1000000;     // SPIクロックを設定
      cfg.pin_sclk = 18;     // SCLKが接続されているピン番号
      cfg.pin_mosi = 23;     // MOSIが接続されているピン番号
      cfg.pin_miso = 19;     // MISOが接続されているピン番号
      cfg.pin_cs   =  4;     //   CSが接続されているピン番号

    }
*/

    setPanel(&_panel_instance); // 使用するパネルをセットします。
  }
};
```

```c:Freenove_Camera_Disp.ino
#include "esp_camera.h"
#include <SPI.h>
//#include <SD.h>

#include "myLovyanGFX.hpp"

// Pin definition for CAMERA_MODEL_WROVER_KIT
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 21
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27

#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 19
#define Y4_GPIO_NUM 18
#define Y3_GPIO_NUM 5
#define Y2_GPIO_NUM 4
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22

// Pin definition for TFT HSPI in LovyanGFX
/*
  cfg.pin_sclk = 14;  // SPIのSCLKピン番号を設定
  cfg.pin_mosi = 13;  // SPIのMOSIピン番号を設定
  cfg.pin_miso = 12;  // SPIのMISOピン番号を設定 (-1 = disable)
  // control pins
  cfg.pin_dc   =  0;  // SPIのD/Cピン番号を設定  (-1 = disable)
  cfg.pin_cs   = 15;  // CSが接続されているピン番号   (-1 = disable)
  cfg.pin_rst  =  2;  // RSTが接続されているピン番号  (-1 = disable)
*/
/*
// Telling to SD
#define TFT_SCLK 14
#define TFT_MOSI 13
#define TFT_MISO 12
*/
//#define TFT_DC  0 // Data Command control pin
//#define TFT_CS 15 // Chip select control pin
//#define TFT_RST 2

#define SD_CS 33
#define PIN_BTN 32

static LGFX tft;

int photo_ID = 0;
long n = 0;

int cameraSetup(void) {
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;          // 4
  config.pin_d1 = Y3_GPIO_NUM;          // 5
  config.pin_d2 = Y4_GPIO_NUM;          // 18
  config.pin_d3 = Y5_GPIO_NUM;          // 19
  config.pin_d4 = Y6_GPIO_NUM;          // 36
  config.pin_d5 = Y7_GPIO_NUM;          // 39
  config.pin_d6 = Y8_GPIO_NUM;          // 34
  config.pin_d7 = Y9_GPIO_NUM;          // 35
  config.pin_xclk = XCLK_GPIO_NUM;      // 21
  config.pin_pclk = PCLK_GPIO_NUM;      // 22
  config.pin_vsync = VSYNC_GPIO_NUM;    // 25
  config.pin_href = HREF_GPIO_NUM;      // 23
  config.pin_sscb_sda = SIOD_GPIO_NUM;  // 26
  config.pin_sscb_scl = SIOC_GPIO_NUM;  // 27
  config.pin_pwdn = PWDN_GPIO_NUM;      // -1
  config.pin_reset = RESET_GPIO_NUM;    // -1
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;  // for streaming
  config.frame_size = FRAMESIZE_QVGA;    // _UXGA;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.jpeg_quality = 10;
  config.fb_count = 1;  //2;

  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  // for larger pre-allocated frame buffer.
  if (psramFound()) {
    config.frame_size = FRAMESIZE_HVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
    config.grab_mode = CAMERA_GRAB_LATEST;
  } else {
    // Limit the frame size when PSRAM is not available
    config.frame_size = FRAMESIZE_QVGA;
    config.fb_location = CAMERA_FB_IN_DRAM;
  }

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("カメラの初期化に失敗しました：", err);
    return 0;
  }

  sensor_t *s = esp_camera_sensor_get();
  s->set_brightness(s, 2);  // 撮影画像の明るさ
  s->set_saturation(s, 1);  // 撮影画像の彩度

  Serial.println("OV2640 initialized.");
  return 1;
}

void setup() {
  Serial.begin(115200);
  Serial.println("");
  Serial.println("ESP32-WROVER-CAM Starts.");

  // Use HSPI for tft & SD
  //SPI.begin(TFT_SCLK, TFT_MISO, TFT_MOSI);

  tft.init();
  Serial.println("TFT initialized.");

  tft.setRotation(1);
  tft.clear(TFT_BLACK);
  /*
  if (!SD.begin(SD_CS, SPI, 20000000)) {
    Serial.println("SD Failed.");
  } else {
    Serial.println("SD Initalized.");
    tft.drawBmpFile(SD, "/backScre.bmp", 0, 0);
  }
  */
  delay(2000);
  tft.clear(TFT_GREEN);

  if (cameraSetup() != 1) {
    return;
  }

  pinMode(PIN_BTN, INPUT);
}

void loop() {
    //take a picture();
    camera_fb_t *fb = NULL;
    fb = esp_camera_fb_get();
    if (fb != NULL) {
      Serial.println("Catured");
      tft.drawJpg(fb->buf, fb->len, 0, 0);
      tft.drawNumber(n++, 0, 0);
      // Free buffer
      esp_camera_fb_return(fb);
    } else {
      Serial.println("Capt failed");
    }
    //delay(1);
  
  while (digitalRead(PIN_BTN) == LOW) {
    ;
  }
}
```
これで任意にカード保存が出来れば最低限のデジカメになるのですが、使えるピンが残ってません😢
　最後まで見ていただきありがとうございました。