feat: camera module — OV3660 init and 96x96 grayscale capture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
88
firmware/src/camera.cpp
Normal file
88
firmware/src/camera.cpp
Normal 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
11
firmware/src/camera.h
Normal 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);
|
||||
Reference in New Issue
Block a user