Captura y guarda fotos con ESP32-CAM en tarjeta microSD

Si estás buscando una manera emocionante de adentrarte en el mundo de la fotografía y la tecnología IoT, el ESP32-CAM es una herramienta versátil que te permitirá capturar imágenes y almacenarlas sin complicaciones. Este tutorial te guiará a través del proceso de captura y almacenamiento de fotos en una tarjeta microSD utilizando este dispositivo. ¡Prepárate para explorar el potencial de la fotografía con IoT!

Índice de contenido
  1. Descripción del proyecto
  2. Materiales necesarios
  3. Preparación de la tarjeta microSD
    1. Método 1: Formateo manual
    2. Método 2: Utilizar herramienta oficial de formateo
  4. Conexión del ESP32-CAM a la computadora
    1. Opción 1: ESP32-CAM básico + Adaptador FTDI
    2. Opción 2: ESP32-CAM básico + Adaptador ESP32-CAM-MB
    3. Opción 3: Módulo ESP32-CAM-CH340
  5. Configuración del Arduino IDE
    1. Instalación de la placa ESP32
    2. Selección de la placa y el puerto
  6. Carga del código
  7. Demostración
  8. Explicación del código

Descripción del proyecto

En este proyecto, crearemos un sistema de cámara compacto y de bajo consumo utilizando el ESP32-CAM. Este dispositivo es ideal para aplicaciones como trampas de cámaras en la naturaleza, proyectos de time-lapse, o simplemente para experimentar con fotografía IoT. El ESP32-CAM permanecerá en modo de deep sleep la mayor parte del tiempo para ahorrar energía. Al presionar el botón RESET, el dispositivo despertará, tomará una foto, la guardará en la tarjeta microSD insertada y volverá a entrar en estado de sueño.

Además, el ESP32-CAM almacenará el número de la foto en su memoria flash interna. Esto significa que incluso después de reiniciar el dispositivo, no olvidará cuál es la siguiente foto a tomar. Cada vez que se reinicie y tome una nueva imagen, el número aumentará en uno, facilitando así la organización y localización de tus fotografías más tarde.

Materiales necesarios

Para llevar a cabo este proyecto, necesitarás reunir los siguientes elementos:

  • Módulo ESP32-CAM: Será el cerebro de tu proyecto.
  • Cable micro USB: Para alimentar y programar el ESP32-CAM.
  • Adaptador FTDI o adaptador ESP32-CAM-MB (opcional): Si tu ESP32-CAM no cuenta con un convertidor USB a serie integrado, necesitarás esto para cargar el código.
  • Computadora con Arduino IDE: Utilizarás este software para escribir y cargar el código en tu ESP32.

Es importante elegir el tipo adecuado de placa ESP32-CAM para este proyecto. Te recomendamos optar por modelos que se programen fácilmente desde tu computadora y que sean fáciles de alimentar después. Las opciones recomendadas son el ESP32-CAM con la placa hija ESP32-CAM-MB o el nuevo ESP32-CAM-CH340, que cuentan con un puerto USB para facilitar la programación y la alimentación. Es mejor evitar el módulo ESP32-CAM básico, ya que requiere un convertidor USB-a-serie que puede que no tengas disponible.

Preparación de la tarjeta microSD

Antes de utilizar tu tarjeta microSD en el proyecto, es crucial asegurarte de que esté correctamente formateada con el sistema de archivos adecuado, ya sea FAT16 o FAT32. Esto facilita que el ESP32 escriba archivos sin problemas.

Si estás utilizando una tarjeta SD nueva, es probable que ya venga formateada con un sistema de archivos FAT. Sin embargo, este formateo de fábrica puede no ser perfecto y podrías enfrentar problemas. Si usas una tarjeta más antigua, definitivamente necesitarás reformatearla. En cualquier caso, es recomendable formatear la tarjeta antes de usarla en tu proyecto.

Existen dos métodos para formatear tu tarjeta microSD:

Método 1: Formateo manual

Insertar tu tarjeta microSD en tu computadora. Localiza la unidad de la tarjeta en tu sistema, haz clic derecho sobre ella y selecciona "Formatear" en el menú. Aparecerá una ventana; selecciona FAT32 como sistema de archivos y haz clic en Iniciar para comenzar el formateo. Sigue las instrucciones en pantalla hasta que el proceso termine.

