Подключение TFT-дисплея ST7735 к ESP32: вывод текста, графики и изображений

          В рамках этой статьи мы подробно разберём, как подключить TFT-дисплей ST7735 к отладочной плате ESP32 через SPI, настроить вывод текста (включая кириллицу), отображать графику и картинки. Рассмотрим работу с библиотеками Adafruit GFX в среде разработки VS Code + PlatformIO, используя фреймворк Arduino.

Содержание


Описание модуля

          Для экспериментов будем использовать модель дисплея с расширением 128×128 и диагональю экрана 1,44 дюйма:

          Контроллер ST7735 имеет различные модификации (с индексом R или S), которые отличаются процедурой конфигурирования. К этому мы ещё вернёмся, когда будем писать код.

          Подключение осуществляется по SPI-интерфейсу, что позволяет передавать данные с частотой от 1 до 27 МГц (поскольку частота процессора ESP32 может быть настроена на 80, 160 и 240 МГц). Благодаря этому есть возможность при определённых настройках обновлять экран почти незаметно.

          Контроллер дисплея поддерживает 18-битную цветность, что позволяет отображать до 262 144 оттенков. Но на практике разработчики часто работают с 16-битным форматом (три цветовых пространства: RGB444, RGB565 и RGB888 с поддержкой до 65 тысяч цветов) для удобства, а контроллер автоматически улучшает цветопередачу.

          Для питание модуля требуется 3,3 В и ток до 5 мА. Также к этому надо прибавить ток потребления светодиодной подсветки. Она может потреблять от 20 до 60 мА, поэтому будем использовать токоограничивающий резистор на 220 Ом.


Схема подключения

          Для работы с ЖКИ будем использовать аппаратный SPI-интерфейс на отладочной плате NodeMCU-32S (38 pin), которая построена на модуле ESP-WROOM-32. Ниже представлена схема подключения дисплея ST7735 к ESP32, а также фотография собранного макета:

Схема подключения ST7735 к ESP32

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


Подготовка проекта в VS Code + PlatformIO

          В качестве среды разработки воспользуемся расширением PlatformIO для Visual Studio Code.

          Создаём новый проект. При выборе отладочной платы необходимо указать DOIT ESP32 DEVKIT V1, которая соответствует NodeMCU-32S (38 pin) на базе модуля ESP-WROOM-32. Применяемый фреймворк — Arduino, чтобы обеспечить совместимость с соответствующей IDE.

          Необходимо в platformio.ini  прописать базовые моменты проекта:

[env:esp32doit-devkit-v1]
platform = espressif32
board = esp32doit-devkit-v1
framework = arduino
monitor_speed = 9600

          Теперь добавим библиотеки, которые обеспечат работу с дисплеем. Чтобы открыть встроенный менеджер библиотек, нужно перейти во вкладку PIO Home и выбрать раздел Libraries. Через поисковую форму ищем, а потом добавляем:

          — Adafruit GFX Library;

          — Adafruit ST7735 and ST7789 Library.

          Ниже представлены скриншоты, поясняющие этапы добавления библиотеки Adafruit GFX Library:

          Аналогично добавляем Adafruit ST7735 and ST7789 Library:

          Указанные библиотеки должны отобразиться в platformio.ini:

          Теперь рассмотрим русификатор текста. Библиотека Adafruit GFX Library поддерживает отображение только латиницы. Чтобы выводить кириллицу, нужно скачать архив специальной библиотеки AdafruitGFXRusFonts на GitHub:

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

          Важный момент. Чтобы воспользоваться этими шрифтами, нужно добавить папку FontsRus в директорию библиотеки Adafruit GFX Library. Она у нас уже установлена через менеджер. Но необходимо её добавить в директорию lib нашего проекта в Platform IO. Для этого скачиваем архив библиотеки Adafruit GFX Library на GitHub:

          Распакуйте архив и через проводник положите папку (переименуйте её, убрав в конце «master») в директорию lib проекта. Должно выглядеть потом так:

          Теперь скопируйте папку FontsRus в только что добавленную Adafruit GFX Library:

          Можно приступать к написанию кода.


Текст на латинице

          В исходник main.cpp добавим базовый код, который будет выводить слова на латинице. Для пояснений приведены подробные комментарии:

#include <Adafruit_GFX.h>		
#include <Adafruit_ST7735.h> 
#include <SPI.h>

// Определение пинов для подключения дисплея
#define TFT_CS     5
#define TFT_RST    4 
#define TFT_DC     2
#define TFT_SCLK   18
#define TFT_MOSI   23

