ATOMS3R カメラキット(8MB PSRAM搭載)で簡易カメラ撮影サーバーを作ったメモ

ATOMS3R カメラキット(8MB PSRAM搭載)で簡易カメラ撮影サーバーを作ったメモ

ATOMS3R カメラキット(8MB PSRAM搭載)で簡易カメラ撮影サーバーを作ったメモです。

背景

以前 ESP32 PSRAM Timer Camera で簡易カメラ撮影サーバーを作ったメモ こちらの記事で ESP32 なカメラを作っていましたが ATOMS3R カメラキット(8MB PSRAM搭載) がよりコンパクトでお手軽だったので簡易カメラ撮影サーバーをつくってみました。

こんな感じで 10 円玉よりもと同じくらいの縦横の大きさですごいです。電源は USB-C で給電ですがマイコンとカメラがこれに収まってます。すごい。

実際のプログラム

HTTP Server - ESP32 esp_http_server を使ってサーバーを書くのは ESP32 PSRAM Timer Camera で簡易カメラ撮影サーバーを作ったメモ と同じです。

以下がプログラムです。

#include <WiFi.h>
#include "esp_camera.h"
#include "esp_http_server.h"

#ifndef _M5_ATOM_S3R_CAM_H_
#define _M5_ATOM_S3R_CAM_H_

#define PWDN_GPIO_NUM  -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM  21
#define SIOD_GPIO_NUM  12
#define SIOC_GPIO_NUM  9
#define Y9_GPIO_NUM    13
#define Y8_GPIO_NUM    11
#define Y7_GPIO_NUM    17
#define Y6_GPIO_NUM    4
#define Y5_GPIO_NUM    48
#define Y4_GPIO_NUM    46
#define Y3_GPIO_NUM    42
#define Y2_GPIO_NUM    3
#define VSYNC_GPIO_NUM 10
#define HREF_GPIO_NUM  14
#define PCLK_GPIO_NUM  40

#define POWER_GPIO_NUM 18

#endif

const char* ssid     = "ssid";
const char* password = "password";

// ファームウェアバージョン
#define FW_VERSION "AtomS3R Camera 1.0.1"

// デバイスID
char deviceID[50];

camera_fb_t* fb    = NULL;
uint8_t* out_jpg   = NULL;
size_t out_jpg_len = 0;

static camera_config_t camera_config = {
    .pin_pwdn     = PWDN_GPIO_NUM,
    .pin_reset    = RESET_GPIO_NUM,
    .pin_xclk     = XCLK_GPIO_NUM,
    .pin_sscb_sda = SIOD_GPIO_NUM,
    .pin_sscb_scl = SIOC_GPIO_NUM,
    .pin_d7       = Y9_GPIO_NUM,
    .pin_d6       = Y8_GPIO_NUM,
    .pin_d5       = Y7_GPIO_NUM,
    .pin_d4       = Y6_GPIO_NUM,
    .pin_d3       = Y5_GPIO_NUM,
    .pin_d2       = Y4_GPIO_NUM,
    .pin_d1       = Y3_GPIO_NUM,
    .pin_d0       = Y2_GPIO_NUM,

    .pin_vsync = VSYNC_GPIO_NUM,
    .pin_href  = HREF_GPIO_NUM,
    .pin_pclk  = PCLK_GPIO_NUM,

    .xclk_freq_hz = 20000000,
    .ledc_timer   = LEDC_TIMER_0,
    .ledc_channel = LEDC_CHANNEL_0,

    .pixel_format  = PIXFORMAT_RGB565,
    .frame_size    = FRAMESIZE_QVGA,
    .jpeg_quality  = 0,
    .fb_count      = 2,
    .fb_location   = CAMERA_FB_IN_PSRAM,
    .grab_mode     = CAMERA_GRAB_LATEST,
    .sccb_i2c_port = 0,
};