Método 2: Utilizar herramienta oficial de formateo

Para mejores resultados y menos errores, se recomienda utilizar la herramienta de formateo oficial de la SD Association. Esta herramienta es más confiable que el formateador básico que viene con tu computadora. Puedes descargarla desde el sitio web de la Asociación SD. Una vez instalada, ejecuta el programa, selecciona tu tarjeta SD de la lista de unidades y haz clic en Formatear. Esta herramienta ayuda a evitar problemas comunes causados por un mal o incompleto formateo, lo que puede ahorrarte mucho tiempo de resolución de problemas más adelante.

Conexión del ESP32-CAM a la computadora

El módulo ESP32-CAM, aunque poderoso, carece de una interfaz USB integrada para programar y comunicarse con tu computadora. Esto significa que necesitarás un poco de ayuda para configurarlo. Hay tres opciones principales que puedes elegir:

Opción 1: ESP32-CAM básico + Adaptador FTDI

Si tienes un módulo ESP32-CAM básico, puedes utilizar un adaptador USB a serie (un adaptador FTDI) para conectarlo a tu computadora. Asegúrate de que el jumper del adaptador esté configurado en 5V.

Ten en cuenta que el pin GPIO 0 debe estar conectado a tierra. Esta conexión solo es necesaria mientras programas el ESP32-CAM. Una vez que hayas terminado de programar el módulo, debes desconectar esta conexión. ¡Recuerda hacer esto cada vez que quieras cargar un nuevo sketch!

Opción 2: ESP32-CAM básico + Adaptador ESP32-CAM-MB

Usar el adaptador FTDI para programar el ESP32-CAM puede ser un poco complicado. Por eso, muchos proveedores ahora venden la placa ESP32-CAM junto con una pequeña placa hija llamada ESP32-CAM-MB. Solo debes apilar el ESP32-CAM en la placa hija, conectar un cable micro USB y hacer clic en el botón de Cargar para programar tu placa. ¡Es así de fácil!

Opción 3: Módulo ESP32-CAM-CH340

Si tienes un módulo ESP32-CAM-CH340, ¡estás de suerte! Esta variante cuenta con un chip USB-a-serie CH340 integrado, eliminando la necesidad de hardware adicional. Simplemente conéctalo a tu computadora mediante un cable micro USB y estarás listo para comenzar.

Recuerda que aún necesitarás conectar el pin GPIO0 a GND durante la programación, al igual que con el adaptador FTDI.

Configuración del Arduino IDE

No importa qué opción elijas, la configuración en el Arduino IDE es la misma.

Instalación de la placa ESP32

Para usar el ESP32-CAM, o cualquier ESP32, con el Arduino IDE, primero debes instalar la placa ESP32 (también conocida como el núcleo de Arduino ESP32) a través del Administrador de placas de Arduino.

Si aún no lo has hecho, sigue este tutorial para instalar la placa ESP32.

Selección de la placa y el puerto

Después de instalar el núcleo de Arduino ESP32, reinicia tu Arduino IDE y haz clic en “Seleccionar otra placa y puerto...” en el menú desplegable superior. Aparecerá una nueva ventana. Busca el tipo específico de placa ESP32 que estás usando (en nuestro caso, es el AI Thinker ESP32-CAM).

A continuación, selecciona el puerto que corresponde a tu placa ESP32-CAM. Normalmente, se etiqueta como “/dev/ttyUSB0” (en Linux o macOS) o “COM6” (en Windows).

¡Y eso es todo! El Arduino IDE ahora está configurado para el ESP32-CAM.

Carga del código

A continuación, se presenta el código que necesitarás cargar en tu ESP32-CAM.

#include "esp_camera.h"
#include "Arduino.h"
#include "FS.h"                // SD Card ESP32
#include "SD_MMC.h"            // SD Card ESP32
#include "soc/soc.h"           // Deshabilitar problemas de apagado
#include "soc/rtc_cntl_reg.h"  // Deshabilitar problemas de apagado
#include "driver/rtc_io.h"
#include             // Leer y escribir desde la memoria flash

// Definir el número de bytes que deseas acceder
#define EEPROM_SIZE 1

// Definición de pines para CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

// Pin de LED Flash
#define FLASH_LED_PIN      4

