Графический IPS дисплей с драйвером Sitronix ST7789

Разрешение 240*240 пикселей
Глубина цвета  18 бит (262 144 цвета)*
Диагональ 1,3 дюйма
Размер активной области (матрица) 23,4*23,4 мм
Количество выведенных контактов 24 (под разъем) или 12 (под пайку)
Шина передачи данных Intel8080 (параллельный 8 бит), SPI (одноканальный)
Подсветка 2 белых светодиода

Особенности:

  1. Драйвер TFT дисплеев ST7789 позволяет выводить 262 тысячи цветов, используя 18 бит для кодирования цвета каждого пикселя, используя 6-6-6 бит на цвета R-G-B. 18-битные изображения хранятся и отправляются в виде 24-битных. Из-за увеличенного объема каждого пикселя, изображения хранятся в виде массивов 32-битных чисел, занимают больше памяти и дольше передаются, поэтому часто используется дополнительный режим работы драйвера с глубиной 16 бит и кодированием 5-6-5.
  2. Дисплей имеет два типа подключения к микроконтроллеру: параллельный (i8080) или последовательный (SPI). В теории параллельный интерфейс должен ускорить передачу данных более чем в 8 раз, по сравнению с SPI. В таком режиме работы оправдано использование 18 бит для цвета. Обратите внимание на отсутствие выводов для параллельного интерфейса в версии дисплея "под пайку".

Чертеж дисплея "под пайку"

Чертеж дисплея "под разъем"

 

Распиновка дисплея "под пайку"

 

Распиновка дисплея "под разъем"

 

Покупка

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

 

Используемая библиотека

Для ESP32, ESP32-S2, ESP32-S3, ESP32-C3 рекомендуется использовать библиотеку LovyanGFX. Данная библиотека предоставляет очень широкий функционал для работы с графикой и вывода изображений на дисплей. По проведенным тестам LovyanGFX имеет скорость передачи данных на дисплей до 3 раз выше, чем у Adafruit ST7789. LovyanGFX позволяет использовать многие аппаратные интерфейсы периферии в микроконтроллерах Espressif, например Direct Memory Access (DMA), благодаря которому вероятно и достигается прирост производительности в режиме SPI. Также библиотека позволяет взаимодействовать с дисплеем через параллельный интерфейс. 

Библиотека доступна в Arduino IDE, ESP-IDF, PlatformIO.

#include <Arduino.h>      // Подключение библиотеки Arduino необходмо в PldtformIO
#include <LovyanGFX.hpp>  // Подключение Библиотеки Lovyan GFX
#include <SPI.h>          // Подключение Библиотеки SPI

#define SCK 7
#define MOSI 5
#define DC   3
#define CS   9
#define RST  11

class LGFX : public lgfx::LGFX_Device
{
  lgfx::Panel_ST7789      _panel_instance;    // Предлагается выбрать контроллер дисплея
  lgfx::Bus_SPI        _bus_instance;         // Выбрать тип шины

public:

  LGFX(void)
  {
    {
      auto cfg = _bus_instance.config();

      cfg.spi_host = SPI2_HOST;              // Выбор номера шины SPI в микроконтроллере
      cfg.freq_write = 80000000;             // Частота передачи данных
      cfg.dma_channel = SPI_DMA_CH_AUTO;     // Канал DirectMemoryAccess
      cfg.pin_sclk = SCK;
      cfg.pin_mosi = MOSI;
      cfg.pin_dc   = DC;
      _bus_instance.config(cfg);
      _panel_instance.setBus(&_bus_instance);
    }
    {
      auto cfg = _panel_instance.config();
      cfg.pin_cs           =    CS;
      cfg.pin_rst          =   RST;
      cfg.panel_width      =   240;
      cfg.panel_height     =   240;
      cfg.invert           =  true;        // Инвертирование цвета (необходимо при неправильном отображении цвета)
      _panel_instance.config(cfg);
    }
    setPanel(&_panel_instance);
  }
};

LGFX display;

void setup() {
  display.init();
  display.setRotation(2);       // Поворот изображения на 180 градусов

  uint32_t timer = micros();    // Таймер для измерения скорости работы
  display.clear(TFT_BLUE);
  timer = micros()-timer;
  
  // Время за которое дисплей залит цветом равно времени передачи цвета для каждого пикселя на дисплее

  display.setTextColor(TFT_WHITE, TFT_BLUE);    // Первое значение - цвет текста, второе - цвет фона

  display.println("Hello, world!");
  display.printf("Display clear (fill) takes: %f ms", timer / 1000.0); // Поддерживается Printf
}

void loop() {
}

Пример 1 текст

 

#include <Arduino.h>

#include <LovyanGFX.hpp>

#include <SPI.h>

#include <WiFi.h> 

class LGFX : public lgfx::LGFX_Device
{
  lgfx::Panel_ST7789      _panel_instance;
  lgfx::Bus_SPI        _bus_instance;

public:

