LVGL — Light and Versatile Graphics Library также известная как LittleVGL.
![](https://habrastorage.org/r/w1560/webt/jm/ls/ca/jmlscartj3iiy7yndceei8jkluw.png)
Библиотека поддерживает большое количество микроконтроллеров, таких как STM32, ESP32 и другие. Мне пока удалось запустить полноценную демо программу на ESP32 и STM32f429 Discovery. Библиотека открытая, поддерживает большое количество графических элементов с Dark and Light темами. Распространяется под MIT license. Можно свободно использовать даже в коммерческих продуктах. Можно посмотреть интерактивное Online Demo без установки на устройство
Библиотека поддерживает подключение двух типов дисплея
![](https://habrastorage.org/r/w1560/webt/xt/n1/-w/xtn1-w8dqla-q-shy8mkiyauvd4.png)
Все очень гибко в этом плане. Если у вас есть драйвер, но в библиотеке еще нет порта под этот драйвер, то вы можете сами легко интегрировать библиотеку в свой проект
Самый простой способ, но и самый медленный это переписать callback отрисовки — my_flush_cb
put_px — Это отрисовка пикселя вашего драйвера. Из за того что отрисовка идет попиксельно это и происходит медленно. В документации библиотеки подробно описаны другие более эффективные способы интеграции
Интегрируем LVGL в проект. Первое это делаем инициализацию библиотеки, дисплея и системы ввода
Поскольку библиотека внутри себя имеет диспетчер задач. Да это мультипотоковая система, поправьте меня если это не так, то нам потребуется увеличивать внутренний счетчик диспетчера посредством вызова lv_tick_inc
Причем передавать в функцию надо миллисекунды которые прошли с момента ее последнего вызова или другими словами время с момента предыдущей итерации.
Чтобы не тратить ресурсы на лишний поток можно вызывать эту функцию по прерыванию от таймера. Как раз в STM32 есть SysTick таймер для таких целей:
Так же надо вызывать в цикле lv_task_handler. Рекомендуется дергать его каждые 5 ms для обеспечения хорошего отклика. Я пробовал увеличивать до 20 ms и система все еще оставалась довольно отзывчивой и плавной. Можно оставить как вечный цикл или использовать Thread
Циклы должны быть в разных местах. Я сделал ошибку и запихнул lv_tick_inc и lv_task_handler в один цикл. Вот что из этого получилось — Тормоза
Когда оба метода были разнесены в разные потоки с правильными интервалами все заработало корректно и быстро:
Библиотека имеет возможность настраивать количество внутренних буферов:
На сайте есть конвертер для шрифтов и картинок. Можно спокойно добавить свой шрифт в проект или свою иконку в меню. Причем опционально можно грузить картинки с внешнего хранилища например CD-CARD или из массива байт расположенного во внутренней Flash памяти.
Еще одна важная и приятная плюшка этой библиотеки, что для ее отладки можно использовать Eclipse IDE в среде Linux и Windows.
![](https://habrastorage.org/r/w1560/webt/sg/6o/sg/sg6osg04ntewolvrszlgy6vc5ae.png)
Приятно что, для OpenSource библиотеки она хорошо документирована. Имеется множество примеров и портов. Библиотека обросла довольно большим комьюнити
Я запускал порт для ESP32. Даже при использовании mapping для выводов SPI т.е. не дефолтных, на которых получается лучшая скорость передачи, все работало без замедлений:
ESP32 ST7789 LVGL
ESP32 ILI9341 LVGL
![](https://habrastorage.org/webt/jm/ls/ca/jmlscartj3iiy7yndceei8jkluw.png)
Библиотека поддерживает большое количество микроконтроллеров, таких как STM32, ESP32 и другие. Мне пока удалось запустить полноценную демо программу на ESP32 и STM32f429 Discovery. Библиотека открытая, поддерживает большое количество графических элементов с Dark and Light темами. Распространяется под MIT license. Можно свободно использовать даже в коммерческих продуктах. Можно посмотреть интерактивное Online Demo без установки на устройство
Библиотека поддерживает подключение двух типов дисплея
- Напрямую через интерфейс RGB где буфер будет находится на стороне MCU во внутренней RAM или внешней SDRAM
- Через внешний контроллер дисплея. В этом случае MCU может связываться с контроллером дисплея через SPI или I2C шину. Для улучшения производительности, промежуточные буферы отрисовки внутри MCU также могут быть использованы в этом случае
Two typical hardware set-ups
MCU with TFT/LCD driver If your MCU has a TFT/LCD driver periphery then you can connect a display directly via RGB interface. In this case, the frame buffer can be in the internal RAM (if the MCU has enough RAM) or in the external RAM (if the MCU has a memory interface).
External display controller If the MCU doesn't have TFT/LCD driver interface then an external display controller (E.g. SSD1963, SSD1306, ILI9341) has to be used. In this case, the MCU can communicate with the display controller via Parallel port, SPI or sometimes I2C. The frame buffer is usually located in the display controller which saves a lot of RAM for the MCU.
External display controller If the MCU doesn't have TFT/LCD driver interface then an external display controller (E.g. SSD1963, SSD1306, ILI9341) has to be used. In this case, the MCU can communicate with the display controller via Parallel port, SPI or sometimes I2C. The frame buffer is usually located in the display controller which saves a lot of RAM for the MCU.
![](https://habrastorage.org/webt/xt/n1/-w/xtn1-w8dqla-q-shy8mkiyauvd4.png)
Все очень гибко в этом плане. Если у вас есть драйвер, но в библиотеке еще нет порта под этот драйвер, то вы можете сами легко интегрировать библиотеку в свой проект
Самый простой способ, но и самый медленный это переписать callback отрисовки — my_flush_cb
void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
int32_t x, y;
for(y = area->y1; y <= area->y2; y++) {
for(x = area->x1; x <= area->x2; x++) {
put_px(x, y, *color_p)
color_p++;
}
}
/* IMPORTANT!!!
* Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp);
}
put_px — Это отрисовка пикселя вашего драйвера. Из за того что отрисовка идет попиксельно это и происходит медленно. В документации библиотеки подробно описаны другие более эффективные способы интеграции
Интегрируем LVGL в проект. Первое это делаем инициализацию библиотеки, дисплея и системы ввода
lv_init();
tft_init();
touchpad_init();
lv_demo_widgets(); // Используется для примера из демо
Поскольку библиотека внутри себя имеет диспетчер задач. Да это мультипотоковая система, поправьте меня если это не так, то нам потребуется увеличивать внутренний счетчик диспетчера посредством вызова lv_tick_inc
void * tick_thread (void *args)
{
while(1) {
usleep(5*1000); /*Sleep for 5 millisecond*/
lv_tick_inc(5); /*Tell LVGL that 5 milliseconds were elapsed*/
}
}
Причем передавать в функцию надо миллисекунды которые прошли с момента ее последнего вызова или другими словами время с момента предыдущей итерации.
Чтобы не тратить ресурсы на лишний поток можно вызывать эту функцию по прерыванию от таймера. Как раз в STM32 есть SysTick таймер для таких целей:
void systickInit (uint16_t frequency)
{
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq (&RCC_Clocks);
(void) SysTick_Config (RCC_Clocks.HCLK_Frequency / frequency);
}
extern "C" void SysTick_Handler (void)
{
lv_tick_inc(1); // 1 ms
}
SysTick timer
This timer is dedicated to real-time operating systems, but could also be used as a standard
downcounter. It features:
downcounter. It features:
- A 24-bit downcounter
- Autoreload capability
- Maskable system interrupt generation when the counter reaches 0
- Programmable clock source.
Так же надо вызывать в цикле lv_task_handler. Рекомендуется дергать его каждые 5 ms для обеспечения хорошего отклика. Я пробовал увеличивать до 20 ms и система все еще оставалась довольно отзывчивой и плавной. Можно оставить как вечный цикл или использовать Thread
while(1) {
lv_task_handler();
my_delay_ms(5);
}
Циклы должны быть в разных местах. Я сделал ошибку и запихнул lv_tick_inc и lv_task_handler в один цикл. Вот что из этого получилось — Тормоза
Когда оба метода были разнесены в разные потоки с правильными интервалами все заработало корректно и быстро:
Библиотека имеет возможность настраивать количество внутренних буферов:
- Один буфер, когда LVGL рисует содержимое экрана в буфер и отправляет его на дисплей
- Два буфера с неполным размером экрана, пока идет отрисовка в одном буфере, содержимое другого буфера отправляется для отображения в фоновом режиме
- Два буфера размером с целый экран
На сайте есть конвертер для шрифтов и картинок. Можно спокойно добавить свой шрифт в проект или свою иконку в меню. Причем опционально можно грузить картинки с внешнего хранилища например CD-CARD или из массива байт расположенного во внутренней Flash памяти.
How to use the generated file in LittlevGL?
For C arrays
Copy the result C file into your LittlevGL project
In a C file of your application declare the image as: LV_IMG_DECLARE(my_image_name);
Set the image for an lv_img object: lv_img_set_src(img1, &my_image_name);
For external binary files (e.g. SD card)
Set up a new driver. To learn more read the Tutorial.
Set the image for an lv_img object: lv_img_set_src(img1, «S:/path/to/image»);
Copy the result C file into your LittlevGL project
In a C file of your application declare the image as: LV_IMG_DECLARE(my_image_name);
Set the image for an lv_img object: lv_img_set_src(img1, &my_image_name);
For external binary files (e.g. SD card)
Set up a new driver. To learn more read the Tutorial.
Set the image for an lv_img object: lv_img_set_src(img1, «S:/path/to/image»);
Еще одна важная и приятная плюшка этой библиотеки, что для ее отладки можно использовать Eclipse IDE в среде Linux и Windows.
![](https://habrastorage.org/webt/sg/6o/sg/sg6osg04ntewolvrszlgy6vc5ae.png)
Приятно что, для OpenSource библиотеки она хорошо документирована. Имеется множество примеров и портов. Библиотека обросла довольно большим комьюнити
Я запускал порт для ESP32. Даже при использовании mapping для выводов SPI т.е. не дефолтных, на которых получается лучшая скорость передачи, все работало без замедлений:
ESP32 ST7789 LVGL
ESP32 ILI9341 LVGL