关注+星标公众号,不错过精彩内容
作者 | 量子君
微信公众号 | 极客工作室
【LVGL进阶日记】专栏介绍
本章目录
前言 一、LVGL简介 二、移植LittlevGL到MCU 总结
前言
LVGL 的官方网址(https://littlevgl.com)
LVGL 的github 网址(https://github.com/littlevgl/lvgl)
LVGL 的在线文档网址(https://docs.littlevgl.com/zh-CN/html/ index.html)
一、LVGL简介
具有非常丰富的内置控件,像 buttons、charts、lists、sliders、images等
高级图形效果:动画、反锯齿、透明度、平滑滚动
支持多种输入设备,像 touchpad、mouse、keyboard、encoder等
支持多语言的 UTF-8 编码
支持多个和多种显示设备,例如同步显示在多个彩色屏或单色屏上
完全自定制的图形元素
硬件独立于任何微控制器或显示器
可以缩小到最小内存 (64 kB Flash、16 kB RAM)
支持操作系统、外部储存和 GPU(非必须)
仅仅单个帧缓冲设备就可以呈现高级视觉特效
使用 C 编写以获得最大兼容性(兼容 C++)
支持 PC 模拟器
为加速 GUI 设计,提供教程,案例和主题,支持响应式布局
提供了在线和离线文档
基于自由和开源的 MIT 协议
1.2 LVGL对MCU的要求如下:
16、32 或 64 位的单片机(微控制器)或处理器
微处理器的主频最好高于 16MHZ
Flash / ROM:如果只用 LVGL 核心组件的话,则至少需要 64kB 的容量,如果想完整使用的话,最好保证 180KB 以上的容量
RAM:
① 静态 RAM:
大约 8 到 16 kB,这取决于你所用的组件功能和 objects 控件对象类型;
② 栈:
至少为 2Kb,一般推荐值为 4kB;
③ 动态数据(堆):
至少 4kB,如果你用到了多个或多种控件的话,那么最好设置为 16kB 以上,这个是可以通过**lv_conf.h** 配置文件中的**LV_MEM_SIZE**宏来定义的;
④ 显示缓冲区:
至少要比“水平分辨率像素”要大,一般推介值为 10 倍的“水平分辨率像素”。举个例子:假如我们屏幕的水平分辨率为480个像素,采用16位的颜色深度进行显示,即一个像素占 2 个字节,那么推介的显示缓冲区大小为 10 * 480 * 2 = 9600 个字节;
LV_MEM_SIZE 的大小,这个就是控制 littleVGL 中所谓的动态数据堆的大小,是用来给控件的创建动态分配空间的,我们这里设置为 16KB 的大小 #define LV_MEM_SIZE (16U * 1024U);
用定时器设置其每隔 1ms 进入中断,为 littleVGL 提供 1ms 的心跳节拍,当然你也可以采用其他的定时器,原理都是一样的;
LittleVGL 的内存消耗主要体现在 2 个方面:第一个是显示缓冲区、第二个就是我们这里所要讲到的堆,而 littleVGL 堆的内存分配也是有 2 种方式,如下所示:
①采用内部的 SRAM,原理定义一个静态的局部数组;
②和显示缓冲区一样,采用外部的大容量 SRAM;
定义屏幕的刷新周期和此功能相关的配置项只有1个,为LV_DISP_DEF_REFR_PERIOD,它的默认值为30ms,此值设置的过大的话就可能会出现卡顿的现象,设置的过小的话就会有点浪费性能,我们直接采用默认值就可以了,不用过多理会。
二、移植LittlevGL到MCU
2.1 LVGL源码下载和文件组织
Github下载LittleVGL源代码,移植到工程中;且需要有一个最基本的LCD驱动例程,实现初始化、打点等基础功能。
将lvgl源码包下的lv_conf_template.h拷贝到mg_lvgl目录下,然后改名为lvgl_conf.h,将lv_conf.h中将宏打开 #if 0改为1,如下:
/**
* @file lv_conf.h
* Configuration file for v7.10.0-dev
*/
/*
* COPY THIS FILE AS `lv_conf.h` NEXT TO the `lvgl` FOLDER
*/
/* clang-format off */
2.2 LVGL配置(lvgl_conf.h):
分辨率大小设置:
/* Maximal horizontal and vertical resolution to support by the library.*/
颜色深度设置:
/* Color depth:
3. - 1: 1 byte per pixel
4. - 8: RGB332
5. - 16: RGB565
6. - 32: ARGB8888
*/
#define LV_COLOR_DEPTH 1
界面伸缩比例调节:
用来调节界面缩放比例的,此值越大,控件分布的就越散,控件自身的间隔也会变大,这里设置为60。
/* Dot Per Inch: used to initialize default sizes.
8. E.g. a button with width = LV_DPI / 2 -> half inch wide
9. (Not so important, you can adjust it to modify default sizes and spaces)*/
动态数据堆大小设置:
这个参数是用于控制 littleVGL 中所谓的动态数据堆的大小,是用来给控件的创建动态分配空间的,这里我们设置为16KB。
/* Size of the memory used by `lv_mem_alloc` in bytes (>= 2kB)*/
GPU接口设置:
如果MCU支持GPU,那么配置该项为1,否则为0。
/* 1: Enable GPU interface*/
文件系统功能设置:
这里我们不需要使用lvgl的文件系统功能,将该项配置为0。
/* 1: Enable file system (might be required for images */
根据需求打开与LittlevGL主题相关的配置:
官方会有一些自带的演示demo,所以这里我默认将所有配置全部配置,但是实际使用过程中,根据需求配置,以节省FLASH和RAM。
/*================
* THEME USAGE
*================*/
/*Always enable at least on theme*/
/* No theme, you can apply your styles as you need
* No flags. Set LV_THEME_DEFAULT_FLAG 0 */
/*Simple to the create your theme based on it
* No flags. Set LV_THEME_DEFAULT_FLAG 0 */
/* A fast and impressive theme.
* Flags:
* LV_THEME_MATERIAL_FLAG_LIGHT: light theme
* LV_THEME_MATERIAL_FLAG_DARK: dark theme
* LV_THEME_MATERIAL_FLAG_NO_TRANSITION: disable transitions (state change animations)
* LV_THEME_MATERIAL_FLAG_NO_FOCUS: disable indication of focused state)
* */
/* Mono-color theme for monochrome displays.
1. If LV_THEME_DEFAULT_COLOR_PRIMARY is LV_COLOR_BLACK the
2. texts and borders will be black and the background will be
3. white. Else the colors are inverted.
4. No flags. Set LV_THEME_DEFAULT_FLAG 0 */
为LittlevGL提供心跳节拍,为lvgl提供10ms的心跳:
void lv_tick_handler(void * p_context) {
lv_tick_inc(10);
}
void mg_lvgl_tick_init(void) {
uint32_t err_code = NRF_SUCCESS;
err_code = app_timer_create(&lv_tick_timer, APP_TIMER_MODE_REPEATED, lv_tick_handler);
APP_ERROR_CHECK(err_code);
err_code = app_timer_start(lv_tick_timer, APP_TIMER_TICKS(LV_TICK_TIMER_INTERVAL), NULL);
APP_ERROR_CHECK(err_code);
}
2.3 移植显示驱动
主要在lv_port_disp.h和lv_port_disp.c这两个文件里做修改,lv_port_disp.h将#if 0打开。
lv_port_disp.c选择一种方式写缓存方式:
/*-----------------------------
* Create a buffer for drawing
*----------------------------*/
/* LVGL requires a buffer where it internally draws the widgets.
* Later this buffer will passed your display drivers `flush_cb` to copy its content to your display.
* The buffer has to be greater than 1 display row
*
* There are three buffering configurations:
* 1. Create ONE buffer with some rows:
* LVGL will draw the display's content here and writes it to your display
*
* 2. Create TWO buffer with some rows:
* 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. Create TWO screen-sized buffer:
* Similar to 2) but the buffer have to be screen sized. When LVGL is ready it will give the
* whole frame to display. This way you only need to change the frame buffer's address instead of
* copying the pixels.
* */
/* Example for 1) */
static lv_disp_buf_t draw_buf_dsc_1;
static lv_color_t draw_buf_1[LV_HOR_RES_MAX * 10]; /*A buffer for 10 rows*/
lv_disp_buf_init(&draw_buf_dsc_1, draw_buf_1, NULL, LV_HOR_RES_MAX * 10); /*Initialize the display buffer*/
/* Example for 2) */
static lv_disp_buf_t draw_buf_dsc_2;
static lv_color_t draw_buf_2_1[LV_HOR_RES_MAX * 10]; /*A buffer for 10 rows*/
static lv_color_t draw_buf_2_1[LV_HOR_RES_MAX * 10]; /*An other buffer for 10 rows*/
lv_disp_buf_init(&draw_buf_dsc_2, draw_buf_2_1, draw_buf_2_1, LV_HOR_RES_MAX * 10); /*Initialize the display buffer*/
/* Example for 3) */
static lv_disp_buf_t draw_buf_dsc_3;
static lv_color_t draw_buf_3_1[LV_HOR_RES_MAX * LV_VER_RES_MAX]; /*A screen sized buffer*/
static lv_color_t draw_buf_3_1[LV_HOR_RES_MAX * LV_VER_RES_MAX]; /*An other screen sized buffer*/
lv_disp_buf_init(&draw_buf_dsc_3, draw_buf_3_1, draw_buf_3_2, LV_HOR_RES_MAX * LV_VER_RES_MAX); /*Initialize the display buffer*/
修改LCD显示大小,在lv_port_disp_init函数中;
/*Set up the functions to access to your display*/
/*Set the resolution of the display*/
disp_drv.hor_res = 128;
disp_drv.ver_res = 160;
添加LCD初始化函数,在lv_port_disp_init函数中:
/* Initialize your display and the required peripherals. */
static void disp_init(void)
{
/*You code here*/
mg_app_lcd_init();
}
移植关键点:
单色LCD的1bit表示一个像素的处理,更新像素时如何去修改中的一个bit位。
/*Set a display buffer*/
disp_drv.buffer = &disp_buf_3;
disp_drv.set_px_cb = &set_px_cb;
disp_drv.rounder_cb = &rounder_cb;
/* 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)
{
uint8_t row1 = area->y1 >> 3;//get where page
uint8_t row2 = area->y2 >> 3;
uint8_t *buf = (uint8_t *) color_p;
for(uint8_t row = row1; row <= row2; row++) {
drv_st75160_address(area->x1, row);
for(uint16_t x = area->x1; x <= area->x2; x++) {
drv_st75160_write_data(*buf);
buf++;
}
}
/* IMPORTANT!!!
* Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
static void set_px_cb(struct _disp_drv_t *disp_drv, uint8_t *buf, lv_coord_t buf_w,
lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa) {
uint16_t byte_index = x + (( y >> 3 ) * buf_w);
uint8_t bit_index = 7 - y & 0x7;
// == 0 inverts, so we get blue on black
if (color.full == 0) {
BIT_SET(buf[byte_index], bit_index);
} else {
BIT_CLEAR(buf[byte_index], bit_index);
}
}
static void rounder_cb(struct _disp_drv_t *disp_drv, lv_area_t *area) {
area->y1 = (area->y1 & (~0x7));
area->y2 = (area->y2 & (~0x7)) + 7;
}
/** OPTIONAL: Extend the invalidated areas to match with the display drivers requirements
* E.g. round `y` to, 8, 16 ..) on a monochrome display*/
void (*rounder_cb)(struct _disp_drv_t * disp_drv, lv_area_t * area);
/** OPTIONAL: Set a pixel in a buffer according to the special requirements of the display
* Can be used for color format not supported in LittelvGL. E.g. 2 bit -> 4 gray scales
* @note Much slower then drawing with supported color formats. */
void (*set_px_cb)(struct _disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y,
lv_color_t color, lv_opa_t opa);
2.4 在makefile或MDK工程中添加相应的LVGL的.C和头文件
主循环中添加 lv_task_handler(),最后下载到mcu中测试LittlevGL是否移植成功。
while (true) {
mg_float_arithmetic_manager();
APP_ERROR_CHECK(sd_app_evt_wait());
app_sched_execute();
lv_task_handler();
}
总结
本章介绍了LVGL主要特性、对MCU性能要求、移植到MCU的关键步骤及重要代码块等详解。