ESP32-S3-WROOM CAMボード【Freenove FNK0085】を試してみました【Arduino使用】

Raspberry Pi/電子工作
スポンサーリンク

こんにちは、あろっちです。

ESP32-S3-WROOM-1(技適マーク付き)搭載のCAMボードを入手したのでご紹介したいと思います。

Freenove Official Store (AliExpress)で入手しました。

https://ja.aliexpress.com/item/1005004960637276.html

現在は国内(Amazon)でも販売されています。

本記事ではArduino(C/C++)で試してみます。

参考URL:

GitHub - Freenove/Freenove_ESP32_S3_WROOM_Board: Apply to FNK0085
Apply to FNK0085. Contribute to Freenove/Freenove_ESP32_S3_WROOM_Board development by creating an account on GitHub.

ESP32-WROVER-E搭載モデルの記事も書いていますので、ぜひご覧ください。

スポンサーリンク

特徴

Freenove ESP32-S3-WROOM Board FNK0085

モジュールESP32-S3-WROOM-1 N8R8
(技適マーク付き)
工事設計認証番号: 201-220052
フラッシュメモリ 8MB
PSRAM 8MB
カメラオンボードカメラ
(コネクタ)
付属カメラモジュール
OV2640 66°
SDカードスロットオンボード
Micro SDカードスロット
SDカード(1GB)
USB SDカードリーダー
が付属していてすぐに試せる
USBコネクタUSB-UART
USB-OTG
いずれもType-C
シリアル変換IC
CH343

ESP32-S3-WROOM-1に技適マークが付いていて、USBコネクタがType-C、しかもカメラとSDカードスロットが使えるというかなりいい具合にまとまったボードだと感じますね。
SDカードスロット付きのカメラボードにESP32-CAMという製品はありますが、技適マークがないという課題があったので、ここをクリアできているのは貴重ですよね。

参考までにモジュールの写真を載せておきます。

わかりにくいかもしれませんが、(中央右あたり)技適マークと201-220052が確認できます。

esptool.pyでchip_idをチェックしたところ以下の内容でした。

ギャラリー

外箱
内容物

ボード本体の他、USBケーブル、SDカード(1GB)、USB SDカードリーダー、リーフレットが付属しています。

ESP32-WROVER CAMボードと並べてみました。

ボードの大きさは同じですね。
サイズ感はESP32-WROVER CAMボードの記事でご確認ください。

裏面

ESP32-WROVER CAMボードにはなかったSDカードスロットが確認できますね。

ピン配列

カメラ&SDカードスロット使用時に使用できるGPIOについて

まとめてみました。

GPIO備考
1
3JTAG_EN
(JTAG signal source)
14
21
41
42
45VSPI
(VDD_SPI voltage)
46LOG
(ROM messages printing)
47

備考に記載のピンはStrapping Pin Function(ボード起動時に影響する機能)がありますが、通常時はIOピンとして使用できるようです。
※Strapping Pin Functionsはピン図(Pinout)の内側から2列目のオレンジ枠で示されています。

参考リンク(pdf):

https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf

※「2.6 Strapping Pins」を参考にしました。

ピン配置について (ESP32-S3-DevkitC-1 v1.0互換)

電源関連ピンが上下1ピン分ずつ減っているのを除くと、基本的にESP32-S3-DevKitC-1 v1.0と同じピン配置のようです。

ESP32-S3-DevKitC-1 v1.0互換の開発ボードとして使えるのは嬉しいポイントですね。

ESP32-S3-DevKitC-1との比較

ESP32-S3-DevKitC-1と並べてみました

左: ESP32-S3-DevKitC-1 v1.0 右: Freenove ESP32-S3-WROOM

参考サイト:

https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitc-1-v1.0.html

ESP32-S3-DevKitC-1にはv1.1もあります。
確認したところv1.1ではRGB LEDのGPIOが48から38に変更されているようです。

参考サイト:

