Видео с 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


Изменено:

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

Ваш адрес email не будет опубликован.