Skip to Content

Tirar Fotos com ESP32-CAM

Tirar Fotos com ESP32-CAM

Neste tutorial, vais aprender a tirar fotos com um ESP32-CAM quando um botão é pressionado e a guardá-las no cartão SD. Estamos basicamente a construir uma câmara digital simples.

Peças Necessárias

Vais precisar de um ESP32-CAM para experimentar os exemplos de código. Podes obter um ESP32-CAM com um USB-TTL Shield para programação ou um Adaptador FTDI USB-TTL. O Adaptador FTDI USB-TTL é um pouco mais complicado de usar, mas deixa os pinos GPIO facilmente acessíveis. O USB-TTL Shield é mais fácil de usar, mas é mais difícil de ligar aos pinos GPIO.

Se quiseres usar o teu PC para ver as fotos tiradas pelo ESP32-CAM e guardadas no cartão SD, também podes precisar de um leitor de cartão SD.

ESP32-CAM com USB-TTL Shield

Adaptador FTDI USB-TTL

Dupont wire set

Conjunto de fios Dupont

Half_breadboard56a

Breadboard

Leitor de cartão SD

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Diagrama de Ligações

Queremos tirar uma foto com o ESP32-CAM sempre que um botão for pressionado. No diagrama de ligações seguinte, ligamos um botão entre o pino GND e o pino GPIO13:

Button wired to ESP32-CAM
Botão ligado ao ESP32-CAM

Sempre que o botão é pressionado (fechado), o GPIO13 é puxado para o terra. Isto funciona porque o GPIO13 (e outros pinos GPIO) tem resistores pull-up internos que mantêm o GPIO13 em nível alto quando nada está ligado. Isto significa que a função do botão está invertida (quando fechadoGPIO13 == LOW) e vais ver isso na secção do código.

A imagem seguinte mostra como podes montar este circuito numa breadboard com o ESP32-CAM:

Circuit on breadboard with button and ESP32-CAM
Circuito na breadboard com botão e ESP32-CAM

Como podes ver, usei o USB-TTL Shield e consegui fazer uma pequena folga entre o ESP32-CAM e o shield para ligar os fios do botão ao GND e ao GPIO13:

Connecting wires at gap between ESP32-CAM and USB-TTL Shield
Ligação dos fios na folga entre o ESP32-CAM e o USB-TTL Shield

Alternativamente, podes usar o Adaptador FTDI USB-TTL ou ligar o ESP32-CAM ao USB-TTL Shield através de um conjunto de cabos Dupont.

Código para tirar fotos e guardar

O sketch Arduino seguinte tira uma foto com a câmara do ESP32-CAM e guarda-a no cartão SD quando o botão ligado ao GPIO13 é pressionado (fechado).

Para este código vais precisar da esp32cam biblioteca. Podes instalá-la via o LIBRARY MANAGER no Arduino IDE. Basta procurar por “esp32cam” e carregar em INSTALL. A imagem abaixo mostra a instalação concluída:

esp32cam library installed via Library Manager
Biblioteca esp32cam instalada via LIBRARY MANAGER

Abaixo está o código completo para a nossa pequena aplicação de fotografia. Dá uma vista de olhos primeiro e depois discutiremos os detalhes:

#include "FS.h"
#include "SD_MMC.h"
#include "esp32cam.h"

const byte btnPin = GPIO_NUM_13;
const auto RES = esp32cam::Resolution::find(1600, 1200);

void takePicAndSave() {
  static int cnt = 0;

  auto frame = esp32cam::capture();
  if (frame == nullptr) {
    Serial.println("Capture failed!");
    return;
  }
  
  String path = "/img" + String(cnt++) + ".jpg";
  File file = SD_MMC.open(path.c_str(), FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file!");
  }
  frame->writeTo(file);
  Serial.printf("Wrote: %s\n", path.c_str());
  file.close();
}

void enableFlash(bool enable) {
  digitalWrite(GPIO_NUM_4, enable ? HIGH : LOW);
}

void initCamera() {
    using namespace esp32cam;
    Config cfg;
    cfg.setPins(pins::AiThinker);
    cfg.setResolution(RES);
    cfg.setJpeg(80);

    bool ok = Camera.begin(cfg);
    Serial.println(ok ? "CAMERA OK" : "CAMERA FAIL");
}