int pictureNumber = 0;

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);  // Deshabilitar detector de apagado

  Serial.begin(115200);
  Serial.println("Inicio de captura de fotos con ESP32-CAM...");

  // Inicializar EEPROM primero para obtener el número de fotos
  EEPROM.begin(EEPROM_SIZE);
  pictureNumber = EEPROM.read(0) + 1;
  
  // Manejar el desbordamiento (restablecer a 1 si excede 255)
  if (pictureNumber > 255) {
    pictureNumber = 1;
  }
  
  Serial.printf("Tomando foto número: %dn", pictureNumber);

  // Inicializar la tarjeta SD ANTES de la cámara para reducir la presión de memoria
  Serial.println("Iniciando tarjeta SD");
  if (!SD_MMC.begin()) {
    Serial.println("Error al montar la tarjeta SD");
    goToSleep(); 
    return;
  }

  uint8_t cardType = SD_MMC.cardType();
  if (cardType == CARD_NONE) {
    Serial.println("No hay tarjeta SD adjunta");
    goToSleep(); 
    return;
  }

  Serial.printf("Tipo de tarjeta SD: ");
  if (cardType == CARD_MMC) {
    Serial.println("MMC");
  } else if (cardType == CARD_SD) {
    Serial.println("SDSC");
  } else if (cardType == CARD_SDHC) {
    Serial.println("SDHC");
  } else {
    Serial.println("DESCONOCIDO");
  }

  // Configuración de la cámara - REDUCIR configuraciones para evitar desbordamiento
  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.pixel_format = PIXFORMAT_JPEG;

  // CRÍTICO: Reducir tamaño de cuadro y calidad para evitar desbordamiento
  if (psramFound()) {
    Serial.println("PSRAM encontrado");
    config.frame_size = FRAMESIZE_XGA;  // Reducido de UXGA a XGA
    config.jpeg_quality = 12;           // Aumentado de 10 a 12 (menor calidad, archivo más pequeño)
    config.fb_count = 1;                // Reducido de 2 a 1 para ahorrar memoria
  } else {
    Serial.println("PSRAM no encontrado");
    config.frame_size = FRAMESIZE_VGA;  // Reducido de SVGA a VGA
    config.jpeg_quality = 15;           // Menor calidad para placas sin PSRAM
    config.fb_count = 1;
  }
  
  // Configuraciones adicionales para ahorrar memoria
  config.grab_mode = CAMERA_GRAB_LATEST; // Cambiado de WHEN_EMPTY a LATEST

  // Inicializar la cámara
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Error al inicializar la cámara: 0x%xn", err);
    goToSleep();
    return;
  }
  Serial.println("Cámara inicializada correctamente");

  // Configuraciones adicionales del sensor para optimizar el uso de memoria
  sensor_t * s = esp_camera_sensor_get();
  if (s != NULL) {
    // Optimizar otras configuraciones
    s->set_brightness(s, 0);     // -2 a 2
    s->set_contrast(s, 0);       // -2 a 2
    s->set_saturation(s, 0);     // -2 a 2
    s->set_special_effect(s, 0); // 0 a 6 (0 - Sin efecto)
    s->set_whitebal(s, 1);       // 0 = deshabilitar, 1 = habilitar
    s->set_awb_gain(s, 1);       // 0 = deshabilitar, 1 = habilitar
    s->set_wb_mode(s, 0);        // 0 a 4 - si awb_gain habilitado
    s->set_exposure_ctrl(s, 1);  // 0 = deshabilitar, 1 = habilitar
    s->set_aec2(s, 0);           // 0 = deshabilitar, 1 = habilitar
    s->set_ae_level(s, 0);       // -2 a 2
    s->set_aec_value(s, 300);    // 0 a 1200
    s->set_gain_ctrl(s, 1);      // 0 = deshabilitar, 1 = habilitar
    s->set_agc_gain(s, 0);       // 0 a 30
    s->set_gainceiling(s, (gainceiling_t)0);  // 0 a 6
    s->set_bpc(s, 0);            // 0 = deshabilitar, 1 = habilitar
    s->set_wpc(s, 1);            // 0 = deshabilitar, 1 = habilitar
    s->set_raw_gma(s, 1);        // 0 = deshabilitar, 1 = habilitar
    s->set_lenc(s, 1);           // 0 = deshabilitar, 1 = habilitar
    s->set_hmirror(s, 0);        // 0 = deshabilitar, 1 = habilitar
    s->set_vflip(s, 0);          // 0 = deshabilitar, 1 = habilitar
    s->set_dcw(s, 1);            // 0 = deshabilitar, 1 = habilitar
    s->set_colorbar(s, 0);       // 0 = deshabilitar, 1 = habilitar
  }

  // Esperar un momento para estabilizar la cámara
  delay(1000);

  // Tomar la foto con la cámara
  Serial.println("Tomando foto...");
  camera_fb_t *fb = NULL;
  
  // Intentar tomar la foto con reintentos
  for (int retry = 0; retry len);

  // Crear la ruta del archivo
  String path = "/picture" + String(pictureNumber) + ".jpg";
  
  // Guardar la foto en la tarjeta SD
  fs::FS &fs = SD_MMC;
  Serial.printf("Nombre del archivo de la foto: %sn", path.c_str());

  File file = fs.open(path.c_str(), FILE_WRITE);
  if (!file) {
    Serial.println("Error al abrir el archivo en modo escritura");
    esp_camera_fb_return(fb);
    goToSleep();
    return;
  }

  // Escribir el archivo en fragmentos para evitar problemas de memoria
  size_t totalBytes = fb->len;
  size_t bytesWritten = 0;
  const size_t chunkSize = 1024; // Escribir en fragmentos de 1KB
  
  while (bytesWritten buf + bytesWritten, toWrite);
    if (written != toWrite) {
      Serial.println("Ocurrió un error de escritura");
      break;
    }
    bytesWritten += written;
  }
  
  file.close();
  
  if (bytesWritten == totalBytes) {
    Serial.printf("Archivo guardado en la ruta: %s (%zu bytes)n", path.c_str(), bytesWritten);
    
    // Actualizar el número de la foto en EEPROM solo después de una guardado exitoso
    EEPROM.write(0, pictureNumber);
    EEPROM.commit();
    Serial.println("¡Foto guardada exitosamente!");
  } else {
    Serial.printf("Escritura de archivo incompleta: %zu/%zu bytesn", bytesWritten, totalBytes);
  }
  
  // Devolver el buffer de fotogramas inmediatamente después de su uso
  esp_camera_fb_return(fb);
  
  // Breve retraso antes de dormir
  delay(500);
  
  goToSleep();
}

