Difficulty: Beginner
Time: 15 minutes
Updated: 2 days ago

Introduction

LVGL (Light and Versatile Graphics Library) is a powerful graphics library designed for embedded systems. It provides a comprehensive set of widgets, themes, and tools for creating beautiful user interfaces on resource-constrained devices like single-board computers.

This tutorial will guide you through setting up LVGL on your SBC and creating your first application. We'll cover the essential concepts, configuration, and provide working code examples that you can immediately use in your projects.

Prerequisites

Before starting this tutorial, make sure you have:

  • A single-board computer (Raspberry Pi, ESP32, STM32, etc.)
  • A display connected to your SBC (SPI, I2C, or parallel interface)
  • Basic knowledge of C programming
  • Development environment set up for your specific SBC
  • Git installed on your development machine

Step 1: Download and Setup LVGL

1.1 Clone LVGL Repository

Start by cloning the LVGL repository to your development environment:

Bash
git clone https://github.com/lvgl/lvgl.git
cd lvgl
git checkout release/v8.3

1.2 Copy Core Files

Copy the essential LVGL files to your project directory:

Bash
mkdir my_lvgl_project
cd my_lvgl_project

# Copy core LVGL files
cp -r ../lvgl/src ./
cp -r ../lvgl/demos ./
cp ../lvgl/lv_conf_template.h ./lv_conf.h

Step 2: Configure LVGL

2.1 Basic Configuration

Edit the lv_conf.h file to configure LVGL for your specific hardware:

C
#ifndef LV_CONF_H
#define LV_CONF_H

#include 

// Basic display configuration
#define LV_HOR_RES_MAX          320
#define LV_VER_RES_MAX          240
#define LV_COLOR_DEPTH          16
#define LV_COLOR_16_SWAP        0

// Memory configuration
#define LV_MEM_CUSTOM           0
#define LV_MEM_SIZE            (32U * 1024U)    // 32KB
#define LV_MEM_POOL_INCLUDE    
#define LV_MEM_POOL_ALLOC      malloc
#define LV_MEM_POOL_FREE       free

// Enable required widgets
#define LV_USE_LABEL           1
#define LV_USE_BUTTON          1
#define LV_USE_SLIDER          1
#define LV_USE_BAR             1
#define LV_USE_IMG             1

// Enable themes
#define LV_USE_THEME_DEFAULT   1

// Enable logging
#define LV_USE_LOG             1
#define LV_LOG_LEVEL           LV_LOG_LEVEL_INFO

// Enable GPU acceleration (if available)
#define LV_USE_GPU             0

#endif // LV_CONF_H

Configuration Notes

  • Display Resolution: Set LV_HOR_RES_MAX and LV_VER_RES_MAX to match your display
  • Color Depth: Use 16-bit for most embedded displays, 24-bit for HDMI output
  • Memory Size: Allocate enough memory for your UI complexity
  • Widgets: Enable only the widgets you need to save memory

Step 3: Create Display Driver

3.1 Basic Display Driver Structure

Create a display driver file to interface with your specific hardware:

C
#include "lvgl.h"
#include "lv_conf.h"

// Display buffer
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf1[LV_HOR_RES_MAX * 10];
static lv_color_t buf2[LV_HOR_RES_MAX * 10];

// Display driver
static lv_disp_drv_t disp_drv;

// Display flush callback
static void disp_flush_cb(lv_disp_drv_t * disp_drv, 
                         const lv_area_t * area, 
                         lv_color_t * color_p) {
    // TODO: Implement your display driver here
    // This function should send the color data to your display
    
    // Example for SPI display:
    // spi_send_data(area->x1, area->y1, area->x2, area->y2, color_p);
    
    // Example for framebuffer:
    // fb_write_data(area->x1, area->y1, area->x2, area->y2, color_p);
    
    // Tell LVGL that the flush is ready
    lv_disp_flush_ready(disp_drv);
}

// Initialize display driver
void lvgl_display_init(void) {
    // Initialize display buffers
    lv_disp_draw_buf_init(&draw_buf, buf1, buf2, LV_HOR_RES_MAX * 10);
    
    // Initialize display driver
    lv_disp_drv_init(&disp_drv);
    disp_drv.flush_cb = disp_flush_cb;
    disp_drv.draw_buf = &draw_buf;
    disp_drv.hor_res = LV_HOR_RES_MAX;
    disp_drv.ver_res = LV_VER_RES_MAX;
    
    // Register display driver
    lv_disp_drv_register(&disp_drv);
}

3.2 Hardware-Specific Implementation

Here's an example for a common SPI display (ST7789):

C
// ST7789 SPI display driver example
#include 
#include 

#define SPI_CHANNEL 0
#define SPI_SPEED   40000000  // 40MHz

// Initialize SPI
void spi_init(void) {
    wiringPiSetup();
    wiringPiSPISetup(SPI_CHANNEL, SPI_SPEED);
    
    // Configure GPIO pins for CS, DC, RST
    pinMode(8, OUTPUT);  // CS
    pinMode(9, OUTPUT);  // DC
    pinMode(7, OUTPUT);  // RST
    
    // Reset display
    digitalWrite(7, LOW);
    delay(100);
    digitalWrite(7, HIGH);
    delay(100);
    
    // Initialize ST7789
    st7789_init();
}