Arduinoボード設定

Arduino IDEにESP32ボードを追加しておきます。

追加方法については、以下の記事をご参照ください。

  • ボードインストール

[ツール] > [ボード] > [ボードマネージャ]からESP32ボードをインストールします。

  • ボードの選択

[ツール] > [ボード] > [ESP32 Arduino] > [ESP32S3 Dev Module]を選択します。

  • ボード設定
Flash Size8MB (64Mb)
PSRAMOPI PSRAM
これ以外の項目は基本的にデフォルト値
  • シリアルポートの選択

シリアル通信を使用する場合USBコネクタはUSB-UARTに接続します。

ボードが接続されているポートを選択します。

環境ポート名
Mac/dev/cu.wchusbserial*
WindowsCOM*
*(アスタリスク)は任意の文字列を表しています。

※シリアルポートが認識されない場合はシリアルドライバを以下からダウンロードしてインストールしてください。

GitHub
https://github.com/Freenove/Freenove_ESP32_S3_WROOM_Board/tree/main/CH343

サンプルプログラム (Arduinoスケッチ)

開発環境

Arduino IDE バージョン1.8.19 (※1系)
2系(2.x.x)は最新版でOK
ESP32 Arduinoボード基本的に最新版でOK

以下にpdfやサンプルスケッチがあります。

https://github.com/Freenove/Freenove_ESP32_S3_WROOM_Board/tree/main/C

CameraWebServer

サンプルスケッチのSketch_07.1_CameraWebServerを試してみました。

[ツール] > [Partition Scheme] > [Huge APP (3MB No OTA/1MB SPIFFS)]を選択してください。

Wi-Fi設定

ssidとpasswordを接続先Wi-FiのSSIDとパスワードに書き換えてスケッチを書き込みます。

Arduino IDE 2.0.3(例)

スケッチを書き込んだのち、シリアルモニターを開きリセットボタンを押すとCameraWebServerのURLが確認できます。

ブラウザからシリアルモニターに表示されたURLにアクセスすると以下のような画面が表示されます。

動作例

映像を液晶ディスプレイ(LCD)に表示する(カメラ、SDカード、SPI3使用例)

映像がLCDに表示されるというスケッチ例です。
このボードはSDカードスロット搭載ということでSDカードに画像を保存するシャッター機能をつけてみました。

製作例

タクトスイッチがシャッターになっていて押すとLCDがグレイになり(シャッター押下を表現)、その時のカメラ画像がSDカードに保存されます。保存画像のサイズはUXGA(1600×1200)です。

この動画内で撮影した画像をサンプル画像として掲載します。

パーツについて
  • LCD
    SPI接続のものを使っています。
    製作例
    ST7735S 1.44インチ(128×128)
    ST7735S 1.8インチ(128×160)
    ILI9341(240×320)
    を掲載しています。

  • ボタン
    LCD ON/OFFスイッチにスライドスイッチ
    シャッターボタンにタクトスイッチ
    を使っています。

  • ブレッドボード
    今回の製作例ではニューブレッドボード SAD-01(サンハヤト)を使っています。

スケッチ

使用しているライブラリについて
  • LovyanGFX

Arduino IDEのライブラリマネージャーから検索してインストールできます。

#include "esp_camera.h"
#include "SD_MMC.h"
#include <LovyanGFX.hpp>

// Pin definition for CAMERA_MODEL_ESP32S3_EYE
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 15
#define SIOD_GPIO_NUM 4
#define SIOC_GPIO_NUM 5

#define Y2_GPIO_NUM 11
#define Y3_GPIO_NUM 9
#define Y4_GPIO_NUM 8
#define Y5_GPIO_NUM 10
#define Y6_GPIO_NUM 12
#define Y7_GPIO_NUM 18
#define Y8_GPIO_NUM 17
#define Y9_GPIO_NUM 16

#define VSYNC_GPIO_NUM 6
#define HREF_GPIO_NUM 7
#define PCLK_GPIO_NUM 13