  LGFX(void)
  {
    {
      auto cfg = _bus_instance.config();

      cfg.spi_host = SPI2_HOST;
      cfg.freq_write = 80000000;
      cfg.dma_channel = SPI_DMA_CH_AUTO;
      cfg.pin_sclk = 7;
      cfg.pin_mosi = 5;
      cfg.pin_dc   = 3;

      _bus_instance.config(cfg);
      _panel_instance.setBus(&_bus_instance);
    }
    {
      auto cfg = _panel_instance.config();

      cfg.pin_cs           =    9;
      cfg.pin_rst          =    11;

      cfg.panel_width      =   240;
      cfg.panel_height     =   240;

      cfg.invert           = true;

      _panel_instance.config(cfg);
    }

    setPanel(&_panel_instance);
  }
};

#include "USB.h"
USBCDC USBSerial;

#include <GyverNTP.h>
GyverNTP ntp(3);

LGFX display;

static LGFX_Sprite canvas(&display);

uint16_t* frameBuffer;

void drawClock();

void setup() {
  USB.begin();
  USBSerial.begin();
  display.setColorDepth(18);
  display.init();

  canvas.setPsram(false);
  canvas.createSprite(240,240);
  
  display.setRotation(2);

  uint32_t timer = micros();

  display.clear(TFT_BLUE);

  timer = micros()-timer;

  display.setTextColor(TFT_WHITE, TFT_BLUE);

  

  display.printf("Color Depth: %d Timer: %f\n", display.getColorDepth(), timer/1000.0);
  display.printf("Display clear (fill) takes: %f ms\n", timer / 1000.0);

  display.print("Connecting to WiFi");

  WiFi.begin("SSID", "PASSWORD");

  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
    display.print(".");
  }
  display.println();
  display.println("Connected!");

  ntp.setHost("ntp.msk-ix.ru");

  ntp.begin();
  ntp.tick();

  ntp.updateNow();

  delay(500);

  display.clear(TFT_BLACK);

  canvas.setTextColor(TFT_GREEN, TFT_BLACK);

  canvas.setFont(&fonts::Orbitron_Light_24);

  canvas.setTextSize(1);

}

void loop() {
  ntp.tick();
  canvas.clear(TFT_BLACK);
  drawClock();
}

void drawClock() {
  canvas.setColor(TFT_CYAN);
  int halfScreen = display.width() >> 1;
  int clockRad =  halfScreen - 40;
  canvas.drawCircle(halfScreen, halfScreen, clockRad);
  canvas.drawCircle(halfScreen, halfScreen, clockRad+1);
  for(int i = 0; i < 12; i++) {
    float angle = (i / 12.0) * PI * 2.0 + HALF_PI;
    int x1 = halfScreen - cos(angle) * clockRad;
    int y1 = halfScreen - sin(angle) * clockRad;
    int x2 = halfScreen - cos(angle) * (clockRad - 20);
    int y2 = halfScreen - sin(angle) * (clockRad - 20);
    canvas.setColor(TFT_CYAN);
    canvas.drawWideLine(x1, y1, x2, y2, 1, TFT_CYAN);


    x1 = halfScreen - cos(angle) * (clockRad+25) - (canvas.textWidth(String(i)) >> 1);
    y1 = halfScreen - sin(angle) * (clockRad+25) - (canvas.fontHeight()>>1);

    canvas.setCursor(x1, y1);

    USBSerial.printf("num: %d len: %d\n", i, canvas.textWidth(String(i)));
    canvas.print(String(i));
  }

  for(int i = 0; i < 60; i++) {
    float angle = (i / 60.0) * PI * 2.0 + HALF_PI;
    int x1 = halfScreen - cos(angle) * (clockRad - 1);
    int y1 = halfScreen - sin(angle) * (clockRad - 1);
    int x2 = halfScreen - cos(angle) * (clockRad - 10);
    int y2 = halfScreen - sin(angle) * (clockRad - 10);

    canvas.setColor(TFT_CYAN);
    canvas.drawWideLine(x1, y1, x2, y2, 1, TFT_CYAN);
  }
  int hour = ntp.hour();
  int minute = ntp.minute();
  int ms = ntp.second()*1000+ntp.ms();

  canvas.setColor(TFT_RED);
  float angle = (ms / 60000.0) * PI * 2.0 + HALF_PI;
  int x1 = halfScreen - cos(angle) * (clockRad-5);
  int y1 = halfScreen - sin(angle) * (clockRad-5);
  canvas.drawWideLine(x1, y1, halfScreen, halfScreen, 1.2, TFT_RED);

  canvas.setColor(TFT_GREEN);
  angle = (minute / 60.0) * PI * 2.0 + HALF_PI;
  x1 = halfScreen - cos(angle) * (clockRad-30);
  y1 = halfScreen - sin(angle) * (clockRad-30);
  canvas.drawLine(halfScreen, halfScreen, x1, y1);
  canvas.drawWideLine(x1, y1, halfScreen, halfScreen, 2, TFT_GREEN);

  canvas.setColor(TFT_MAGENTA);
  angle = (hour / 12.0) * PI * 2.0 + HALF_PI;
  x1 = halfScreen - cos(angle) * (clockRad-50);
  y1 = halfScreen - sin(angle) * (clockRad-50);
  canvas.drawWideLine(x1, y1, halfScreen, halfScreen, 2, TFT_MAGENTA);

  canvas.pushRotatedWithAA(0, -1);
}

пример 2 часы