From 99756bdbafbf7069e0e2268c8e6cb170373a28b4 Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Mon, 13 Apr 2026 17:33:10 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20camera=20module=20=E2=80=94=20OV3660=20?= =?UTF-8?q?init=20and=2096x96=20grayscale=20capture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- firmware/src/camera.cpp | 88 +++++++++++++++++++++++++++++++++++++++++ firmware/src/camera.h | 11 ++++++ 2 files changed, 99 insertions(+) create mode 100644 firmware/src/camera.cpp create mode 100644 firmware/src/camera.h diff --git a/firmware/src/camera.cpp b/firmware/src/camera.cpp new file mode 100644 index 0000000..2e73e23 --- /dev/null +++ b/firmware/src/camera.cpp @@ -0,0 +1,88 @@ +// firmware/src/camera.cpp +// OV3660 pin assignments for M5Stack TimerCamera-F +// Ref: https://docs.m5stack.com/en/unit/timercam_f +#include "camera.h" +#include "cv.h" +#include "esp_camera.h" +#include + +#define CAM_PIN_PWDN -1 +#define CAM_PIN_RESET 15 +#define CAM_PIN_XCLK 27 +#define CAM_PIN_SIOD 25 +#define CAM_PIN_SIOC 23 +#define CAM_PIN_D7 19 +#define CAM_PIN_D6 36 +#define CAM_PIN_D5 18 +#define CAM_PIN_D4 39 +#define CAM_PIN_D3 5 +#define CAM_PIN_D2 34 +#define CAM_PIN_D1 35 +#define CAM_PIN_D0 32 +#define CAM_PIN_VSYNC 22 +#define CAM_PIN_HREF 26 +#define CAM_PIN_PCLK 21 + +bool camera_init() { + camera_config_t cfg = {}; + cfg.ledc_channel = LEDC_CHANNEL_0; + cfg.ledc_timer = LEDC_TIMER_0; + cfg.pin_d0 = CAM_PIN_D0; + cfg.pin_d1 = CAM_PIN_D1; + cfg.pin_d2 = CAM_PIN_D2; + cfg.pin_d3 = CAM_PIN_D3; + cfg.pin_d4 = CAM_PIN_D4; + cfg.pin_d5 = CAM_PIN_D5; + cfg.pin_d6 = CAM_PIN_D6; + cfg.pin_d7 = CAM_PIN_D7; + cfg.pin_xclk = CAM_PIN_XCLK; + cfg.pin_pclk = CAM_PIN_PCLK; + cfg.pin_vsync = CAM_PIN_VSYNC; + cfg.pin_href = CAM_PIN_HREF; + cfg.pin_sscb_sda = CAM_PIN_SIOD; + cfg.pin_sscb_scl = CAM_PIN_SIOC; + cfg.pin_pwdn = CAM_PIN_PWDN; + cfg.pin_reset = CAM_PIN_RESET; + cfg.xclk_freq_hz = 20000000; + cfg.pixel_format = PIXFORMAT_GRAYSCALE; + cfg.frame_size = FRAMESIZE_QVGA; // 320x240 + cfg.fb_count = 1; + cfg.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + + esp_err_t err = esp_camera_init(&cfg); + if (err != ESP_OK) return false; + + // Flip vertically — adjust if mounting orientation differs + sensor_t* s = esp_camera_sensor_get(); + s->set_vflip(s, 1); + s->set_hmirror(s, 0); + + return true; +} + +// Box-filter downscale from QVGA (320x240) to 96x96 grayscale +static void downscale(const uint8_t* src, int src_w, int src_h, uint8_t* dst) { + int bx = src_w / CV_W; + int by = src_h / CV_H; + for (int dy = 0; dy < CV_H; dy++) { + for (int dx = 0; dx < CV_W; dx++) { + int sum = 0, cnt = 0; + for (int ky = 0; ky < by; ky++) + for (int kx = 0; kx < bx; kx++) { + int sx = dx * bx + kx; + int sy = dy * by + ky; + sum += src[sy * src_w + sx]; + cnt++; + } + dst[dy * CV_W + dx] = (uint8_t)(sum / cnt); + } + } +} + +bool camera_capture_96(uint8_t* buf) { + camera_fb_t* fb = esp_camera_fb_get(); + if (!fb) return false; + downscale(fb->buf, fb->width, fb->height, buf); + esp_camera_fb_return(fb); + return true; +} diff --git a/firmware/src/camera.h b/firmware/src/camera.h new file mode 100644 index 0000000..950fdbe --- /dev/null +++ b/firmware/src/camera.h @@ -0,0 +1,11 @@ +// firmware/src/camera.h +#pragma once +#include +#include + +// Initialise OV3660 camera for TimerCamera-F. Returns false on failure. +bool camera_init(); + +// Capture one frame, downscale to 96x96 grayscale, write into buf. +// buf must be CV_PIXELS (9216) bytes. Returns false on capture failure. +bool camera_capture_96(uint8_t* buf);