LittlevGL is a nice open source graphics library for generating graphical user interfaces (buttons, sliders, graphs and so forth) on microcontrollers. It provides most of the UI “smarts” behind the scenes, but on its own doesn’t communicate with any specific display or input device. Instead, one normally must write their own intermediary code to tie these together.

We’ve made an Arduino library — Adafruit_LvGL_Glue — that simplifies the task of sticking LittlevGL on many Adafruit displays: PyPortal, TFT FeatherWings, and most other Adafruit devices with (or connected to) a color TFT or OLED screen, using fast DMA transfers when possible. Touchscreens can be used for responsive interfaces. For non-touch displays, LittlevGL can still be useful for its nice graphing and text display capabilities. Mostly, though, we think this will shine on Adafruit PyPortal devices, with their extra fast displays and touch sensitivity.

The following microcontrollers are supported: SAMD21 (“M0”), SAMD51 (“M4”), nRF52840 and ESP32. More 32-bit devices might get added in the future. LittlevGL exceeds the capabilities of 8-bit AVR devices (such as Arduino Uno), so don’t hold out for support there.

This guide is not an extensive tutorial into LittlevGL. We’ll get things set up on Adafruit hardware and run a basic “hello world” type of demo. Going forward from there, you’ll find explanations and examples on the LittlevGL documentation site and elsewhere. Many existing examples can be copied-and-pasted with only minor changes needed!

We’ll assume you already have basic Arduino usage set up for your board — SAMD, nRF and so forth — the corresponding introduction guide for each board explains that part. If you can get the “blink” sketch running, you’re in good shape.

The required software components for LittlevGL usage can then be installed with the Arduino Library Manager

Sketch→Include Library→Manage Libraries…

Search for and install the following libraries:

  • lv_arduino (LittlevGL library for Arduino) (select the latest 2.X version, NOT a 3.X version!)
  • Adafruit_LvGL_Glue
  • Adafruit_GFX
  • Adafruit_BusIO
  • Adafruit_Touchscreen
  • Adafruit_STMPE610

Even if you’re not using a touchscreen, those latter two libraries are still required for the code to compile.

In addition, you’ll need to install one or more libraries specific to the display you’re using…

  • Adafruit_ILI9341 (320x240 PyPortal, TFT FeatherWing, etc.)
  • Adafruit_HX8357 (480x320 PyPortal Titano, TFT FeatherWing, etc.)
  • Adafruit_ST7735 (includes ST7789 support) (CLUE, TFT Gizmo, HalloWing, PyGamer, etc.)
  • Other color Adafruit displays can usually work, see the “Using” page for further details
Install the latest 2.X version of lv_arduino. Version 3 introduced a number of breaking changes. Adafruit_LvGL_Glue has not yet been updated for this, and most existing LittlevGL sketches will require changes to use it.

Configure LittlevGL

By default, lv_arduino is pretty well set up already for our displays. But if you’d like to get in there and tune some settings, the LittlevGL configuration file can be found at:

(home directory)/Documents/Arduino/Libraries/lv_conf.h

Of possible interest in there, LV_USE_LOG and LV_LOG_LEVEL can be set if you want to use LittlevGL’s status logging capabilities (which will print to the Serial Console when using Adafruit_LvGL_Glue). Set the former to 1 and the latter to LV_LOG_LEVEL_INFO or LV_LOG_LEVEL_TRACE. Logging also must be enabled by passing true as an optional extra argument to our library’s begin() function.

Entirely optional, but if using a PyPortal Titano or large TFT FeatherWing, you can also change these resolution values:

Download: file
#define LV_HOR_RES_MAX          (320)
#define LV_VER_RES_MAX          (240)

These are not hard-set screen size limits, they just affect how screen memory is allocated. If bumped up to 480 and 320, some additional memory is set aside and screen updates might go a little faster and smoother. Or there’s no harm just leaving them as they are.

Hello Arduino example

To verify that everything’s installed and working properly, you can try the minimal “Hello” program…

File→Examples→Adafruit LittlevGL Glue Library→Hello *

There are different versions for Adafruit CLUE, TFT FeatherWing, TFT Gizmo and PyPortal (all versions). Other Adafruit boards and displays can also work…see the “Using” page. It’s fairly straightforward to “mash up” an existing graphics example with the LittlevGL hello example and verify that it’s working.

Most of these need no modification. Only the hello_featherwing example, where you’ll need to edit this line if using a 3.5" (480x320) TFT FeatherWing:

Download: file
#define BIG_FEATHERWING 0 // Set this to 1 for 3.5" (480x320) FeatherWing!

Select your board type from the Tools menu, verify the code compiles, and upload. If you get a legible “Hello Arduino!” centered on the screen, everything’s in good shape!

From there, you can try one of the widgets examples. These just scratch the surface of what LittlevGL can do, but give a nice hint of what’s in store visually, and you can look through the code to see how a simple application is implemented.

As explained in the Overview, this guide is not an in-depth tutorial to writing LittlevGL applications. For that, have a look through the explanations and examples on the LittlevGL documentation site.

Let’s look at the basics of using Adafruit_LvGL_Glue though. Once that’s understood, many LittlevGL examples can be copied-and-pasted and work with only minor modification.

Something common to ALL of the examples is the inclusion of the LittlevGL and Glue library header files, and then a few lines down a global Adafruit_LvGL_Glue object declaration (which has no arguments):

Download: file
#include <lvgl.h>
#include <Adafruit_LvGL_Glue.h>

Adafruit_LvGL_Glue glue;

For the PyPortal’s touchscreen, the TouchScreen.h header is included, and a global TouchScreen object is declared. We’ve abbreviated here…in the examples, you’ll see all the values for the XP, YP and other arguments are #defined.

