LVGL8.1笔记1--显示移植

  • 前言
  • 一、移植前准备
  • 二、LVGL-8.1目录简介
    • 主要用的的内容
      • examples文件夹内容介绍
      • src文件夹内容介绍
      • lv_conf_template.h文件内容介绍
  • 三、开始移植
    • 1. 编译准备好的带屏幕显示的keil工程
    • 2. 添加LVGL-8.1到工程中
    • 3. API接口移植(重点)
    • 4.修改配置文件lv_conf.h
  • 四、运行
    • 设置心跳
    • 测试使用
    • 参考资料

转载请注明出处:https://blog.csdn.net/qq_38685043/article/details/124786944?spm=1001.2014.3001.5501


前言

LVGL是目前比较流行的一款嵌入式GUI图形库,特点是高度可裁剪,占用资源低,界面简洁美观。详细介绍可以看LVGL官方介绍。理论上来说LVGL属于上层程序,不依赖特点的硬件平台,只要是硬件配置(Flash、RAM)满足要求都可以运行LVGL,比如STM32、ESP32。

以前稍微学习过LVGL6.0,但是很长时间没有使用上,而且现在已经发展的8.0以上了,所以再从头学习一下,并且做一些详细的学习笔记。


一、移植前准备

  1. 硬件(带屏幕的STM32F407VE核心板)
  2. Keil工程,实现屏幕显示、按键或触摸等
  3. LVGL-8.1源码(点击这里下载)

二、LVGL-8.1目录简介

目录中内容挺多,主要用的的也就是三部分,下面也只简单介绍这三部分,其他的详细介绍可以参考这个链接lvgl 主要文件目录树

主要用的的内容

  • examples

    • 一些演示例程和可以用到的接口函数
  • src
    • LVGL的源码,最重要的内容
  • lv_conf_template.h
    • LVGL配置文件,用来裁剪控制LVGL的功能,通常重命名为lv_conf.h
  • lvgl.h
    • LVGL用到的一些头文件的声明

examples文件夹内容介绍



examples->porting文件夹中的文件主要是和显示、文件系统、触摸按键相关的接口。

需要将屏幕显示用的画点函数放入lv_port_disp.c文件中,否则屏幕不会正常显示

需要将触摸或是按键驱动放入lv_port_indev.c文件中,否则无法使用触摸屏幕或按键操作

文件系统相关的内容有需要才用得到。

src文件夹内容介绍

src目录中存放的是LVGL的源码,是最主要的内容。其中extra/lib中是一些第三方的库,比如生成二维码用的库。这些非必要可以不用添加,后续有需要再添加进工程也可。

font中是一些自带的字库,只支持英文,想要显示中文需要其他方法。后面再介绍。

lv_conf_template.h文件内容介绍

这是LVGL的配置文件,里面都是一些宏定义。可以通过修改这些宏的值,来控制某些功能的开启或关闭。

三、开始移植

其实LVGL移植起来还是很简单的,简单的几步就可以了

  • 准备好硬件平台以及这个硬件带屏幕显示的keil工程,最好是彩屏(虽然LVGL支持单色屏幕,但我还不会用)
  • 添加LVGL-8.1到工程中
  • API接口移植
  • 修改配置文件
  • 配置LVGL心跳函数
  • 启动LVGL
  • 写一个函数测试

1. 编译准备好的带屏幕显示的keil工程

确保编译成功,可以正常显示,最重要的是有刷屏函数

2. 添加LVGL-8.1到工程中

  1. 在keil工程目录中新建文件夹LVGL_GUI

  2. 将examples文件夹复制到LVGL_GUI

  3. 将src文件夹复制到LVGL_GUI

  4. 将lv_conf_template.h复制到LVGL_GUI,并重命名为lv_conf.h

  5. 将lvgl.h复制到LVGL_GUI

  6. 将src目录下的全部添加到工程中LVGL_GUI组下(除了上面介绍过的第三方库)

  7. 将examples/porting目录下的文件添加到工程中(可以添加到LVGL_Port组下)

    注意:添加工程时内容有些多,要注意别漏掉某些文件。别忘了添加头文件路径

​ 添加完后编译一下,可能会出现大量的error,先不要慌,看一下keil的配置,因为LVGL是基于C99编写的,所以keil编译时一定要选择C99模式编译,不选择的话默认使用的是C89模式,所以会出很多错误。

