ESP32-CAMで撮影してみた

## 1. 概要

eBayなどでUS$10以下で売られているカメラ付きマイコン「[ESP32-CAM](https://www.ebay.com/itm/163454666990)」が届いたので早速使ってみました。
ちなみに5枚購入して、5,100円くらいでした。

![DSC_6711.JPG](https://qiita-image-store.s3.amazonaws.com/0/58479/65f63805-cfd0-5c0e-7af9-6aa562048eb5.jpeg)
![DSC_6712.JPG](https://qiita-image-store.s3.amazonaws.com/0/58479/e6b5fe76-acf9-6ba6-aa8a-ddf229de6288.jpeg)

## 2. プログラムの書き込み

ESP32-CAMにはUSBシリアル変換器などは載っていないので、別途、シリアル経由で書き込む手段を確保する必要があります。
今回はスイッチサイエンスで販売されている「[FLINT ESP Programmer](https://www.switch-science.com/catalog/5241/)」を使いました。結線は下表の通りです。

| No | FLINT ESP Programmer | ESP32-CAM |
|---:|:---|:---|
| 1 | `GND` | `GND` |
| 2 | `IO0` | `IO0`(`CSI_MCLK_IO0`) |
| 3 | `+3.3V` | `3V3` |
| 4 | `TX` | `U0R`(`U0RXD`) |
| 5 | `RX` | `U0T`(`U0TXD`) |
| 6 | `RESET` | (NC) |

注意点として、ESP32の`RESET`端子はピンヘッダに出ておらず、ボード上にタクトスイッチがあります。そのため、プログラムの書き込み時には手動でタクトスイッチを押す必要があり、少々不便です。
このボードを使うのであれば、OTA（Over The Air）で書き込むのが良いかと思います。

![DSC_6721.JPG](https://qiita-image-store.s3.amazonaws.com/0/58479/46e1b6b1-c48f-68e6-9974-f73ad3b36b1d.jpeg)

## 3. 動作確認

動作確認のため、`esptool.py`を使ってチップIDを調べてみたところ「ESP32D0WDQ5 (revision 1)」でした。

```text
$ esptool.py --port /dev/tty.usbserial-DA01OFVE chip_id
esptool.py v2.5.1
Serial port /dev/tty.usbserial-DA01OFVE
Connecting.....
Detecting chip type... ESP32
Chip is ESP32D0WDQ5 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse
MAC: 30:ae:a4:d7:42:30
Uploading stub...
Running stub...
Stub running...
Warning: ESP32 has no Chip ID. Reading MAC instead.
MAC: 30:ae:a4:d7:42:30
Hard resetting via RTS pin...
```

## 4. 撮影した画像

実際に撮影した画像は以下の通り。画質としてはイマイチ。ただ、部屋は暗く、撮影条件としてはよい状態ではありませんでした。

![1552535371715.jpg](https://qiita-image-store.s3.amazonaws.com/0/58479/3939ac6b-77d8-0e0f-62a4-0e7149a79db3.jpeg)

## 5. ソースコード

実験に使用したソースコードは https://github.com/nayutaya/iot/tree/master/sandbox/esp32/camera にあります。主要な部分（`main.cpp`）は以下の通り。

```cpp
#include <Arduino.h>
#include <ArduinoOTA.h>
#include <esp_camera.h>
#include <WiFi.h>

#include <PubSubClient.h>

extern const char    *kWifiSsid;
extern const char    *kWifiPassword;
extern const char    *kMqttServerAddress;
extern const uint16_t kMqttServerPort;

// REF: https://github.com/SeeedDocument/forum_doc/blob/master/reg/ESP32_CAM_V1.6.pdf
constexpr int kCameraPin_PWDN   =  32;
constexpr int kCameraPin_RESET  =  -1;  // NC
constexpr int kCameraPin_XCLK   =   0;
constexpr int kCameraPin_SIOD   =  26;
constexpr int kCameraPin_SIOC   =  27;
constexpr int kCameraPin_Y9     =  35;
constexpr int kCameraPin_Y8     =  34;
constexpr int kCameraPin_Y7     =  39;
constexpr int kCameraPin_Y6     =  36;
constexpr int kCameraPin_Y5     =  21;
constexpr int kCameraPin_Y4     =  19;
constexpr int kCameraPin_Y3     =  18;
constexpr int kCameraPin_Y2     =   5;
constexpr int kCameraPin_VSYNC  =  25;
constexpr int kCameraPin_HREF   =  23;
constexpr int kCameraPin_PCLK   =  22;

WiFiClient g_wifi_client;
PubSubClient g_pub_sub_client(g_wifi_client);

void setupSerial() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();
}

void setupOta() {
  Serial.print("Connecting to ");
  Serial.print(kWifiSsid);
  Serial.println("...");

  WiFi.mode(WIFI_STA);
  WiFi.begin(kWifiSsid, kWifiPassword);
  while ( WiFi.waitForConnectResult() != WL_CONNECTED ) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }

  ArduinoOTA.onStart([]() {
    Serial.println("Start updating...");
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if      ( error == OTA_AUTH_ERROR    ) Serial.println("Auth Failed");
    else if ( error == OTA_BEGIN_ERROR   ) Serial.println("Begin Failed");
    else if ( error == OTA_CONNECT_ERROR ) Serial.println("Connect Failed");
    else if ( error == OTA_RECEIVE_ERROR ) Serial.println("Receive Failed");
    else if ( error == OTA_END_ERROR     ) Serial.println("End Failed");
  });
  ArduinoOTA.begin();

  Serial.print("WiFi.localIP: ");
  Serial.println(WiFi.localIP().toString());
  Serial.print("ArduinoOTA.getHostname: ");
  Serial.println(ArduinoOTA.getHostname());
}

void setupCamera() {
  const camera_config_t config = {
    .pin_pwdn     = kCameraPin_PWDN,
    .pin_reset    = kCameraPin_RESET,
    .pin_xclk     = kCameraPin_XCLK,
    .pin_sscb_sda = kCameraPin_SIOD,
    .pin_sscb_scl = kCameraPin_SIOC,
    .pin_d7       = kCameraPin_Y9,
    .pin_d6       = kCameraPin_Y8,
    .pin_d5       = kCameraPin_Y7,
    .pin_d4       = kCameraPin_Y6,
    .pin_d3       = kCameraPin_Y5,
    .pin_d2       = kCameraPin_Y4,
    .pin_d1       = kCameraPin_Y3,
    .pin_d0       = kCameraPin_Y2,
    .pin_vsync    = kCameraPin_VSYNC,
    .pin_href     = kCameraPin_HREF,
    .pin_pclk     = kCameraPin_PCLK,
    .xclk_freq_hz = 20000000,
    .ledc_timer   = LEDC_TIMER_0,
    .ledc_channel = LEDC_CHANNEL_0,
    .pixel_format = PIXFORMAT_JPEG,
    .frame_size   = FRAMESIZE_SVGA,
    .jpeg_quality = 10,
    .fb_count     = 1,
  };

  esp_err_t err = esp_camera_init(&config);
  Serial.printf("esp_camera_init: 0x%x\n", err);
}

void setupMqtt() {
  g_pub_sub_client.setServer(kMqttServerAddress, kMqttServerPort);
}

void setup() {
  setupSerial();
  setupOta();
  setupCamera();
  setupMqtt();
}

void handleOta() {
  ArduinoOTA.handle();
}

void handleMqtt() {
  if ( !g_pub_sub_client.connected() ) {
    g_pub_sub_client.connect("esp32");
  }
  g_pub_sub_client.loop();
}

void loop() {
  handleOta();
  handleMqtt();

  camera_fb_t *fb = esp_camera_fb_get();
  if ( fb ) {
    Serial.printf("width: %d, height: %d, buf: 0x%x, len: %d\n", fb->width, fb->height, fb->buf, fb->len);
    g_pub_sub_client.publish("test", fb->buf, fb->len);
    esp_camera_fb_return(fb);
  }

  delay(1000);  // [ms]
}
```

## 6. 参考

* [Ai Thinker CAM Development Board(with camera) - Seeed Studio](https://www.seeedstudio.com/ESP32-CAM-Development-Board-with-camer-p-3153.html)
* [forum_doc/ESP32_CAM_V1.6.pdf at master · SeeedDocument/forum_doc](https://github.com/SeeedDocument/forum_doc/blob/master/reg/ESP32_CAM_V1.6.pdf)
* [arduino-esp32/libraries/ESP32/examples/Camera/CameraWebServer at master · espressif/arduino-esp32](https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/Camera/CameraWebServer)
* [esp32-camera/esp_camera.h at master · espressif/esp32-camera](https://github.com/espressif/esp32-camera/blob/master/driver/include/esp_camera.h)
* [esp32-camera/sensor.h at master · espressif/esp32-camera](https://github.com/espressif/esp32-camera/blob/master/driver/include/sensor.h)