LVGL学习 stm32f407-board-lvglv8.3移植
移植过程有问题,请参考正点原子的教程或者视频
硬件平台
- STM32F407ZGT6核心板
- 3.2寸屏幕
LVGL
LVGL(Light and Versatile Graphics Library)是一个免费的开源图形库,提供创建具有易 于使用的图形元素、漂亮的视觉效果和低内存占用的嵌入式 GUI。
LVGL 是一款具有丰富的部件,具备高级图形特性,支持多种输入设备, 多国语言和独立于硬件之外等免费的开源图形库。接下来我们来看一下 LVGL 图形用户库的主要特点:
- 强大的构建块:按钮、图表、列表、滑块、图像等部件。
- 具有高级图形属性:具有动画、抗锯齿、不透明度、平滑滚动的高级图形。
- 支持各种输入设备:如触摸、鼠标、键盘、编码器。
- 支持多语言:UTF-8 编码。
- 支持多显示器:它可以同时使用多个 TFT 或者单色显示器。
- 支持多种样式属性:它具有类 CSS 样式的完全可定制的图形元素。
- 独立于硬件之外:它与任何微控制器或显示器一起使用。
- 可扩展性:它能够以小内存运行(最低 64 kB 闪存,16 kB RAM 的 MCU)。
- 支持操作系统、外部存储器和 GPU(不是必需的)。
- 具有高级图形效果:可进行单帧缓冲区操作。
- 纯 C 编写: C 语言编写以获得最大的兼容性。
移植工作
- 获取LVGL源码
从 LVGL 官方 GitHub 网址(https://github.com/lvgl/lvgl/)下载。
- 把文件中的 lv_conf_template.h 文件名修改成 lv_conf.h 文件名
- .打开 lv_conf.h 文件,修改条件编译指令,如下源码所示。
- 打开 examples 文件夹,除了 porting 文件夹外,用户可以删除其他文件和文件夹
把porting文件夹重新命名为lvgl_driver,并修改文件名称。
- 准备一个裸机工程,其中包括lcd,touch驱动,能正常使用,建立LVGL文件夹。
- 在工程中,创建lvgl/src,lvgl/config/,lvgl/port,lvgl/app
- 添加lvgl源码文件
lvgl/config中主要是lvgl.h lv_conf.h配置文件
lvgl/port中主要是lv_port_disp.c,lv_port_indev.c文件,显示与输入接口文件
lvgl/src中主要是lvgl组件的源代码文件,比较重要,把src文件夹下面的所有的C文件全部添加进去,包括子文件夹下面的,添加比较繁琐。(也可以不用添加全部的,参考正点原子的教程,在这里,我是添加了所有的)
lvgl/app中主要是lvgl案例演示源代码
- 添加文件路径(参考正点原子教程,这里有的教程会添加非常的多的路径,但是正点原子的教程没有很多路径,我也是参考的,建议参考正点原子的文件夹架构)
如果使用我的文件结果这样子是可以的编译通过的,不过我在后面应该是修改了一下头文件包含的位置,可能与lvgl源码中的写法不一样,仅供参考。
工程结构如下
- 添加部分相关文件
lvgl 的 1ms 心跳 通过调用定时器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); //清除中断标志位
}
修改disp代码
/**
* @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
基本上只要修改disp_flush函数,不同的屏幕显示接口移植
修改indev代码,配置好触摸屏驱动
/**
* @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
- 注意:移植 LVGL 必须开启 C99 模式
主函数配置
参考实际工程的东西,这里的许多的头文件包含,全部在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的事务处理
}
}
移植效果
实用工具 | GUI-Guider的使用分享
工具使用教程文章链接
GUI Guider是恩智浦为LVGL开发了一个上位机GUI设计工具,可以通过拖放控件的方式设计LVGL GUI页面,加速GUI的设计。
设计完成的GUI页面可以在PC上仿真运行,确认设计完毕之后可以生成C代码,再整合到MCU项目中。
具体使用以后再说,先看看效果
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的事务处理
}
}
仿真效果
实际移植效果
代码参考
stm32f407-lvgl-board