2026年9月6日日曜日

ESP32-P4でRaspberry Pi Cameraを使ってみた。

 昨年末に購入していたESP32-P4-Picoなんだけど、Arduino core for the ESP32のバージョンが3.3.11に上がってIDF v5.5.4 with ESP-Videoとなっていたので、Arduinoでもカメラモジュールが簡単に使えるようになってる!ということで試してみた。

ESP32-P4-PicoはRaspberry Pi Zero 2Wなどと同じ、0.5mmピッチの22Pinタイプのフラットケーブルが刺さるようになっている。カメラ側は1.0mmピッチの15Pinのものが多いので変換ケーブルが必要かも。

ESP-VideoではOV5647をサポートしているので、Raspberry Pi Camera v1.3がそのまま使えそう。昔購入したラズパイカメラの互換品と、Raspberry Pi Zero用に購入したカメラ変換ケーブルを使用してテストしてみることに。

フラットケーブルはカメラによって反対だったりするので注意かも。黒い可動部分と反対側に端子面が接触するように差し込む感じ。

とりあえずテストでOV5647で写真を撮影して、ESP32-P4のハードウェアエンコーダでJPEGに変換してmicroSDに保存してみた。

#include <Arduino.h>
#include <FS.h>
#include <SD_MMC.h>
#include <ESP_Video.h>
#include "driver/jpeg_encode.h"

#define CAMERA_SCCB_PORT      0
#define CAMERA_SCCB_SCL       8
#define CAMERA_SCCB_SDA       7
#define CAMERA_I2C_FREQ       400000
#define CAMERA_BUFFERS        2

#define SD_CLK                43
#define SD_CMD                44
#define SD_D0                 39
#define SD_D1                 40
#define SD_D2                 41
#define SD_D3                 42

#define JPEG_QUALITY          80
#define JPEG_OUTPUT_BUFFER_SIZE (4UL * 1024UL * 1024UL)
#define CAPTURE_INTERVAL_MS   5000

ESPVideoClass video;
ESPVideoCaptureDevClass capture_dev;
jpeg_encoder_handle_t jpeg_encoder = nullptr;
uint8_t *jpeg_buffer = nullptr;
size_t jpeg_buffer_size = 0;
uint32_t photo_number = 1;
unsigned long last_capture = 0;

bool initSD() {
    return SD_MMC.setPins(SD_CLK, SD_CMD, SD_D0, SD_D1, SD_D2, SD_D3) && SD_MMC.begin("/sdcard", false);
}

bool initCamera() {
    ESPVideoCamConfigClass cam_cfg;
    ESPVideoCSIConfigClass csi_cfg;
    return cam_cfg.begin(CAMERA_SCCB_PORT, CAMERA_SCCB_SCL, CAMERA_SCCB_SDA, CAMERA_I2C_FREQ) &&
           csi_cfg.begin(cam_cfg) &&
           video.begin(csi_cfg) &&
           capture_dev.begin(ESP_VIDEO_MIPI_CSI_DEVICE_NAME, CAMERA_BUFFERS) &&
           capture_dev.setFormat(ESP_VIDEO_FORMAT_RGB565) &&
           capture_dev.startCapture();
}

bool initJPEG() {
    jpeg_encode_engine_cfg_t eng_cfg = {.intr_priority = 0, .timeout_ms = 100};
    if (jpeg_new_encoder_engine(&eng_cfg, &jpeg_encoder) != ESP_OK) return false;

    jpeg_encode_memory_alloc_cfg_t mem_cfg = {.buffer_direction = JPEG_ENC_ALLOC_OUTPUT_BUFFER};
    jpeg_buffer = (uint8_t *)jpeg_alloc_encoder_mem(JPEG_OUTPUT_BUFFER_SIZE, &mem_cfg, &jpeg_buffer_size);
    if (!jpeg_buffer) {
        jpeg_del_encoder_engine(jpeg_encoder);
        jpeg_encoder = nullptr;
        return false;
    }
    return true;
}