​ 改完编译模式之后再次编译应该就没有错误了,但是会有很多警告,比如一些类型转换的警告,文档结尾没有换行的警告。这些警告建议直接忽略就好了,因为一般这种类似于库函数的代码是不建议自行修改的。要是觉得这些警告看着别扭可以在Keil中设置一下忽略指定类型的警告(警告68、111可以这样设置--diag_suppress=68 --diag_suppress=111)。

如果还有error,可以检查一下有没有添加头文件路径,看看error类型是什么,慢慢解决 不要慌。有时候很多的error,可能只是某个细节疏忽了。

3. API接口移植(重点)

这应该是整个LVGL移植中最重要的,这一步就是把你的程序和LVGL源码连接起来,主要就是两部分,一是输出、一是输入;

输出:说白了就是屏幕显示,所以需要移植一下显示接口函数

输入:可以是触摸板、触摸屏、按键、编码器、甚至是鼠标等都可

  1. 屏幕显示接口的移植

    将examples/porting目录下的lv_port_disp_template.c和lv_port_disp_template.h文件改名为lv_port_disp.clv_port_disp.h,当然不改名也可以用,但是一般都习惯改一下,显得比较规范。

    打开.c和.h文件,将最开头的#if 0 改为#if 1,这样这两个文件编译时就都有效了


    主要修改三个函数:

    void disp_init(void)这个函数是屏幕外设初始化用的,比如我用的屏幕,初始化函数是LCD_Init()。可以复制放到这里来初始化,也可以自己在其他地方初始化,但是要确保在lvgl初始化之前调用。

    /*Initialize your display and the required peripherals.在这里调用屏幕初始化(如果在其他地方调用了,这里可以不写)*/
    static void disp_init(void)
    {/*You code here*/LCD_Init();                                       //LCD初始化
    }
    

    void disp_flush(....)这个函数是刷屏用的,需要将我们自己实现的刷屏函数函数放到这里,函数需要实现的功能是在指定位置绘制指定大小和颜色的矩形,最小可以画一个像素点。

    通常我们用屏幕,厂家都会有配套的资料,资料里面一般有可以点亮屏幕的程序,通常都会有显示一个字符函数LCD_ShowChar、清屏函数LCD_Clear、显示一条直线函数LCD_DrawLine、画一个带颜色的点函数LCD_DrawPoint画一个填充了颜色的矩形函数LCD_Color_Fill

    其中这两个函数必要有一个,没有的话需要自己写一下

    void LCD_DrawPoint(u16 x, u16 y, u16 color);                              //画点
    void LCD_Color_Fill(u16 x1, u16 y1, u16 x2, u16 y2, u16 *color);         //填充指定颜色
    

    如果使用画点函数,直接放到最里层的那个for循环就行,把参数填充一下就行了

    如果想让LVGL运行更流畅可以使用LCD_Color_Fill这个函数,把两个for循环值删掉,放入这个函数,把参数填充一下就行了。

    // 这是LVGL接口函数原型,我们需要进行修改
    /*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)*/color_p++;}}/*IMPORTANT!!!*Inform the graphics library that you are ready with the flushing*/lv_disp_flush_ready(disp_drv);
    }// 下面是我修改好的函数
    /*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*/// 以X1,y1为起点 x2,y2为终点画一个颜色为color的矩形LCD_Color_Fill(area->x1, area->y1, area->x2, area->y2,(uint16_t *)color_p);/*IMPORTANT!!!*Inform the graphics library that you are ready with the flushing*/lv_disp_flush_ready(disp_drv);    // 这个函数非常重要,千万不能删掉
    }
    

    void lv_port_disp_init(void)这个函数是LVGL_显示接口初始化函数,用来初始化LVGL库用的绘制缓冲区、调用刷屏函数、设置屏幕大小等功能。看函数的声明就会发现,前两个函数都有static修饰,表示函数的使用范围只在这个lv_port_disp.c文件中,而这个函数是需要在main函数中调用的,所以没有static修饰,而且还要我们手动在lv_port_disp.h文件中声明一下。

    • 这里有3中缓冲区定义方法,注释中写的还是挺清楚的。方式1一个数组做缓冲区,节省空间,速度偏慢。方式2两个数组做缓冲区,空间需要大,速度快点。方式3也是双缓冲区,但是是每次都刷新整个屏幕。这里使用方式2
    • 把屏幕的水平和垂直像素点数也写到里面,如果不常换屏幕可以直接写死,不用定义宏
    • 把用不到的内容注释掉就OK了
    // 在这定义两个宏来表示屏幕的分辨率大小,实际应该把宏放在lv_conf.h文件中
    #define LV_HOR_RES_MAX          (320)//定义屏幕的最大水平像素点数
    #define LV_VER_RES_MAX          (240)//定义屏幕的最大垂直像素点数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:3种方式* 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[LV_HOR_RES_MAX * 10];                        /*A buffer for 10 rows*/static lv_color_t buf_2_2[LV_HOR_RES_MAX * 10];                        /*An other buffer for 10 rows*/lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, LV_HOR_RES_MAX * 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 * LV_VER_RES_MAX);   /*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 = LV_HOR_RES_MAX;       //添加屏幕的最大水平像素点数;disp_drv.ver_res = LV_VER_RES_MAX;      //添加屏幕的最大垂直像素点数/*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_2;  // 这里和我们选的缓冲区方式2要一致/*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);
    }
    