void initSDCard() {
  if (!SD_MMC.begin("/sdcard", true)) {
    Serial.println("SD Card Mount Failed!");
  } else if (SD_MMC.cardType() == CARD_NONE) {
    Serial.println("No SD card inserted!");
  }
  Serial.println("SD card ready.");
}

void setup() {
  Serial.begin(115200);
  initCamera();
  initSDCard();
  pinMode(btnPin, INPUT);
  pinMode(GPIO_NUM_4, OUTPUT);
  enableFlash(false);  
  delay(1000);
}

void loop() {
  if (!digitalRead(btnPin)) {
    enableFlash(true);
    takePicAndSave();
    enableFlash(false);
    delay(500);
  }
}

Bibliotecas

No início do código, três bibliotecas são incluídas:

#include "FS.h"
#include "SD_MMC.h"
#include "esp32cam.h"

Estas bibliotecas são essenciais para o projeto ESP32-CAM. AFS.h biblioteca fornece operações de sistema de ficheiros, SD_MMC.h permite que o ESP32 comunique com cartões SD via barramento MMC (MultiMediaCard), e esp32cam.h é uma biblioteca de nível superior que simplifica a configuração da câmara e a captura de imagens no módulo ESP32-CAM.

Constantes

Depois, é definida uma constante para o pino do botão e uma configuração de resolução da câmara:

const byte btnPin = GPIO_NUM_13;
const auto RES = esp32cam::Resolution::find(1600, 1200);

Aqui, btnPin está atribuído ao GPIO 13, onde o botão está ligado. Podes também usar o GPIO 12, mas em ambos os casos deves usar a interface do cartão SD em modo 1-bit! Para mais detalhes, vê o More GPIO pins for ESP32-CAM tutorial.

ORES objeto define a resolução da imagem, neste caso, 1600×1200 pixels (UXGA), que será usado mais tarde para configurar a câmara. Aqui está uma lista dos valores possíveis para a resolução da câmara, embora dependendo da câmara nem todos possam funcionar:

  • 96×96
  • 160×120
  • 128×128
  • 176×144
  • 240×176
  • 240×240
  • 320×240
  • 320×320
  • 400×296
  • 480×320
  • 640×480
  • 800×600
  • 1024×768
  • 1280×720
  • 1280×1024
  • 1600×1200

takePicAndSave

A função takePicAndSave() é a função principal que captura uma imagem e grava-a no cartão SD:

void takePicAndSave() {
  static int cnt = 0;

A variávelcnt é declarada static, o que significa que mantém o seu valor entre chamadas da função. Usamo-la para gerar nomes de ficheiros únicos para cada foto.

  auto frame = esp32cam::capture();
  if (frame == nullptr) {
    Serial.println("Capture failed!");
    return;
  }

A função chamaesp32cam::capture() para capturar um frame da câmara. Se a captura falhar (ou seja, frame for nullptr), uma mensagem de erro é impressa e a função termina prematuramente.

  String path = "/img" + String(cnt++) + ".jpg";
  File file = SD_MMC.open(path.c_str(), FILE_WRITE);

O código constrói um nome de ficheiro concatenando “img” com o valor crescente de cnt e adicionando “.jpg”. O ficheiro é então aberto no cartão SD para escrita.

  if (!file) {
    Serial.println("Failed to open file!");
  }

Se o ficheiro não puder ser aberto por algum motivo (ex.: cartão SD ausente, erro no sistema de ficheiros), imprime uma mensagem de erro.

  frame->writeTo(file);
  Serial.printf("Wrote: %s\n", path.c_str());
  file.close();
}

Se o ficheiro abrir com sucesso, o frame capturado é escrito no ficheiro, uma mensagem de confirmação é impressa no Monitor Serial mostrando o nome do ficheiro guardado, e finalmente o ficheiro é fechado para garantir a integridade dos dados.

enableFlash

A função enableFlash(bool enable) controla o LED flash incorporado do ESP32-CAM:

void enableFlash(bool enable) {
  digitalWrite(GPIO_NUM_4, enable ? HIGH : LOW);
}

Esta função define o nível de tensão no GPIO 4. Quando enable é verdadeiro, define o GPIO 4 para HIGH, ligando o LED flash; caso contrário, define para LOW, desligando o flash.

Para mais informações sobre como controlar o LED flash, vê o Control ESP32-CAM Flash LED tutorial.

initCamera

