ESP32-CAM: стриминг видео и фото в браузере
ESP32-CAM у меня месяц лежал нераспакованный, потому что первая же попытка её прошить свелась к трем часам возни: на плате нет USB-разъёма, нужен переходник FTDI на 3V3, и GPIO0 при прошивке замыкается на землю проводом. Когда всё-таки поднял — изображение с камеры пошло в браузер за вечер. Это самый дешёвый способ «смотреть что-то через интернет» из существующих: плата с ESP32, OV2640-камерой, слотом microSD и PSRAM стоит 700-1000 ₽, а стрим JPEG уходит в браузер по простому HTTP.
Ниже — три рабочих сценария стриминга и захвата с ESP32-CAM: CameraWebServer из примеров ESP32 (видео в браузер с панелью настроек), фото по таймеру на SD-карту и отправка кадра в Telegram-бот. Пины камеры для платы AI-Thinker, грабли с PSRAM и питанием, и почему UXGA при 15 fps съест всю память за минуту. Симулятор camera не поддерживает — кодовые блоки иллюстративные, проверяются на живой плате.
Что внутри ESP32-CAM: чип, камера, флеш
ESP32-D0WDQ6 — два ядра Xtensa LX6 на 240 МГц, 520 КБ SRAM на кристалле, плюс внешняя PSRAM 4 МБ на AI-Thinker-плате. Камера OV2640 — 2 мегапикселя, сжатие в JPEG на чипе, до UXGA (1600x1200) при 15 кадрах в секунду. Флеш 4 МБ, и слот microSD в SPI-режиме. Из 38 пинов чипа половина занята шлейфом камеры и SD — на гребёнке свободны GPIO 0, 4, 12-16, 17-19, 33. GPIO0 — кнопка загрузчика, GPIO33 — пин выбора PSRAM.
- ESP32-D0WDQ6: 2 ядра × 240 МГц, 520 КБ SRAM, WiFi b/g/n + BT 4.2 в чипе.
- OV2640: 2 Мп JPEG, до UXGA при 15 fps, 16-пинный шлейф к плате.
- PSRAM 4 МБ: внешняя SRAM для кадров; без неё UXGA не вытянуть, хватает на QVGA.
- microSD по SPI: 1-битная шина, до 32 ГБ на FAT32 карте.
- Питание строго 5 В через пин 5V0 (не USB!): на плате нет USB-разъёма, нужен FTDI/CH340.
Прошивка: FTDI, GPIO0 на землю, 115200
Прошивается ESP32-CAM через UART-переходник: VCC-5V к пину 5V0 платы, GND к GND, TX переходника к RX платы (U0R), RX переходника к TX (U0T), и провод-перемычка с GPIO0 на GND на время прошивки. После успешной заливки перемычку снимаю, и плата стартует в нормальном режиме. Arduino IDE: плата «AI Thinker ESP32-CAM», скорость загрузки 115200, Flash Mode DIO. Без перемычки на GPIO0 — ошибка Failed to connect, на форумах её лечат именно этим проводом.
Сценарий 1: CameraWebServer — видео в браузер
В Arduino IDE открываете Файл → Примеры → ESP32 → Camera → CameraWebServer. Вписываете SSID и пароль, раскомментируете строку #define CAMERA_MODEL_AI_THINKER. Заливаете, открываете монитор порта на 115200, ждёте строку Camera ready! Use 'http://192.168.1.42' to connect. Открываете IP в браузере — панель с кнопкой Start Stream, и MJPEG-поток идёт в окно. Это самый быстрый способ получить изображение в браузер: без написания кода, за 10 минут с момента включения.
#include "esp_camera.h"#include <WiFi.h> const char* SSID = "HomeNet";const char* PASS = "passw0rd"; void setup() { Serial.begin(115200); camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = 5; config.pin_d1 = 18; config.pin_d2 = 19; config.pin_d3 = 21; config.pin_d4 = 36; config.pin_d5 = 39; config.pin_d6 = 34; config.pin_d7 = 35; config.pin_xclk = 0; config.pin_pclk = 22; config.pin_vsync = 25; config.pin_href = 23; config.pin_sccb_sda = 26; config.pin_sccb_scl = 27; config.pin_pwdn = 32; config.pin_reset = -1; config.xclk_freq_hz = 20000000; config.frame_size = FRAMESIZE_VGA; // 640x480, баланс памяти и качества config.jpeg_quality = 10; // 0-63, меньше — лучше config.fb_count = 2; // двойной буфер для плавности esp_camera_init(&config); WiFi.begin(SSID, PASS); while (WiFi.status() != WL_CONNECTED) { delay(300); Serial.print("."); } Serial.println(); Serial.println(WiFi.localIP()); startCameraServer(); // запускает /stream и /capture} void loop() { delay(10000); }Открываете http://192.168.1.42/ — панель CameraWebServer. Кнопка Start Stream даёт MJPEG-поток, Get Still — одиночный кадр, Resolution переключает от QVGA до UXGA. Питание в этот момент обязательно 5V/1A через пин: при USB-зарядке на 500 мА плата при захвате ребутится с Brownout detector. Полный разбор SSE-потока в браузер — в уроке про серверные события.
Сценарий 2: фото по таймеру на SD-карту
Один раз в минуту делаем снимок и пишем в /photo/2025-01-15/14-32-01.jpg. Скетч: esp_camera_fb_get захватывает кадр, fs.write пишет буфер в файл, fs.close закрывает. На SD 16 ГБ помещается 15-20 тысяч кадров — на неделю интервальной съёмки раз в минуту. Грабля: fb->len в байтах, fs.write требует uint8_t*, и fb после записи обязательно возвращают через esp_camera_fb_return(fb), иначе память кончится через 50 кадров. Пример SD-логгера с меткой времени — в уроке про SD-веб-сервер на ESP32.
Сценарий 3: кадр в Telegram-бот
POST multipart/form-data на api.telegram.org/bot<TOKEN>/sendPhoto — отправка JPEG в чат. На ESP32 это WiFiClientSecure + BearSSL + ручная сборка multipart-тела. Скетч на 80 строк, кадр из камеры уходит за 2-3 секунды на хорошем WiFi. Ловушка — отпечаток сертификата Telegram меняется раз в полгода, и скетч молча падает с SSL error. Лечится проверкой по корневому сертификату (BearSSL) — он живёт годами. Подробно — в уроке про HTTPS на ESP32.
Грабли ESP32-CAM из личного опыта
- Brownout detector: питание проседает в момент захвата кадра. Лечится блоком 5V/1A минимум и конденсатором 470-1000 мкФ по питанию.
- PSRAM не включена: в меню платы включите PSRAM, иначе кадры UXGA не выделяются и плата падает с Guru Meditation Error.
- Flash Mode QIO: с ним CameraWebServer иногда не стартует; DIO — стабильнее, медленнее на 10%, но надёжнее.
- GPIO0 на земле после прошивки: забыл снять перемычку — плата молчит, CAM_ready не печатает. Перед первым стартом проверьте провод на GPIO0.
- Поток рвётся через 30-60 секунд:WiFi-роутер «устал» от UDP-флуда. Перевод на HTTP/1.1 keep-alive лечит, или ставьте RTSP-сервер (тяжелее).