4.修改配置文件lv_conf.h

这个文件顾名思义,就是对LVGL进行配置用的,里面都是一些宏定义,用来打开或关闭某些功能。

首先,还是要打开文件将#if 0 改为 #if 1, 然后我们自己定义两个宏,用来表示屏幕的像素尺寸,这两个宏在上面的API接口移植中用到了,定义成宏方便我们后续更换大或小的屏幕。

// 在这定义两个宏来表示屏幕的分辨率大小
#define LV_HOR_RES_MAX          (320)//定义屏幕的最大水平像素点数
#define LV_VER_RES_MAX          (240)//定义屏幕的最大垂直像素点数

这个配置文件中有挺多内容的,注释也挺详细,大家可以直接看着理解。

/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 16  // 这个宏用来定义屏幕色彩深度,一般用的彩屏深度就是16,单色屏就是1,我还没用过单色屏所以都是默认16
/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 10      /*[ms]*/ //这里控制刷屏的速度,默认是30,改小一点速度更快,但最终限制刷屏速度的是硬件速度,比如SPI的主频。/*Input device read period in milliseconds*/
#define LV_INDEV_DEF_READ_PERIOD 30     /*[ms]*/ // 这里控制按键扫描的速度,根据实际自己修改就行

以上这些宏直接影响LVGL的运行效率和使用体验,所以应该根据需要具体进行配置。

其他的宏就是一些功能的选配了。所谓的裁剪,其实就是通过宏打开或关闭功能嘛。

还有下面这两个宏在调试阶段也可以打开,这是用来在屏幕上显示帧数和MCU占用、内存占用的。

/*1: Show CPU usage and FPS count in the right bottom corner*/
#define LV_USE_PERF_MONITOR 1
#if LV_USE_PERF_MONITOR
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
#endif/*1: Show the used memory and the memory fragmentation in the left bottom corner* Requires LV_MEM_CUSTOM = 0*/
#define LV_USE_MEM_MONITOR 1
#if LV_USE_PERF_MONITOR
#define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT
#endif

基本上这些完成后,LVGL就算移植完了。接下来就可以编译了。当然编译很可能出错。不要心急,慢慢的分析错误,如果出了很多很多错误,可以看一下第一个error是啥,解决了可能全部error就都解决了。

比如可能出现的错误:

  • 文件没有添加到工程中,或者有的漏掉了。

    • 仔细检查一下,添加上文件、路径就好了
  • 在API接口移植中,会有这个头文件#include “lvgl/lvgl.h”,如果编译报error,改成#include "lvgl.h"就好了,这是因为路径引用的不太对,毕竟lv_port_disp文件是LVGL的示例文件,人家用的路径可能和我们的本地路径不同,需要修改一下。
  • 再有就是一些C语言语法错误了,函数传参类型不匹配等

四、运行

如果以上那些步骤都完成了,编译也没有错误,那么之差一小步就可以在屏幕上运行LVGL了。

设置心跳