void goToSleep() {
  // Desinicializar la cámara para liberar memoria antes de dormir
  esp_camera_deinit();
  
  // Apagar el LED blanco integrado de la ESP32-CAM (flash) conectado a GPIO 4
  pinMode(FLASH_LED_PIN, OUTPUT);
  digitalWrite(FLASH_LED_PIN, LOW);
  rtc_gpio_hold_en(GPIO_NUM_4);

  Serial.println("Ahora entrando en modo de sueño");
  Serial.flush();
  
  delay(100);
  
  esp_deep_sleep_start();
}

void loop() {
  // Bucle vacío - todo sucede en setup()
}

Demostración

Una vez que estés listo, haz clic en el botón "Cargar" en el Arduino IDE para transferir el código a tu ESP32-CAM. Después de que la carga se complete, si estás utilizando un adaptador FTDI o un módulo ESP32-CAM-CH340, asegúrate de desconectar el pin GPIO 0 de GND. Esto es crucial para que el ESP32-CAM funcione correctamente en la operación normal.

A continuación, abre el Monitor Serial en el Arduino IDE y ajusta la velocidad de baudios a 115200. Para asegurarte de que el ESP32-CAM comience a ejecutar tu código recién cargado, presiona rápidamente el botón RST. La cámara tomará una foto, y cuando lo haga, verás el flash (que está conectado a GPIO 4) encenderse brevemente.

Revisa el Monitor Serial para obtener mensajes. Deberías ver el nombre del archivo de la foto, junto con un mensaje que confirma que la imagen se ha guardado en la tarjeta microSD.

Puedes continuar presionando el botón RESET para tomar más fotos. Cada nueva foto tendrá un nombre de archivo con un número incrementado. Cuando termines de tomar fotos, apaga el ESP32-CAM y retira la tarjeta microSD. Coloca la tarjeta en tu computadora y deberías encontrar todas tus fotos guardadas allí.

Explicación del código

