From 2b553ce6ff6035c9873fa36ad1e568d01ca60a7d Mon Sep 17 00:00:00 2001 From: laride <198868291+laride@users.noreply.github.com> Date: Tue, 27 May 2025 11:48:31 +0800 Subject: [PATCH] feat: add camera functions (ESP-SparkBot) (#687) Co-authored-by: lvhaiyu --- main/boards/common/esp32_camera.cc | 45 +++++++++++++++--- main/boards/esp-sparkbot/config.h | 28 +++++++++++ .../boards/esp-sparkbot/esp_sparkbot_board.cc | 46 +++++++++++++++++++ 3 files changed, 112 insertions(+), 7 deletions(-) diff --git a/main/boards/common/esp32_camera.cc b/main/boards/common/esp32_camera.cc index d4889901..277c16bc 100644 --- a/main/boards/common/esp32_camera.cc +++ b/main/boards/common/esp32_camera.cc @@ -30,13 +30,34 @@ Esp32Camera::Esp32Camera(const camera_config_t& config) { preview_image_.header.cf = LV_COLOR_FORMAT_RGB565; preview_image_.header.flags = LV_IMAGE_FLAGS_ALLOCATED | LV_IMAGE_FLAGS_MODIFIABLE; - if (config.frame_size == FRAMESIZE_VGA) { - preview_image_.header.w = 640; - preview_image_.header.h = 480; - } else if (config.frame_size == FRAMESIZE_QVGA) { - preview_image_.header.w = 320; - preview_image_.header.h = 240; + switch (config.frame_size) { + case FRAMESIZE_SVGA: + preview_image_.header.w = 800; + preview_image_.header.h = 600; + break; + case FRAMESIZE_VGA: + preview_image_.header.w = 640; + preview_image_.header.h = 480; + break; + case FRAMESIZE_QVGA: + preview_image_.header.w = 320; + preview_image_.header.h = 240; + break; + case FRAMESIZE_128X128: + preview_image_.header.w = 128; + preview_image_.header.h = 128; + break; + case FRAMESIZE_240X240: + preview_image_.header.w = 240; + preview_image_.header.h = 240; + break; + default: + ESP_LOGE(TAG, "Unsupported frame size: %d, image preview will not be shown", config.frame_size); + preview_image_.data_size = 0; + preview_image_.data = nullptr; + return; } + preview_image_.header.stride = preview_image_.header.w * 2; preview_image_.data_size = preview_image_.header.w * preview_image_.header.h * 2; preview_image_.data = (uint8_t*)heap_caps_malloc(preview_image_.data_size, MALLOC_CAP_SPIRAM); @@ -81,6 +102,16 @@ bool Esp32Camera::Capture() { } } + // 如果预览图片 buffer 为空,则跳过预览 + // 但仍返回 true,因为此时图像可以上传至服务器 + if (preview_image_.data_size == 0) { + ESP_LOGW(TAG, "Skip preview because of unsupported frame size"); + return true; + } + if (preview_image_.data == nullptr) { + ESP_LOGE(TAG, "Preview image data is not initialized"); + return true; + } // 显示预览图片 auto display = Board::GetInstance().GetDisplay(); if (display != nullptr) { @@ -169,7 +200,7 @@ std::string Esp32Camera::Explain(const std::string& question) { frame2jpg_cb(fb_, 80, [](void* arg, size_t index, const void* data, size_t len) -> unsigned int { auto jpeg_queue = (QueueHandle_t)arg; JpegChunk chunk = { - .data = (uint8_t*)heap_caps_malloc(len, MALLOC_CAP_SPIRAM), + .data = (uint8_t*)heap_caps_aligned_alloc(16, len, MALLOC_CAP_SPIRAM), .len = len }; memcpy(chunk.data, data, len); diff --git a/main/boards/esp-sparkbot/config.h b/main/boards/esp-sparkbot/config.h index b26cf162..3c6e6f2e 100644 --- a/main/boards/esp-sparkbot/config.h +++ b/main/boards/esp-sparkbot/config.h @@ -68,4 +68,32 @@ typedef enum { LIGHT_MODE_MAX } light_mode_t; +/* Camera PINs*/ +#define SPARKBOT_CAMERA_XCLK (GPIO_NUM_15) +#define SPARKBOT_CAMERA_PCLK (GPIO_NUM_13) +#define SPARKBOT_CAMERA_VSYNC (GPIO_NUM_6) +#define SPARKBOT_CAMERA_HSYNC (GPIO_NUM_7) +#define SPARKBOT_CAMERA_D0 (GPIO_NUM_11) +#define SPARKBOT_CAMERA_D1 (GPIO_NUM_9) +#define SPARKBOT_CAMERA_D2 (GPIO_NUM_8) +#define SPARKBOT_CAMERA_D3 (GPIO_NUM_10) +#define SPARKBOT_CAMERA_D4 (GPIO_NUM_12) +#define SPARKBOT_CAMERA_D5 (GPIO_NUM_18) +#define SPARKBOT_CAMERA_D6 (GPIO_NUM_17) +#define SPARKBOT_CAMERA_D7 (GPIO_NUM_16) + +#define SPARKBOT_CAMERA_PWDN (GPIO_NUM_NC) +#define SPARKBOT_CAMERA_RESET (GPIO_NUM_NC) +#define SPARKBOT_CAMERA_XCLK (GPIO_NUM_15) +#define SPARKBOT_CAMERA_PCLK (GPIO_NUM_13) +#define SPARKBOT_CAMERA_VSYNC (GPIO_NUM_6) +#define SPARKBOT_CAMERA_HSYNC (GPIO_NUM_7) + +#define SPARKBOT_CAMERA_XCLK_FREQ (16000000) +#define SPARKBOT_LEDC_TIMER (LEDC_TIMER_0) +#define SPARKBOT_LEDC_CHANNEL (LEDC_CHANNEL_0) + +#define SPARKBOT_CAMERA_SIOD (GPIO_NUM_NC) +#define SPARKBOT_CAMERA_SIOC (GPIO_NUM_NC) + #endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp-sparkbot/esp_sparkbot_board.cc b/main/boards/esp-sparkbot/esp_sparkbot_board.cc index ad780ed8..90614045 100644 --- a/main/boards/esp-sparkbot/esp_sparkbot_board.cc +++ b/main/boards/esp-sparkbot/esp_sparkbot_board.cc @@ -13,6 +13,8 @@ #include #include +#include "esp32_camera.h" + #define TAG "esp_sparkbot" LV_FONT_DECLARE(font_puhui_20_4); @@ -45,6 +47,7 @@ private: i2c_master_bus_handle_t i2c_bus_; Button boot_button_; Display* display_; + Esp32Camera* camera_; void InitializeI2c() { // Initialize I2C peripheral @@ -122,6 +125,44 @@ private: }); } + void InitializeCamera() { + camera_config_t camera_config = {}; + + camera_config.pin_pwdn = SPARKBOT_CAMERA_PWDN; + camera_config.pin_reset = SPARKBOT_CAMERA_RESET; + camera_config.pin_xclk = SPARKBOT_CAMERA_XCLK; + camera_config.pin_pclk = SPARKBOT_CAMERA_PCLK; + camera_config.pin_sscb_sda = SPARKBOT_CAMERA_SIOD; + camera_config.pin_sscb_scl = SPARKBOT_CAMERA_SIOC; + + camera_config.pin_d0 = SPARKBOT_CAMERA_D0; + camera_config.pin_d1 = SPARKBOT_CAMERA_D1; + camera_config.pin_d2 = SPARKBOT_CAMERA_D2; + camera_config.pin_d3 = SPARKBOT_CAMERA_D3; + camera_config.pin_d4 = SPARKBOT_CAMERA_D4; + camera_config.pin_d5 = SPARKBOT_CAMERA_D5; + camera_config.pin_d6 = SPARKBOT_CAMERA_D6; + camera_config.pin_d7 = SPARKBOT_CAMERA_D7; + + camera_config.pin_vsync = SPARKBOT_CAMERA_VSYNC; + camera_config.pin_href = SPARKBOT_CAMERA_HSYNC; + camera_config.pin_pclk = SPARKBOT_CAMERA_PCLK; + camera_config.xclk_freq_hz = SPARKBOT_CAMERA_XCLK_FREQ; + camera_config.ledc_timer = SPARKBOT_LEDC_TIMER; + camera_config.ledc_channel = SPARKBOT_LEDC_CHANNEL; + camera_config.fb_location = CAMERA_FB_IN_PSRAM; + + camera_config.sccb_i2c_port = I2C_NUM_0; + + camera_config.pixel_format = PIXFORMAT_RGB565; + camera_config.frame_size = FRAMESIZE_240X240; + camera_config.jpeg_quality = 12; + camera_config.fb_count = 1; + camera_config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + camera_ = new Esp32Camera(camera_config); + } + // 物联网初始化,添加对 AI 可见设备 void InitializeIot() { auto& thing_manager = iot::ThingManager::GetInstance(); @@ -137,6 +178,7 @@ public: InitializeDisplay(); InitializeButtons(); InitializeIot(); + InitializeCamera(); GetBacklight()->RestoreBrightness(); } @@ -155,6 +197,10 @@ public: static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); return &backlight; } + + virtual Camera* GetCamera() override { + return camera_; + } }; DECLARE_BOARD(EspSparkBot);