需要调用两个函数

lv_tick_inc(1);          // 这个函数设置心跳,参数1代表1ms。通常将他放在1毫秒中断一次的定时器中断处理中
lv_task_handler();      // 这个函数用来处理LVGL的工作,每心跳一次,这里面就执行一次。

如果使用STM32并且用的cubemx来配置的功能,那么一般默认会开启滴答定时器,一般就是1ms中断一次,所以把这个lv_tick_inc(1);函数放在systick中断中就行了,不用额外的占用定时器。lv_task_handler();这个函数可不用放在中断里,通常不用RTOS的话,放在main函数里的while循环就行了

在main函数里还需要调用LVGL初始化函数,然后就可以使用了。

lv_init();                   //LVGL初始化
lv_port_disp_init();        //LVGL 显示接口初始化,放在 lv_init()的后面

下面这是我写的main函数和定时器中断处理函数

//定时器3中断服务函数,每1ms中断一次
void TIM3_IRQHandler(void)
{if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断{lv_tick_inc(1);}TIM_ClearITPendingBit(TIM3,TIM_IT_Update);  //清除中断标志位
}int main(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //设置系统中断优先级分组2delay_init(168);                                  //初始化延时函数uart_init(115200);                             //初始化串口波特率为115200W25QXX_Init();                                 //外部Flash--W25Q16初始化TIM3_Int_Init(999,83);                          //定时器配置1ms中断KEY_Init();                                     //按键初始化 LED_Init();                                     //初始化LED LCD_Init();                                        //LCD初始化 tp_dev.init();             //触摸屏初始化lv_init();                  //LVGL初始化lv_port_disp_init();       //LVGL 显示接口初始化,放在 lv_init()的后面lv_ex_label();                // 测试函数,显示点动态内容测试LVGL是否成功while(1){tp_dev.scan(0);        // 这是触摸扫描函数,大家可以用自己的按键扫描等lv_task_handler();}}

测试使用

写个小函数来测试一下我们移植是否成功,直接把这函数复制到程序中就能用,先不要研究里面这些函数是干啥的。直接用一下测试自己移植成功没。

static void lv_ex_label(void)
{static char* github_addr = "https://gitee.com/WRS0923";lv_obj_t * label = lv_label_create(lv_scr_act());lv_label_set_recolor(label, true);lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); /*Circular scroll*/lv_obj_set_width(label, 120);lv_label_set_text_fmt(label, "#ff0000 Gitee: %s#", github_addr);lv_obj_align(label, LV_ALIGN_CENTER, 0, 10);lv_obj_t * label2 = lv_label_create(lv_scr_act());lv_label_set_recolor(label2, true);lv_label_set_long_mode(label2, LV_LABEL_LONG_SCROLL_CIRCULAR); /*Circular scroll*/lv_obj_set_width(label2, 120);lv_label_set_text_fmt(label2, "#ff0000 Hello# #0000ff world !123456789#");lv_obj_align(label2, LV_ALIGN_CENTER, 0, -10);
}

如果一切正常,屏幕上会显示如下

我的完整程序放在了gitee上,https://gitee.com/WRS0923/stm32_little-vgl/tree/dev/ 有需要的可以自行下载,以后分享的笔记也会在这个程序上修改。争取把LVGL玩明白

参考资料

LittleVGL(LVGL) V8版本 干货入门教程一之移植到STM32并运行:https://blog.csdn.net/qq_26106317/article/details/120610353

lvgl 主要文件目录树:https://blog.csdn.net/qq_39567970/article/details/122126329

【LVGL学习之旅 01】移植LVGL到STM32:https://blog.csdn.net/qq_40831286/article/details/107633216

LVGL系列(二)之二 LVGL常见问题解答 整理自官方文档:https://blog.csdn.net/qq1445104593/article/details/119809376

