目录

开发环境

目录

一、开发环境

二、准备工作

三、移植步骤

1.修改lv_conf.h

2.显示功能移植

3.触摸功能移植

4.tick及timer

四、结果展示

五、参考资料


一、开发环境

1.TencentOS物联网开发套件,MCU为RT1060

2.IDE:MCUXpresso v11.5

3.IIC设备为微雪4.3寸IPS屏幕上的触摸芯片,汇顶的GT911

4.LVGL版本:v8.0.2

5.未使用OS

二、准备工作

首先,在NXP板卡上移植LVGL最好的办法是利用官方已配置好的例程。因为官方配置好的LVGL程序会使用芯片中的PXP等功能来优化显示性能。个人配置这些细节是比较困难的。

本文为了学习LVGL的移植过程及MCUXpresso的用法,手动移植了LVGL。

还要按照本专栏前两个文章编写好显示和触摸的底层函数。

三、移植步骤

1. 获取LVGL文件。在MCUXpresso中,获取LVGL文件比较简单,只需要利用NXP的SDK管理器加载LVGL组件。

勾选LVGL后,IDE会自动向你的工程中添加LVGL文件。

2.按照LVGL官网的文档,在单片机中移植LVGL包括以下几个步骤:

1. Call lv_init().

2. Initialize your drivers.

3. Register the display and input devices drivers in LVGL. Learn more about Display and Input device registration.

4. Call lv_tick_inc(x) every x milliseconds in an interrupt to report the elapsed time to LVGL. Learn more.

5. Call lv_timer_handler() every few milliseconds to handle LVGL related tasks. Learn more.

第3步要求我们在程序注册显示与输入设备的函数,也就是要把底层的显示以及触控函数与LVGL中的函数绑定起来。

这个代码可以参考LVGL的例子,例子位于我们刚刚导入的LVGL文件的\lvgl\lvgl\examples\porting路径下。这些文件可能默认不显示在MCUXpresso的侧边栏中,因为它们文件内的代码内容是被注释掉的,IDE将它识别成无效文件。

1.修改lv_conf.h

第一步要根据你的实际需求来配置lvgl的一些基本属性,这些属性在lv_conf.h文件中。该文件的注释比较详细,基本上不需要讲解,对于不清楚的地方保持默认值即可。

比较重要的是需要设置屏幕的颜色类型,如RGB888或者RGB565。

/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH     32

2.显示功能移植

To register a display for LVGL, a lv_disp_draw_buf_t and a lv_disp_drv_t variable have to be initialized.

  • lv_disp_draw_buf_t contains internal graphic buffer(s) called draw buffer(s).

  • lv_disp_drv_t contains callback functions to interact with the display and manipulate low level drawing behavior.

LVGL文档要求程序定义一个缓冲区和displayu设备。 
以下选自例子lv_port_disp_templ.c中的内容

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[MY_DISP_HOR_RES * 10];                          /*A buffer for 10 rows*/lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/
//
//    /* Example for 2) */
//    static lv_disp_draw_buf_t draw_buf_dsc_2;
//    //static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10];                        /*A buffer for 10 rows*/
//    static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10];                        /*An other buffer for 10 rows*/
//    lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_1, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/
//
//    /* Example for 3) also set disp_drv.full_refresh = 1 below*/
//    static lv_disp_draw_buf_t draw_buf_dsc_3;
//    static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES];            /*A screen sized buffer*/
//    static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES];            /*An other screen sized buffer*/
//    //lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, MY_DISP_VER_RES * MY_DISP_HOR_RES);   /*Initialize the display buffer*/
//    lv_disp_draw_buf_init(&draw_buf_dsc_3, LCDIF_Buffer[0], LCDIF_Buffer[1], MY_DISP_VER_RES * MY_DISP_HOR_RES);   /*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 = MY_DISP_HOR_RES;disp_drv.ver_res = MY_DISP_VER_RES;/*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);
}

lv_port_disp_init(void)函数用于初始化显示屏并注册lvgl中的显示设备,十分重要。

disp_init();函数用于初始化显示屏硬件。在本专栏的前两篇文章中可知,我的lcd显示屏是在程序一开始就初始化好了的。因此本程序不实现这个函数 。

接下来一种创建缓冲区的办法,缓冲区大小为水平分辨率乘以10个lv_color_t。这种方法比较简单且容易理解,占用资源也少。大家可以在lv_port_disp_templ.c文件中查看其他缓冲区的定义方法。