// Инициализация контроллера дисплея
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

void setup(void) {
  // инициализация последовательного порта UART для отладки в терминале
  Serial.begin(9600);

  // Инициализация дисплея
  tft.initR(INITR_BLACKTAB); // Инициализация дисплея (обычно для 1.44")
  tft.fillScreen(ST7735_BLACK); // Заливка экрана черным

  // стартовая позиция отображаемого текста (в пикселях)
  // первое число — отступ слева
  // второе число — отступ сверху
  tft.setCursor(10, 10);  

  tft.setTextColor(ST7735_WHITE);  // цвет текста
  tft.setTextSize(1);  // размер шрифта
  tft.println("SPRYTRON.RU");
}

//
void loop() {
  
}

          Чтобы скомпилировать код, необходимо нажать на пиктограмму в виде галочки в нижнем левом углу окна:

          В первый раз компиляция может занять длительное время — порядка 30 секунд. В дальнейшем, при малом количестве изменений, перекомпиляция будет занимать до 10 секунд. При отсутствии ошибок сборка должна пройти успешно:

          Чтобы прошить микроконтроллер, подключите отладочную плату к компьютеру коротким USB-кабелем (настоятельно рекомендуется длина менее 1 метра). Убедитесь, что драйвер для USB-UART преобразователя успешно установлен на компьютер. На используемой в нашем эксперименте отладочной плате установлен чип CP2102 от компании Silicon Labs.

Читайте также:  Как сделать диктофон на ESP32 с микрофоном INMP441 и записью на microSD

          Запуск процесса прошивки осуществляется нажатием пиктограммы в виде стрелочки, указывающей направо:

          Контроллер прошивается достаточно долго — примерно 10…15 секунд. Когда в терминале появится надпись «Connecting…», начнут появляться точки после названия COM-порта, к которому подключена отладочная плата. В этот момент нужно успеть зажать на пару секунд на плате кнопку «BOOT», которая справа от microUSB-разъёма.

          Если успеть зажать, то стартует процесс прошивки, и в терминале будет отображаться прогресс:

          При успешной загрузки в терминале появится соответствующее сообщение, а отладочная плата автоматически перезагрузится и начнёт исполнять прошивку.


Смещение зоны отображения

          На фото выше видно, что в левой части экрана, рядом с зелёным ярлыком защитной плёнки, есть полоска шумов синего цвета. Это указывает на то, что зона отображение несколько смещена вправо. Причина — некорректная инициализация дисплея посредством метода tft.initR.

          Эта связано с тем, что у разных партий и модификаций контроллера (с индексом R или S) дисплея свои особенности при инициализации. Чтобы решить проблему, попробуйте разные варианты функции:

tft.initR(INIT_BLACKTAB);
tft.initR(INIT_REDTAB);
tft.initR(INIT_GREENTAB);

          Изначально у нас в коде стоял аргумент INIT_BLACKTAB. Попробуем использовать INIT_REDTAB:

          Как видим, шумы слева исчезли. Значит, подобран правильный аргумент для функции инициализации tft.initR.


Смена ориентации (поворот) изображения на экране

          Мы вывели текст на латинице, и на фотографиях выше видно, что неплохо бы вывод надписи перевернуть «вверх ногами». При помощи метода tft.setRotation() можно задать нужную ориентацию изображению:

tft.setRotation(0);  // стандартная ориентация (альбомная)
tft.setRotation(1);  // поворот на 90 градусов (портретная)
tft.setRotation(2);  // перевернутая альбомная на 180 градусов
tft.setRotation(3);  // перевёрнутая портретная (поворот на 270 градусов относительно стандартной ориентации)

          Эту строчку нужно прописать при инициализации дисплея:

  tft.initR(INITR_REDTAB);  // Инициализация дисплея
  tft.setRotation(2); // Переворот на 180 градусов
  tft.fillScreen(ST7735_BLACK); // Заливка экрана черным

Текст на кириллице

          Чтобы вывести русский текст на экран, необходимо добавить в начальную часть кода строчку, регламентирующее подключение шрифта из папки FontsRus, которую мы добавили ранее.

          В папке FontsRus есть множество различных шрифтов. Чтобы выбрать какой-нибудь из них, необходимо в include прописать название .h-файла для конкретного шрифта. Например, выберем шрифт FreeMono с размером 8:

          Внутри прописан массив, описывающий шрифт:

          Таким образом, в include пишем следующее:

#include "FontsRus/FreeMono8.h"  // шрифт FreeMono, кириллица, цифра 8 - размер

          Теперь, чтобы вывести на экран слова, нужно использовать следующий код:

// указание шрифта, идентификатор соответствует названию массива
// цифра 8 означает размер шрифта
tft.setFont(&FreeMono8pt8b);

// текст на кириллице 
tft.print("Александр Степанович Попов (1859-1906) - физик, электротехник, создатель радио.");

// необходимо объявить, что возвращаемся к шрифту по умолчанию
// если далее будет отображаться текст на латинице
tft.setFont();

          Полный листинг кода:

#include <Adafruit_GFX.h>		
#include <Adafruit_ST7735.h> 
#include <SPI.h>
#include "FontsRus/FreeMono8.h"  // шрифт FreeMono, кириллица, цифра 8 - размер

// Определение пинов для подключения дисплея
#define TFT_CS     5
#define TFT_RST    4 
#define TFT_DC     2
#define TFT_SCLK   18
#define TFT_MOSI   23

// Инициализация контроллера дисплея
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

void setup(void) {
  // инициализация последовательного порта UART для отладки в терминале
  Serial.begin(9600);

  // Инициализация дисплея
  tft.initR(INITR_REDTAB);
  tft.setRotation(2); // Переворот на 180 градусов
  tft.fillScreen(ST7735_BLACK); // Заливка экрана черным

  // стартовая позиция отображаемого текста (в пикселях)
  // первое число — отступ слева
  // второе число — отступ сверху
  tft.setCursor(0, 10);  

// указание шрифта, идентификатор соответствует названию массива
// цифра 8 означает размер шрифта
tft.setFont(&FreeMono8pt8b);

// текст на кириллице 
tft.print("Александр Степанович Попов (1859-1906) - физик, электротехник, создатель радио.");

// необходимо объявить, что возвращаемся к шрифту по умолчанию
// если далее будет отображаться текст на латинице
tft.setFont();
}

// основной бесконечный цикл программы 
void loop() {
  
}

          Важно отметить, что метод tft.print не поддерживает отображение букв «Ё» и «ё». Для исправления этой ситуации необходимо добавить в наш проект файл utf8rus2.ino из скаченного ранее репозитория AdafruitGFXRusFonts:

          Добавляем в папку src, рядом с основным модулем — main.cpp. При этом среда разработки в нижнем правом углу покажет сообщение, что проект не поддерживает файлы с расширением .INO. Рекомендуется переименовать из utf8rus2.ino в utf8rus2.cpp.

          Полный листинг кода utf8rus2.cpp:

#define maxString 100 // ограничиваем строку шириной экрана
char target[maxString + 1] = "";

char *utf8rus2(char *source)
{
  int i,j,k;
  unsigned char n;
  char m[2] = { '0', '\0' };
  strcpy(target, ""); k = strlen(source); i = j = 0;
  while (i < k) {
    n = source[i]; i++;

    if (n >= 127) {
      switch (n) {
        case 208: {
          n = source[i]; i++;
          if (n == 129) { n = 192; break; } // перекодируем букву Ё
          break;
        }
        case 209: {
          n = source[i]; i++;
          if (n == 145) { n = 193; break; } // перекодируем букву ё
          break;
        }
      }
    }

    m[0] = n; strcat(target, m);
    j++; if (j >= maxString) break;
  }
  return target;
}

          Последуем совету — переименуем файл (изменим расширение).

Читайте также:  Датчик расхода воды YF-S401

          Далее нужно доработать и адаптировать его, чтобы исправить подсвечивающиеся красной линией предупреждения:

          Нас предупреждают о том, что выделенные функции не определены. Поэтому в первых строчках кода utf8rus2.cpp объявим недостающую библиотеку cstring:

#include <cstring>

          Красные подчеркивания с предупреждениями исчезли.

          Теперь необходимо адаптировать этот файл для применения в основном коде. В исходнике main.cpp будет использоваться функция utf8rus2 для отображения надписей на кириллице с буквами «Ё» и «ё». Для импорта функции создадим заголовочный файл (header) utf8rus2.h, который необходимо добавить в папку include:

#ifndef UTF8RUS2_H
#define UTF8RUS2_H
#include <cstring> // для strlen и strcpy
#define maxString 100
char *utf8rus2(char *source);
#endif

          Объявим этот файл в main.cpp и utf8rus2.cpp:

#include “utf8rus2.h”

          Теперь, чтобы написать слова с целевыми буквами, нужно в аргумент метода tft.print() вставить функцию utf8rus2(), у которой, в свою очередь, нужный текст:

tft.print(utf8rus2(“Ёлка, ёмкость”));

