Manual

do

Maker

.

com

TTGO T-Camera com ESP32 WROVER

TTGO T-Camera com ESP32 WROVER

Eis mais uma novidade trazida pelo nosso parceiro Saravati. A TTGO T-Camera, que possui display OLED, botão de uso geral, botão de reset, sensor PIR e, claro, a câmera. Além desses recursos, ela tem um pad para adicionar o sensor BME280, que não vem por padrão, mas uma das razões é que a temperatura da placa influencia no sensor de temperatura, por ser muito sensível.

Características

Essa placa é incrível. Já escrevi alguns artigos sobre o ESP32-CAM, mas são placas bastante distintas.

Os botões quadrados da TTGO T-Camera são emborrachados, bastante agradáveis. Na borda inferior possui uma porta micro-USB, logo acima está o sensor PIR.

A câmera é uma OV2640, podendo ser a tradicional ou a fish-eye. A minha é a fish-eye, com uma imagem boa o suficiente em modo SVGA.

Ela possui outros formatos, inclusive grayscale para quem quiser imagens em formatos maiores sem perder fps - o que digo assim de passagem, pode ser o ideal para utilizar com visão computacional e inteligência artificial, pois é comum utilizar grayscale.

Na parte de trás da placa estão dispostos pinos de 5V, 3V3, GND e os pinos 21 e 22 para acesso ao barramento I2C, permitindo adicionar outros dispositivos, como por exemplo, um expansor de IO. Por essa razão, a ausência de outros pinos de IO não é um problema.

Na lateral, próximo à câmera, ela possui um conector para 5V onde podemos colocar uma bateria li-ion para alimentar o ESP32. O conector acompanha a placa.

Inicialização

Por padrão, essa placa vem configurada como AP, utilizando (infelizmente) o IP 192.168.1.1, que é comum ser o gateway de muitas redes domésticas. A rede deve aparecer como TTGO-CAMERA-xx:yy, sendo dois octetos referentes a uma parte do MAC address. A senha padrão é 12345678.

Ao acessar diretamente o IP no browser, deve iniciar uma streaming. Usando /jpg ao final do endereço, apenas um frame é capturado. Só que um dos meus roteadores WiFi utiliza justamente esse endereço IP, daí quis de imediato subir outro sketch. Pense em um sofrimento.

Subir novo sketch na TTGO T-Camera

O código que disponibilizo aqui foi um trabalho de mais de 12 horas de testes, quase desisti de fazer isso agora pela falta de informação e dificuldade de debug. Vamos começar a falar dos detalhes, depois assista ao vídeo também em nosso canal DobitAoByteBrasil no Youtube para ficar mais claro ainda.

Não use a biblioteca OV2640 - use a esp_camera

Para instanciar a câmera, é necessário alimentar uma struct contida na biblioteca que gerenciará a câmera. Acontece que a OV2640 não tem a configuração para o pino PWDN, que no caso dessa câmera, não pode ser nulo. Esse pino (PWDN) deve ser colocado em OUTPUT e HIGH. Não foi necessário fazê-lo, mas li algumas documentações que diziam ser necessário colocar IOD e IOS em PULLUP, então deixei comentado no código para um teste posterior, caso necessário fosse.

Outra coisa importante é que essa placa tem algumas variações de modelo e para cada modelo tem uma pinagem diferente da câmera. A melhor referência é a desse repositório, do qual será necessário cloná-lo para dentro de seu diretório de bibliotecas do Arduino.

Mais bibliotecas

Nesse primeiro sketch que disponibilizo nesse artigo não adicionei os demais recursos, como PIR e botão. Farei isso em outro artigo, não tem complicação nessa parte.

Instale a biblioteca ESP8266-OLED-SSD1306. NO repositório oficial do Arduino está nomeado como ESP8266-OLED, mas você pode pegá-la também clonando esse repositório.

Para o botão, uma das opções é utilizar a biblioteca OneButton, conforme será mostrado em outro artigo. Também está disponível no repositório oficial do Arduino.