代码中的MY_DISP_HOR_RES 和MY_DISP_VER_RES是屏幕水平、垂直分辨率。 、

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

上面两行代码设置了屏幕显示函数以及显示缓冲区。

我们需要在disp_flush函数实现在显示屏上绘制像素点的功能。

/*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)
{/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/int32_t x;int32_t y;for(y = area->y1; y <= area->y2; y++) {for(x = area->x1; x <= area->x2; x++) {/*Put a pixel to the display. For example:*//*put_px(x, y, *color_p)*/uint32_t rgb_color = RGB(color_p->ch.red, color_p->ch.green, color_p->ch.blue);put_pixel(x, y,rgb_color);color_p++;}}//lcdif_fill_next_buff();/*IMPORTANT!!!*Inform the graphics library that you are ready with the flushing*/lv_disp_flush_ready(disp_drv);
}

其中,put_pixel(x, y,rgb_color)是需要我实现的显示像素的函数,功能就是在显示屏坐标为x.y的点显示rgb_color这个颜色 。因为LVGL的颜色类型color_p(color_p是一个结构体,其中包含我不需要的透明度等信息,我需要的颜色类型是一个代表RGB的32位整数类型)与我设计的底层显示函数的颜色格式不同,我又加了一个颜色转换代码。

完成这一步后,在main函数中初始化LVGL和LVGL显示函数,并设计一个显示按钮的程序,应该可以在显示屏上看到该按钮。但这个时候还不具备输入、刷新等功能。可以用来验证显示功能是否正常。

//main函数lv_init(); //lvgl 系统初始化lv_port_disp_init(); //lvgl 显示接口初始化,放在 lv_init()的后面//创建按钮的例子lv_example_get_started_1();while (1) {lv_task_handler();delay_ms(10);}

例子可以在LVGL官网文档中找到:

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++;/*Get the first child of the button which is the label and change its text*/lv_obj_t *label = lv_obj_get_child(btn, 0);lv_label_set_text_fmt(label, "Button: %d", cnt);}
}/*** Create a button with a label and react on click event.*/
void lv_example_get_started_1(void) {lv_obj_t *btn = lv_btn_create(lv_scr_act()); /*Add a button the current screen*/lv_obj_set_pos(btn, 10, 10); /*Set its position*/lv_obj_set_size(btn, 120, 50); /*Set its size*/lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); /*Assign a callback to the button*/lv_obj_t *label = lv_label_create(btn); /*Add a label to the button*/lv_label_set_text(label, "Button"); /*Set the labels text*/lv_obj_center(label);
}

3.触摸功能移植

LVGL支持鼠标、触摸板、按键等多种输入方式。本文以触摸屏为例,移植设备的输入功能。该代码参照例子lv_port_indev_template.c中的代码。

与显示功能类似,lv_port_indev_init是一个重要的函数,用于初始化并注册LVGL的输入设备,代码如下。

