LVGL learning stm32f407-board-lvglv8.3 transplantation
There is a problem with the transplantation process, please refer to the tutorial or video of punctual atom
Hardware platform
- STM32F407ZGT6 core board
- 3.2 inch screen
LVGL
LVGL (Light and Versatile Graphics Library) is a free and open-source graphics library that provides the ability to create embedded GUIs with easy-to-use graphic elements, beautiful visual effects, and low memory footprint.
LVGL is a free open source graphics library with rich components, advanced graphics features, support for multiple input devices, multi-language and independent of hardware. Next, let's take a look at the main features of the LVGL graphics user library:
- Powerful building blocks: buttons, charts, lists, sliders, images, and more.
- With advanced graphics properties: Advanced graphics with animation, anti-aliasing, opacity, smooth scrolling.
- Support various input devices: such as touch, mouse, keyboard, encoder.
- Multilingual support: UTF-8 encoding.
- Multi-monitor support: It can use multiple TFT or monochrome monitors at the same time.
- Supports multiple styling properties: it has fully customizable graphic elements with CSS-like styles.
- Independent of hardware: it works with any microcontroller or display.
- Scalability: It is able to run with small memory (minimum 64 kB Flash, 16 kB RAM for MCU).
- Supports OS, external storage and GPU (not required).
- With advanced graphics effects: single frame buffer operation is possible.
- Written in pure C: Written in C for maximum compatibility.
transplant work
- Get LVGL source code
Download from LVGL official GitHub website (https://github.com/lvgl/lvgl/).
- Change the lv_conf_template.h file name in the file to the lv_conf.h file name
- .Open the lv_conf.h file and modify the conditional compilation instructions, as shown in the source code below.
- Open the examples folder, except the porting folder, users can delete other files and folders.
Rename the porting folder to lvgl_driver, and modify the file name.
- Prepare a bare-metal project, including lcd, touch drivers, which can be used normally, and create an LVGL folder.
- In the project, create lvgl/src, lvgl/config/, lvgl/port, lvgl/app
- Add the lvgl source code file
lvgl/config mainly lvgl.h lv_conf.h configuration file
lvgl/port mainly lv_port_disp.c, lv_port_indev.c files, display and input interface file
lvgl/src mainly source code files of lvgl components , it is more important to add all the C files under the src folder, including those under the subfolder, it is more cumbersome to add. (You don’t need to add all of them, please refer to the tutorial of punctual atoms. Here, I added all of them.)
lvgl/app is mainly the source code of lvgl case demonstration
- Add the file path (refer to the punctual atom tutorial, some tutorials here will add a lot of paths, but the punctual atom tutorial does not have many paths, I also refer to it, it is recommended to refer to the punctual atom folder structure) If you use my file
result It can be compiled and passed in this way, but I should modify the location of the header file later, which may be different from the writing method in the lvgl source code, for reference only.
The project structure is as follows
- Added
a 1ms heartbeat for some related files lvgl by calling timer 3
//==================================================================================================
// 实现功能:定时器3中断服务函数
// 函数说明: TIM3_IRQHandler
// 函数备注:
//--------------------------------------------------------------------------------------------------
// | - | - | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
//==================================================================================================
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
lv_tick_inc(1);//lvgl的1ms中断
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
Modify the disp code
/**
* @file lv_port_disp_templ.c
*
*/
/*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/
#if 1
/*********************
* INCLUDES
*********************/
#include "lv_port_disp.h"
#include <stdbool.h>
#include "lcd.h"
/*********************
* DEFINES
*********************/
//#ifndef MY_DISP_HOR_RES
// #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen width, default value 320 is used for now.
// #define MY_DISP_HOR_RES 320
//#endif
//#ifndef MY_DISP_VER_RES
// #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen height, default value 240 is used for now.
// #define MY_DISP_VER_RES 240
//#endif
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void disp_init(void);
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
//static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
// const lv_area_t * fill_area, lv_color_t color);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_port_disp_init(void)
{
/*-------------------------
* Initialize your display
* -----------------------*/
// disp_init();
/*-----------------------------
* Create a buffer for drawing
*----------------------------*/
/**
* LVGL requires a buffer where it internally draws the widgets.
* Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display.
* The buffer has to be greater than 1 display row
*
* There are 3 buffering configurations:
* 1. Create ONE buffer:
* LVGL will draw the display's content here and writes it to your display
*
* 2. Create TWO buffer:
* LVGL will draw the display's content to a buffer and writes it your display.
* You should use DMA to write the buffer's content to the display.
* It will enable LVGL to draw the next part of the screen to the other buffer while
* the data is being sent form the first buffer. It makes rendering and flushing parallel.
*
* 3. Double buffering
* Set 2 screens sized buffers and set disp_drv.full_refresh = 1.
* This way LVGL will always provide the whole rendered screen in `flush_cb`
* and you only need to change the frame buffer's address.
*/
/* Example for 1) */
static lv_disp_draw_buf_t draw_buf_dsc_1;
static lv_color_t buf_1[LV_HOR_RES_MAX * 10]; /*A buffer for 10 rows*/
lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, LV_HOR_RES_MAX * 10); /*Initialize the display buffer*/
/*-----------------------------------
* Register the display in LVGL
*----------------------------------*/
static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
/*Set up the functions to access to your display*/
/*Set the resolution of the display*/
disp_drv.hor_res = lcddev.width;
disp_drv.ver_res = lcddev.height;
/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = disp_flush;
/*Set a display buffer*/
disp_drv.draw_buf = &draw_buf_dsc_1;
/*Required for Example 3)*/
//disp_drv.full_refresh = 1
/* Fill a memory array with a color if you have GPU.
* Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL.
* But if you have a different GPU you can use with this callback.*/
//disp_drv.gpu_fill_cb = gpu_fill;
/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);
}
/**********************
* STATIC FUNCTIONS
**********************/
/*Initialize your display and the required peripherals.*/
//static void disp_init(void)
//{
// /*You code here*/
//}
volatile bool disp_flush_enabled = true;
/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL
*/
void disp_enable_update(void)
{
disp_flush_enabled = true;
}
/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL
*/
void disp_disable_update(void)
{
disp_flush_enabled = false;
}
/*Flush the content of the internal buffer the specific area on the display
*You can use DMA or any hardware acceleration to do this operation in the background but
*'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
LCD_Color_Fill(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);
lv_disp_flush_ready(disp_drv);
}
#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif
Basically, just modify the disp_flush function, and port different screen display interfaces
Modify the indev code and configure the touch screen driver
/**
* @file lv_port_indev_templ.c
*
*/
/*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1
/*********************
* INCLUDES
*********************/
#include "lv_port_indev.h"
#include "../lvgl.h"
#include "touch.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void touchpad_init(void);
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static bool touchpad_is_pressed(void);
static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y);
/**********************
* STATIC VARIABLES
**********************/
lv_indev_t * indev_touchpad;
static int32_t encoder_diff;
static lv_indev_state_t encoder_state;
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_port_indev_init(void)
{
static lv_indev_drv_t indev_drv;
/*------------------
* Touchpad
* -----------------*/
/*Initialize your touchpad if you have*/
touchpad_init();
/*Register a touchpad input device*/
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = touchpad_read;
indev_touchpad = lv_indev_drv_register(&indev_drv);
/*------------------
* Touchpad
* -----------------*/
/*Initialize your touchpad*/
static void touchpad_init(void)
{
/*Your code comes here*/
tp_dev.init();
if (0 == (tp_dev.touchtype & 0x80))
{
TP_Adjust();
TP_Save_Adjdata();
}
}
/*Will be called by the library to read the touchpad*/
static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
static lv_coord_t last_x = 0;
static lv_coord_t last_y = 0;
/*Save the pressed coordinates and the state*/
if(touchpad_is_pressed())
{
touchpad_get_xy(&last_x, &last_y);
data->state = LV_INDEV_STATE_PR;
}
else {
data->state = LV_INDEV_STATE_REL;
}
/*Set the last pressed coordinates*/
data->point.x = last_x;
data->point.y = last_y;
}
/*Return true is the touchpad is pressed*/
static bool touchpad_is_pressed(void)
{
if(tp_dev.sta & TP_PRES_DOWN)
return true;
return false;
}
/*Get the x and y coordinates if the touchpad is pressed*/
static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
{
(*x) = tp_dev.x[0];
(*y) = tp_dev.y[0];
}
#else /*Enable this file at the top*/
/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif
- Note: porting LVGL must enable C99 mode
Main function configuration
Referring to the actual project, many header files here are included, all in main.h
int main(void)
{
Hareware_Iint();
printf("Hareware_Iint [OK] \r\n");
lv_init(); // lvgl系统初始化
lv_port_disp_init(); // lvgl显示接口初始化,放在lv_init()的后面
lv_port_indev_init(); // lvgl输入接口初始化,放在lv_init()的后面
//lv_example_event_1();
//lv_example_get_started_3();
//lv_example_anim_timeline_1();
//lv_demo_benchmark();
//lv_example_event_3();
//lv_example_chart_5();
//lv_example_list_2();
//lv_example_roller_3();
lv_example_meter_1();
while (1)
{
tp_dev.scan(0);
lv_task_handler(); // lvgl的事务处理
}
}
transplant effect
Practical tools | GUI-Guider usage sharing
Tool usage tutorial article link
GUI Guider is a host computer GUI design tool developed by NXP for LVGL. It can design LVGL GUI pages by dragging and dropping controls to speed up GUI design.
The designed GUI page can be simulated and run on the PC. After the design is confirmed, the C code can be generated and integrated into the MCU project.
Let’s talk about the specific use later, let’s see the effect first
lv_ui guider_ui;
int main(void)
{
Hareware_Iint();
printf("Hareware_Iint [OK] \r\n");
lv_init(); // lvgl系统初始化
lv_port_disp_init(); // lvgl显示接口初始化,放在lv_init()的后面
lv_port_indev_init(); // lvgl输入接口初始化,放在lv_init()的后面
//lv_example_event_1();
//lv_example_get_started_3();
//lv_example_anim_timeline_1();
//lv_demo_benchmark();
//lv_example_event_3();
//lv_example_chart_5();
//lv_example_list_2();
//lv_example_roller_3();
//lv_example_meter_1();
setup_ui(&guider_ui);
events_init(&guider_ui);
while (1)
{
tp_dev.scan(0);
lv_task_handler(); // lvgl的事务处理
}
}
Simulation effect
Actual transplant effect