Essa tabela contém o pinout de todos os modelos da TTGO-Camera, incluindo a TTGO T-Camera, que é essa sem microfone.

NameBME280/NoBME280-VersionMicrophone-VersionT-JornalT-Camera Plus
Y939361936
Y836153637
Y723121838
Y618393939
Y51535535
Y44143426
Y314133513
Y25341734
VSNC275225
HREF25272627
PCLK19252125
PWD26N/AN/AN/A
XCLK324274
SIOD13182518
SIOC12232323
RESETN/AN/AN/AN/A
SDA212114!
SCL222213!
Button34032N/A
PIR3319N/AN/A

Na caixa da placa vem anotado o pinout, mas não é muito intuitivo, porque tanto na struct quanto na declaração de exemplo de qualquer sketch, os nomes e números são completamente diferentes.

ttgo-t_camera-box.webp

Outra coisa interessante é que nos códigos de exemplo o display OLED é configurado com a resolução de 128x32, sendo que ele é um display de 128x64. Enfim, sequer chegou a ser um problema. Pra por a câmera pra funcionar sem usar o sketch padrão, aí sim foi um problemão, mas pelo qual você não precisará passar, bastando seguir esse artigo.

Código para TTGO T-Camera

Esse código está funcional para streaming e captura de uma amostra. Para streaming, use:

http://<IP que aparecer no display>

E para pegar uma amostra JPG, use:

http://<IP que aparece no display>/jpg

Uma outra forma de suprir as dependências é colocar esse código para compilar e ler as mensagens de erro retornadas pelo compilador. Mas se leu o artigo até aqui, não terá problema para fazer a placa funcionar. Lembre-se apenas de escolher o modelo WROVER na IDE do Arduino quando criar seu projeto.

#include <Arduino.h>
#include "esp_camera.h"
#include "SSD1306.h"
#include "OLEDDisplayUi.h"
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiClient.h>

/* define dos pinos do display*/
#define PWDN_GPIO_NUM 26

#define ENABLE_OLED

#ifdef ENABLE_OLED
#include "SSD1306.h"
#define OLED_ADDRESS 0x3c
#define I2C_SDA 21
#define I2C_SCL 22
SSD1306Wire display(OLED_ADDRESS, I2C_SDA, I2C_SCL, GEOMETRY_128_64);
#endif

//OV2640 cam;
WebServer server(80);

const char *ssid = "seuSSID";
const char *password = "suaSENHA";

void info(char *msg);

void info(char *msg)
{
    display.clear();
    display.drawString(128 / 2, 32 / 2, msg);
    display.display();
}
void handle_jpg_stream(void)
{
    WiFiClient client = server.client();
    String response = "HTTP/1.1 200 OK\r\n";
    response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n";
    server.sendContent(response);

    camera_fb_t *fb = NULL;
    size_t fb_len   = 0;

    while (1)
    {
        //cam.run();
        if (!client.connected())
            break;
        response = "--frame\r\n";
        response += "Content-Type: image/jpeg\r\n\r\n";
        server.sendContent(response);

        fb = esp_camera_fb_get();
        if (!fb) {
            Serial.printf("Camera capture failed");
            info("Camera failed :(");
            return;
        }
        fb_len = fb->len;
        client.write((const char *)fb->buf, fb->len);

        //client.write((char *)cam.getfb(), cam.getSize());
        server.sendContent("\r\n");
        if (!client.connected())
            break;
    }
}

void handle_jpg(void)
{
    info("JPG requested.");
    Serial.println("JPG requested.");
    WiFiClient client = server.client();

    info("Taking a shot...");
    Serial.println("Taking a shot...");

    camera_fb_t *fb = NULL;
    fb = esp_camera_fb_get();

    if (!fb) {
        Serial.printf("Camera capture failed");
        info("Camera failed :(");
        return;
    }

    size_t fb_len = 0;
    fb_len = fb->len;

    //cam.run();
    if (!client.connected())
    {
        Serial.println("fail ... \n");
        return;
    }
    String response = "HTTP/1.1 200 OK\r\n";
    response += "Content-disposition: inline; filename=capture.jpg\r\n";
    response += "Content-type: image/jpeg\r\n\r\n";
    server.sendContent(response);
    info("Sending sample...");
    Serial.println("Sending sample...");
    //client.write((char *)cam.getfb(), cam.getSize());
    client.write((const char *)fb->buf, fb->len);
    Serial.println("Done.");
    info("Done.");
}

