0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

70歳の挑戦... ESP32-CAMで撮影した画像をSDカードに保存そして表示 謎の不具合に悩みましたが

Last updated at Posted at 2024-07-29

【悲報と朗報】
つい先日投稿したばかりなのですが、SDカードライブラリの関係か

libraries\SD\src/utility/Sd2PinMap.h:527:2: error: #error Architecture or board not supported.
527 | #error Architecture or board not supported.

というエラーが発生、コンパイル出来なくなりました。理由が解らず困惑しております。どなたかわかる方がいらっしゃれば教えてください。
 その後 Arduino IDE において、esp関連のボードマネージャ(Arduino,espressif 両方)を一度外し、espressif/arduino-esp32 V3.0.1 のみ再インストールして解決しました。espressif/arduino-esp v3.0.4 の不具合のようです。備忘録としてここに残します。


CameraWebServerを見て、久しぶりに「Freenove ESP32-Wrover カメラ付きボード」を使ってみました。それ自体は Arduino IDE 2.3.2 に取込んで直ぐに使えました。あまりにあっけなかったので少し工夫を。デジカメ風に撮影画像を ”TFTLCD” に表示しようと目論見ましたが、Webページ上への表示みたく簡単にはいかないようです。なので一度マイクロSDカード保存し、そののち液晶画面に表示する方法に落ち着きました。どこかのサイトを参考したのですがどこか忘れました、ごめんなさい。
 今回使用したブツは以下です。

1.Arduino IDE 2.3.2
2.Freenove ESP32-Wrover カメラ付きボード(FNK0060)
3.マイクロSDカードスロットモジュール
4.サンハヤト Ck-40
5.TFT LCD ディスプレイモジュール (ILI9488 SPI接続 480x320)
6.TFT LCD用ライブラリは"LovyanGFX"

最初、使い慣れたマイクロSDカードスロットモジュール(SPI)を使いましたが、謎現象に悩まされ、ずっと前に使用していたメモリーカード変換用基板(SDMMC)(CK-40)に変更しました(DATA3,CMD,DATA0 は67kΩでプルアップ)。

EspCam.jpg

EspCam_Arduino.jpg

 BUTTON_PIN が GND から離されているとき2秒毎に対象を撮影、SDカードに保存後ディスプレイに表示させるというものです。カメラ用にほとんどのピンが使われており、僅かな残ピンでカードスロットモジュールと接続しました。

ESP32 SPI(HSPI) SDMMC
13 MOSI CMD/DI
12 MISO DATA0/DO
14 CLK CLK
33 CS(SDCS) DATA3/CS
GND GND VSS
3.3V 3V3 VDD

LovyanGFX を使用するために次のファイルをArduinoスケッチと同じディレクトリ内に用意します。

myLoyanGFX.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等でバス制御を行います)

      _panel_instance.config(cfg);
    }

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

スケッチ本体です。

Freenove_Cam_SD_9488.ino
#include "esp_camera.h"
#include <FS.h>
#include <SD.h>
#include <sd_defines.h>
#include <sd_diskio.h>

#include "myLovyanGFX.hpp"

#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

#define SDCS 33
#define BUTTON_PIN 32

static LGFX lcd;
//static LGFX_Sprite sprite1(&lcd);  // スプライトを使う場合はLGFX_Spriteのインスタンスを作成。

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(false);
  Serial.println();
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  //sprite0.createSprite(480, 320);  // 幅65、高さ65でスプライトを作成。
  sprite1.createSprite(320, 240);  // 幅65、高さ65でスプライトを作成。
  lcd.init();
  lcd.setRotation(1);
  lcd.clear(TFT_GREEN);
  if (!SD.begin(SDCS, SPI, 20000000)) {
    Serial.println("SD Failed.");
  } else {
    Serial.println("SD Initalized.");
    lcd.drawBmpFile(SD, "/backScre.bmp", 0, 0);// 不要です。
  }

  if (cameraSetup() == 1) {

  } else {

    return;
  }
}

void loop() {

  while (digitalRead(BUTTON_PIN) == LOW) { ; }

  delay(2000);

  camera_fb_t* fb = NULL;
  fb = esp_camera_fb_get();
  if (fb != NULL) {
    int photo_index = 0;  // ファイル名の連番初期値
    while (SD.exists("/" + String(photo_index) + ".jpg")) {
      photo_index++;
    }
    String path = "/" + String(photo_index) + ".jpg";
    File file = SD.open(path, FILE_WRITE);
    if (file) {
      file.write(fb->buf, fb->len);
      file.close();
      Serial.println("Saved : " + path);

      lcd.drawJpgFile(SD, path, 50, 50);

    } else {
      Serial.println("写真の保存に失敗しました");
    }
    esp_camera_fb_return(fb);
  } else {
    Serial.println("カメラのキャプチャに失敗しました");
  }
}

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;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  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 = 12;
  config.fb_count = 1;

  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  // for larger pre-allocated frame buffer.
  if (psramFound()) {
    config.jpeg_quality = 10;
    config.fb_count = 2;
    config.grab_mode = CAMERA_GRAB_LATEST;
  } else {
    // Limit the frame size when PSRAM is not available
    config.frame_size = FRAMESIZE_SVGA;
    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();
  // initial sensors are flipped vertically and colors are a bit saturated
  //s->set_vflip(s, 1); // 撮影画像の反転 ※画像が反転する場合はコメントアウトを消去
  s->set_brightness(s, 1);  // 撮影画像の明るさ
  s->set_saturation(s, 0);  // 撮影画像の彩度

  Serial.println("カメラの設定が完了しました");
  return 1;
}

 SPI接続のスロットモジュールで遭遇した謎現象とは。スケッチをアップロードし本体を立ち上げるとエラーループに陥ります。たまたまスロットの電源ケーブルを抜き差しすると無事プログラムが起動するのでとりあえずそれでやっていました。でも面倒になりSDMMCのモジュールに変更したところ問題なく動作を開始しました。理由はわかりません。
 最後までお読みいただきありがとうございます。

0
2
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
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?