Размер шрифта, цвет текста и фона

          tft.setTextSize — размер шрифта можно задавать при помощи данного метода. Это особенно актуально для букв на латинице. Вывод букв на кириллице дополнительно определяется шрифтом, выбранным из папки FontsRus (числовой индекс в названии шрифта соответствует его размеру).

          tft.setTextColor — с помощью этого метода можно определять цвет для букв и буквенного фона. В библиотеке Adafruit_ST7735 доступны стандартные цвета, определенные как 16-битные значения в формате RGB565 (5 бит для красного, 6 для зеленого, 5 для синего).

tft.setTextColor(ST7735_RED);  // буквы красного цвета

          Ниже представлена таблица предустановленных цветов:

          Если нужного цвета нет в списке, можно создать свой с помощью макроса tft.Color565(R, G, B):

uint16_t special_color = tft.Color565(255, 128, 0);  // тёмно-оранжевый (R=255, G=128, B=0)
tft.setTextColor(special_color);

          При желании можно задать фон для букв:

// Красный текст на зелёном фоне
  tft.setTextColor(ST7735_RED, ST7735_GREEN);
  tft.println("Red on Green");

          tft.fillScreen — посредством этого метода задаётся общий фон для всего экрана целиком:

tft.fillScreen(ST7735_BLACK);  // заливка экрана чёрным

          Полный листинг кода с рассмотренными функциями:

#include <Adafruit_GFX.h>		
#include <Adafruit_ST7735.h> 
#include <SPI.h>

// Определение пинов для подключения дисплея
#define TFT_CS     5
#define TFT_RST    4 
#define TFT_DC     2
#define TFT_SCLK   18
#define TFT_MOSI   23

// Инициализация контроллера дисплея
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

void setup(void) {
  // инициализация последовательного порта UART для отладки в терминале
  Serial.begin(9600);

  // Инициализация дисплея
  tft.initR(INITR_REDTAB);
  tft.setRotation(2); // Переворот на 180 градусов
  tft.fillScreen(ST7735_BLUE); // Заливка экрана синим

  // стартовая позиция отображаемого текста (в пикселях)
  // первое число — отступ слева
  // второе число — отступ сверху
  tft.setCursor(0, 20);  

  tft.setTextColor(ST7735_WHITE);
  tft.setTextSize(1);
  tft.println("Welcome");

  tft.setCursor(0, 30);  
  tft.setTextColor(ST7735_RED);
  tft.setTextSize(2);
  tft.println("to");

  tft.setCursor(0, 50);  
  tft.setTextColor(ST7735_GREEN);
  tft.setTextSize(3);
  tft.println("New");

  tft.setCursor(0, 70);  
  tft.setTextColor(ST7735_BLACK);
  tft.setTextSize(4);
  tft.println("York");

  tft.setCursor(0, 100); 
  tft.setTextColor(ST7735_RED, ST7735_GREEN);
  tft.setTextSize(1);
  tft.println("Red on Green");


}

// основной бесконечный цикл программы 
void loop() {
  
}

Отображение графики

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

tft.drawLine(x0, y0, x1, y1, color); // линии
tft.drawRect(x, y, width, height, color); // прямоугольник (контур)
tft.fillRect(x, y, width, height, color); // залитый прямоугольник
tft.drawCircle(x, y, radius, color); // круг (контур)
tft.fillCircle(x, y, radius, color); // залитый круг
tft.drawTriangle(x0, y0, x1, y1, x2, y2, color); // треугольник (контур)
tft.fillTriangle(x0, y0, x1, y1, x2, y2, color); // залитый треугольник
tft.drawPixel(x, y, color); // точка (пиксель)

         Пример кода с некоторыми фигурами:

#include <Adafruit_GFX.h>		
#include <Adafruit_ST7735.h> 
#include <SPI.h>

// Определение пинов для подключения дисплея
#define TFT_CS     5
#define TFT_RST    4 
#define TFT_DC     2
#define TFT_SCLK   18
#define TFT_MOSI   23

// Инициализация контроллера дисплея
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