void lv_port_indev_init(void)
{/*** Here you will find example implementation of input devices supported by LittelvGL:*  - Touchpad*  - Mouse (with cursor support)*  - Keypad (supports GUI usage only with key)*  - Encoder (supports GUI usage only with: left, right, push)*  - Button (external buttons to press points on the screen)**  The `..._read()` function are only examples.*  You should shape them according to your hardware*/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);//    /*------------------
//     * Mouse
//     * -----------------*/
//
//    /*Initialize your touchpad if you have*/
//    mouse_init();
//
//    /*Register a mouse input device*/
//    lv_indev_drv_init(&indev_drv);
//    indev_drv.type = LV_INDEV_TYPE_POINTER;
//    indev_drv.read_cb = mouse_read;
//    indev_mouse = lv_indev_drv_register(&indev_drv);
//
//    /*Set cursor. For simplicity set a HOME symbol now.*/
//    lv_obj_t * mouse_cursor = lv_img_create(lv_scr_act());
//    lv_img_set_src(mouse_cursor, LV_SYMBOL_HOME);
//    lv_indev_set_cursor(indev_mouse, mouse_cursor);
//
//    /*------------------
//     * Keypad
//     * -----------------*/
//
//    /*Initialize your keypad or keyboard if you have*/
//    keypad_init();
//
//    /*Register a keypad input device*/
//    lv_indev_drv_init(&indev_drv);
//    indev_drv.type = LV_INDEV_TYPE_KEYPAD;
//    indev_drv.read_cb = keypad_read;
//    indev_keypad = lv_indev_drv_register(&indev_drv);
//
//    /*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
//     *add objects to the group with `lv_group_add_obj(group, obj)`
//     *and assign this input device to group to navigate in it:
//     *`lv_indev_set_group(indev_keypad, group);`*/
//
//    /*------------------
//     * Encoder
//     * -----------------*/
//
//    /*Initialize your encoder if you have*/
//    encoder_init();
//
//    /*Register a encoder input device*/
//    lv_indev_drv_init(&indev_drv);
//    indev_drv.type = LV_INDEV_TYPE_ENCODER;
//    indev_drv.read_cb = encoder_read;
//    indev_encoder = lv_indev_drv_register(&indev_drv);
//
//    /*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
//     *add objects to the group with `lv_group_add_obj(group, obj)`
//     *and assign this input device to group to navigate in it:
//     *`lv_indev_set_group(indev_encoder, group);`*/
//
//    /*------------------
//     * Button
//     * -----------------*/
//
//    /*Initialize your button if you have*/
//    button_init();
//
//    /*Register a button input device*/
//    lv_indev_drv_init(&indev_drv);
//    indev_drv.type = LV_INDEV_TYPE_BUTTON;
//    indev_drv.read_cb = button_read;
//    indev_button = lv_indev_drv_register(&indev_drv);
//
//    /*Assign buttons to points on the screen*/
//    static const lv_point_t btn_points[2] = {
//            {10, 10},   /*Button 0 -> x:10; y:10*/
//            {40, 100},  /*Button 1 -> x:40; y:100*/
//    };
//    lv_indev_set_button_points(indev_button, btn_points);
}

该代码在官方例子的基础上修改的,只使用与touchpad类型有关的代码。在touchpad_read函数中,开发者要实现对应自己硬件的屏幕触摸功能。

本例中使用的是GT911触摸芯片,MCU通过IIC接口与之通信,这在我之前的文章中可以查到。

其主要逻辑可以从程序中看出来,touchpad_is_pressed还有touchpad_get_xy都是由我编写的,只需要返回触摸屏的按压状态以及触点坐标即可,不同硬件程序不同,就不放在文章里了。

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;
}

4.tick及timer

屏幕要定期刷新,程序也要是周期性地读取触摸状态。因此,LVGL有程序中有一个不断循环的程序,用来周期性地处理这些任务。lv_tick_inc(x)为LVGL提供了心跳功能,需要每x毫秒调用一次,程序据此获得运行的时间。lv_tick_inc(x)需要有一个较高的执行优先级,因此本程序把它放在systick定时器的中断中,并设置systick定时周期为1ms。在main函数的while(1)中,执行lv_timer_handler()函数,用于处理LVGL的各种任务。最终main函数如下文所示。