LVGL8.1笔记1--显示移植(2022-0515)相关推荐

  1. LVGL8.3 集成 ST7789V 显示驱动和 CST816T 触摸屏驱动

    LVGL8.3 集成 ST7789V 显示驱动和 CTS816S 触摸屏驱动 起因 效果(正常显示,触摸屏可调换X,Y轴) 使用方式 前提 操作步骤 最后 参考 起因 LVGL的ESP32 Drive ...

  2. 白皮书显示,2022年仅有28.4%企业实现社保基数完全合规,有38.1%企业额外购买补充商业保险 | 美通社头条...

    美通社消息:8月26日,由众合云科旗下51社保举办的主题为"三位一体,重构未来"的第十届中国企业社保高峰论坛暨<中国企业社保白皮书>十周年盛典在京举行,众合云科创始人兼 ...

  3. ESP32 LVGL8.1 实现太空人显示(29)

    文章目录 一.ESP32 LVGL工程配置 1.1从库中下载LVGL代码 1.2配置适合ESP32 液晶屏 1.3编译下载测试 二.GIF图片处理 2.1下载gif图片 2.2将gif图片按照帧率导出 ...

  4. CESM2笔记——porting-新机器移植

    CESM2相比CESM1_2_2更新了很多,尤其是我要用的CAM-chem,所以打算重新porting.一开始一点也不懂自己瞎搞,一直理不清头绪,网页太多,自己看来看去抓不到重点.后来请教了一位师姐, ...

  5. LwIP学习笔记——STM32 ENC28J60移植与入门

    0.前言 去年(2013年)的整理了LwIP相关代码,并在STM32上"裸奔"成功.一直没有时间深入整理,在这里借博文整理总结.LwIP的移植过程细节很多,博文也不可能一一详解个别 ...

  6. web前端开发笔记46-71,78-83 2022/11/04

    web前端开发笔记 一.标签群组通配等选择器(TAG) 1.标签选择器 2.群组选择器(不同标签) 3.通配选择器(一锅端) 二.层次选择器 - 后代():M N - 父子:M > N - 兄弟 ...

  7. seo笔记——搜索显示

    一.搜索显示的几个列表形式 1.经典搜索结果列表:    用户搜索时,出现的第一行都是网页的标题(title),颜色醒目的部分是用户搜索的相关内容:    使用百度搜索则第二第三行是网页的说明内容(D ...

  8. 前端笔记-thymeleaf显示数据及隐藏数据

    源码如下: <form id="loginFrom" name="loginFrom" method="post" th:action ...

  9. Leaflet笔记-把leaflet-tilelayer-wmts移植到vue cli中(含思路)

    目录 前言 过程 前言 关于leaflet的webpackage使用npm安装官方是有明显的解析 但是关于插件特别是TileLayer.WMTS是不提供的,但提供了源码,可以稍微修改下,就能在vue ...

最新文章

  1. KafkaManager中Group下不显示对应Topic的解决方案
  2. JQuery-FullCalendar 多数据源实现日程展示
  3. 数据库操作之增删改查CRUD
  4. 电商行业知识汇集 这里有你想要的东西
  5. Spring--SPeL
  6. CAE+VBR如何提升用户体验?
  7. [Buzz.Today]2013.03.14
  8. long mode 分页_在Spring Boot中使用Spring-data-jpa实现分页查询(转)
  9. 闭包会造成内存泄漏吗?
  10. 爬取小说《重生之狂暴火法》 1~140章
  11. 键盘上所有键位的ascii值
  12. 数组求极值——Java
  13. Android9.0 PM机制系列(一)PackageInstaller初始化解析
  14. vscode编写C++代码出现collect2.exe: error: ld returned 1 exit status问题的解决方案
  15. 开源项目(VC++,MFC)
  16. 南审计算机科学与技术学什么,南京审计大学是几本?是一本、二本还是三本?
  17. Windows 好用的软件安装清单 持续更新
  18. 福建省一级计算机考试文字录入,2015福建省机关事业工勤人员计算机文字录入员工作总结.doc...
  19. Kronecker积
  20. 用Python画圣诞树

热门文章

  1. oracle数据按条件清表,ociuldr v2.1 支持CLOB,BLOB数据类型
  2. 微信小程序向原数组添加数组
  3. 【Linux入门指北】磁盘配额管理 实验篇
  4. 单片机c语言LONG变量,单片机C语言编程当中定义的变量类型决定了什么
  5. CRC32/MPEG2
  6. 定时远眺,保护视力程序
  7. springboot dubbo 多模块项目dubbo提供者和消费者配置及代码
  8. SpringBoot免费视频教程
  9. wo de boke
  10. 7-23 币值转换(转)