Видео с ESP32CAM на TFT дисплей ST7735

Одна из самых удачных разновидностей отладочных плат семейства ESP32 — это ESP32CAM. Все дело в разъёме для видеокамеры, к которому обычно уже подключена OV2640.

Камера по современным меркам имеет скудные характеристики, но вполне подойдет для большинства DIY-идей. Матрица 2Мп выдаёт максимальное разрешение 1600 x 1200 точек. Поддерживаются функции автоматической выдержки, баланса белого, усиления сигнала.

ESP32-CAM OV2640
Если возможностей камеры на хватает, её можно заменить, например, на OV5640.

Дисплей

Возникает вопрос: а можно ли изображение с камеры не сохранять на SD-карту и не передавать через Wi-Fi, а транслировать на дисплей? Справится ли esp32 с такой работой?

Попробуем это сделать. Для примера возьмём небольшой TFT модуль с матрицей 1,8 дюйма и контроллером ST7735.

Для эксперимента потребуется esp32cam, tft дисплей, макетная плата и немного проводов. Если чего-то не хватает, это можно купить в RobotClass:

В корзину
В корзину
В корзину
В корзину

Подключение

Если бы у нас был любой другой модуль ESP32, данный раздел состоял бы из одного абзаца, но у ESP32CAM есть особенность. Дело в том, что камера подключается к ESP32 по интерфейсу SCCB и такое подключение «съедает» один из SPI интерфейсов микроконтроллера, а именно VSPI.

Эта особенность негативно сказывается на работе многих библиотек для работы с дисплеями, они начинают использовать программный SPI и жутко тормозить, так как работают с этим интерфейсом используя стандартные библиотеки ESP32 ядра для Arduino IDE.

Мы будем использовать самую лучшую на данный момент библиотеку для работы с дисплеями — TFT_eSPI (ссылка в конце статьи). Даже продвинутые разработчики Adafruit не смогли в своей Adafruit_GFX добиться таких скоростей для программного SPI.

Итак, подключаем дисплей к любым свободным контактам:

ESP32CAM212131415
TFT 1.8 ST7735RS(DC)RSTCSCLKSDA(MOSI)

Программа

Начнём с проверки работоспособности дисплея. И первый шаг — настройка параметров библиотеки. Это необходимо сделать вручную, открыв в текстовом редакторе файл:

путь_к_библиотекам\Arduino\libraries\TFT_eSPI-master\User_Setup.h

Можно удалить всё содержимое этого файла и оставить только следующие строки:

#define USER_SETUP_INFO "User_Setup"

#define ST7735_DRIVER

#define TFT_WIDTH  128
#define TFT_HEIGHT 160

#define ST7735_BLACKTAB

#define TFT_MISO 16
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS   15
#define TFT_DC    2
#define TFT_RST  12

#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_GFXFF

#define SMOOTH_FONT

#define SPI_FREQUENCY  27000000

#define SPI_READ_FREQUENCY  20000000
#define SPI_TOUCH_FREQUENCY  2500000

Назначение параметров хорошо описаны в файле User_Setup. Не поленитесь перевести и прочитать, если планируете работать с другими дисплеями.

О модификациях ST7735

Указанные настройки подойдут только для дисплея 160×128 с контроллером ST7735. Но важно отметить, что в природе имеется несколько разновидностей таких модулей. DIY-энтузиастами замечена корреляция между цветом ярлычка за защитной плёнке дисплея и его особенностями.

Разработчики библиотеки выделили несколько разновидностей дисплеев и дали пользователю возможность выбрать. В нашем конкретном случае оказалось, что лучше всего подходит такая настройка:

#define ST7735_BLACKTAB

BlackTab означает, что на матрице была наклеена плёнка с ярлычком чёрного цвета. Вот только фактически на дисплее был зелёный ярлычок 🙂 Так что если у вас на дисплее появляются полосы шума по краям или ещё какие-то аномалии изображения, то стоит повыбирать разные модели в данном разделе настроек.

Сохраняем файл, идём дальше.

Тестовый скетч для дисплея

Открываем стандартный пример из библиотеки TFT_eSPI под названием Arduino_Life. Это модель клеточного автомата, описанная впервые английским математиком Джоном Конвеем в далёком 1970.

