Интеллектуальная панель Sunton 7″ на базе ESP32S3

image
Отображение информации это один из важнейших аспектов автоматизированных систем.

В IoT системах роль терминала чаще всего выполняет смартфон или компьютер. Но иногда и в умном доме удобно иметь автономную операторскую панель

На сайте Makerfabs в разделе OpenHardware появились интеллектуальные дисплеи Sunton 4.3″, 5″ и 7″ на базе ESP32S3. Из них и герой обзора — панель ESP32–8048S070, купленная на Алиэкспресс за 3000 руб. вместе с доставкой.

Чем мне приглянулось данное устройство:


  • Использование нового ESP32S3 позволило подключить дисплей по аппаратному параллельному 16-ти битному интерфейсу, что позволило достичь неплохой скорости выдачи информации на экран (по сравнению с тем же SPI)
  • В отличие тех же дисплеев Nextion, обязательно подключаемых к внешнему контроллеру, данный дисплей может работать как автономное устройство
  • Открытая понятная схемотехника и программирование напрямую в Arduino IDE или PlatformIO (Те же Nextion или DWIN используют для программирования свой проприоритарный софт)
  • Ну и немаловажный фактор для DIY, это цена.


nargque8wkcrivx7s7xdwodzt2w.jpeg

5tx0crm5q37o0fdw3e_ptb4faey.jpeg

r52fbffhwof1dstjyejcxpqdkra.jpeg

Ну, а что там с конкурентами?


Свел в табличку доступные аналогичные дисплеи. Первые два, Nextion и DWIN, программируются своим специализированным софтом и работают только с внешним контроллером по UART интерфейсу. Вывести на них произвольную картинку из интернета не так и просто.

Дисплей от ELECROW очень близок к обозреваемому по схемотехнике и функционалу. Только при покупке на Алиэкспресс дороже почти в полтора раза. И использует другую библиотеку LovyanGFX.


(*) — Цена с доставкой в Пермь на момент написания статьи

Железо


  • Микроконтроллер — ESP32-S3-WROOM-1-N16R8 (16Мб flash + 8Мб PSRAM)
  • Сенсорный контроллер — емкостной пятиточечный GT911 с интерфейсом I2C распаянный на шлейфе сенсорной панели
  • Дисплей TFT 7.0″ 65K цветов 800×480 с драйвером EK9716 c интерфейсом RGB565 16 бит
  • ЦАП с усилителем MAX98357
  • USB/UART CH320C (аналог «народного» CH340G, но без внешнего кварца) с USB-C
  • Три импульсных стабилизатора — 3.3В для MCU, питание дисплея, питания подсветки дисплея


Дополнительная периферия


sqjovgvkz_uikktvl9m2ncfgjte.jpeg

  • Разъем UART0 (Параллельно CH340) HC 1.25×4
  • Разъем под TF карта (SPI IO10, IO11, IO12, IO13)
  • Разъем SPI (IO10, IO11, IO12, IO13) HC 1.25×4
  • Разъем UART (IO17, IO18) + USB (IO19, IO20) HC 1.25×4
  • Разъем I2C (IO17, IO18) HC 1.25×4
  • Разъем I2C (IO17, IO18) SH 1.0×4
  • Разъем на внешний динамик от ЦАП JST 1.25×2


Принципиальная схема


ocntg-_bicslm1phouic-frbjag.png

56bv11ipp7rogaik45ltebnwgy0.png

Софт


Отдельного WYSISYG редактора интерфейса, как у NEXTION, у данного дисплея нет. Хотя можно использовать Squareline Studio, генерирующий код для фреймвока LVGL. Вместо этого предлагается программирование в среде Arduino IDE. В магазине есть ссылка на папку на гугл-диске с файлами для всей линейки дисплеев данного производителя.

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

Последние версии ставятся из менеджера библиотек Arduino IDE
aqr8afclqxlplz29jql7w4s7c6u.jpeg

Программирование


Для программирования нужно взять последнюю ESP32 Core for Arduino IDE с поддержкой новых модулей ESP32S2, ESP32S3 и ESP32C3