El código comienza incluyendo varias bibliotecas esenciales:

  • La biblioteca esp_camera.h nos permite controlar la cámara del ESP32-CAM, incluyendo herramientas para tomar fotos y ajustar configuraciones.
  • La biblioteca Arduino.h es una biblioteca estándar que incluye funciones básicas como delay() y Serial.print().
  • La biblioteca FS.h proporciona comandos para leer y escribir en archivos en la tarjeta SD.
  • La biblioteca SD_MMC.h ayuda a trabajar con tarjetas SD a través de la interfaz SD_MMC, que es la forma en que el ESP32-CAM se comunica con su ranura microSD.
  • Las bibliotecas soc.h y rtc_cntl_reg.h nos permiten deshabilitar el detector de apagado, que puede causar reinicios no deseados cuando la tensión de la placa baja.
  • La biblioteca rtc_io.h nos ayuda a controlar los pines RTC (Reloj en Tiempo Real), que es útil para mantener los GPIO durante el sueño profundo.
  • La biblioteca EEPROM.h permite guardar datos (como el número de foto) en la memoria flash interna del ESP32, evitando que se borren con el reinicio.

También definimos cuántos bytes de EEPROM queremos usar; en este caso, solo 1 byte es suficiente para almacenar números hasta 255.

// Definir el número de bytes que deseas acceder
#define EEPROM_SIZE 1

A continuación, definimos todos los pines que la placa ESP32-CAM utiliza para comunicarse con la cámara y controlar otras funciones. Estos números de pin son específicos del modelo AI-Thinker, por lo que debemos asegurarnos de que estén correctamente asignados. También definimos el pin del LED flash, que ayuda a tomar fotos en condiciones de poca luz.

// Definición de pines para CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

// Pin de LED Flash
#define FLASH_LED_PIN      4

También configuramos una variable llamada pictureNumber que utilizaremos para llevar un registro de cuántas fotos hemos tomado.

En la función setup(), comenzamos deshabilitando el detector de apagado, una función que reinicia la placa si la tensión baja demasiado. Esto puede causar problemas, por lo que lo desactivamos para un funcionamiento más fluido.

WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);  // Deshabilitar detector de apagado

A continuación, iniciamos el Monitor Serial para poder ver los mensajes del dispositivo en el IDE de Arduino. También imprimimos un mensaje de bienvenida para saber que el programa ha comenzado.

Serial.begin(115200);
Serial.println("Inicio de captura de fotos con ESP32-CAM...");

Luego inicializamos la EEPROM para leer el último número de foto guardado. Agregamos 1 a ese número para que la próxima foto obtenga un nuevo nombre. Si ese número alguna vez se vuelve demasiado grande (más de 255), lo restablecemos a 1 para evitar errores.

// Inicializar EEPROM primero para obtener el número de fotos
EEPROM.begin(EEPROM_SIZE);
pictureNumber = EEPROM.read(0) + 1;

// Manejar el desbordamiento (restablecer a 1 si excede 255)
if (pictureNumber > 255) {
  pictureNumber = 1;
}

Serial.printf("Tomando foto número: %dn", pictureNumber);

Seguido de esto, configuramos la tarjeta SD, un paso muy importante que debe realizarse antes de inicializar la cámara, ya que esta utiliza mucha memoria.

Si la tarjeta SD no se monta correctamente o no hay tarjeta insertada, imprimimos un error y enviamos la placa a modo de sueño profundo para ahorrar energía.

// Inicializar la tarjeta SD ANTES de la cámara para reducir la presión de memoria
Serial.println("Iniciando tarjeta SD");
if (!SD_MMC.begin()) {
  Serial.println("Error al montar la tarjeta SD");
  goToSleep(); 
  return;
}

Una vez que la tarjeta SD está lista, verificamos su tipo y imprimimos qué tipo es. Esto solo ayuda a solucionar problemas si algo sale mal.

uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
  Serial.println("No hay tarjeta SD adjunta");
  goToSleep(); 
  return;
}

Serial.printf("Tipo de tarjeta SD: ");
if (cardType == CARD_MMC) {
  Serial.println("MMC");
} else if (cardType == CARD_SD) {
  Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
  Serial.println("SDHC");
} else {
  Serial.println("DESCONOCIDO");
}

Después de eso, configuramos los ajustes de la cámara. Esta sección puede parecer extensa, pero se trata solo de asignar cada pin para que coincida con el hardware. Estas configuraciones indican al ESP32-CAM cómo conectarse con el módulo de la cámara.