A função initCamera() inicializa o módulo da câmara:

void initCamera() {
    using namespace esp32cam;
    Config cfg;
    cfg.setPins(pins::AiThinker);
    cfg.setResolution(RES);
    cfg.setJpeg(80);

Dentro de initCamera(), primeiro traz o namespace esp32cam para o escopo local. Depois, cria um objeto Config, cfg, e configura-o para usar os pinos predefinidos para o layout da placa Ai-Thinker, que é o tipo típico de placa para módulos ESP32-CAM. Define a resolução da câmara para RES (1600×1200) e a qualidade de compressão JPEG para 80 (onde 100 é a melhor qualidade, e valores mais baixos significam mais compressão e pior qualidade).

    bool ok = Camera.begin(cfg);
    Serial.println(ok ? "CAMERA OK" : "CAMERA FAIL");
}

A função Camera.begin(cfg) tenta iniciar a câmara usando as configurações especificadas. Imprime “CAMERA OK” se for bem-sucedido, ou “CAMERA FAIL” se não.

initSDCard

A função initSDCard() monta o cartão SD:

void initSDCard() {
  if (!SD_MMC.begin("/sdcard", true)) {
    Serial.println("SD Card Mount Failed!");
  } else if (SD_MMC.cardType() == CARD_NONE) {
    Serial.println("No SD card inserted!");
  }
  Serial.println("SD card ready.");
}

Aqui, SD_MMC.begin() inicializa o barramento SD_MMC e tenta montar o sistema de ficheiros sob o caminho /sdcard. O segundo argumento true ativa o modo de 1 linha, que simplifica as ligações à custa de menor velocidade. Se a montagem falhar, imprime “SD Card Mount Failed!”. Se o cartão estiver montado mas nenhum cartão estiver fisicamente inserido, imprime “No SD card inserted!”. Caso contrário, assume que está tudo bem e imprime “SD card ready.”

setup

A função setup() prepara tudo uma vez na inicialização:

void setup() {
  Serial.begin(115200);
  initCamera();
  initSDCard();
  pinMode(btnPin, INPUT);
  pinMode(GPIO_NUM_4, OUTPUT);
  enableFlash(false);  
  delay(1000);
}

Em setup(), a porta serial é iniciada a 115200 baud para depuração. A câmara e o cartão SD são inicializados chamando as funções initCamera() e initSDCard(). Depois, o pino do botão (btnPin) é configurado como entrada, e o pino de controlo do flash (GPIO 4) é configurado como saída. enableFlash(false) garante que o LED do flash começa desligado. É adicionada uma pausa de 1 segundo (1000 milissegundos) no final para deixar o sistema estabilizar antes de começar o loop principal.

loop

Finalmente, a função loop() verifica constantemente se o botão está pressionado:

void loop() {
  if (!digitalRead(btnPin)) {
    enableFlash(true);
    takePicAndSave();
    enableFlash(false);
    delay(500);
  }
}

Em loop(), lê o estado de btnPin. Se o botão estiver pressionado, digitalRead(btnPin) retorna LOW porque o pino é puxado para baixo quando pressionado. Quando o botão é detectado como pressionado, o flash é ligado chamando enableFlash(true), depois uma foto é capturada e guardada chamando takePicAndSave(), e finalmente o flash é desligado novamente com enableFlash(false). Segue-se uma pausa de 500 milissegundos para evitar tirar várias fotos com um único pressionar do botão.

Conclusões

Neste tutorial aprendeste a construir uma pequena câmara digital usando o ESP32-CAM. O exemplo de código configura um ESP32-CAM para monitorizar um botão. Quando o botão é pressionado, liga o flash, captura uma foto a 1600×1200 de resolução, guarda-a no cartão SD com um nome sequencial como /img0.jpg, /img1.jpg, etc., desliga o flash e espera brevemente antes de voltar a escutar.

Se quiseres colocar o ESP32 em deep-sleep entre as fotos, vê o Motion Activated ESP32-CAM tutorial e para streaming de vídeo vê o Stream Video with ESP32-CAM artigo.

Além disso, se quiseres ser notificado sempre que for detetado movimento, vê o nosso ESP32 send Telegram Message tutorial, que ensina como enviar mensagens para a app Telegram no teu telemóvel.

E se tiveres alguma dúvida, não hesites em deixar nos comentários.

Boas experiências 😉