Если взять библиотеки из архива, то в принципе, примеры собираются и работают. Но когда я поставил последнюю версию библиотеки Arduino_GFX от Moononournatio, выяснилось, что формат функций работы с шиной RGB 16 bit поменялся. Можно конечно было остаться на старой библиотеке, но есть вероятность рано или поздно получить несовместимость. И придется программировать в старой IDE со старым набором библиотек. Лучше разобраться.

Итак, в старой версии мы имеем такое определение шины и экземпляра GFX с пинами нашего дисплея:

Arduino_ESP32RGBPanel *bus = new Arduino_ESP32RGBPanel(
    GFX_NOT_DEFINED /* CS */, GFX_NOT_DEFINED /* SCK */, GFX_NOT_DEFINED /* SDA */,
    41 /* DE */, 40 /* VSYNC */, 39 /* HSYNC */, 42 /* PCLK */,
    14 /* R0 */, 21 /* R1 */, 47 /* R2 */, 48 /* R3 */, 45 /* R4 */,
    9 /* G0 */, 46 /* G1 */, 3 /* G2 */, 8 /* G3 */, 16 /* G4 */, 1 /* G5 */,
    15 /* B0 */, 7 /* B1 */, 6 /* B2 */, 5 /* B3 */, 4 /* B4 */
);
// option 1:
// 7寸 50PIN 800*480
Arduino_RPi_DPI_RGBPanel *gfx = new Arduino_RPi_DPI_RGBPanel(
  bus,
    800 /* width */, 0 /* hsync_polarity */, 210 /* hsync_front_porch */, 30 /* hsync_pulse_width */, 16 /* hsync_back_porch */,
    480 /* height */, 0 /* vsync_polarity */, 22 /* vsync_front_porch */, 13 /* vsync_pulse_width */, 10 /* vsync_back_porch */,
    1 /* pclk_active_neg */, 16000000 /* prefer_speed */, true /* auto_flush */);


В документации по нашему интерфейсу видим новый формат конструктора Arduino_ESP32RGBPanel. Ну и вместо класса Arduino_RPi_DPI_RGBPanel, используемого во всех примерах, нужно теперь применять Arduino_RGB_Display. Итоговое объявление получилось
таким:

Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
    41 /* DE */, 40 /* VSYNC */, 39 /* HSYNC */, 42 /* PCLK */,
    14 /* R0 */, 21 /* R1 */, 47 /* R2 */, 48 /* R3 */, 45 /* R4 */,
    9 /* G0 */, 46 /* G1 */, 3 /* G2 */, 8 /* G3 */, 16 /* G4 */, 1 /* G5 */,
    15 /* B0 */, 7 /* B1 */, 6 /* B2 */, 5 /* B3 */, 4 /* B4 */,
    0 /* hsync_polarity */, 210 /* hsync_front_porch */, 30 /* hsync_pulse_width */, 16 /* hsync_back_porch */,
    0 /* vsync_polarity */, 22 /* vsync_front_porch */, 13 /* vsync_pulse_width */, 10 /* vsync_back_porch */);

Arduino_RGB_Display *gfx = new Arduino_RGB_Display(800, 480, rgbpanel);


Теперь любой пример можно собрать заменив параметры конструктора класса дисплея.

Для сборки всех скетчев Arduino для данного дисплея нужно выбрать в менеджере плат ESP32S3 Dev Module, а в меню модуля выбрать Flash Size — 16MB (128Mb) и PSRAM — OPI PSRAM

Примеры и LvglWidgets и lvgl_music_gt911_7.0 собираются только после того как папка demos библиотеки LVGL помещается внутрь src. Ну и файл lv_cinfig.h из каталога с примером нужно поместить в корневой каталог всех библиотек Arduino
nlcyk5fpjwqp2kalbrfhnmtohag.jpeg
a6nzhdrsd_csu5gdnq6uxf_fboq.jpeg

Отображения картинки на ArduinoGFX