// Configuración de la cámara - REDUCIR configuraciones para evitar desbordamiento
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.pixel_format = PIXFORMAT_JPEG;

Luego verificamos si PSRAM (memoria extra) está disponible en la placa. Si está presente, usamos configuraciones mejores como mayor resolución y mejor calidad JPEG. Si no, reducimos las configuraciones para asegurarnos de que la placa no se bloquee por usar demasiada memoria.

// CRÍTICO: Reducir tamaño de cuadro y calidad para evitar desbordamiento
if (psramFound()) {
  Serial.println("PSRAM encontrado");
  config.frame_size = FRAMESIZE_XGA;  // Reducido de UXGA a XGA
  config.jpeg_quality = 12;           // Aumentado de 10 a 12 (menor calidad, archivo más pequeño)
  config.fb_count = 1;                // Reducido de 2 a 1 para ahorrar memoria
} else {
  Serial.println("PSRAM no encontrado");
  config.frame_size = FRAMESIZE_VGA;  // Reducido de SVGA a VGA
  config.jpeg_quality = 15;           // Menor calidad para placas sin PSRAM
  config.fb_count = 1;
}

También cambiamos la forma en que la cámara captura fotogramas, utilizando la imagen más reciente en lugar de esperar a que la memoria esté vacía, lo que ayuda a que funcione más suavemente con poca memoria.

// Configuraciones adicionales para ahorrar memoria
config.grab_mode = CAMERA_GRAB_LATEST; // Cambiado de WHEN_EMPTY a LATEST

Ahora intentamos inicializar la cámara utilizando todas esas configuraciones. Si algo sale mal, imprimimos un error y vamos a dormir. Pero si funciona, imprimimos un mensaje de éxito.

// Inicializar la cámara
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
  Serial.printf("Error al inicializar la cámara: 0x%xn", err);
  goToSleep();
  return;
}
Serial.println("Cámara inicializada correctamente");

Posteriormente, ajustamos algunas configuraciones adicionales de la cámara para asegurarnos de que la imagen se vea bien. Estos ajustes controlan aspectos como brillo, contraste, balance de blancos y exposición. Puedes modificar estos valores según tus necesidades para obtener mejores fotos en diferentes condiciones de iluminación.

// Configuraciones adicionales del sensor para optimizar el uso de memoria
sensor_t * s = esp_camera_sensor_get();
if (s != NULL) {
  // Optimizar otras configuraciones
  s->set_brightness(s, 0);     // -2 a 2
  s->set_contrast(s, 0);       // -2 a 2
  s->set_saturation(s, 0);     // -2 a 2
  s->set_special_effect(s, 0); // 0 a 6 (0 - Sin efecto)
  s->set_whitebal(s, 1);       // 0 = deshabilitar, 1 = habilitar
  s->set_awb_gain(s, 1);       // 0 = deshabilitar, 1 = habilitar
  s->set_wb_mode(s, 0);        // 0 a 4 - si awb_gain habilitado
  s->set_exposure_ctrl(s, 1);  // 0 = deshabilitar, 1 = habilitar
  s->set_aec2(s, 0);           // 0 = deshabilitar, 1 = habilitar
  s->set_ae_level(s, 0);       // -2 a 2
  s->set_aec_value(s, 300);    // 0 a 1200
  s->set_gain_ctrl(s, 1);      // 0 = deshabilitar, 1 = habilitar
  s->set_agc_gain(s, 0);       // 0 a 30
  s->set_gainceiling(s, (gainceiling_t)0);  // 0 a 6
  s->set_bpc(s, 0);            // 0 = deshabilitar, 1 = habilitar
  s->set_wpc(s, 1);            // 0 = deshabilitar, 1 = habilitar
  s->set_raw_gma(s, 1);        // 0 = deshabilitar, 1 = habilitar
  s->set_lenc(s, 1);           // 0 = deshabilitar, 1 = habilitar
  s->set_hmirror(s, 0);        // 0 = deshabilitar, 1 = habilitar
  s->set_vflip(s, 0);          // 0 = deshabilitar, 1 = habilitar
  s->set_dcw(s, 1);            // 0 = deshabilitar, 1 = habilitar
  s->set_colorbar(s, 0);       // 0 = deshabilitar, 1 = habilitar
}

