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 応答が返ってきます。