В рамках этой статьи мы подробно разберём, как подключить TFT-дисплей ST7735 к отладочной плате ESP32 через SPI, настроить вывод текста (включая кириллицу), отображать графику и картинки. Рассмотрим работу с библиотеками Adafruit GFX в среде разработки VS Code + PlatformIO, используя фреймворк Arduino.
Содержание
- Описание модуля
- Схема подключения
- Подготовка проекта в VS Code + PlatformIO
- Текст на латинице
- Смещение зоны отображения
- Смена ориентации (поворот) изображения на экране
- Текст на кириллице
- Размер шрифта, цвет текста и фона
- Отображение графики
- Вывод изображений (картинки, фотографии)
Описание модуля
Для экспериментов будем использовать модель дисплея с расширением 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, а также фотография собранного макета:


При необходимости можно переназначать пины для 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.
Запуск процесса прошивки осуществляется нажатием пиктограммы в виде стрелочки, указывающей направо:

Контроллер прошивается достаточно долго — примерно 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;
}
Последуем совету — переименуем файл (изменим расширение).
Далее нужно доработать и адаптировать его, чтобы исправить подсвечивающиеся красной линией предупреждения:

Нас предупреждают о том, что выделенные функции не определены. Поэтому в первых строчках кода 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, вывели текст и простую графику. Далее разберём, как отображать полноцветные картинки и фотографии (растровые изображения).
Есть два важных условия, которые надо соблюдать для вывода картинки на экран:
— формат: 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() {
}

















