Arduino環境で手軽にカメラモジュールを使用することができるESP32-CAMを使ってみます。
シリアルモジュールを使用した書き込みとサンプルのWebServerを動かします。
小型LCD ST7735にカメラ撮影画像表示します。
ESP32-CAM
小型の基板に、CAM(OV-2640)を搭載したESP32基板。
撮影に使用できる高輝度LEDフラッシュや、保存するためのマイクロSDカードスロットを搭載。
多くは技適が対応していないものですが、AitendoのESP32-CAMキットなど技適対応品もあります。
主なスペック
接続 | 無し |
CPU | ESP32-WROOM-32 Xtensa 32bit LX6 240MHz |
フラッシュMemory | 4MB |
GPIO | 10 |
ADC | 0 |
UART | 1(スケッチ書き込み用) |
I2C | 0 |
SPI | 1 |
LED | 1 撮影用高輝度(GP4) |
ピン配置

外観
画像はAitendoで購入したESP32-CAMキットです。
ESP-WROOM32を半田実装が必要で大変ですが技適付きです。


使ってみた
画像はESP32-CAMのサンプルコードを実行した画面です。
ESP32-CAMがCAMERA Serverになり、Wifiで撮影画像を表示しています。
モーションセンサーなどを使い、自宅の防犯カメラなどがワイヤレスでできそうです。

使用感
Good
手軽にカメラモジュールで撮影できる。
Bad
書き込み用のUSBコネクタがないため、書き込みに手間がかかる。
使えるピンが少ないので、LCDを使用すると他にできることがなくなる。
そのほか
国内、AliExpressでも\1,000前後で入手性もよいですが、ほぼ技適対応していません。
一部偽技適マーク(番号のない技適マークだけ)などがあるので注意が必要です。
技適付きESP32カメラモジュールは、ESP32-WROVER, ESP32-S3なども対応してきました。
基板は少し大柄ですが使用できるピンが多いのでESP32-WROOMよりも使い勝手はよいです。
小型のマイコン基板で撮影できるので、モーションセンサやフラッシュLEDと組み合わせて防犯カメラを作ったり、野菜や花の成長をタイムラプス撮影などできそうです。
OV2640画角の違うモジュールがいくつかあり、ほとんどは標準で66°がセット売りされているようです。
他にも画角の広い120°、160°があり、これらの撮影した画像は準備ができたら追加していきます。
準備
ライブラリのインストール
ボードライブラリ
ESP32-CAMを使用するためにArduino IDEのボードマネージャからESP32用のライブラリのインストールとボードの選択をします。
ボードマネージャのURL | https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json |
検索 | ESP |
ボードライブラリ | esp32 by Espressif Systems バージョン x.x.x※ |
選択するボード | ツール > ボード > esp32 > AI Thinker ESP32 |
モジュールライブラリ
サンプルスケッチでは、LCD ST7735モジュールを使用します。
モジュールを使用しない場合インストールの必要はありません。
機能/モジュール | ライブラリ名 | 検索 | 確認時のバージョン |
---|---|---|---|
ST7735 | Adafruit ST7735 and ST7789 Library by Adafruit | ST7735 | 1.9.3 |
ST7735 | Adafruit GFX Library by Adafruit | GFX | 1.11.3 |
スケッチの書き込み
USB-シリアル変換アダプタ
ESP32-WROOMでの書き込みと同じで、GPIO0とGNDをショートした状態でUARTから書き込みを行います。
当方がAitendoで購入したESP32-CAMではなぜかアダプタが使用できないのでこちらの方法で書き込みを行っています。
シリアルUART変換アダプタにはFT232RLを使用し、VCCからは5Vを出力する設定にしています。
スケッチの書き込み前に、GPIO0とGNDをショートさせた状態で電源を投入します。
ArduinoIDEからスケッチの書き込みボタンを押すことで書き込みを開始します。
書き込み終了後はGPIO0とGNDの配線を外して電源を再投入します。
ESP32-CAM | 配線 | USB-シリアル |
---|---|---|
5V | 赤 | VCC |
GND | 黒 | GND |
GPIO0 | 白 | GND |
GPIO1 | 黄 | RX |
GPIO3 | 緑 | TX |

ESP32-CAM書き込みアダプタ
ESP32-CAMの専用アダプタでソケットにピンを挿すだけで書き込みができます。
GPIO0とGNDをショートさせなくてもスケッチの書き込みができるので便利です。

ESP32-CAMと合体するとすっきりとした状態で書き込みができます。
アダプタ側のMicroBとパソコンを接続し、ArduinoIDEの書き込みボタンからスケッチの書き込みをします。
Wifiを使用したカメラスケッチではこのまま使用することができますが、LCDなどを使用する場合はアダプタを外してLCD側の基板に載せ替える手間が発生するので少し面倒です。

