【LVGL进阶日记】① 开源LVGL在MCU上的移植

文摘   科技   2024-10-08 23:09   山东  

关注+星标公众,不错过精彩内容


作者 | 量子君

微信公众号 | 极客工作室

【LVGL进阶日记】专栏介绍

LVGL 可以说是这两年才刚开始流行的一个小型开源嵌入式 GUI 库,具有界面精美,消耗资源小、可移植度高、响应式布局等特点,全库采用纯 C 语言开发,LVGL 库的更新速度非常快。

本章目录

  • 前言
  • 一、LVGL简介
  • 二、移植LittlevGL到MCU
  • 总结



 前言

本章介绍了LVGL主要特性、对MCU性能要求、移植到MCU的关键步骤及重要代码块等详解。
LVGL 官方资料丰富,相关链接如下:
  • LVGL 的官方网址(https://littlevgl.com)

  • LVGL 的github 网址(https://github.com/littlevgl/lvgl)

  • LVGL 的在线文档网址(https://docs.littlevgl.com/zh-CN/html/ index.html)


一、LVGL简介

1.1 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的要求如下:

  1.  16、32 或 64 位的单片机(微控制器)或处理器

  2.  微处理器的主频最好高于 16MHZ

  3.  Flash / ROM:如果只用 LVGL 核心组件的话,则至少需要 64kB 的容量,如果想完整使用的话,最好保证 180KB 以上的容量

  4.  RAM:

    ① 静态 RAM:

        大约 8 到 16 kB,这取决于你所用的组件功能和 objects 控件对象类型; 

    ② 栈:

        至少为 2Kb,一般推荐值为 4kB; 

    ③ 动态数据(堆):

        至少 4kB,如果你用到了多个或多种控件的话,那么最好设置为 16kB 以上,这个是可以通过**lv_conf.h** 配置文件中的**LV_MEM_SIZE**宏来定义的; 

    ④ 显示缓冲区:

        至少要比“水平分辨率像素”要大,一般推介值为 10 倍的“水平分辨率像素”。举个例子:假如我们屏幕的水平分辨率为480个像素,采用16位的颜色深度进行显示,即一个像素占 2 个字节,那么推介的显示缓冲区大小为 10 * 480 * 2 = 9600 个字节; 

  5.  LV_MEM_SIZE 的大小,这个就是控制 littleVGL 中所谓的动态数据堆的大小,是用来给控件的创建动态分配空间的,我们这里设置为 16KB 的大小 #define LV_MEM_SIZE (16U * 1024U);

  6.  用定时器设置其每隔 1ms 进入中断,为 littleVGL 提供 1ms 的心跳节拍,当然你也可以采用其他的定时器,原理都是一样的;

  7.  LittleVGL 的内存消耗主要体现在 2 个方面:第一个是显示缓冲区、第二个就是我们这里所要讲到的堆,而 littleVGL 堆的内存分配也是有 2 种方式,如下所示:     

    ①采用内部的 SRAM,原理定义一个静态的局部数组;      

    ②和显示缓冲区一样,采用外部的大容量 SRAM;

  8. 定义屏幕的刷新周期和此功能相关的配置项只有1个,为LV_DISP_DEF_REFR_PERIOD,它的默认值为30ms,此值设置的过大的话就可能会出现卡顿的现象,设置的过小的话就会有点浪费性能,我们直接采用默认值就可以了,不用过多理会。

二、移植LittlevGL到MCU

2.1 LVGL源码下载和文件组织

Github下载LittleVGL源代码,移植到工程中;且需要有一个最基本的LCD驱动例程,实现初始化、打点等基础功能。

在工程中新建mg_lvgl文件夹将下载好的lvgl源码发在此处,新建lvgl_driver文件夹将lvgl源码包下porting文件夹中与LCD相关的配置模板拷贝出来放到lvgl_driver下,并分别更名为lv_port_disp.c和lv_port_disp.h:

将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 */

#if 1 /*Set it to "1" to enable content*/

#ifndef LV_CONF_H#define LV_CONF_H/* clang-format off */#include <stdint.h>


2.2 LVGL配置(lvgl_conf.h):

  1. 分辨率大小设置: 

    /* Maximal horizontal and vertical resolution to support by the library.*/#define LV_HOR_RES_MAX          (128)  #define LV_VER_RES_MAX          (160)
  2. 颜色深度设置: 

    /* Color depth:3. - 1:  1 byte per pixel4. - 8:  RGB3325. - 16: RGB5656. - 32: ARGB8888*/#define LV_COLOR_DEPTH     1
  3. 界面伸缩比例调节:

       用来调节界面缩放比例的,此值越大,控件分布的就越散,控件自身的间隔也会变大,这里设置为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)*/#define LV_DPI              60     /*[px]*/
  4. 动态数据堆大小设置:

    这个参数是用于控制 littleVGL 中所谓的动态数据堆的大小,是用来给控件的创建动态分配空间的,这里我们设置为16KB。 

    /* Size of the memory used by `lv_mem_alloc` in bytes (>= 2kB)*/#  define LV_MEM_SIZE    (16U * 1024U)
  5. GPU接口设置:

    如果MCU支持GPU,那么配置该项为1,否则为0。

    /* 1: Enable GPU interface*/#define LV_USE_GPU              0   /*Only enables `gpu_fill_cb` and `gpu_blend_cb` in the disp. drv- */#define LV_USE_GPU_STM32_DMA2D  0
  6. 文件系统功能设置:

     这里我们不需要使用lvgl的文件系统功能,将该项配置为0。

    /* 1: Enable file system (might be required for images */#define LV_USE_FILESYSTEM       0
  7.  根据需求打开与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 */#define LV_USE_THEME_EMPTY 1

    /*Simple to the create your theme based on it * No flags. Set LV_THEME_DEFAULT_FLAG 0 */#define LV_USE_THEME_TEMPLATE 1

    /* 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) * */#define LV_USE_THEME_MATERIAL 1

    /* 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 */#define LV_USE_THEME_MONO 1
  8. 为LittlevGL提供心跳节拍,为lvgl提供10ms的心跳:

    #define LV_TICK_TIMER_INTERVAL (10)

    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打开。

  1. 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*/
  2. 修改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;
  3. 添加LCD初始化函数,在lv_port_disp_init函数中:

    /* Initialize your display and the required peripherals. */static void disp_init(void){    /*You code here*/    mg_app_lcd_init();}
  4. 移植关键点:

    单色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);}
    #define BIT_SET(a, b) ((a) |= (1U << (b)))#define BIT_CLEAR(a, b) ((a) &= ~(1U << (b)))

    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的关键步骤及重要代码块等详解






若觉得文章对你有帮助,随手点『好看』、转发分享,也是对我的支持
关注我的微信公众号回复“加群”按规则加入技术交流群,回复“1024”查看更多内容。
点击“阅读原文”查看更多分享

极客工作室
一个专注于嵌入式系统、智能硬件、AIoT的极客自媒体
 最新文章