// Pin definition for SD MMC
#define SD_MMC_CMD 38  //Please do not modify it.
#define SD_MMC_CLK 39  //Please do not modify it.
#define SD_MMC_D0 40   //Please do not modify it.

// Pin definition for LCD
#define LCD_SCLK 41
#define LCD_MOSI 42
#define LCD_MISO -1
#define LCD_DC 3    // Data Command control pin
#define LCD_CS 1    // Chip select control pin
#define LCD_RST 46  // Reset pin

// カメラ画像の更新間隔 (ミリ秒で指定) 500ミリ秒間隔
#define UPDATE_INTERVAL (500u)

// Switch
#define SWITCH 47

// Shutter button
#define SHUTTER_BUTTON 21

int32_t lcd_width;
int32_t lcd_height;

// LCD表示画像の倍率
float zoom;

// カメラ画像フレームバッファ
camera_fb_t* fb;

// シャッター
volatile bool shutter = false;

class LGFX : public lgfx::LGFX_Device {
  lgfx::Panel_ST7735S _panel_instance;
  lgfx::Bus_SPI _bus_instance;
public:
  LGFX(void) {
    {
      auto cfg = _bus_instance.config();

      cfg.spi_host = SPI3_HOST;           // 使用するSPIを選択  ESP32-S2,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
      cfg.spi_mode = 0;                   // SPI通信モードを設定 (0 ~ 3)
      cfg.freq_write = 40000000;          // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
      cfg.dma_channel = SPI_DMA_CH_AUTO;  // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設定)
      cfg.pin_sclk = LCD_SCLK;            // SPIのSCLKピン番号を設定
      cfg.pin_mosi = LCD_MOSI;            // SPIのMOSIピン番号を設定
      cfg.pin_miso = LCD_MISO;            // SPIのMISOピン番号を設定 (-1 = disable)
      cfg.pin_dc = LCD_DC;                // SPIのD/Cピン番号を設定  (-1 = disable)

      _bus_instance.config(cfg);               // 設定値をバスに反映します。
      _panel_instance.setBus(&_bus_instance);  // バスをパネルにセットします。
    }

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

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

      cfg.panel_width = 128;   // 実際に表示可能な幅
      cfg.panel_height = 128;  // 実際に表示可能な高さ
      cfg.offset_x = 2;        // パネルのX方向オフセット量
      cfg.offset_y = 1;        // パネルのY方向オフセット量

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


static LGFX lcd;
static LGFX_Sprite picture(&lcd);

void cameraInit(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.frame_size = FRAMESIZE_UXGA;
  config.pixel_format = PIXFORMAT_JPEG;  // for streaming
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  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("Camera init failed with error 0x%x", err);
    return;
  }

  sensor_t* s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  s->set_vflip(s, 0);       // flip it back
  s->set_brightness(s, 1);  // up the brightness just a bit
  s->set_saturation(s, 0);  // lower the saturation
}

void sdmmcInit(void) {
  SD_MMC.setPins(SD_MMC_CLK, SD_MMC_CMD, SD_MMC_D0);
  if (!SD_MMC.begin("/sdcard", true, true, SDMMC_FREQ_DEFAULT, 5)) {
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD_MMC.cardType();
  if (cardType == CARD_NONE) {
    Serial.println("No SD_MMC card attached");
    return;
  }
  Serial.print("SD_MMC Card Type: ");
  if (cardType == CARD_MMC) {
    Serial.println("MMC");
  } else if (cardType == CARD_SD) {
    Serial.println("SDSC");
  } else if (cardType == CARD_SDHC) {
    Serial.println("SDHC");
  } else {
    Serial.println("UNKNOWN");
  }
  uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
  Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize);
  Serial.printf("Total space: %lluMB\r\n", SD_MMC.totalBytes() / (1024 * 1024));
  Serial.printf("Used space: %lluMB\r\n", SD_MMC.usedBytes() / (1024 * 1024));
}

void createDir(fs::FS& fs, const char* path) {
  Serial.printf("Creating Dir: %s\n", path);
  if (fs.mkdir(path)) {
    Serial.println("Dir created");
  } else {
    Serial.println("mkdir failed");
  }
}

int readFileNum(fs::FS& fs, const char* dirname) {
  File root = fs.open(dirname);
  if (!root) {
    Serial.println("Failed to open directory");
    return -1;
  }
  if (!root.isDirectory()) {
    Serial.println("Not a directory");
    return -1;
  }

  File file = root.openNextFile();
  int num = 0;
  while (file) {
    file = root.openNextFile();
    num++;
  }
  return num;
}

void writejpg(fs::FS& fs, const char* path, const uint8_t* buf, size_t size) {
  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for writing");
    return;
  }
  file.write(buf, size);
  Serial.printf("Saved file to path: %s\r\n", path);
}

