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!
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 comodelay()
ySerial.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
yrtc_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()
}
Deja una respuesta
Estos temas te pueden interesar