トラブル
■起動しない
ESP32系ではWifiを使用すると突入で電力不足になり、起動ループになることがあります。
カメラモジュールを使用するだけでも何度か起動ループになったので、対策方法を同じく電源強化をします。
・USBハブ電源供給の場合
バスパワーのみだと不足することがあるので、AC電源付きのUSBハブを使用する。
使い方
カメラ webserver
説明
ESP32-CAMをwebserverにして映像配信します。
Wifiに接続するので、SSIDとパスワードを用意してスケッチに書き込みを行ってください。
スケッチを書き込みESP32-CAMを起動したら、シリアルモニタでIPを確認します。
ブラウザを起動し、IPを入力するとカメラの操作画面に切り替わります。
簡単な操作は結果にて紹介します。
スケッチ
サンプルスケッチは以下の操作で読み込みます。
ファイル(F) > スケッチ例 > ESP32 > Camera > CameraWebServer
スケッチ上部の、Select camera mode の#define定義個所を
#define CAMERA_MODEL_AI_THINKER // Has PSRAM のコメントを外し、
それ以外をすべてコメントにします。
36行目の const char* ssid = “*****” 箇所をWifiのSSIDに
37行目の const char* password = “*****” をWifiのパスワードに書き換えをします。

結果
ESP32-CAMを起動するとシリアルモニタに接続の様子と取得したIPアドレスが表示されます。
なぜか1行目は文字化けしやすいです。
… はWifiと接続中のようですが、電波が弱かったりすると接続できません。
表示されたIPアドレスをブラウザのURLに入力すると、カメラのWebサーバー画面が表示されます。

Get Still ボタンをおすことで撮影します。
画面左側の設定で画質やサイズを変更できます。
Start Streamボタンを押すことで動画配信ができます。

撮影画像をST7735に表示
説明
タクトスイッチとST7735を使い、ボタンが押されたら撮影してST7735に表示します。
そのほかのグラフィックのサンプルはArduinoサンプルスケッチを参照してください。
ファイル(F) > スケッチ例 > Adafruit ST7735 and ST7789 Library > graphicstest
配線
スケッチ書き込みと電源供給のためにFT232RLを使用していますが、配線図からは省略しています。

スケッチ
カメラを使用するための関連ファイルが必要なのでサンプルスケッチ単体で動作しません。
ArduinoIDEからCameraWebServerのサンプルスケッチを読み込み、CameraWebServer.inoの内容をサンプルスケッチに置き換えることで動作します。
#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include <TJpg_Decoder.h>
#include "esp_camera.h"
#define CAMERA_MODEL_AI_THINKER // Has PSRAM
#include "camera_pins.h"
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS 15 // Chip select control pin
#define TFT_DC 2 // Data Command control pin
#define TFT_RST 12
#define PIN_BTN 16
Adafruit_ST7735 tft = Adafruit_ST7735(&SPI, TFT_CS, TFT_DC, TFT_RST);
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap)
{
// Stop further decoding as image is running off bottom of screen
if ( y >= tft.height() ) return 0;
tft.drawRGBBitmap(x, y, bitmap, w, h);
// Return 1 to decode next block
return 1;
}
void init_camera() {
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;
if(psramFound()){
config.frame_size = FRAMESIZE_QQVGA;
config.jpeg_quality = 10;
config.fb_count = 1;
Serial.println("PSRAM");
} else {
config.frame_size = FRAMESIZE_QQVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
#if defined(CAMERA_MODEL_ESP_EYE)
pinMode(13, INPUT_PULLUP);
pinMode(14, INPUT_PULLUP);
#endif
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
for(;;)
{
}
}
sensor_t * s = esp_camera_sensor_get();
// initial sensors are flipped vertically and colors are a bit saturated
if (s->id.PID == OV3660_PID) {
Serial.println("PID");
s->set_vflip(s, 1);
s->set_brightness(s, 2);
s->set_saturation(s, 0);
}
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("ESP32-CAM Picture");
SPI.begin(TFT_SCLK, -1, TFT_MOSI, TFT_CS);
tft.initR(INITR_BLACKTAB);
tft.setRotation(1);
tft.fillScreen(ST77XX_BLACK);
init_camera();
tft.setCursor(20, 50);
tft.setTextColor(ST77XX_RED);
tft.setTextSize(2);
tft.println("DEADBEEF");
pinMode(PIN_BTN, INPUT);
TJpgDec.setJpgScale(1);
TJpgDec.setCallback(tft_output);
}
void take_picture(){
camera_fb_t * fb = NULL;
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
return;
}
uint16_t w = 0, h = 0;
TJpgDec.getJpgSize(&w, &h, fb->buf, fb->len);
TJpgDec.drawJpg(0, 0, fb->buf, fb->len);
esp_camera_fb_return(fb);
}
void loop() {
while (true) {
int state = digitalRead(PIN_BTN);
if (state == LOW) {
take_picture();
}
}
}
結果
ボタンを押すことで撮影した画像がST7735に表示されました。
ボタンを押したままにすることで連続で撮影した画像が動画のように見えます。

コメント