void shutterTask(void* arg) {
  for (;;) {
    if (!shutter && digitalRead(SHUTTER_BUTTON) == HIGH) {
      shutter = true;
    }
    yield();
  }
}

void setup() {
  Serial.begin(115200);
  // デバッグ用
  // Serial.setDebugOutput(true);
  // Serial.println();

  // LCD ON/OFFスイッチ
  pinMode(SWITCH, INPUT);
  // シャッターボタン
  pinMode(SHUTTER_BUTTON, INPUT_PULLDOWN);

  // LCD初期化
  lcd.init();
  lcd.setRotation(1);
  lcd.setColorDepth(16);
  lcd.clear(TFT_BLACK);

  lcd_width = lcd.width();
  lcd_height = lcd.height();

  // LCD画像用スプライト
  picture.setColorDepth(16);
  picture.createSprite(lcd_width, lcd_height);

  // カメラ初期化
  cameraInit();
  // カメラ画像の横幅を取得
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    // カメラ画像が取得できない場合は停止
    for (;;)
      yield();
  }
  // LCD画像の倍率をセット
  zoom = lcd_width / (float)fb->width;
  // フレームバッファ解放
  esp_camera_fb_return(fb);

  // SDカード初期化
  sdmmcInit();
  createDir(SD_MMC, "/camera");

  //Start Shutter tasks
  xTaskCreateUniversal(shutterTask, "shutterTask", 8192, NULL, 1, NULL, CONFIG_ARDUINO_RUNNING_CORE);
}

void loop() {
  static auto last = millis();
  static bool cleared = true;

  // スイッチONでLCD表示
  if (digitalRead(SWITCH) == HIGH) {
    auto now = millis();
    if (now - last > UPDATE_INTERVAL) {
      if (cleared) {
        cleared = false;
      }

      // フレームバッファ解放
      esp_camera_fb_return(fb);

      // カメラのフレームを取得
      fb = esp_camera_fb_get();
      if (!fb) {
        Serial.println("Camera capture failed");
        // カメラ画像が取得できない場合は停止
        for (;;)
          yield();
      }
      Serial.println("Taking picture..");

      picture.drawJpg(fb->buf, fb->len, 0, 0, lcd_width, lcd_height, 0, 0, zoom);
      picture.pushSprite(0, 0);

      last = now;
    }
    if (shutter) {
      while (digitalRead(SHUTTER_BUTTON) == HIGH) yield();
      if (fb != NULL) {
        int photo_index = readFileNum(SD_MMC, "/camera");
        if (photo_index != -1) {
          lcd.clear(TFT_DARKGREY);
          Serial.println("camera shutter");

          String path = "/camera/" + String(photo_index) + ".jpg";
          // 画像保存
          writejpg(SD_MMC, path.c_str(), fb->buf, fb->len);
          // LCDの画像を元に戻す
          picture.pushSprite(0, 0);
          shutter = false;
        }
      }
    }
  } else if (digitalRead(SWITCH) == LOW && !cleared) {
    lcd.clear(TFT_BLACK);
    cleared = true;
  }
}