В коде примера ничего менять не нужно, всё уже настроено в User_Setup. Для правильной работы библиотеки в Arduino Ide необходимо выбрать целевую плату «ESP32 Wrover Kit (all versions)«. Вероятно, можно подобрать и какие-то другие, но на родной для этого контроллера «AiThinker ESP32-CAM» дисплей не оживал.

Загружаем на ESP32CAM и наблюдаем.

Получение изображения с камеры

Всё, что нам нужно делать для достижения главной цели, это брать кадр из камеры и тут же выводить его на дисплей.

Инициализацию камеры можно подсмотреть в стандартном примере для ESP32 плат:

ESP32/Camera/CameraWebServer

В этих настройках нам важен параметр:

config.frame_size = FRAMESIZE_QQVGA;

Разрешение QQVGA соответствует 160×120 точкам, почти в размер нашего дисплея. Также можно поиграть со степенью сжатия jpeg_quality. Хотя на скорость работы системы это особо не повлияет.

Полный конфиг камеры для нашего эксперимента будет выглядеть так:

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 10000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_QQVGA;
  config.jpeg_quality = 8;
  config.fb_count = 1;

Инициализация камеры осуществляется вызовом функции:

esp_camera_init(&config);

Изображение хранится в буфере, который можно добыть так:

camera_fb_t* fb = esp_camera_fb_get()

А вот дальше интересно. Ведь камера нам возвращает кадр сжатый с помощью jpeg. Такой формат дисплей, разумеется, есть не захочет. Нужно декодировать jpeg в обычный bmp.

Преобразование JPEG в BMP

На этом шаге мы знакомимся с библиотекой TJpg_Decoder, которую можно скачать из репозитория Arduino IDE или по ссылке в конце статьи (установка библиотеки в Arduino IDE).

Первая функция, которая нам пригодится:

TJpgDec.setCallback(tft_output);

Она вызывается на этапе инициализации. В качестве аргумента — указатель на другую функцию, которая будет вызываться по завершении процедуры сжатия.

Другая функция — drawJpg, собственно, осуществляет преобразование:

TJpgDec.drawJpg(0, 0, (const uint8_t*)fb->buf, fb->len); 

Таким образом, каждый раз, получив кадр из камеры мы должны совершать преобразование. По завершении работы drawJpg, будет вызвана другая — которая передаст результат в дисплей.

Итоговый код

#include "esp_camera.h"
#include <SPI.h>
#include <TFT_eSPI.h>
#include <TJpg_Decoder.h>

#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

TFT_eSPI tft = TFT_eSPI();

void setup() {
  displayInit();
  cameraInit();
}

void loop() {
  showingImage();
}

void cameraInit(){
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 10000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_QQVGA; //320x240
  config.jpeg_quality = 8;
  config.fb_count = 1;

  esp_camera_init(&config);
}

void showingImage(){
  camera_fb_t* fb = esp_camera_fb_get();
  TJpgDec.drawJpg(0, 0, (const uint8_t*)fb->buf, fb->len);
  esp_camera_fb_return(fb);
}

bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap){
  if( y>= tft.height()) return 0;
  tft.pushImage(x, y, w, h, bitmap);
  return 1;
}

void displayInit(){
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_WHITE);

  TJpgDec.setJpgScale(1);
  TJpgDec.setSwapBytes(true);
  TJpgDec.setCallback(tft_output);
} 

Загружаем программу на ESP32CAM и наблюдаем результат:

Полезные ссылки

https://github.com/Bodmer/TJpg_Decoder

https://github.com/Bodmer/TFT_eSPI


Изменено:

Видео с ESP32CAM на TFT дисплей ST7735: 3 комментария

  1. Почему сразу в Pixel_format не выбрать bmp вместо jpg, чтобы избежать лишней траты ресурсов на конвертирование? Bmp формат любые камеры для esp32 поддерживают, если верить даташитам.

  2. Схему бы.. уже которую неделю бьюсь, пытаюсь и по видео и на стопкадрах понять, что куда подключено, сам код компилируется без проблем, схема из статьи неполная., так как даже на видео видно что проводов используется больше, при этом в комментах еще одна схема подключения. при этом по этим схемам подключить не получается.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.