Complete guide to setting up LVGL on your SBC and creating your first UI application
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.
Before starting this tutorial, make sure you have:
Start by cloning the LVGL repository to your development environment:
git clone https://github.com/lvgl/lvgl.git
cd lvgl
git checkout release/v8.3
Copy the essential LVGL files to your project directory:
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
Edit the lv_conf.h file to configure LVGL for your specific hardware:
#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
LV_HOR_RES_MAX and LV_VER_RES_MAX to match your displayCreate a display driver file to interface with your specific hardware:
#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);
}
Here's an example for a common SPI display (ST7789):
// 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
}
Create your main application file with a simple UI:
#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;
}
Create a Makefile for your project:
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
Cause: Incorrect display configuration or initialization
Solution: Check your display driver initialization and ensure proper SPI/I2C communication
Cause: Insufficient memory or slow display interface
Solution: Increase buffer size, optimize display driver, or reduce UI complexity
Cause: LVGL memory pool too small
Solution: Increase LV_MEM_SIZE in your configuration