feat: camera module — OV3660 init and 96x96 grayscale capture

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 17:33:10 -07:00
parent 0a6470a096
commit 99756bdbaf
2 changed files with 99 additions and 0 deletions

88
firmware/src/camera.cpp Normal file
View File

@@ -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 <string.h>
#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;
}

11
firmware/src/camera.h Normal file
View File

@@ -0,0 +1,11 @@
// firmware/src/camera.h
#pragma once
#include <stdint.h>
#include <stddef.h>
// 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);