void handleNotFound()
{
    String message = "Server is running!\n\n";
    message += "URI: ";
    message += server.uri();
    message += "\nMethod: ";
    message += (server.method() == HTTP_GET) ? "GET" : "POST";
    message += "\nArguments: ";
    message += server.args();
    message += "\n";
    server.send(200, "text/plain", message);
}

void setup()
{
    pinMode(PWDN_GPIO_NUM, PULLUP);
    digitalWrite(PWDN_GPIO_NUM, HIGH);

    //pinMode(13, INPUT_PULLUP);
    //pinMode(14, INPUT_PULLUP);
    
    display.init();
    //display.flipScreenVertically();
    display.setFont(ArialMT_Plain_16);
    display.setTextAlignment(TEXT_ALIGN_CENTER);
    display.clear();
    display.display();
    info("Starting serial...");
    Serial.begin(9600);
    while (!Serial);

    camera_config_t camera_config;
    camera_config.ledc_channel = LEDC_CHANNEL_0;
    camera_config.ledc_timer   = LEDC_TIMER_0;
    camera_config.pin_pwdn     = 26;
    camera_config.pin_d0       = 5;
    camera_config.pin_d1       = 14; //input_pullup ?
    camera_config.pin_d2       = 4;
    camera_config.pin_d3       = 15;
    camera_config.pin_d4       = 18;
    camera_config.pin_d5       = 23;
    camera_config.pin_d6       = 36;
    camera_config.pin_d7       = 39;
    camera_config.pin_xclk     = 32;
    camera_config.pin_pclk     = 19;
    camera_config.pin_vsync    = 27;
    camera_config.pin_href     = 25;
    camera_config.pin_sscb_sda = 13;  //input_pullup ?
    camera_config.pin_sscb_scl = 12;  //12
    camera_config.pin_reset    = 255;
    camera_config.xclk_freq_hz = 20000000;
    camera_config.pixel_format = PIXFORMAT_JPEG;
    camera_config.frame_size   = FRAMESIZE_SVGA;
    camera_config.jpeg_quality = 12;
    camera_config.fb_count     = 1;

    //sensor_t *s = esp_camera_sensor_get();
    //s->set_framesize(s, FRAMESIZE_QVGA);

    info("Starting camera...");
    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK) {
        Serial.printf("Camera init Fail");
        info("Camera failed :(");
    }

    info("Connecting...");
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(F("."));
    }
    Serial.println(F("WiFi connected"));
    Serial.println("");
    Serial.println(WiFi.localIP());

    display.clear();
    display.drawString(128 / 2, 32 / 2, WiFi.localIP().toString());
    display.display();

    server.on("/", HTTP_GET, handle_jpg_stream);
    server.on("/jpg", HTTP_GET, handle_jpg);
    server.onNotFound(handleNotFound);
    server.begin();
}

void loop()
{
    server.handleClient();
}

Essas 200 linhas são o resultado após ter escrito umas 2k linhas de testes, sem exagero.

Onde comprar a TTGO T-Camera?

A TTGO T-Camera está disponível no nosso parceiro Saravati, que possui loja física na Santa Efigênia e para quem é da capital de São Paulo, deve valer o passeio. De outro modo, a compra pode ser feita diretamente pelo site, através desse link.

Vídeo

O vídeo, como supracitado, pode ser visto em nosso canal Dobitaobytebrasil no Youtube. Se não é inscrito, inscreva-se, clique no sininho para receber notificações assim que o vídeo estiver online e deixe seu like, que é muito importante para o canal!

Inscreva-se no nosso canal Manual do Maker no YouTube.

Também estamos no Instagram.

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.