前一回將 Prompt 出來的程式編譯後,寫進板子後,發生了一些問題⋯以下先列出今天預計處理的部分。
由於 AI 可能會有幻覺,所以檢查 code 這部分,也不能完全交給 AI。 肉眼看一下大致流程吧!
sdmmc_init_ocr: send_op_cond returned 0x107
sdmmc_card_init failed

GPIO38 - SD-CMD
GPIO39 - SD-CLK
GPIO40 - SD-DATA
mount_to_vfs failed (0xffffffff)
#include "esp_camera.h"
#include "FS.h"
#include "SD_MMC.h"
#include <WiFi.h>
#include <WebServer.h>
WebServer server(80);
// ======================================================
// GOOUUU ESP32-S3-CAM V1.5
// OV3660 + SR602 PIR + microSD
// ======================================================
// ---------- SR602 ----------
#define PIR_PIN 2
// 必須連續偵測 3 秒
const unsigned long MOTION_TRIGGER_MS = 3000;
// ---------- Wi-Fi ----------
const char* WIFI_SSID = "<修改成 Wifi 名稱>";
const char* WIFI_PASSWORD = "<修改成 Wifi 密碼>";
unsigned long lastWiFiCheck = 0;
const unsigned long WIFI_TIMEOUT = 15000;
const unsigned long WIFI_CHECK_INTERVAL = 10000;
// ---------- Camera ----------
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 15
#define SIOD_GPIO_NUM 4
#define SIOC_GPIO_NUM 5
#define Y9_GPIO_NUM 16
#define Y8_GPIO_NUM 17
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 12
#define Y5_GPIO_NUM 10
#define Y4_GPIO_NUM 8
#define Y3_GPIO_NUM 9
#define Y2_GPIO_NUM 11
#define VSYNC_GPIO_NUM 6
#define HREF_GPIO_NUM 7
#define PCLK_GPIO_NUM 13
// ---------- microSD ----------
#define SD_CMD 38
#define SD_CLK 39
#define SD_D0 40
unsigned long motionStart = 0;
bool timingMotion = false;
bool photoTaken = false;
uint32_t photoNumber = 0;
// ======================================================
// Camera Initialization
// ======================================================
bool initCamera() {
camera_config_t config = {};
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
// 直接讓 Camera 輸出 JPEG
config.pixel_format = PIXFORMAT_JPEG;
// 第一階段先 VGA,確認整套流程穩定
config.frame_size = FRAMESIZE_VGA;
config.jpeg_quality = 10;
if (psramFound()) {
Serial.println("PSRAM detected");
config.fb_location = CAMERA_FB_IN_PSRAM;
config.fb_count = 2;
config.grab_mode = CAMERA_GRAB_LATEST;
} else {
Serial.println("WARNING: PSRAM not detected");
config.fb_location = CAMERA_FB_IN_DRAM;
config.fb_count = 1;
}
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf(
"Camera init failed: 0x%x\n",
err
);
return false;
}
sensor_t *sensor = esp_camera_sensor_get();
if (sensor != nullptr) {
Serial.printf(
"Camera sensor PID: 0x%04X\n",
sensor->id.PID
);
}
Serial.println("Camera OK");
return true;
}
// ======================================================
// SD Initialization
// ======================================================
bool initSD() {
// GOOUUU V1.5 SD pins
SD_MMC.setPins(
SD_CLK,
SD_CMD,
SD_D0
);
// true = 1-bit SDMMC mode
if (!SD_MMC.begin(
"/sdcard",
true
)) {
Serial.println(
"SD initialization failed"
);
return false;
}
if (SD_MMC.cardType() == CARD_NONE) {
Serial.println(
"No SD card detected"
);
return false;
}
uint64_t sizeMB =
SD_MMC.cardSize() / (1024 * 1024);
Serial.printf(
"SD Card: %llu MB\n",
sizeMB
);
if (!SD_MMC.exists("/photos")) {
SD_MMC.mkdir("/photos");
}
Serial.println("SD OK");
return true;
}
// ======================================================
// Wi-Fi Initialization
// ======================================================
bool initWiFi() {
Serial.println();
Serial.println("[3] Wi-Fi");
// ESP32 作為 Wi-Fi Client
WiFi.mode(WIFI_STA);
// 避免使用之前儲存的連線狀態
WiFi.disconnect();
delay(100);
Serial.printf(
"Connecting to: %s\n",
WIFI_SSID
);
WiFi.begin(
WIFI_SSID,
WIFI_PASSWORD
);
unsigned long startTime = millis();
// 最多等待 15 秒
while (
WiFi.status() != WL_CONNECTED &&
millis() - startTime < 15000
) {
Serial.print(".");
delay(500);
}
Serial.println();
if (WiFi.status() != WL_CONNECTED) {
Serial.println(
"Wi-Fi connection FAILED"
);
return false;
}
Serial.println(
"Wi-Fi connected!"
);
Serial.print(
"IP address : "
);
Serial.println(
WiFi.localIP()
);
Serial.print(
"Gateway : "
);
Serial.println(
WiFi.gatewayIP()
);
Serial.print(
"Subnet Mask: "
);
Serial.println(
WiFi.subnetMask()
);
Serial.print(
"RSSI : "
);
Serial.print(
WiFi.RSSI()
);
Serial.println(
" dBm"
);
return true;
}
// ======================================================
// Wi-Fi Connection Check
// ======================================================
void checkWiFi() {
if (millis() - lastWiFiCheck < WIFI_CHECK_INTERVAL) {
return;
}
lastWiFiCheck = millis();
if (WiFi.status() == WL_CONNECTED) {
return;
}
Serial.println(
"Wi-Fi disconnected. Reconnecting..."
);
WiFi.disconnect();
WiFi.begin(
WIFI_SSID,
WIFI_PASSWORD
);
}
// ======================================================
// Find next photo number
// 避免 ESP32 重開機後從 000001 開始覆寫
// ======================================================
void findNextPhotoNumber() {
File dir = SD_MMC.open("/photos");
if (!dir) {
return;
}
uint32_t maxNumber = 0;
while (true) {
File file = dir.openNextFile();
if (!file) {
break;
}
String name = file.name();
int start =
name.lastIndexOf("photo_");
if (start >= 0) {
String number =
name.substring(
start + 6,
start + 12
);
uint32_t n =
number.toInt();
if (n > maxNumber) {
maxNumber = n;
}
}
file.close();
}
dir.close();
photoNumber = maxNumber;
Serial.printf(
"Last photo: %06lu\n",
(unsigned long)photoNumber
);
}
// ======================================================
// Capture
// ======================================================
bool takePhoto() {
Serial.println();
Serial.println("------------------------");
Serial.println("Taking photo...");
camera_fb_t *fb =
esp_camera_fb_get();
if (!fb) {
Serial.println(
"Capture failed"
);
return false;
}
photoNumber++;
char path[40];
snprintf(
path,
sizeof(path),
"/photos/photo_%06lu.jpg",
(unsigned long)photoNumber
);
Serial.printf(
"Resolution: %d x %d\n",
fb->width,
fb->height
);
Serial.printf(
"JPEG: %u bytes\n",
fb->len
);
File file =
SD_MMC.open(
path,
FILE_WRITE
);
if (!file) {
Serial.println(
"Cannot create photo"
);
esp_camera_fb_return(fb);
return false;
}
size_t written =
file.write(
fb->buf,
fb->len
);
file.close();
esp_camera_fb_return(fb);
if (written == 0) {
Serial.println(
"SD write failed"
);
return false;
}
Serial.printf(
"Saved: %s\n",
path
);
Serial.printf(
"Written: %u bytes\n",
written
);
Serial.println("------------------------");
return true;
}
// ======================================================
// Setup
// ======================================================
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println();
Serial.println("==============================");
Serial.println(" Smart Fridge Camera");
Serial.println(" GOOUUU ESP32-S3-CAM V1.5");
Serial.println(" OV3660 + SR602");
Serial.println("==============================");
pinMode(
PIR_PIN,
INPUT
);
Serial.println();
Serial.println("[1] Camera");
if (!initCamera()) {
Serial.println(
"FATAL: Camera failed"
);
while (true) {
delay(1000);
}
}
Serial.println();
Serial.println("[2] SD Card");
if (!initSD()) {
Serial.println(
"FATAL: SD failed"
);
while (true) {
delay(1000);
}
}
findNextPhotoNumber();
Serial.println();
Serial.println("[3] Wi-Fi");
bool wifiOK = initWiFi();
if (!wifiOK) {
Serial.println(
"WARNING: Wi-Fi unavailable"
);
Serial.println(
"Continue in offline mode..."
);
}
Serial.println();
Serial.println("==============================");
Serial.println("SYSTEM READY");
if (WiFi.status() == WL_CONNECTED) {
Serial.print("Wi-Fi IP: ");
Serial.println(
WiFi.localIP()
);
initWebServer();
} else {
Serial.println(
"Wi-Fi: OFFLINE"
);
}
Serial.println(
"Waiting for motion..."
);
Serial.println(
"=============================="
);
Serial.println("Waiting for motion...");
Serial.println("==============================");
}
// ======================================================
// handlePhoto
// ======================================================
void handlePhoto() {
// 使用方式:
// http://172.16.71.51/photo?name=photo_000001.jpg
if (!server.hasArg("name")) {
server.send(
400,
"text/plain",
"Missing parameter: name"
);
return;
}
String filename = server.arg("name");
// 基本防止 path traversal
if (
filename.indexOf("..") >= 0 ||
filename.indexOf("/") >= 0
) {
server.send(
400,
"text/plain",
"Invalid filename"
);
return;
}
String path =
"/photos/" + filename;
Serial.printf(
"HTTP request: %s\n",
path.c_str()
);
if (!SD_MMC.exists(path)) {
server.send(
404,
"text/plain",
"Photo not found"
);
return;
}
File file =
SD_MMC.open(
path,
FILE_READ
);
if (!file) {
server.send(
500,
"text/plain",
"Cannot open photo"
);
return;
}
server.streamFile(
file,
"image/jpeg"
);
file.close();
}
// ======================================================
// initWebServer
// ======================================================
void initWebServer() {
server.on(
"/",
HTTP_GET,
[]() {
server.send(
200,
"text/plain",
"Smart Fridge Camera Online"
);
}
);
server.on(
"/photo",
HTTP_GET,
handlePhoto
);
server.begin();
Serial.println(
"HTTP server started"
);
Serial.print(
"URL: http://"
);
Serial.println(
WiFi.localIP()
);
}
// ======================================================
// Loop
// ======================================================
void loop() {
checkWiFi();
server.handleClient();
bool motion =
digitalRead(PIR_PIN);
// ==============================================
// SR602 HIGH
// ==============================================
if (motion == HIGH) {
// 第一次 HIGH
if (!timingMotion) {
timingMotion = true;
motionStart = millis();
Serial.println(
"Motion detected..."
);
}
// HIGH 必須連續 3 秒
if (
!photoTaken &&
millis() - motionStart >= MOTION_TRIGGER_MS
) {
Serial.println(
"Motion sustained for 3 seconds"
);
if (takePhoto()) {
photoTaken = true;
} else {
Serial.println(
"Photo failed"
);
}
}
}
// ==============================================
// SR602 LOW
// ==============================================
else {
// HIGH 不足三秒
if (
timingMotion &&
!photoTaken
) {
Serial.println(
"Motion ended before 3 seconds"
);
}
// 已經拍照,等人離開才重新 Arm
if (
timingMotion &&
photoTaken
) {
Serial.println(
"Motion ended - system rearmed"
);
}
timingMotion = false;
photoTaken = false;
motionStart = 0;
}
delay(20);
}