Для начала нужно преобразовать саму картинку в двухмерную матрицу (768000 байт на картинку 800×460 16 бит/пиксел). Для этого рyководство AdafruitGFX отсылает на онлайновый сервис Image2Cpp.
zf2rgcm0uvxi7fmruzf0cw053fa.jpeg
Полученный результат нужно вставить в файл Img.h И положить его рядом с файлом скетча. Сам скетч предельно простой

Скетч загрузки картинки
#include 
#include "Img.h" //Файл с картинкой
#define TFT_BL 2

Arduino_ESP32RGBPanel *rgbpanel;
Arduino_RGB_Display *gfx;

void setup() { 
  Serial.begin(115200);
  delay(1000);   
// Инициализация дисплея
  rgbpanel = new Arduino_ESP32RGBPanel(
       41 /* DE */, 40 /* VSYNC */, 39 /* HSYNC */, 42 /* PCLK */,
       14 /* R0 */, 21 /* R1 */, 47 /* R2 */, 48 /* R3 */, 45 /* R4 */,
       9 /* G0 */, 46 /* G1 */, 3 /* G2 */, 8 /* G3 */, 16 /* G4 */, 1 /* G5 */,
       15 /* B0 */, 7 /* B1 */, 6 /* B2 */, 5 /* B3 */, 4 /* B4 */,
       0 /* hsync_polarity */, 210 /* hsync_front_porch */, 30 /* hsync_pulse_width */, 16 /* hsync_back_porch */,
       0 /* vsync_polarity */, 22 /* vsync_front_porch */, 13 /* vsync_pulse_width */, 10 /* vsync_back_porch */);
  gfx = new Arduino_RGB_Display(800, 480, rgbpanel);
  gfx->begin();
  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, HIGH);
  gfx->fillScreen(BLACK);
// Выдача картинки с подсчетом затраченного времени  
  uint32_t ms = millis(); 
  gfx->draw16bitRGBBitmap(0,0,(uint16_t *)sav001,800,480);
  ms = millis() - ms;
  Serial.printf("Load IMG %ld ms\n",ms);
 }
  
void loop() {
   delay(1000);
}


Выдача картинки записанной флэш-памяти ESP32 происходит за 61 мс, что дает скорость выдачи примерно 12 кадров/сек
efiv1g4mrb2lljuoxgkr3qpzfeq.jpeg

Проверка сенсорного экрана


Данный скетч реализует пять простейших экранных кнопок с фиксацией нажатия

Скетч тестирования сенсорного экрана
#include 
#include 
#include 

#define TFT_BL 2
#define BTN_HEIGHT  100
#define BTN_WIDTH   150

struct btn_t {
   int x;
   int y;
   char text[10];
   bool enable;
} btns[] = { 
  {0,100,"BTN01",false},
  {160,100,"BTN02",false},
  {320,100,"BTN03",false},
  {480,100,"BTN04",false},
  {640,100,"BTN05",false} 
};
int btn_n = 5;

Arduino_ESP32RGBPanel *rgbpanel;
Arduino_RGB_Display *gfx;
TAMC_GT911 tp(19, 20, -1, 38, 800, 480);