// Send command to display
void st7789_cmd(uint8_t cmd) {
    digitalWrite(9, LOW);   // DC low for command
    digitalWrite(8, LOW);   // CS low
    wiringPiSPIDataRW(SPI_CHANNEL, &cmd, 1);
    digitalWrite(8, HIGH);  // CS high
}

// Send data to display
void st7789_data(uint8_t *data, int len) {
    digitalWrite(9, HIGH);  // DC high for data
    digitalWrite(8, LOW);   // CS low
    wiringPiSPIDataRW(SPI_CHANNEL, data, len);
    digitalWrite(8, HIGH);  // CS high
}

// Initialize ST7789 display
void st7789_init(void) {
    st7789_cmd(0x11);    // Sleep out
    delay(120);
    
    st7789_cmd(0x36);    // Memory access control
    uint8_t madctl = 0x00;
    st7789_data(&madctl, 1);
    
    st7789_cmd(0x3A);    // Interface pixel format
    uint8_t pixfmt = 0x55;  // 16-bit color
    st7789_data(&pixfmt, 1);
    
    st7789_cmd(0x2A);    // Column address set
    uint8_t caset[4] = {0x00, 0x00, 0x00, 0xEF};
    st7789_data(caset, 4);
    
    st7789_cmd(0x2B);    // Row address set
    uint8_t raset[4] = {0x00, 0x00, 0x00, 0xEF};
    st7789_data(raset, 4);
    
    st7789_cmd(0x29);    // Display on
}

Step 4: Create Your First LVGL Application

4.1 Main Application Structure

Create your main application file with a simple UI:

C
#include "lvgl.h"
#include "lv_conf.h"
#include "display_driver.h"

// Button event callback
static void btn_event_cb(lv_event_t * e) {
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * btn = lv_event_get_target(e);
    
    if(code == LV_EVENT_CLICKED) {
        static uint8_t cnt = 0;
        cnt++;
        
        // Update button label
        lv_obj_t * label = lv_obj_get_child(btn, 0);
        lv_label_set_text_fmt(label, "Button: %d", cnt);
    }
}

// Create main UI
void create_main_ui(void) {
    // Create a button
    lv_obj_t * btn = lv_btn_create(lv_scr_act());
    lv_obj_set_size(btn, 120, 50);
    lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
    lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
    
    // Add label to button
    lv_obj_t * label = lv_label_create(btn);
    lv_label_set_text(label, "Button");
    lv_obj_center(label);
    
    // Create a label
    lv_obj_t * title_label = lv_label_create(lv_scr_act());
    lv_label_set_text(title_label, "Hello LVGL!");
    lv_obj_align(title_label, LV_ALIGN_TOP_MID, 0, 20);
    
    // Create a slider
    lv_obj_t * slider = lv_slider_create(lv_scr_act());
    lv_obj_set_size(slider, 200, 10);
    lv_obj_align(slider, LV_ALIGN_CENTER, 0, 60);
    lv_slider_set_range(slider, 0, 100);
    lv_slider_set_value(slider, 50, LV_ANIM_ON);
}

// Main function
int main(void) {
    // Initialize hardware
    spi_init();
    
    // Initialize LVGL
    lv_init();
    
    // Initialize display driver
    lvgl_display_init();
    
    // Create UI
    create_main_ui();
    
    // Main loop
    while(1) {
        lv_timer_handler();
        delay(5);
    }
    
    return 0;
}

4.2 Build and Compile

Create a Makefile for your project:

Makefile
CC = gcc
CFLAGS = -Wall -Wextra -O2
LIBS = -lwiringPi

# LVGL source files
LVGL_SRC = $(wildcard src/*.c)
LVGL_SRC += $(wildcard src/*/*.c)

# Your source files
SRC = main.c display_driver.c
OBJ = $(SRC:.c=.o) $(LVGL_SRC:.c=.o)

TARGET = lvgl_app

$(TARGET): $(OBJ)
	$(CC) $(OBJ) -o $(TARGET) $(LIBS)

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(OBJ) $(TARGET)

.PHONY: clean

Step 5: Testing and Debugging

5.1 Common Issues and Solutions

Display Shows Garbage

Cause: Incorrect display configuration or initialization

Solution: Check your display driver initialization and ensure proper SPI/I2C communication

Low Frame Rate

Cause: Insufficient memory or slow display interface

Solution: Increase buffer size, optimize display driver, or reduce UI complexity

Memory Allocation Errors

Cause: LVGL memory pool too small

Solution: Increase LV_MEM_SIZE in your configuration

Next Steps

Congratulations! You've successfully set up LVGL and created your first application. Here are some suggestions for what to explore next:

  • Widgets: Explore more LVGL widgets like tables, charts, and keyboards
  • Themes: Customize the appearance with themes and styles
  • Animations: Add smooth animations to your UI elements
  • Touch Input: Implement touch screen support
  • Performance: Optimize your application for better performance