void setup(void) {
  // инициализация последовательного порта UART для отладки в терминале
  Serial.begin(9600);

  // Инициализация дисплея
  tft.initR(INITR_REDTAB);
  tft.setRotation(2); // Переворот на 180 градусов
  tft.fillScreen(ST7735_WHITE); // Заливка экрана белым

  // стартовая позиция отображаемого текста (в пикселях)
  // первое число — отступ слева
  // второе число — отступ сверху
  tft.setCursor(30, 5);

  // Выводим надпись
  tft.setTextColor(ST7735_BLACK);
  tft.setTextSize(1);
  tft.println("SPRYTRON.RU");
 
  // Рисуем линии
  tft.drawLine(0, 0, tft.width(), tft.height(), ST7735_BLUE);  // Диагональная синяя линия
  tft.drawLine(tft.width(), 0, 0, tft.height(), ST7735_RED); // Диагональная красная линия

  // Рисуем прямоугольники
  tft.drawRect(10, 20, 50, 30, ST7735_GREEN);  // Контур зелёного прямоугольника
  tft.fillRect(70, 20, 50, 30, ST7735_YELLOW); // Залитый жёлтый прямоугольник

  // Рисуем круги
  tft.drawCircle(40, 70, 20, ST7735_CYAN);    // Контур голубого круга
  tft.fillCircle(100, 70, 20, ST7735_MAGENTA); // Залитый пурпурный круг

  // Рисуем треугольники
  tft.drawTriangle(20, 100, 40, 120, 60, 100, ST7735_ORANGE);  // Контур оранжевого треугольника
  tft.fillTriangle(80, 100, 100, 120, 120, 100, ST7735_CYAN); // Залитый голубой треугольник

}

// основной бесконечный цикл программы 
void loop() {
  
}

Вывод изображений

         Итак, мы подключили ST7735 к ESP32, вывели текст и простую графику. Далее разберём, как отображать полноцветные картинки и фотографии (растровые изображения).

Читайте также:  Датчик температуры и влажности ZS05

        Есть два важных условия, которые надо соблюдать для вывода картинки на экран:

         — формат: RGB565 (16-битный цвет, 5 бит красный, 6 бит зелёный, 5 бит синий);

         — размер: не должен превышать разрешение дисплея (для нашего экземпляра 128×128).

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

         Чтобы картинка не искажалась при выводе на экран, её размер должен соответствовать пропорции 1:1. Например, исходное изображение имеет размер 1026×1246. Это соответствует 1:1. Как и у нашего ЖКИ: 128×128 — 1:1.

         Для того, чтобы конвертировать изображение, можно воспользоваться онлайн сервисом FileToCArray:

         Загружаем нашу картинку через кнопку «Выбор файла» и видим набор настраиваемых параметров:

         Нас как минимум интересует два параметра: формат (Palette mod) и размер (Resize). Формат выбираем из выпадающего списка:

16bit RRRRRGGGGGGBBBBB (2byte/pixel) — как раз соответствует RGB565

         Размер ставим максимальный для нашего экрана: 128×128. И можем смело нажимать кнопку «Convert» внизу слева под настройками.

         Прокручиваем страничку вниз и видим, что сгенерировался массив данных:

         Сохраняем данные в виде файла, нажав соответствующую кнопку «Save as file». Загруженный модуль кладём в папку include нашего проекта. Подключаем в основном скрипте main.cpp:

         В основном коде добавляем функцию, которая отвечает за вывод изображения на экран:

         Функция имеет 5 аргументов:

         0 — начальная позиция курсора, который выделяет зону отображения, по оси X;

         0 — начальная позиция по оси Y;

         my_image_two — название массива данных. Оно же соответствует названию картинки, которую мы загружали в сервис онлайн конвертера. Также соответствует названию загруженного файла;

         128 — конечная позиция по оси X;

         128 — конечная позиция по оси Y.

         Среда программирования красным подчеркивает метод tft.drawRGBBitmap, потому что модуль my_image_two.h имеет не корректно объявленный массив:

         Его объявление нужно скорректировать:

const uint16_t my_image_two[MY_IMAGE_TWO_HEIGHT * MY_IMAGE_TWO_WIDTH] PROGMEM  = { 
// массив данных
};

         Полный листинг скетча:

#include <Adafruit_GFX.h>		
#include <Adafruit_ST7735.h> 
#include <SPI.h>
#include <my_image_two.h>

// Определение пинов для подключения дисплея
#define TFT_CS     5
#define TFT_RST    4 
#define TFT_DC     2
#define TFT_SCLK   18
#define TFT_MOSI   23

// Инициализация контроллера дисплея
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

void setup(void) {
  // инициализация последовательного порта UART для отладки в терминале
  Serial.begin(9600);

  // Инициализация дисплея
  tft.initR(INITR_REDTAB);
  tft.setRotation(2); // Переворот на 180 градусов
  tft.fillScreen(ST7735_WHITE); // Заливка экрана белым

  // Отображение картинки
  tft.drawRGBBitmap(0, 0, my_image_two, 128, 128);

}

// основной бесконечный цикл программы 
void loop() {
  
}