LCDの座標について

LCDが座標ずれを起こす場合は基本的に以下の変数で調整してください。

      cfg.offset_x = 2;        // パネルのX方向オフセット量
      cfg.offset_y = 1;        // パネルのY方向オフセット量

例えば今回使用しているST7735S 1.44インチLCDではこの座標(オフセット量)で調整しています。

パーツ類の接続について

LCD

LCDGPIO
VCC3.3V
GNDGND
SCK41
SDA42
DC3
CS1
RST46
LED(バックライト)3.3V

ボタン類

ボタンパーツGPIO入力について
LCD ON/OFFスイッチスライドスイッチ47INPUT
シャッターボタンタクトスイッチ21INPUT_PULLDOWN

LCDの例

以下に紹介するLCDで表示する場合、LGFX定義を書き換えてください。

ST7735S 1.8インチ 128×160

LGFX定義

class LGFX : public lgfx::LGFX_Device {
  lgfx::Panel_ST7735S _panel_instance;
  lgfx::Bus_SPI _bus_instance;
public:
  LGFX(void) {
    {
      auto cfg = _bus_instance.config();

      cfg.spi_host = SPI3_HOST;           // 使用するSPIを選択  ESP32-S2,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
      cfg.spi_mode = 0;                   // SPI通信モードを設定 (0 ~ 3)
      cfg.freq_write = 40000000;          // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
      cfg.dma_channel = SPI_DMA_CH_AUTO;  // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設定)
      cfg.pin_sclk = LCD_SCLK;            // SPIのSCLKピン番号を設定
      cfg.pin_mosi = LCD_MOSI;            // SPIのMOSIピン番号を設定
      cfg.pin_miso = LCD_MISO;            // SPIのMISOピン番号を設定 (-1 = disable)
      cfg.pin_dc = LCD_DC;                // SPIのD/Cピン番号を設定  (-1 = disable)

      _bus_instance.config(cfg);               // 設定値をバスに反映します。
      _panel_instance.setBus(&_bus_instance);  // バスをパネルにセットします。
    }

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

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

      cfg.panel_width = 128;   // 実際に表示可能な幅
      cfg.panel_height = 160;  // 実際に表示可能な高さ
      cfg.rgb_order = true;  // パネルの赤と青が入れ替わってしまう場合 trueに設定

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

ILI9341

VCCは3.3Vだと表示されなかったので5Vを接続しました。

LGFX定義以外同じソースコードですがST7735Sに比べて動作は重めです。

LGFX定義

class LGFX : public lgfx::LGFX_Device {
  lgfx::Panel_ILI9341 _panel_instance;
  lgfx::Bus_SPI _bus_instance;
public:
  LGFX(void) {
    {
      auto cfg = _bus_instance.config();

      cfg.spi_host = SPI3_HOST;           // 使用するSPIを選択  ESP32-S2,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
      cfg.spi_mode = 0;                   // SPI通信モードを設定 (0 ~ 3)
      cfg.freq_write = 40000000;          // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
      cfg.dma_channel = SPI_DMA_CH_AUTO;  // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設定)
      cfg.pin_sclk = LCD_SCLK;            // SPIのSCLKピン番号を設定
      cfg.pin_mosi = LCD_MOSI;            // SPIのMOSIピン番号を設定
      cfg.pin_miso = LCD_MISO;            // SPIのMISOピン番号を設定 (-1 = disable)
      cfg.pin_dc = LCD_DC;                // SPIのD/Cピン番号を設定  (-1 = disable)

      _bus_instance.config(cfg);               // 設定値をバスに反映します。
      _panel_instance.setBus(&_bus_instance);  // バスをパネルにセットします。
    }

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

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

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

関連記事

当ブログのマイコン記事です。ぜひご覧ください。

コメント

タイトルとURLをコピーしました