Начало работы с библиотекой LVGL. Первые шаги в создании графических интерфейсов
Введение
В настоящее время графические интерфейсы пользователя (GUI) используются практически во всех видах устройств, от бытовых приборов до сложных систем автоматизации и управления. Создание эффективных и удобных в использовании интерфейсов становится все более важным. Одним из инструментов, которые могут помочь в этом, является библиотека LVGL (Light and Versatile Graphics Library). LVGL - это бесплатная библиотека GUI на языке программирования Си, которая позволяет разработчикам создавать интерфейсы для различных устройств, включая микроконтроллеры и системы IoT. В этой статье мы рассмотрим основы работы с библиотекой LVGL, чтобы помочь начинающим разработчикам создавать эффективные и удобные графические интерфейсы.
Преимущества LVGL
Библиотека LVGL имеет несколько преимуществ, которые делают ее одним из лучших инструментов для создания графических интерфейсов:
- Платформонезависимость: LVGL может быть использован на различных устройствах, включая микроконтроллеры и системы IoT, что делает его очень гибким и удобным для разработчиков.
- Простота использования: LVGL имеет простой и интуитивно понятный API, что делает его очень доступным для начинающих разработчиков.
- Низкое потребление ресурсов: LVGL использует очень мало ресурсов, что делает его идеальным для использования на микроконтроллерах и других устройствах с ограниченными ресурсами.
- Широкие возможности настройки: LVGL предоставляет различные настройки и опции для создания интерфейсов, которые могут быть настроены под конкретные потребности проекта.
- Открытый исходный код: LVGL является проектом с открытым исходным кодом, что позволяет разработчикам вносить изменения в код библиотеки и адаптировать ее под свои нужды.
- Богатый функционал: LVGL предоставляет широкий набор функций и возможностей для создания интерфейсов, включая поддержку различных типов элементов управления, анимаций, шрифтов и других функций.
- Оптимизация производительности: LVGL был специально разработан для оптимальной производительности и быстрого рендеринга графических элементов, что позволяет создавать интерфейсы с плавной анимацией и отзывчивым пользовательским интерфейсом.
- Поддержка сообщества: LVGL имеет активное сообщество разработчиков, которые работают над поддержкой библиотеки, исправлением ошибок и развитием новых функций. Это обеспечивает надежность и стабильность работы библиотеки и облегчает поиск решений в случае проблем при использовании LVGL.
- Лицензия: LVGL использует лицензию MIT, что позволяет использовать его в коммерческих и некоммерческих проектах без ограничений. Это делает его доступным для всех разработчиков и позволяет использовать его в любых проектах, включая те, которые не могут использовать библиотеки с другими типами лицензий.
Подключение дисплея и сенсорного экрана
Библиотека LVGL предоставляет инструменты для создания и управления графическими элементами интерфейса, такими как кнопки, текстовые поля, прогресс-бары и т. д. Напрямую работать с дисплеем она не умеет. Поэтому для использования LVGL необходимо и подключить соответствующую библиотеку. Например TFT_eSPI или одну из библиотек-драйверов Adafruit, которые предоставляют функции для работы с дисплеем, такие как управление яркостью, цветом и т. д.
Про TFT_eSPI я уже ранее писал в статье TFT_eSPI. Мощная и быстрая библиотека для работы с TFT дисплеями. А библиотеки-драйверы для дисплеев Adafruit были рассмотрены в статье Подключение TFT дисплея к ESP8266. Поэтому, я думаю, что лишний раз повторяться не имеет никакого смысла.
Что касается сенсорного экрана, то практически все доступные на Aliexpress дисплеи с возможностью сенсорного ввода, используют один из этих драйверов:
- FT6X36
- GT911
- XPT2046
FT6X36 и GT911 - это емкостные сенсорные экраны с поддержкой мультитач, работающие через I2C интерфейс.
XPT2046 - резистивный сенсорный экран, распознающий одновременно только одно нажатие, и работающий по SPI интерфейсу.
Подключаются они к соответствующим пинам микроконтроллера и для работы каждый использует свою библиотеку:
- FT6X36: https://github.com/strange-v/FT6X36.git
- GT911: https://github.com/TAMCTec/gt911-arduino.git
- XPT2046: https://github.com/PaulStoffregen/XPT2046_Touchscreen.git
Ниже приведу универсальный скетч, который можно подключить к вашему проекту и использовать любой из этих драйверов на ваш выбор. Достаточно лишь убрать символ комментария в начале программы, относящиеся к нужному вам сенсорному экрану. Затем изменить номера пинов на те, которые использует ваш микроконтроллер и тач полностью готов к работе.
/*******************************************************************************
* Touch libraries:
* FT6X36: https://github.com/strange-v/FT6X36.git
* GT911: https://github.com/TAMCTec/gt911-arduino.git
* XPT2046: https://github.com/PaulStoffregen/XPT2046_Touchscreen.git
******************************************************************************/
/* uncomment for FT6X36 */
// #define TOUCH_FT6X36
// #define TOUCH_FT6X36_SCL 19
// #define TOUCH_FT6X36_SDA 18
// #define TOUCH_FT6X36_INT 39
// #define TOUCH_SWAP_XY
// #define TOUCH_MAP_X1 480
// #define TOUCH_MAP_X2 0
// #define TOUCH_MAP_Y1 0
// #define TOUCH_MAP_Y2 320
/* uncomment for GT911 */
#define TOUCH_GT911
#define TOUCH_GT911_SCL 32
#define TOUCH_GT911_SDA 33
#define TOUCH_GT911_INT 21
#define TOUCH_GT911_RST 25
#define TOUCH_GT911_ROTATION ROTATION_LEFT
#define TOUCH_MAP_X1 480
#define TOUCH_MAP_X2 0
#define TOUCH_MAP_Y1 320
#define TOUCH_MAP_Y2 0
/* uncomment for XPT2046 */
// #define TOUCH_XPT2046
// #define TOUCH_XPT2046_SCK 12
// #define TOUCH_XPT2046_MISO 13
// #define TOUCH_XPT2046_MOSI 11
// #define TOUCH_XPT2046_CS 38
// #define TOUCH_XPT2046_INT 18
// #define TOUCH_XPT2046_ROTATION 0
// #define TOUCH_MAP_X1 4000
// #define TOUCH_MAP_X2 100
// #define TOUCH_MAP_Y1 100
// #define TOUCH_MAP_Y2 4000
int touch_last_x = 0, touch_last_y = 0;
#if defined(TOUCH_FT6X36)
#include <Wire.h>
#include <FT6X36.h>
FT6X36 ts(&Wire, TOUCH_FT6X36_INT);
bool touch_touched_flag = true, touch_released_flag = true;
#elif defined(TOUCH_GT911)
#include <Wire.h>
#include <TAMC_GT911.h>
TAMC_GT911 ts = TAMC_GT911(TOUCH_GT911_SDA, TOUCH_GT911_SCL, TOUCH_GT911_INT, TOUCH_GT911_RST, max(TOUCH_MAP_X1, TOUCH_MAP_X2), max(TOUCH_MAP_Y1, TOUCH_MAP_Y2));
#elif defined(TOUCH_XPT2046)
#include <XPT2046_Touchscreen.h>
#include <SPI.h>
XPT2046_Touchscreen ts(TOUCH_XPT2046_CS, TOUCH_XPT2046_INT);
#endif
#if defined(TOUCH_FT6X36)
void touch(TPoint p, TEvent e)
{
if (e != TEvent::Tap && e != TEvent::DragStart && e != TEvent::DragMove && e != TEvent::DragEnd)
{
return;
}
// translation logic depends on screen rotation
#if defined(TOUCH_SWAP_XY)
touch_last_x = map(p.y, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, TOUCH_MAP_X1);
touch_last_y = map(p.x, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, TOUCH_MAP_Y1);
#else
touch_last_x = map(p.x, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, TOUCH_MAP_X1);
touch_last_y = map(p.y, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, TOUCH_MAP_Y1);
#endif
switch (e)
{
case TEvent::Tap:
Serial.println("Tap");
touch_touched_flag = true;
touch_released_flag = true;
break;
case TEvent::DragStart:
Serial.println("DragStart");
touch_touched_flag = true;
break;
case TEvent::DragMove:
Serial.println("DragMove");
touch_touched_flag = true;
break;
case TEvent::DragEnd:
Serial.println("DragEnd");
touch_released_flag = true;
break;
default:
Serial.println("UNKNOWN");
break;
}
}
#endif
void touch_init()
{
#if defined(TOUCH_FT6X36)
Wire.begin(TOUCH_FT6X36_SDA, TOUCH_FT6X36_SCL);
ts.begin();
ts.registerTouchHandler(touch);
#elif defined(TOUCH_GT911)
Wire.begin(TOUCH_GT911_SDA, TOUCH_GT911_SCL);
ts.begin();
ts.setRotation(TOUCH_GT911_ROTATION);
#elif defined(TOUCH_XPT2046)
SPI.begin(TOUCH_XPT2046_SCK, TOUCH_XPT2046_MISO, TOUCH_XPT2046_MOSI, TOUCH_XPT2046_CS);
ts.begin();
ts.setRotation(TOUCH_XPT2046_ROTATION);
#endif
}
bool touch_has_signal()
{
#if defined(TOUCH_FT6X36)
ts.loop();
return touch_touched_flag || touch_released_flag;
#elif defined(TOUCH_GT911)
return true;
#elif defined(TOUCH_XPT2046)
return ts.tirqTouched();
#else
return false;
#endif
}
bool touch_touched()
{
#if defined(TOUCH_FT6X36)
if (touch_touched_flag)
{
touch_touched_flag = false;
return true;
}
else
{
return false;
}
#elif defined(TOUCH_GT911)
ts.read();
if (ts.isTouched)
{
#if defined(TOUCH_SWAP_XY)
touch_last_x = map(ts.points[0].y, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, TOUCH_MAP_X1-1);
touch_last_y = map(ts.points[0].x, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, TOUCH_MAP_Y1-1);
#else
touch_last_x = map(ts.points[0].x, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, TOUCH_MAP_X1-1);
touch_last_y = map(ts.points[0].y, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, TOUCH_MAP_Y1-1);
#endif
return true;
}
else
{
return false;
}
#elif defined(TOUCH_XPT2046)
if (ts.touched())
{
TS_Point p = ts.getPoint();
#if defined(TOUCH_SWAP_XY)
touch_last_x = map(p.y, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, gfx->width() - 1);
touch_last_y = map(p.x, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, gfx->height() - 1);
#else
touch_last_x = map(p.x, TOUCH_MAP_X1, TOUCH_MAP_X2, 0, gfx->width() - 1);
touch_last_y = map(p.y, TOUCH_MAP_Y1, TOUCH_MAP_Y2, 0, gfx->height() - 1);
#endif
return true;
}
else
{
return false;
}
#else
return false;
#endif
}
bool touch_released()
{
#if defined(TOUCH_FT6X36)
if (touch_released_flag)
{
touch_released_flag = false;
return true;
}
else
{
return false;
}
#elif defined(TOUCH_GT911)
return true;
#elif defined(TOUCH_XPT2046)
return true;
#else
return false;
#endif
}
На этом настройка технической части завершена.
Первый проект на LVGL
После настройки дисплея и сенсорного экрана следующим шагом будет установка и адаптация библиотеки LVGL под ваши нужды. В данном руководстве будет использоваться версия библиотеки LVGL 8.3.6, поэтому для работы на более старых или новых версиях скорее всего в код программы придется внести некоторые изменения. Скачать её можно через Менеджер библиотек Arduino IDE:
Редактирование конфигурационного файла
После установки библиотеки вам нужно будет создать для нее конфигурационный файл. Для этого перейдите в папку с библиотекой с установленной библиотекой. В Windows она обычно находится по этому пути:
C:\Users\ИмяПользователя\Documents\Arduino\libraries\lvgl
Далее найдите файл lv_conf_template.h и переименуйте его в lv_conf.h и откройте в текстовом редакторе.
Прежде всего найдите строку:
#if 0
Измените её на
#if 1
Затем найдите строку:
#define LV_TICK_CUSTOM 0
Измените её на:
#define LV_TICK_CUSTOM 1
Хотя в данном файле достаточно много настроек, изменения только этих 2 параметров достаточно для использования LVGL в ваших проектах для Arduino IDE. Сохраните файл.
Далее важный момент. Авторы библиотеки настойчиво рекомендуют, после создания файла lv_conf.h, переместить его в папку c библиотеками Arduino IDE. То есть путь к файлу должен быть таким:
C:\Users\ИмяПользователя\Documents\Arduino\libraries\lv_conf.h
Возможно и стоит это сделать, но у меня все прекрасно работает даже если этот файл никуда не перемещать, а оставить в папке с библиотекой.
Код программы
Как только с редактированием конфигурационного файла будет покончено, можно, наконец, приступить к созданию первой программы с использованием LVGL. Это будет простой проект с одной надписью и кнопкой. При нажатии на кнопку, на ней появляется счетчик с числом касаний.
Создайте в Arduino IDE новый проект и скопируйте в него следующий код:
#include <lvgl.h>
#include <TFT_eSPI.h>
#include "touch.h"
static const uint16_t screenWidth = 480; //ширина экрана
static const uint16_t screenHeight = 320; //высота экрана
//Объявление служебных переменных для LVGL
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 6];
lv_obj_t * btn1;
lv_obj_t * screenMain;
lv_obj_t * label;
TFT_eSPI tft = TFT_eSPI(); //инициализация библиотеки TFT_eSPI
//Здесь напишем функцию для вывода содержимого буфера на экран
void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )
{
uint32_t w = ( area->x2 - area->x1 + 1 );
uint32_t h = ( area->y2 - area->y1 + 1 );
tft.startWrite();
tft.setAddrWindow( area->x1, area->y1, w, h );
tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
tft.endWrite();
lv_disp_flush_ready( disp );
}
// Вычисление координат касания
void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
if (touch_has_signal()) //если есть касание
{
if (touch_touched())
{
data->state = LV_INDEV_STATE_PR; //сообщаем библиотеке, что есть касание и удерживается
//Отправка координат
data->point.x = touch_last_x; //координата касания по X
data->point.y = touch_last_y; //координата касания по Y
}
else if (touch_released())
{
data->state = LV_INDEV_STATE_REL; //сообщаем библиотеке, что касания больше нет
}
}
else
{
data->state = LV_INDEV_STATE_REL;
}
}
//Функция обработки нажатия экранной кнопки
static void event_handler_btn(lv_event_t * event){
static uint32_t cnt = 1;
lv_obj_t * btn = lv_event_get_target(event);
lv_obj_t * label = lv_obj_get_child(btn, 0);
lv_label_set_text_fmt(label, "%"LV_PRIu32, cnt);
cnt++;
}
void setup()
{
Serial.begin( 115200 ); //открытие серийного порта
touch_init(); //иницилизация тача
lv_init();//инициализация LVGL
//инициализация дисплея в библиотеке TFT_ESPi и изменение его ориентации на альбомную
tft.begin();
tft.setRotation(1);
//Далее идут функции настройки LVGL
lv_disp_draw_buf_init( &draw_buf, buf, NULL, screenWidth * screenHeight / 6 ); //создаем буфер для вывода информации на экран
//далее идет настройка параметров экрана
static lv_disp_drv_t disp_drv; //объявляем переменную для хранения драйвера дисплея
lv_disp_drv_init( &disp_drv ); //базовая инициализация драйвера
disp_drv.hor_res = screenWidth; //ширина экрана
disp_drv.ver_res = screenHeight; //высота экрана
disp_drv.flush_cb = my_disp_flush; //функция которая выводит содержимое буфера в заданное место экрана. Указываем имя функции которую мы написали выше
disp_drv.draw_buf = &draw_buf; //объявляем библиотеке, что содержимое буфера экрана находится в переменной draw_buf
lv_disp_drv_register( &disp_drv ); //регистрируем драйвер дисплея и сохранем его настройки
// Инициализируем драйвер тачскрина
static lv_indev_drv_t indev_drv; //объявляем переменные для хранения драйвера тачскрина
lv_indev_drv_init( &indev_drv ); // базовая инициализация драйвера
indev_drv.type = LV_INDEV_TYPE_POINTER; //указываем тип драйвера. В данном случае это тачскрин
indev_drv.read_cb = my_touchpad_read; //указываем имя функции обработчика нажатий на тачскрин, которую мы создали
lv_indev_drv_register( &indev_drv ); //регистрация драйвера тачскрина и сохранение его настроек
//Создаем экранные объекты
lv_obj_t * screenMain = lv_obj_create(NULL); //создаем экранный объект, который будет содержать все другие объекты
//Создадим объект надпись и опишем его свойства
label = lv_label_create(screenMain); //создаем объект Надпись как дочерний объект screenMain
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); //текст можно переносить по строкам если не вмещается
lv_label_set_text(label, "Press the button!"); //сам текст для надписи
lv_obj_set_size(label, 240, 40); //размеры надписи
lv_obj_set_pos(label, 0, 15); //положение на экране
//создадим объект Кнопка
btn1 = lv_btn_create(screenMain); //создаем объект Кнопка как дочерний объект screenMain
lv_obj_add_event_cb(btn1, event_handler_btn, LV_EVENT_CLICKED, NULL); //функция, которая вызывается при нажатии на кнопку
lv_obj_set_width(btn1, 120); //ширина
lv_obj_set_height(btn1, 30); //высота
lv_obj_set_pos(btn1,10, 40); //положение
//далее создадим надпись на кнопке
lv_obj_t * label1 = lv_label_create(btn1); //создаем объект Надпись как дочерний объект созданной ранее кнопки
lv_label_set_text(label1, "Press me!"); //Надпись на кнопке
// далее выводим все на экран
lv_scr_load(screenMain);
}
void loop()
{
//функция обновления экрана и параметров LVGL
lv_timer_handler();
delay( 10 );
}
Затем нажмите в Arduino IDE комбинацию клавиш CTRL+SHIFT+N и в появившемся поле введите touch.h:
В открывшейся вкладке вставьте код обработки сенсорного экрана, который был выложен несколькими абзацами выше, внесите необходимые правки и сохраните изменения.
После прошивки вашего устройства на экране вы увидите следующее:
После нескольких нажатий на кнопку значение счетчика изменится:
Объяснение кода программы
Я постарался максимально подробно закомментировать данный скетч, но на некоторые моменты все же стоит обратить ваше внимание.
Прежде всего это критически важная для работы библиотеки функция my_disp_flush. Она выводит содержимое буфера в заданные координаты экрана:
void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )
{
uint32_t w = ( area->x2 - area->x1 + 1 );
uint32_t h = ( area->y2 - area->y1 + 1 );
tft.startWrite();
tft.setAddrWindow( area->x1, area->y1, w, h );
tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
tft.endWrite();
lv_disp_flush_ready( disp );
}
Библиотека LVGL предварительно кэширует все элементы интерфейса в специально отведенной области памяти устройства, потом с помощью данной функции моментально выводит их на экран. Это позволяет достичь высокой скорости отрисовки и плавности анимации интерфейса.
В данном примере функция отрисовки из буфера адаптирована для библиотеки tft_eSPI. Для Adafruit она выглядит несколько иначе:
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
uint32_t wh = w*h;
tft.startWrite();
tft.setAddrWindow(area->x1, area->y1, w, h);
while (wh--) tft.pushColor(color_p++->full);
tft.endWrite();
lv_disp_flush_ready(disp);
}
Переменные “w”, “h” и “wh” упрощают использование функций s, потому что они должны передаваться переменными с такими значениями. Следующая функция “tft.startWrite()” инициирует процесс записи данных на ЖК-дисплей. Затем “tft.setaddr Window(area-> x1, area-> y1, w, h)” записывает данные на ЖК-дисплей, который начинается с “x1”, “y1” и имеет размеры “w“ и ”h". После этого цикл записывает все данные с помощью функции “tft.pushColor(color_p++-> full)”. Наконец, “tft.EndWrite()” завершает запись данных.
В конце “lv_disp_flush_ready (disp)” информирует LVGL о том, что мы записали необходимые данные.
Теперь перейдем к функции определения координат касания тачскрина:
// Вычисление координат касания
void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
if (touch_has_signal()) //если есть касание
{
if (touch_touched())
{
data->state = LV_INDEV_STATE_PR; //сообщаем библиотеке, что есть касание и удерживается
//Отправка координат
data->point.x = touch_last_x; //координата касания по X
data->point.y = touch_last_y; //координата касания по Y
}
else if (touch_released())
{
data->state = LV_INDEV_STATE_REL; //сообщаем библиотеке, что касания больше нет
}
}
else
{
data->state = LV_INDEV_STATE_REL;
}
}
Здесь мы с помощью функции "touch_touched()" определяем было ли касание. Если было, то устанавливаем состояние "Нажато" и сохраняются последние координаты касания. При отпускании устанавливается состояние "Отпущено" и происходит вызод из функции.
Далее в Setup() настраиваем непосредственно параметры LVGL
lv_disp_draw_buf_init( &draw_buf, buf, NULL, screenWidth * screenHeight / 6 ); //создаем буфер для вывода информации на экран
//далее идет настройка параметров экрана
static lv_disp_drv_t disp_drv; //объявляем переменную для хранения драйвера дисплея
lv_disp_drv_init( &disp_drv ); //базовая инициализация драйвера
disp_drv.hor_res = screenWidth; //ширина экрана
disp_drv.ver_res = screenHeight; //высота экрана
disp_drv.flush_cb = my_disp_flush; //функция которая выводит содержимое буфера в заданное место экрана. Указываем имя функции которую мы написали выше
disp_drv.draw_buf = &draw_buf; //объявляем библиотеке, что содержимое буфера экрана находится в переменной draw_buf
lv_disp_drv_register( &disp_drv ); //регистрируем драйвер дисплея и сохранем его настройки
// Инициализируем драйвер тачскрина
static lv_indev_drv_t indev_drv; //объявляем переменные для хранения драйвера тачскрина
lv_indev_drv_init( &indev_drv ); // базовая инициализация драйвера
indev_drv.type = LV_INDEV_TYPE_POINTER; //указываем тип драйвера. В данном случае это тачскрин
indev_drv.read_cb = my_touchpad_read; //указываем имя функции обработчика нажатий на тачскрин, которую мы создали
lv_indev_drv_register( &indev_drv ); //регистрация драйвера тачскрина и сохранение его настроек
Здесь вы можете найти ссылки на функции “my_disp_flush” и “my_input_read", которые мы ранее написали.
Когда все будет инициализировано, нам нужно создать объект "Экран".
lv_obj_t * screenMain = lv_obj_create(NULL); //создаем экранный объект, который будет содержать все другие объекты
Это будет родительский объект для всех остальных элементов интерфейса. Если вы скроете “screenMain", то все дочерние объекты также будут скрыты. Это удобное поведение, когда вам нужно переключаться между экранами.
далее создаем элементы интерфейса. Первым будет объект "Надпись":
//Создадим объект надпись и опишем его свойства
label = lv_label_create(screenMain); //создаем объект Надпись как дочерний объект screenMain
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); //текст можно переносить по строкам если не вмещается
lv_label_set_text(label, "Press the button!"); //сам текст для надписи
lv_obj_set_size(label, 240, 40); //размеры надписи
lv_obj_set_pos(label, 0, 15); //положение на экране
Она создается как дочерний объект “screenMain”. Кроме того, заданы некоторые параметры надписи, такие как перенос строки, если текст не вмещается по ширине родительского объекта, текст надписи, размер и положение.
Теперь создадим кнопку:
btn1 = lv_btn_create(screenMain); //создаем объект Кнопка как дочерний объект screenMain
lv_obj_add_event_cb(btn1, event_handler_btn, LV_EVENT_CLICKED, NULL); //функция, которая вызывается при нажатии на кнопку
lv_obj_set_width(btn1, 120); //ширина
lv_obj_set_height(btn1, 30); //высота
lv_obj_set_pos(btn1,10, 40); //положение
Она тоже является дочерним элемент “screenMain“. Далее идут несколько строк кода, которые задают ширину, высоту и положение кнопки. Также есть строка, которая задает функцию обработки событий, но об этом позже поговорим чуть позже.
Добавим надпись на кнопку:
//далее создадим надпись на кнопке
lv_obj_t * label1 = lv_label_create(btn1); //создаем объект Надпись как дочерний объект созданной ранее кнопки
lv_label_set_text(label1, "Press me!"); //Надпись на кнопке
Надпись является дочерним элементом “btn1”, а не всего экрана. Это означает, что при перемещении кнопки надпись также перемещается. Поэтому вам не нужно устанавливать положение надписи.
Далее идет функция отображения экрана:
lv_scr_load(screenMain);
Она просто выводит содержимое созданного нами экранного объекта на дисплей.
Для динамического отображения содержимого экрана и обработки касаний в loop() добавляем следующую функцию:
lv_timer_handler();
delay( 10 );
И, наконец, добавим функцию-обработчик нажатия на кнопку:
//Функция обработки нажатия экранной кнопки
static void event_handler_btn(lv_event_t * event){
static uint32_t cnt = 1;
lv_obj_t * btn = lv_event_get_target(event);
lv_obj_t * label = lv_obj_get_child(btn, 0);
lv_label_set_text_fmt(label, "%"LV_PRIu32, cnt);
cnt++;
}
Она при возникновении события "Нажатие кнопки" она создает переменную счетчик, затем создает объект "Надпись", родителем которого является объект вызвавший эту функцию, после чему устанавливает ему свойство "Текст" равное значению счетчика.
На этом разбор кода программы можно считать завершенным.
Заключение
В заключении можно отметить, что библиотека LVGL предоставляет широкие возможности для создания качественных и функциональных графических интерфейсов на устройствах Arduino и ESP32. В статье были рассмотрены основные преимущества использования LVGL, а также отмечено, что для работы с графическим дисплеем все равно требуются дополнительные библиотеки.
Это только первая статья из планируемого цикла статей по данной библиотеке. В последующих статьях мы рассмотрим более подробно возможности LVGL, создание конкретных элементов интерфейса, а также примеры реализации проектов на базе этой библиотеки.
Библиотека LVGL - это мощный инструмент для создания качественных и производительных графических интерфейсов на устройствах Arduino и ESP32, который может быть использован в широком диапазоне проектов. Она имеет все необходимые инструменты для создания интерфейсов любой сложности и поддерживается активным сообществом разработчиков.