(a) Conceito e Arquitetura
Câmera em um assistente de bancada não é “tirar foto”: é medir presença, atenção e movimento. Este post trata o módulo ESP32-S3 CAM como um sensor do sistema — quem entra no campo de visão, se há uma pessoa olhando para o dispositivo, se algo mudou no ambiente — e não como um stream de vídeo para a nuvem. Todo o processamento fica no próprio chip.
O pipeline é curto e vale conhecê-lo em detalhe: o sensor (OV2640 ou OV5640) entrega pixels crus via barramento DVP; o periférico de câmera do ESP32-S3 despeja os dados em DMA direto na PSRAM; o encoder JPEG do sensor comprime o quadro (tipicamente 10 a 40 KB em VGA); o resultado é um buffer que o firmware pode enviar pela rede, gravar no cartão SD ou analisar localmente com a biblioteca ESP-DL. Duas operações nunca acontecem no mesmo buffer ao mesmo tempo, e é por isso que a escolha de fb_count e do modo de captura define o comportamento do sistema.
Há dois modos de operação que se excluem na prática. O modo streaming maximiza quadros por segundo: buffers duplos, JPEG, prioridade para entregar imagem. O modo inferência na borda troca taxa de quadros por decisão: um modelo quantizado em int8 roda sobre o quadro e devolve uma caixa, um contador ou um booleano. O truque usado em quase todos os projetos que funcionam é desacoplar os dois — capturar a 15–25 fps e rodar detecção a cada N quadros, mantendo o número de quadros capturados estável enquanto a inferência roda a 3–8 fps.
Por que ESP32-S3 e não o ESP32 clássico: o S3 tem vetorização de instruções (PIE) que acelera modelos convolucionais, suporta PSRAM octal a 80 MHz e tem mais largura de banda de DMA. É a diferença entre detecção facial a 10–20 fps em QVGA e detecção engasgada em 3 fps.
(b) BOM — Lista de Materiais
| Item | Qtd | Especificação | Observação |
|---|---|---|---|
| MCU | 1 | ESP32-S3-WROOM-1 N16R8 | 16 MB flash + 8 MB PSRAM octal — obrigatório para VGA em diante |
| Módulo câmera | 1 | OV2640 (2 MP) com FPC de 24 pinos | Barato, JPEG on-chip, bom para borda |
| Alternativa | 1 | OV5640 (5 MP, autofoco) | Mais nitidez, mais consumo e mais calor |
| Lente | 1 | 66° padrão ou 120° grande angular | 120° cobre a bancada toda com distorção de borda |
| Iluminação | 1 | LED IR 850 nm + filtro IR-CUT removível | Visão noturna; sem IR-CUT a imagem fica roxa de dia |
| Gatilho | 1 | Sensor PIR HC-SR501 | Acorda a câmera só quando há movimento |
| Armazenamento | 1 | microSD SPI | Snapshots e datasets de calibração |
| Gimbal | 1 | Pescoço pan/tilt do post anterior | Fecha o loop de rastreamento |
| Alimentação | 1 | Fonte 5 V / 2 A + LDO 3,3 V de 500 mA | CRÍTICO: picos do Wi-Fi + câmera causam brownout |
(c) Wiring & Pinagem
A câmera consome 15 pinos de dados e controle, mais dois de SCCB (o I2C do sensor). O mapeamento varia por placa: use sempre a tabela do fabricante. O exemplo abaixo é o padrão de fato em módulos ESP32-S3 CAM de 24 pinos.
| Sinal da câmera | GPIO típico (S3 CAM) | Observação |
|---|---|---|
| XCLK | GPIO15 | Clock gerado pelo LEDC; 20 MHz é o ponto de estabilidade para JPEG |
| SIOD / SIOC | GPIO4 / GPIO5 | SCCB do sensor (endereço 7 bits 0x30). Pode dividir o barramento com o PCA9685 (0x40) |
| VSYNC | GPIO6 | Sincronismo vertical |
| HREF | GPIO7 | Sincronismo horizontal |
| PCLK | GPIO13 | Pixel clock do DVP |
| D0–D7 | GPIO11, 9, 8, 10, 12, 18, 17, 16 | Barramento de 8 bits. Ordem invertida em várias placas — confira |
| RESET / PWDN | −1 / −1 | Quando o módulo não expõe esses pinos, deixe −1 na config |
| 3V3 / GND | 3V3 e GND | Somente 3,3 V: 5 V no DVP queima o sensor e o GPIO |
Dois cuidados evitam metade dos problemas. Corrente: o módulo de câmera puxa picos de ~200 mA além do consumo do S3 e do rádio; a linha de 3,3 V precisa de capacitor de 100 µF + 100 nF junto ao módulo, e o LDO deve ter folga. FPC: o cabo flat de 24 pinos é frágil e sensível a dobra; fixe-o com fita e evite movimentá-lo com o sistema ligado — mau contato no FPC se manifesta como erro de inicialização, não como imagem ruim.
(d) Código — captura, medição de FPS e inferência desacoplada
A API é a própria do componente esp32-camera da Espressif. A configuração abaixo é para VGA/JPEG com dois buffers na PSRAM.
#include "esp_camera.h"
#include "esp_timer.h"
static camera_config_t cfg = {
.pin_pwdn = -1, .pin_reset = -1,
.pin_xclk = 15,
.pin_sccb_sda = 4, .pin_sccb_scl = 5,
.pin_d7 = 16, .pin_d6 = 17, .pin_d5 = 18, .pin_d4 = 12,
.pin_d3 = 10, .pin_d2 = 8, .pin_d1 = 9, .pin_d0 = 11,
.pin_vsync = 6, .pin_href = 7, .pin_pclk = 13,
.xclk_freq_hz = 20000000, // 20 MHz
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAMESIZE_VGA, // 640x480
.jpeg_quality = 12, // 0..63 — menor = melhor
.fb_count = 2, // dois buffers na PSRAM
.fb_location = CAMERA_FB_IN_PSRAM,
.grab_mode = CAMERA_GRAB_LATEST, // sempre o quadro mais novo
};
void setup() {
Serial.begin(115200);
if (esp_camera_init(&cfg) != ESP_OK) {
Serial.println("Falha ao iniciar a camera");
return;
}
}
void loop() {
static uint32_t frames = 0;
static int64_t t0 = esp_timer_get_time();
camera_fb_t *fb = esp_camera_fb_get(); // bloqueia ate haver buffer
if (!fb) return;
// aqui vai a inferencia: rode a cada N frames, nao a cada frame
// ex.: if ((frames % 4) == 0) detecta_pessoa(fb);
frames++;
int64_t agora = esp_timer_get_time();
if (agora - t0 > 2000000) { // janela de 2 s
Serial.printf("VGA/JPEG %.1f fps %u bytes/frame\n",
frames * 1e6 / (double)(agora - t0), fb->len);
frames = 0; t0 = agora;
}
esp_camera_fb_return(fb); // devolve o buffer ao pool
}
Três parâmetros governam tudo. fb_count: com 1 buffer, esp_camera_fb_get() só retorna quando o quadro está completo e você perde captura durante o processamento; com 2, o driver mantém um quadro em escrita e outro pronto, e CAMERA_GRAB_LATEST garante baixa latência em vez de fila. jpeg_quality: a escala é invertida — 12 é qualidade alta e arquivo maior, 30 é qualidade baixa e mais quadros por segundo. frame_size: cada passo de resolução custa banda de PSRAM e tempo de encode.
Taxas realistas em S3 a 240 MHz, PSRAM octal, JPEG, fb_count=2 (valores de ordem de grandeza; variam com clock, barramento e qualidade):
| Resolução | JPEG típico | Captura | Com detecção facial (ESP-DL) |
|---|---|---|---|
| QVGA 320×240 | 8–15 KB | 25–40 fps | 10–20 fps |
| VGA 640×480 | 20–40 KB | 15–25 fps | 4–8 fps |
| SVGA 800×600 | 40–60 KB | 10–15 fps | 2–4 fps |
| XGA 1024×768 | 60–100 KB | 6–10 fps | 1–2 fps |
Para presença e contagem, QVGA é o ponto ótimo: o modelo roda em int8 com entrada 320×240, a imagem inteira já é JPEG pequeno e o S3 sobra para o Wi-Fi. Para leitura de display ou medidor, suba a resolução e aceite 2–5 fps — ninguém precisa de um medidor a 30 fps.
(e) Troubleshooting
| Sintoma | Causa provável | Correção |
|---|---|---|
esp_camera_init retorna 0x105 / NOT_FOUND |
Pinagem errada, FPC mal encaixado ou placa sem PSRAM | Confira o mapeamento do fabricante e reseat o cabo FPC |
| Inicia, mas sem imagem | XCLK alto demais ou polaridade de PCLK invertida |
Baixe para 10–20 MHz e teste pixel_format JPEG/GRAYSCALE |
| PSRAM não detectada | Modo QSPI em vez de OPI/octal | Habilite PSRAM octa na menuconfig e use placa R8 |
| Reinicia ao enviar pela rede | Brownout: pico do Wi-Fi somado à câmera | LDO com folga, 100 µF + 100 nF no rail de 3,3 V, fonte 5 V/2 A |
| Imagem rasgada ou “de outro momento” | Buffer devolvido cedo ou inferência sobre quadro antigo | fb_count=2 + CAMERA_GRAB_LATEST; processe dentro do escopo do framebuffer |
| Artefatos em cena com movimento do gimbal | Rolling shutter do sensor | Reduza velocidade do servo ou faça captura estática antes de inferir |
| Superaquecimento | Wi-Fi + câmera contínuos, sem duty cycle | Capture por janelas, use PIR como gatilho, ligue Wi-Fi só para enviar |
(f) Aplicações reais — o que roda on-device
Com ESP-DL, tudo abaixo roda sem nuvem: detecção facial (caixa e landmarks), detecção de pessoa, classificação de presença e comparação de similaridade simples entre os embeddings faciais (útil para “é o mesmo usuário que chegou”). O que não roda bem é reconhecimento de texto genérico, detecção de dezenas de classes e qualquer modelo acima de poucos MB — isso fica para um host na rede.
As aplicações que amadureceram em produção são prosaicas: contador de pessoas em porta ou bancada, gatilho de presença para acordar o assistente sem manter o microfone sempre ativo, monitoramento de bancada com snapshot por evento, leitura de medidores e displays analógicos (o projeto AI-on-the-edge-device faz exatamente isso em um ESP32 com um modelo convivendo em flash e PSRAM) e o rastreamento do gimbal — o centroide da caixa facial virando comando de pan/tilt, fechando o loop com o hardware do post anterior.
Vale o registro de engenharia: privacidade aqui não é marketing, é consequência da arquitetura. Se a imagem nunca sai do chip, não há stream para vazar. Publique apenas eventos — “1 pessoa detectada às 14:32” — em vez de quadros, e o assistente de bancada passa a ser um sensor de ambiente, não uma câmera de vigilância.
Não some depois da matéria
Radar do hub: Núcleo Hits, Além do Oculto, Bobinho. Sem spray, sem lista comprada.
Radar do hub
Um e-mail quando sair matéria que importa — sem spray de newsletter genérica.

Deixe um comentário
Entre com Google ou Facebook para comentar. Nome e e-mail vêm da sua conta — sem preencher formulário.
Se o login falhar, configure Google e Facebook em Configurações → Nextend Social Login.
Outras opções de login