Manual
do
Maker
.
com
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.
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.
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.
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.
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.
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.
Name | BME280/NoBME280-Version | Microphone-Version | T-Jornal | T-Camera Plus |
---|---|---|---|---|
Y9 | 39 | 36 | 19 | 36 |
Y8 | 36 | 15 | 36 | 37 |
Y7 | 23 | 12 | 18 | 38 |
Y6 | 18 | 39 | 39 | 39 |
Y5 | 15 | 35 | 5 | 35 |
Y4 | 4 | 14 | 34 | 26 |
Y3 | 14 | 13 | 35 | 13 |
Y2 | 5 | 34 | 17 | 34 |
VSNC | 27 | 5 | 22 | 5 |
HREF | 25 | 27 | 26 | 27 |
PCLK | 19 | 25 | 21 | 25 |
PWD | 26 | N/A | N/A | N/A |
XCLK | 32 | 4 | 27 | 4 |
SIOD | 13 | 18 | 25 | 18 |
SIOC | 12 | 23 | 23 | 23 |
RESET | N/A | N/A | N/A | N/A |
SDA | 21 | 21 | 14 | ! |
SCL | 22 | 22 | 13 | ! |
Button | 34 | 0 | 32 | N/A |
PIR | 33 | 19 | N/A | N/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.
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.
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.
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.
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.
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.