Esperamos un segundo para que la cámara se estabilice y luego tomamos la foto.

// Esperar un momento para estabilizar la cámara
delay(1000);

Intentamos hasta tres veces tomar una foto, en caso de que falle la primera vez. Si sigue fallando, nos vamos a dormir. Pero si funciona, imprimimos el tamaño de la imagen que acabamos de tomar.

// Tomar la foto con la cámara
Serial.println("Tomando foto...");
camera_fb_t *fb = NULL;

// Intentar tomar la foto con reintentos
for (int retry = 0; retry len);

A continuación, construimos una ruta de archivo utilizando el número de la foto, como “/picture3.jpg”, y abrimos un nuevo archivo en la tarjeta SD con ese nombre.

// Crear la ruta del archivo
String path = "/picture" + String(pictureNumber) + ".jpg";

// Guardar la foto en la tarjeta SD
fs::FS &fs = SD_MMC;
Serial.printf("Nombre del archivo de la foto: %sn", path.c_str());

File file = fs.open(path.c_str(), FILE_WRITE);
if (!file) {
  Serial.println("Error al abrir el archivo en modo escritura");
  esp_camera_fb_return(fb);
  goToSleep();
  return;
}

Guardamos la foto escribiéndola en pequeños fragmentos de 1 kilobyte (1024 bytes) a la vez. Esto ayuda a prevenir problemas de memoria y mantiene la placa en funcionamiento durante grandes escrituras.

// Escribir el archivo en fragmentos para evitar problemas de memoria
size_t totalBytes = fb->len;
size_t bytesWritten = 0;
const size_t chunkSize = 1024; // Escribir en fragmentos de 1KB

while (bytesWritten buf + bytesWritten, toWrite);
  if (written != toWrite) {
    Serial.println("Ocurrió un error de escritura");
    break;
  }
  bytesWritten += written;
}

Tras escribir, cerramos el archivo y verificamos si todo se guardó correctamente. Si es así, actualizamos el número de la foto en EEPROM y lo guardamos, para que la próxima vez no sobrescribamos la imagen anterior.

file.close();

if (bytesWritten == totalBytes) {
  Serial.printf("Archivo guardado en la ruta: %s (%zu bytes)n", path.c_str(), bytesWritten);
  
  // Actualizar el número de la foto en EEPROM solo después de un guardado exitoso
  EEPROM.write(0, pictureNumber);
  EEPROM.commit();
  Serial.println("¡Foto guardada exitosamente!");
} else {
  Serial.printf("Escritura de archivo incompleta: %zu/%zu bytesn", bytesWritten, totalBytes);
}

Devolvemos el buffer de memoria que utilizamos para la foto, ya que hemos terminado con él. Luego esperamos medio segundo y entramos en modo de sueño profundo para ahorrar energía hasta la próxima vez que se reinicie la placa.

// Devolver el buffer de fotogramas inmediatamente después de su uso
esp_camera_fb_return(fb);

// Breve retraso antes de dormir
delay(500);

goToSleep();

La función goToSleep() apaga la cámara y el LED flash, imprime un mensaje indicando que vamos a dormir y luego inicia el modo de sueño profundo. Esto significa que la placa permanecerá apagada hasta que se presione nuevamente el botón de RESET.

void goToSleep() {
  // Desinicializar la cámara para liberar memoria antes de dormir
  esp_camera_deinit();

  // Apagar el LED blanco integrado de la ESP32-CAM (flash) conectado a GPIO 4
  pinMode(FLASH_LED_PIN, OUTPUT);
  digitalWrite(FLASH_LED_PIN, LOW);
  rtc_gpio_hold_en(GPIO_NUM_4);

  Serial.println("Ahora entrando en modo de sueño");
  Serial.flush();

  delay(100);

  esp_deep_sleep_start();
}

Finalmente, tenemos una función loop() vacía, porque todo lo que necesitamos sucede en la parte de setup().

void loop() {
  // Bucle vacío - todo sucede en setup()
}

Carlos Julián

Carlos Julián es el fundador de Ingtelecto, es Ingeniero Mecatrónico, Profesor y Programador, cuenta con una Maestria en Ciencias de la Educación, creador de contenido activo a través de TikTok @carlosjulian_mx

Estos temas te pueden interesar

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Tu puntuación: Útil

Subir