Download: file
#include <TouchScreen.h>

TouchScreen ts(XP, YP, XM, YM, 300);

A TFT FeatherWing’s touchscreen works a little differently. Adafruit_STMPE610.h is included, and a global Adafruit_STMPE610 object declared, with different arguments (again, abbreviated here, we’re not showing all the #defines):

Download: file
#include <Adafruit_STMPE610.h>

Adafruit_STMPE610 ts(STMPE_CS);

Various displays will have their own device-specific header files. For the PyPortal Titano and 480x320 TFT FeatherWing, that would be Adafruit_HX8357.h. A global Adafruit_HX8357 object is then declared, but the arguments are totally different between PyPortal and FeatherWing. See the example sketches. For FeatherWing, it’s like so:

Download: file
#include <Adafruit_HX8357.h>

Adafruit_HX8357 tft(TFT_CS, TFT_DC, TFT_RST);

For other PyPortal models and 320x240 TFT FeatherWings, it’s Adafruit_ILI9341.h and an Adafruit_ILI9341 object. Again, the arguments vary between PyPortal and FeatherWing, see the examples. For PyPortal it’s:

Download: file
#include <Adafruit_ILI9341.h>

Adafruit_ILI9341 tft(tft8bitbus, TFT_D0, TFT_WR, TFT_DC, TFT_CS, TFT_RST, TFT_RD);

That’s it for the header includes and global objects.

Then, in the begin() function, the display is initialized…calling its begin() (and optionally setRotation()) functions, and (on some devices) switching on the backlight:

Download: file
// Initialize display BEFORE glue setup
  tft.begin();
  tft.setRotation(TFT_ROTATION);
  pinMode(TFT_BACKLIGHT, OUTPUT);    // On devices with controllable backlight
  digitalWrite(TFT_BACKLIGHT, HIGH); // otherwise omit these lines

With the TFT Gizmo for Circuit Playground, the backlight control is a little different:

Download: file
analogWrite(TFT_BACKLIGHT, 255); // USE analogWrite() FOR GIZMO BACKLIGHT!

If using the a TFT FeatherWing (or other screen with STMPE610 touchscreen controller), that library also needs a begin() call. On PyPortal, this isn’t needed.

Download: file
if(!ts.begin()) {
    Serial.println("Couldn't start touchscreen controller");
    for(;;);
  }

Finally, with the display and touch initialized, we call our Glue library’s begin() function, passing in the address of the TFT and (optionally) touchscreen objects (the & before each means “address of” in C). If using a non-touch device (as in the CLUE and Gizmo examples), the touchscreen object can be omitted.

Download: file
// Initialize glue, passing in address of display & touchscreen
  LvGLStatus status = glue.begin(&tft, &ts);
  if(status != LVGL_OK) {
    Serial.printf("Glue error %d\r\n", (int)status);
    for(;;);
  }

You can also optionally add a “true” last argument to begin(), which enables logging to the Serial Console (also requires some configuration of LittlevGL, as explained on the previous page).

All of our examples then have this call at the end of the setup() function:

Download: file
lvgl_setup(); // Call UI-building function above

In the examples, all of the LittlevGL interface-building happens inside a lvgl_setup() function, not in setup(). This makes it easier to just copy-and-paste the boilerplate Arduino code that sets up the display and touchscreen…you don’t need to snip pieces out of setup() every time.

The loop() function often then has very little to do. Just need to periodically call one of the LittlevGL internal functions every few milliseconds. Any special handling of events (such as button presses) is handled with other callback functions (see widgets_pyportal for example).

Download: file
void loop(void) {
  lv_task_handler(); // Call LittleVGL task handler periodically
  delay(5);
}

The CLUE and Gizmo examples have a bit more code inside their loop() functions. Since these devices lack touchscreens, our code needs to process input from the physical buttons on its own.

Using LittlevGL with Other Displays

Most of our color TFT and OLED displays could be used with LittlevGL, but the examples are all written for devices that offer lots of pixels… the utility of LittlevGL might be lost on smaller displays, but you’re welcome to experiment. PyGamer, for example, the screen is only 160x128 pixels and non-touch…but the joystick and buttons might still make it useful in unexpected ways.

To try out a different display, you can usually create a mashup starting with one of our LvGL_Glue examples, plus a simple graphics test from that particular display library. Extract those parts that define and initialize the display, then pass the corresponding display object to the Glue library’s begin() function.

For example, using a SSD1351-based OLED display, one might start with this:

Download: file
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1351.h>
#include <SPI.h>

#define SCREEN_WIDTH  128
#define SCREEN_HEIGHT 128 // Change this to 96 for 1.27" OLED.

#define DC_PIN   4
#define CS_PIN   5
#define RST_PIN  6

Adafruit_SSD1351 tft(SCREEN_WIDTH, SCREEN_HEIGHT, &SPI, CS_PIN, DC_PIN, RST_PIN);

And then, in the setup() function, initialize the screen and hand it off to the glue library:

Download: file
tft.begin();

  // Initialize glue, passing in address of display
  LvGLStatus status = glue.begin(&tft);
  if(status != LVGL_OK) {
    Serial.printf("Glue error %d\r\n", (int)status);
    for(;;);
  }

That’s just the setup…there would be additional code to create the LittlevGL interface and periodically call that library’s task handler, exactly like our examples for other devices.

The lesser resolution of these displays will limit what can be done there with LittlevGL, but it might still prove useful for a few small status indicators. If you really want to work at it, LittlevGL can be “themed” with different visual styles, and you might find something less busy that works well with less screen real estate.

See the LittlevGL documentation for more ideas!

This guide was first published on Apr 09, 2020. It was last updated on Apr 09, 2020.