//systick中断服务子程序void SysTick_Handler(void)
{lv_tick_inc(1);}
int main(void) {char ch;/* Init board hardware. */BOARD_ConfigMPU();BOARD_InitBootPins();BOARD_InitBootClocks();BOARD_InitDebugConsole();BOARD_InitBootPeripherals();gt911_init();systick_init();PRINTF("hello world.\r\n %d", freq);//lvgllv_init(); //lvgl 系统初始化lv_port_disp_init(); //lvgl 显示接口初始化,放在 lv_init()的后面lv_port_indev_init();lv_example_get_started_1();while (1) {lv_task_handler();delay_ms(10);}}

四、结果展示

移植成功后,会在屏幕上出现一个按钮,点击按钮后,按钮的文字会出现变化。因为我在lv_conf.h中开启了CPU占用和FPS的显示功能,所以在屏幕下方可以看到相关信息。屏幕帧率保持33帧。这还是没做过优化的情况,优化后可能会更快。

总的来说,LVGL的移植还是比较方便的,而且NXP官方也对其提供了更多支持,利用官方例程会提升显示性能。

五、参考资料

Quick overview — LVGL documentationhttps://docs.lvgl.io/master/get-started/quick-overview.html

MCUXpresso开发NXP RT1060(3)——移植LVGL到NXP RT1060相关推荐

  1. 乐鑫ESP32移植LVGL 7.10

    零. 声明 本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下: 第一篇:ESP-IDF基本介绍,主要会涉及模组,芯片,开发板的介绍,环境搭建,程序编译下载,启动流程等一些基本的操作,让你对 ...

  2. STM32移植LVGL(LittleVGL)

    STM32移植LVGL(LittleVGL) 一.什么是LVGL https://lvgl.io/ 这是LVGL的官网, http://lvgl.100ask.org/8.2/intro/index. ...

  3. 【LVGL】学习笔记--(1)Keil中嵌入式系统移植LVGL

    一 LVGL简介 最近emwin用的比较烦躁,同时被LVGL酷炫的界面吸引到了,所以准备换用LVGL试试水. LVGL(轻量级和通用图形库)是一个免费和开源的图形库,它提供了创建嵌入式GUI所需的一切 ...

  4. linux笔记(8):东山哪吒D1H移植lvgl(HDMI输出)

    文章目录 1. 下载,修改,编译源码 1.1下载源码 1.1.1新建一个`lvgl`目录,在该目录下下载源码 1.1.2 在`lvgl`目录下再建一个`myspace/lvgl_demo`目录,把参与 ...

  5. 【嵌入式Linux应用开发】SquareLine Studio与LVGL模拟器

    1. 概述 ​ 本篇重点是讲LVGL的开发辅助工具,以及利用这些工具将LVGL制作UI之后移植到嵌入式Linux开发板上显示. 温湿度监控系统应用开发所有文章 [嵌入式Linux应用开发]移植LVGL ...

  6. UEFI移植LVGL

    自己组装过游戏主机的应该都有看到过,进入BIOS设置,酷炫的界面便呈现在眼前,而很多BIOS,使用的还是标准的界面.现在有个趋势,phoenix和insyde也在慢慢朝这种GUI界面发展,而AMI的使 ...

  7. i.MX6ULL裸机篇(二)NXP官方SDK移植之踩坑 网口驱动

    MX6ULL官方SDK移植之踩坑 网口驱动 一.引言 NXP MX6ULL 网口移植其实主要修改底层驱动程序,LWIP协议通用,一般不用修改,这里主要和大家分享在Linux环境下移植修改网口驱动程序. ...

  8. 基于Domoticz智能家居系统(十三)Domoticz-3.8153在Tiny6410开发板上的移植

    Domoticz-3.8153在Tiny6410开发板上的移植 本文将在友善之臂Tiny6410开发板上移植Domoticz-3.8153,起因是去年在mini2440上移植的3.5877版本编译出来 ...

  9. GD32F303固件库开发(16)----移植兆易创新SPI Nor Flash之GD25Q64Flash

    spi概述 SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的 ...

最新文章

  1. Windows Azure-2.5天免费深度技术训练营——面向软件工程师和架构师
  2. xml方式实现aop-切点表达式的抽取
  3. oracle11 删除表空间,oracle11g启动停止服务,修改字符集,导入导出,创建删除表空间,卸载oracle等...
  4. ABP VNext从单体切换到微服务
  5. js jq 一些属性
  6. 容器编排技术 -- Kubernetes kubectl patch 命令详解
  7. 记一次设备网络问题的排查
  8. Objective-c官方文档 怎么自定义类
  9. [工具]TS 视频合并工具
  10. 信息系统项目管理师(2022年)—— 重点内容:项目变更管理(16)
  11. Windows10无法启动windows安全中心服务怎么办?
  12. LCP 17. 速算机器人
  13. 【参赛作品66】快速搭建一套openGauss主备高可用集群
  14. Linux面试必备基础知识(十一)——系统管理命令
  15. Java版人脸检测详解上篇:运行环境的Docker镜像(CentOS+JDK+OpenCV)
  16. @所有人:你即将被AI移出群聊
  17. 全新小龟双端影视1.6壳+反编译视频教程
  18. Pandas —— resample()重采样和asfreq()频度转换
  19. 柯马机器人示教器编程_柯马COMAURacer3协作机器人维修示教器故障
  20. 图书馆:模拟一天的借阅过程

热门文章

  1. kali使用笔记本自带无线网卡_生活日常(教你笔记本电脑如何正确使用WiFi)
  2. C51与4*4矩阵键盘
  3. 云服务器怎么增加d盘_云服务器怎么加d盘
  4. Arm服务器芯片编年史
  5. H5如何拉起微信扫一扫
  6. javascript获取本周、本月、本季度、本年时间
  7. 51时钟含闹钟(可按键设置)
  8. 算法设计与分析第四章作业
  9. 计算机进制试题,计算机各种进制转换练习题(附答案)
  10. 华为云语音识别:一句话识别API调用