void setup() {
    Serial.begin(115200);
    pinMode(POWER_GPIO_NUM, OUTPUT);
    digitalWrite(POWER_GPIO_NUM, LOW);
    delay(500);
    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK) {
        Serial.println("Camera Init Fail");
        delay(1000);
        esp_restart();
    } else {
        Serial.println("Camera Init Success");
    }
    delay(100);

    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    WiFi.setSleep(false);
    Serial.println("");

    Serial.print("Connecting to ");
    Serial.println(ssid);

    // Wait for connection
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(WiFi.SSID());
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.print("MAC address: ");
  Serial.println(WiFi.macAddress());

  // deviceID
  String macStr = String(WiFi.macAddress());
  macStr.replace(":", "");
  sprintf(deviceID, "%s", macStr);
    
  start_webserver();
}

void loop() {
  
}

esp_err_t get_health_check_handler(httpd_req_t *req) {
  const char resp[] = "Alive";
  httpd_resp_send(req, resp, strlen(resp));
  return ESP_OK;
}

httpd_uri_t uri_get_health_check = {
  .uri      = "/api/healthcheck",
  .method   = HTTP_GET,
  .handler  = get_health_check_handler,
  .user_ctx = NULL
};

esp_err_t get_snapshot_handler(httpd_req_t *req) {
  camera_fb_t * fb = NULL;
  esp_err_t res = ESP_OK;
  size_t fb_len = 0;
  int64_t fr_start = esp_timer_get_time();

  fb = esp_camera_fb_get();

  if (fb) {
    frame2jpg(fb, 255, &out_jpg, &out_jpg_len);
    
    Serial.printf("pic size: %d\n", out_jpg_len);

    int32_t fb_len      = out_jpg_len;
    int32_t to_sends    = out_jpg_len;
    int32_t now_sends   = 0;
    uint8_t* out_buf    = out_jpg;

    res = httpd_resp_set_type(req, "image/jpeg");
    if (res == ESP_OK) {
      res = httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
    }
    if (res == ESP_OK) {
      res = httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
    }
    if (res == ESP_OK) {
      res = httpd_resp_set_hdr(req, "Connection", "close");
    }
    if (res == ESP_OK) {
      res = httpd_resp_send(req, (const char *)out_buf, fb_len);
    }
    int64_t fr_end = esp_timer_get_time();

    if (fb) {
        esp_camera_fb_return(fb);
        fb = NULL;
    }
    
    Serial.printf("time: %ums\n", (uint32_t)((fr_end - fr_start) / 1000));
    
  }

  return res;
}

httpd_uri_t uri_get_snapshot = {
  .uri      = "/api/snapshot",
  .method   = HTTP_GET,
  .handler  = get_snapshot_handler,
  .user_ctx = NULL
};

esp_err_t get_version_handler(httpd_req_t *req) {
  const char resp[] = FW_VERSION;
  httpd_resp_send(req, resp, strlen(resp));
  return ESP_OK;
}

httpd_uri_t uri_get_version = {
  .uri      = "/api/version",
  .method   = HTTP_GET,
  .handler  = get_version_handler,
  .user_ctx = NULL
};


httpd_handle_t start_webserver(void) {
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();

  httpd_handle_t server = NULL;

  if (httpd_start(&server, &config) == ESP_OK) {
    httpd_register_uri_handler(server, &uri_get_health_check);
    httpd_register_uri_handler(server, &uri_get_snapshot);
    httpd_register_uri_handler(server, &uri_get_version);
  }

  return server;
}

void stop_webserver(httpd_handle_t server) {
  if (server) {
    httpd_stop(server);
  }
}

こちらを持ってきたら、

const char* ssid     = "ssid";
const char* password = "password";

Wi-Fi の設定をして設定完了です。

こちらを書き込みます。

メモリ部分を使うので書き込むときは OPI PSRAM はオンにしておきます。

動かしてみる

USB に電源を入れます。

Wi-Fi につながった様子なら、同じローカルネットワークから IP を探して /api/snapshot に GET リクエストでアクセスすると画像が表示されます。

/api/version ではバージョンが返ってきます。

/api/healthcheck ではヘルスチェック用のシンプルな Alive 応答が返ってきます。