ESP32-CAM (aithinker) module with stable camera

After reading a bit, I found a good write about it on:

I edited the standard cameraserver.ino and ended up with something that makes it run more stable.

This is just the “main” code, you still need to have the standard libraries that come with the example installed.

Features: Static IP, wifi connection, wifi connection checking, LED indicator, automatic reboot, internal watchdog (in case the system hangs) and logging details on serial out.

If the system really hangs and the internal watchdog doesn’t catch it, then you need an external watchdog that checks a signal on one of the pins and acts accordingly to this signal, e.g. a repeating signal on an interrupt or in loop(). Let this signal reset an external timer, if the timer is not reset, it will trigger power, or a reboot.

It is a lot of work for something that probably doesn’t happen, so I’m not sure if I will ever implement a hardware watchdog for this little system.

Have fun, there’s no copyright, just grab the code if you want.

Edit: 2024-oct-17: updated code
Edit: 2024-oct-20: updated code again, was hanging on first connect.
Edit: 2024-oct-20: added flashing LED 3 times before connecting and, when all went fine, flasing 3 more times when connected
Edit: 2024-nov-07: updated code, added internal watchdog

#include "esp_camera.h"
#include <esp_task_wdt.h>
#include <WiFi.h>

// Watchdog timeout in seconds
#define WDT_TIMEOUT_SEC 20

#define CAMERA_MODEL_AI_THINKER // Has PSRAM
#include "camera_pins.h"

// Enter your WiFi credentials
const char *ssid = "SSID"; // edit this
const char *password = "xxxxxxxxxxxxxxxxxxxxxx"; // edit this

unsigned long currentMillis;
unsigned long wakeup;
unsigned long last_wakeup;

void startCameraServer();
void setupLedFlash(int pin);

// Set your Static IP address
IPAddress local_IP(192, 168, 100, 247); // edit this

// Set your Gateway IP address
IPAddress gateway(192, 168, 100, 254); // edit this
IPAddress subnet(255, 255, 255, 0); // edit this
IPAddress primaryDNS(8, 8, 8, 8); // edit this
IPAddress secondaryDNS(1, 1, 1, 1); // edit this

// Define red LED
const int ledPin = 33;

void blinkLED(int pin, int times, int interval) {
  for (int i = 0; i < times; i++) {
    digitalWrite(pin, LOW);
    delay(interval);
    digitalWrite(pin, HIGH);
    delay(interval);
  }
}

void initializeWiFi() {
  if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
    Serial.println("** Static IP configuration failed. Rebooting...");
    ESP.restart();
  }
  WiFi.begin(ssid, password);
  WiFi.setSleep(false);
}

// Watchdog timer configuration
esp_task_wdt_config_t twdt_config = {
        .timeout_ms = 20000,      // 20s
        .idle_core_mask = 0,      // Mask for idle cores (0 means no specific core)        
        .trigger_panic = true     // If the watchdog times out, trigger a panic (reset)
};

void setup() {

  // Initialize the watchdog timer configuration
  esp_task_wdt_deinit(); //wdt is enabled by default, so we need to 'deinit' it first
  esp_task_wdt_init(&twdt_config); //enable panic so ESP32 restarts

  // Setup red LED
  pinMode(ledPin, OUTPUT);

  Serial.begin(115200);
  Serial.println("\n** Serial started, speed 115200.");  

  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;
  config.frame_size = FRAMESIZE_UXGA;
  config.pixel_format = PIXFORMAT_JPEG;  // for streaming
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  config.jpeg_quality = 12;
  config.fb_count = 1;

  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.
  if (config.pixel_format == PIXFORMAT_JPEG) {
    if (psramFound()) {
      config.jpeg_quality = 10;
      config.fb_count = 2;
      config.grab_mode = CAMERA_GRAB_LATEST;
    } else {
      // Limit the frame size when PSRAM is not available
      config.frame_size = FRAMESIZE_SVGA;
      config.fb_location = CAMERA_FB_IN_DRAM;
    }
  } else {
    // Best option for face detection/recognition
    config.frame_size = FRAMESIZE_240X240;
  #if CONFIG_IDF_TARGET_ESP32S3
    config.fb_count = 2;
  #endif
  }

  #if defined(CAMERA_MODEL_ESP_EYE)
    pinMode(13, INPUT_PULLUP);
    pinMode(14, INPUT_PULLUP);
  #endif

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("** Camera init failed with error 0x%x", err);
    ESP.restart();
    return;
  }

  sensor_t *s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  if (s->id.PID == OV3660_PID) {
    s->set_brightness(s, 1);   // up the brightness just a bit
    s->set_saturation(s, -2);  // lower the saturation
    s->set_framesize(s, FRAMESIZE_VGA); // 640x480
    s->set_hmirror(s, 1); // 0 = disable , 1 = enable -> Mirror correctly
    s->set_vflip(s, 1); // 0 = disable , 1 = enable -> Flip it back
  }
  
  // drop down frame size for higher initial frame rate
  if (config.pixel_format == PIXFORMAT_JPEG) {
    s->set_framesize(s, FRAMESIZE_VGA);
  }

  #if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
    s->set_vflip(s, 1);
    s->set_hmirror(s, 1);
  #endif

  #if defined(CAMERA_MODEL_ESP32S3_EYE)
    s->set_vflip(s, 1);
  #endif

  // Setup LED FLash if LED pin is defined in camera_pins.h
  #if defined(LED_GPIO_NUM)
    setupLedFlash(LED_GPIO_NUM);
  #endif

  blinkLED(ledPin, 3, 1000);

  initializeWiFi();

  wakeup = millis();
  last_wakeup = wakeup;

  Serial.print("** Connecting to WiFi.");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    if (millis() - wakeup > 60000) {
      ESP.restart();
    }
  }

  Serial.println("\n** WiFi Connected");
  Serial.print("** Local IP ");
  Serial.println(WiFi.localIP());

  startCameraServer();
  Serial.println("** Camera server started.");

  // Watchdog, add the current task (i.e., the main loop) to Watchdog Timer
  esp_task_wdt_add(NULL);
  
  // Feed the dog in loop

}

void reconnectWiFi() {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("** Reconnecting WiFi...");
    WiFi.disconnect();
    initializeWiFi();
    unsigned long startAttemptTime = millis();
    while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 8000) {
      delay(500);
      Serial.print(".");
    }

    if (WiFi.status() != WL_CONNECTED) {
      Serial.println("** WiFi reconnect failed, rebooting.");
      ESP.restart();
    } else {
      Serial.println("** WiFi reconnected successfully.");
    }
  }
}

void loop() {

  // Feed the watchdog timer at the beginning of each loop iteration
  esp_task_wdt_reset();

  wakeup = millis();

  if (wakeup - last_wakeup >= 60000) { // Check every minute
    Serial.println("** Probing WiFi connection...");
    digitalWrite(ledPin, LOW); // LED on
    delay(200);

    reconnectWiFi();

    last_wakeup = wakeup;
    digitalWrite(ledPin, HIGH); // LED off

  }

  // Print wifi strength
  int32_t rssi = WiFi.RSSI();  // Get the current RSSI
  Serial.print("WiFi Signal Strength: ");
  Serial.print(rssi);
  if (rssi > -50) {
    Serial.println(" (Excellent)");
  } else if (rssi > -60) {
    Serial.println(" (Good)");
  } else if (rssi > -70) {
    Serial.println(" (Fair)");
  } else {
    Serial.println(" (Weak)");
  }
  delay(1000); // Small delay to prevent excessive looping
}
This entry was posted in arduino, News. Bookmark the permalink.