String nextFilename() {
    char filename[32];
    while (true) {
        snprintf(filename, sizeof(filename), "/photo_%04lu.jpg", (unsigned long)photo_number);
        if (!SD_MMC.exists(filename)) {
            photo_number = (photo_number >= 999999) ? 1 : photo_number + 1;
            return String(filename);
        }
        photo_number = (photo_number >= 999999) ? 1 : photo_number + 1;
    }
}

bool capturePhoto() {
    if (!capture_dev.isOpened() || !capture_dev.isCaptureStarted()) return false;

    ESPVideoBufferClass buffer = capture_dev.captureBuffer();
    if (!buffer.valid() || buffer.formatType() != ESP_VIDEO_FORMAT_RGB565) return false;

    uint32_t width = buffer.getWidth();
    uint32_t height = buffer.getHeight();
    size_t input_size = buffer.size();
    uint8_t *input = buffer.data();
    if (!input || width == 0 || height == 0) return false;

    jpeg_encode_cfg_t enc_cfg = {};
    enc_cfg.src_type      = JPEG_ENCODE_IN_FORMAT_RGB565;
    enc_cfg.sub_sample    = JPEG_DOWN_SAMPLING_YUV420;
    enc_cfg.image_quality = JPEG_QUALITY;
    enc_cfg.width         = width;
    enc_cfg.height        = height;
    enc_cfg.pixel_reverse = false;

    uint32_t jpeg_size = 0;
    unsigned long start = millis();
    if (jpeg_encoder_process(jpeg_encoder, &enc_cfg, input, (uint32_t)input_size,
                             jpeg_buffer, (uint32_t)jpeg_buffer_size, &jpeg_size) != ESP_OK) {
        return false;
    }
    unsigned long elapsed = millis() - start;

    String filename = nextFilename();
    File file = SD_MMC.open(filename.c_str(), FILE_WRITE);
    if (!file) return false;
    size_t written = file.write(jpeg_buffer, jpeg_size);
    file.close();

    if (written == jpeg_size) {
        Serial.printf("Saved: %s (%lu bytes, %lu ms)\n", filename.c_str(), (unsigned long)jpeg_size, elapsed);
        return true;
    }
    return false;
}

void setup() {
    Serial.begin(115200);
    delay(1000);
    Serial.println("\n=== SYSTEM INIT ===");

    #ifdef BOARD_HAS_PSRAM
    Serial.printf("PSRAM: ENABLED (%u / %u bytes free)\n", 
                  (unsigned int)ESP.getFreePsram(), (unsigned int)ESP.getPsramSize());
    #endif

    if (!initSD() || !initCamera() || !initJPEG()) {
        Serial.println("SYSTEM ERROR: Init failed");
        return;
    }
    Serial.println("SYSTEM READY");
    last_capture = millis() - CAPTURE_INTERVAL_MS;
}

void loop() {
    if (!capture_dev.isOpened() || !capture_dev.isCaptureStarted()) {
        delay(1000);
        return;
    }

    if (millis() - last_capture >= CAPTURE_INTERVAL_MS) {
        last_capture = millis();
        capturePhoto();
        #ifdef BOARD_HAS_PSRAM
        Serial.printf("Free PSRAM: %u bytes\n", (unsigned int)ESP.getFreePsram());
        #endif
    }
    delay(10);
}

Arduino coreは3.3.11を使用。それ以下だとESP_Video.hが見つからない。ビルド設定はデフォルトからPSRAMはEnableにしただけ。もし新しいリビジョンのESP32-P4を使っている場合はChip Variantをv3.00 or Newerにしないとうまく動かないかも。

とりあえずシリアルコンソールを確認してみたらちゃんと動いてそう。適当なタイミングで電源を切ってmicroSDの中身を確認してみるとちゃんとjpgファイルがたくさん生成されていて写真が取れてる。

解像度は800x800で動いてるみたいだけどどうやらドライバーのデフォルト値?どうやって解像度を変更できるのかESP_Video.hの仕様を調べる必要がありそう。

0 件のコメント:

コメントを投稿