void setup() { 
  Serial.begin(115200); //Инициализация порта отладки
  delay(1000);   
  tp.begin(); // Инициализация сенсорного экрана
  tp.setRotation(ROTATION_INVERTED);
// Инициализация дисплея
  rgbpanel = new Arduino_ESP32RGBPanel(
       41 /* DE */, 40 /* VSYNC */, 39 /* HSYNC */, 42 /* PCLK */,
       14 /* R0 */, 21 /* R1 */, 47 /* R2 */, 48 /* R3 */, 45 /* R4 */,
       9 /* G0 */, 46 /* G1 */, 3 /* G2 */, 8 /* G3 */, 16 /* G4 */, 1 /* G5 */,
       15 /* B0 */, 7 /* B1 */, 6 /* B2 */, 5 /* B3 */, 4 /* B4 */,
       0 /* hsync_polarity */, 210 /* hsync_front_porch */, 30 /* hsync_pulse_width */, 16 /* hsync_back_porch */,
       0 /* vsync_polarity */, 22 /* vsync_front_porch */, 13 /* vsync_pulse_width */, 10 /* vsync_back_porch */);
  gfx = new Arduino_RGB_Display(800, 480, rgbpanel);
  gfx->begin();
  pinMode(TFT_BL, OUTPUT); //Включение дисплея
  digitalWrite(TFT_BL, HIGH);
  gfx->fillScreen(BLACK);
  for( int i=0; i 500 ){ // Убираем дребезг виртуальных кнопок (задержка 0.5 сек)
     if (tp.isTouched){
        ms0 = ms;
        int x = tp.points[0].x; 
        int y = tp.points[0].y;
        int n_new    = -1;
        int n_old    = -1;
        for( int i=0; i btns[i].x && x < btns[i].x+BTN_WIDTH && y > btns[i].y && y < btns[i].y+BTN_HEIGHT )n_new = i;
        }
        Serial.printf("x=%d y=%d old=%d new=%d ms=%ld\n",x,y,n_old,n_new,ms0);
        if( n_new >= 0 && n_old == n_new ){ //Если нажата та же кнопка, инвертируем состояние
           btns[n_new].enable = !btns[n_new].enable;
           displayBTN(n_new);
        }
        else if( n_new >=0  ){ //Фиксируем нажатую кнопку
           btns[n_new].enable = true;
           displayBTN(n_new);
           if( n_old >= 0 ){ //Убираем предыдущую кнопку
              btns[n_old].enable = false;
              displayBTN(n_old);
           }
        }
     }
  }
}

// Отображение одной кнопки из структуры
void displayBTN(int i){
   if( i<0 || i>= btn_n )return;
   struct btn_t btn = btns[i]; 
   uint16_t color_back = BLACK, color_border = LIGHTGREY, color_text = DARKGREEN;
   if( btn.enable ){
      color_back   = DARKGREEN;
      color_border = LIGHTGREY;
      color_text   = WHITE;     
   }
   gfx->fillRect(btn.x,btn.y,BTN_WIDTH,BTN_HEIGHT,color_back); 
   gfx->drawRect(btn.x,btn.y,BTN_WIDTH,BTN_HEIGHT,color_border);  
   gfx->drawRect(btn.x+1,btn.y+1,BTN_WIDTH-2,BTN_HEIGHT-2,color_border);   
   gfx->setCursor(btn.x+20, btn.y+30); 
   gfx->setTextSize(4);
   gfx->setTextColor(color_text);
   gfx->print(btn.text);
}


62saki8ucjobduvqxofy1_nekm4.jpeg

Что-то полезное


В интернете ходит выражение «Что вы вы не делали из ESP, получится метеостанция»)))

Вот и у меня получилась метеостанция. Корпус на 3Д принтере:
dm6kxjpfhmjse2orbfrxptt-yei.jpeg
и скетч выдающий на экран время, дату, пробки в городе, температуру в комнате, за окном и прогноз. Приводить его здесь не буду, так как все это завязано на IoT систему ThingBoard (открывается только через VPN или Proxy), на которой у меня крутится «умный дом». «Но это уже совсем другая история ©» которая выходит за рамки обзора данной панели.
qsy5xycfxz3vrte4scfsozam90i.jpeg

Выводы

Недостатки/недоработки:


  1. В схемотехнике панели мне не понравилось, что подсветку можно только включать/выключать. Гораздо лучше, если бы при помощи ШИМ можно было бы регулировать яркость.
  2. Одноканальный ЦАП. А так хотелось бы сделать интернет радио или какой-нибудь MP3 проигрыватель!
  3. Поддержка/коммунити довольно слабая, во всем приходится разбираться.


Но в целом панель просто огонь по соотношению возможности и цены. Пусть пока повисит в качестве метеостанции, но в дальнейшем планирую ей место пульта управления в умном доме.

Данная статья в моем блоге

© Habrahabr.ru