こんにちは、あろっちです。
ESP32-S3-WROOM-1(技適マーク付き)搭載のCAMボードを入手したのでご紹介したいと思います。
Freenove Official Store (AliExpress)で入手しました。
https://ja.aliexpress.com/item/1005004960637276.html
現在は国内(Amazon)でも販売されています。
本記事ではArduino(C/C++)で試してみます。
参考URL:
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という製品はありますが、技適マークがないという課題があったので、ここをクリアできているのは貴重ですよね。
参考までにモジュールの写真を載せておきます。
esptool.pyでchip_idをチェックしたところ以下の内容でした。
ギャラリー
ボード本体の他、USBケーブル、SDカード(1GB)、USB SDカードリーダー、リーフレットが付属しています。
ESP32-WROVER CAMボードと並べてみました。
ボードの大きさは同じですね。
サイズ感はESP32-WROVER CAMボードの記事でご確認ください。
ESP32-WROVER CAMボードにはなかったSDカードスロットが確認できますね。
ピン配列
カメラ&SDカードスロット使用時に使用できるGPIOについて
まとめてみました。
GPIO | 備考 |
---|---|
1 | |
3 | JTAG_EN (JTAG signal source) |
14 | |
21 | |
41 | |
42 | |
45 | VSPI (VDD_SPI voltage) |
46 | LOG (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.1もあります。
確認したところv1.1ではRGB LEDのGPIOが48から38に変更されているようです。
参考サイト:
Arduinoボード設定
Arduino IDEにESP32ボードを追加しておきます。
追加方法については、以下の記事をご参照ください。
- ボードインストール
[ツール] > [ボード] > [ボードマネージャ]からESP32ボードをインストールします。
- ボードの選択
[ツール] > [ボード] > [ESP32 Arduino] > [ESP32S3 Dev Module]を選択します。
- ボード設定
Flash Size | 8MB (64Mb) |
PSRAM | OPI PSRAM |
- シリアルポートの選択
シリアル通信を使用する場合USBコネクタはUSB-UARTに接続します。
ボードが接続されているポートを選択します。
環境 | ポート名 |
---|---|
Mac | /dev/cu.wchusbserial* |
Windows | COM* |
※シリアルポートが認識されない場合はシリアルドライバを以下からダウンロードしてインストールしてください。
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やサンプルスケッチがあります。
CameraWebServer
サンプルスケッチのSketch_07.1_CameraWebServerを試してみました。
Wi-Fi設定
ssidとpasswordを接続先Wi-FiのSSIDとパスワードに書き換えてスケッチを書き込みます。
スケッチを書き込んだのち、シリアルモニターを開きリセットボタンを押すとCameraWebServerのURLが確認できます。
ブラウザからシリアルモニターに表示されたURLにアクセスすると以下のような画面が表示されます。
動作例
映像を液晶ディスプレイ(LCD)に表示する(カメラ、SDカード、SPI3使用例)
映像がLCDに表示されるというスケッチ例です。
このボードはSDカードスロット搭載ということでSDカードに画像を保存するシャッター機能をつけてみました。
製作例
タクトスイッチがシャッターになっていて押すとLCDがグレイになり(シャッター押下を表現)、その時のカメラ画像がSDカードに保存されます。保存画像のサイズはUXGA(1600×1200)です。
この動画内で撮影した画像をサンプル画像として掲載します。
スケッチ
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
LCD | GPIO |
---|---|
VCC | 3.3V |
GND | GND |
SCK | 41 |
SDA | 42 |
DC | 3 |
CS | 1 |
RST | 46 |
LED(バックライト) | 3.3V |
ボタン類
ボタン | パーツ | GPIO | 入力について |
---|---|---|---|
LCD ON/OFFスイッチ | スライドスイッチ | 47 | INPUT |
シャッターボタン | タクトスイッチ | 21 | INPUT_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); // 使用するパネルをセットします。
}
};
関連記事
当ブログのマイコン記事です。ぜひご覧ください。
コメント