【快速入门 LVGL】-- 1、STM32 工程移植 LVGL
目录
一、LVGL 简述
二、复制一个STM32工程
三、下载 LVGL
四、裁剪 源文件
五、工程添加 LVGL 文件
六、注册 显示
七、注册 触摸屏
八、LVGL 心跳、任务刷新
九、开跑 LVGL
十、控件的事件添加、响应处理
十 一、几个好玩小事情
十 二、显示中文
~~ 约定 ~~
在文中的各个操作阶段,会适当提示你:编译。
请在各阶段提示位,谨遵执行。这样操作,能清晰地定位移植过程中的问题所在。
只要操作没错漏,都能成功开跑LVGL。
一、LVGL 简述
- 丰富且强大的模块化图形组件:按钮 、图表 、列表、滑动条、图片等
- 高级的图形引擎:动画、抗锯齿、透明度、平滑滚动、图层混合等效果
- 支持多种输入设备:触摸屏、 键盘、编码器、按键等
- 不依赖特定的硬件平台
- 配置可裁剪,最低资源占用:64 kB Flash,16 kB RAM
- 基于UTF-8的多语种支持,例如中文、日文、韩文、阿拉伯文等
- 可以通过类CSS的方式来设计、布局图形界面(例如:Flexbox、Grid)
- 支持操作系统、外置内存、以及硬件加速(已内建支持STM32 DMA2D)
- 即便仅有单缓冲区(frame buffer)的情况下,也可保证渲染如丝般顺滑
支持模拟器仿真,可以无硬件依托进行开发
二、复制一个STM32工程
准备好一个STM32的工程,这个工程要求如下:
1、硬件的要求
- 芯片资源:Flash>128K, RAM>64K; (LVGL至少占用: Flash>64K, RAM>16K);
- 与芯片型号无关,F1、F4、H7等系列的芯片,满足上述资源的都行;
- 不建议使用常用的STM32F103C8,资源太小,裁剪难度大,强行移植了也会很卡。
- 显示屏:建议使用16位色深的彩屏, 1.44寸、2.8寸、4.3寸等等;
- 不建议使用常用的0.96寸OLED屏,指甲大小的单色屏,耗100K资源去撑它,没搞头。
2、软件的环境
- 库支持方式:标准库、寄存器、手撸HAL库、CubeMX生成的HAL库、LL库,都可以;
- 开发环境:Keil、CubeIDE,都可以;
3、STM32工程的要求
下面这三项,文字虽少,但很重要,千万别忽视!
- 堆栈大小:Heap、Stack,设置为:0x1000 !否则,移植后会白屏、花屏; ( 链接:如何设置堆栈大小 )
- 准备:画点函数,用于后面注册LVGL的显示功能;
- 准备:触摸检测函数 (返回:0-未按下、1-按下)、和 获取坐标的函数 (也可以用变量);
这三项,是LVGL最基础的资源需要。
如果有一项你没看懂,就先把它盘透,回头再盘LVGL。
本篇,复制了开发板的一个示例作移植的基础工程:"显示屏_2.8寸_触摸检测_XPT2046"。
4、复制源工程后,测试是否可用
- 编译:确保 0 Error;
- 烧录:确保能正常运行,触摸和显示都正常(忽视下图中的文字显示,非必要)。
对于这个工程,最低限度,你必须已懂得调用函数,实现如下的画线效果。
三、下载 LVGL
尽管LVGL已发布了v9.0、v9.1等,但v8.3版,是目前最广泛使用的版本。
v8.3版本,网上教程资源众多、移植简单;
更重要的:多款主流可视化设计工具,都支持LVGL的v8.3版本!
因此,强烈推荐使用v8.3版本。
官方下载链接:https://github.com/lvgl/lvgl
1、选择版本
2、下载
3、下载后,解压缩得到文件夹:lvgl-release-v8.3
四、源文件 裁剪
上一步得到的源文件夹: lvgl-release-v8.3。
里头文件众多:源代码、帮助文档、官方示例等等。
不用发晕,需要用到的,仅仅是:3个文件夹 + 2个h文件。
1、新建一个文件夹
因为LVGL源代码中的头文件,使用了相对路径,如在 "lvgl.h" 中:
为了令移植后的文件能直接使用这些相对路径,我们复制文件时,按下方目录结构来操作:
- 在你喜欢的硬盘位置,新建文件夹:LVGL
- 在源文件夹中,把下图选中的 3个文件夹、2个h文件, 复制到新建的 LVGL文件夹中;
完成后,"LVGL" 文件夹,是这个样子的:
提醒:
- 网上好些教程,在keil工程目录下新建 Middlewares 文件夹,在里面再新建LVGL文件夹。
- 如果你使用的是标准库的工程,或者是自己手撸建立的HAL库工程,都可以那样操作。
- 但是,如果使用CubeMX、CubeIDE生成的工程,就不要使用 “Middlewares” 作文件夹名称。
- 因为 "Middlewares",刚好是CubeMX可能生成的文件夹,用来存放中间件,如:FreeRTOS、FatFS等支持文件。如果你没有使能这些中间件,那么 ,CubeMX重新生成工程时,"Middlewares"文件夹就会被认为不需要了,被删除掉。
2、修改 lv_conf.h 文件名
在 "LVGL" 文件夹中,有 h文件:"lv_conf_template.h",是LVGL配置参数的重要文件。
- 原文件名:“lv_conf_template.h”,修改为: "lv_conf.h";
完成后, "LVGL" 文件夹,是这个样子的:
3、删除不需要的文件夹
打开文件夹:"LVGL / examples":
- 只保留 porting 文件夹,其它的文件夹和文件,都删除掉。
完成后,文件夹"LVGL / examples",是这个样子的:
4、修改 porting 里面的文件名称
打开刚才的 "porting" 文件夹:
- 6个文件的名称,都删除 "_template" 字样
完成后,"porting" 文件夹,是这个样子的:
好了,现在 "LVGL" 文件夹,已经是我们需要的效果。
这个 "LVGL" 文件夹,以后可以复制给各类的工程使用,不限于STM32的工程,通用。
五、STM32工程添加 LVGL 文件
现在,我们开始给STM32工程添加LVGL源文件。
1、复制 LVGL 文件夹,粘贴到STM32工程目录下。
每个人的工程文件夹,几乎都不一样,没关系的。
- 把上一步做好的 LVGL 文件夹,复制到工程目录下
完成后,是这个样子的:
2、打开Keil,在工程里,添加4个文件夹(Groups);
文件夹名称 (Groups) | 用于存放什么文件 |
LVGL_myGui | 用户自己的界面代码文件、官方demo等 |
LVGL_conf | LVGL 的两个h文件 |
LVGL_porting | LVGL 的接口文件, 如显示、触摸屏、键盘等 |
LVGL_src | LVGL 的所有底层c文件 |
操作过程、完成后,是这个样子的:
提示:
- 操作完成后,先点击OK,保存操作。
- 网上好些教程会新建近10个Group, 分开存放各个子功能文件。4个文件夹够了,简单直观。
- 这里用下划线作名称分界线。尝试过使用“ / ”, 感觉没下划线直观。你可以用自己喜欢风格。
添加完后,再编译一次,确认:0 Error, 不要觉得事多。
3、为每一个文件夹组(Groups),添加需要的文件
特别地:这一步,是整个移植里,最容易出错的步骤!务必在开始操作前,反复看两次本步图解。
很多人在后面的编译中,出现error,提示缺少文件,基本是在这一步把某个文件添加漏了。
提醒:一点也不难,但务必细心地操作。
操作过程,步骤如下:
重要:每个文件夹(Group),需要添加的文件,如下表:
文件夹 (Group) | 添加文件 |
---|---|
LVGL_myGUI | 不用添加。 |
LVGL_conf | 共2个文件:"LVGL"下的: lv_conf.h、lvgl.h(要选择文件类型才能看到h文件) |
LVGL_porting | 共4个文件:"LVGL/ examples / porting" 下的:lv_port_disp.c 、lv_port_disp.h、 lv_port_indev.c、lv_port_indev.h;(要选择文件类型才能看到 h 文件) |
LVGL_src | 近200+的c文件:"LVGL / src" 下的所有 c 文件; 重点:包括src里所有子、子子文件夹的 c 文件. 不用添加h和mk文件. |
在操作 LVGL_conf 和 LVGL_porting 添加h文件时,需要在选择窗口中,把文件类型设置 *.*:
细细解释一下 LVGL_src 的添加,:
- src文件夹下,会有多重的子文件夹,必须慢慢地、把每一个子文件夹的C文件全部添加进来;
- 只须添加 c 文件,不用添加其它类型的文件,如:h、mk等 (技巧:文件类型配置为 *.c );
- 添加完毕后,必须点击"OK"保存, 不然,你会后悔。
完成后,Keil的工程文件管理器,是这个样子的:
4、添加头文件路径
打勾C99,并,添加3个头文件路径:
- 添加:LVGL 文件夹的路径
- 添加:LVGL\src 文件夹的路径
- 添加:LVGL\examples\porting 文件夹的路径
操作过程、完成后,是这个样子的:
5、编译验证
来到这一步,需要用到的文件,已经添加完毕。
我们在这里必须先编译一次,以验证文件是否都添加完整。
正常情况下,编译后: 0 Error。会有一大堆 Warning,不用管,不影响的。
如果,编译后有 Error 报错:
- 检查是否打勾: C99
- 先检查头文件路径 ,是否添加完成;
- 排除上述两个原因后,编译还有报错,那就是添加文件那一步有遗漏:删除4个Group里的文件,再次重新添加。
友情提示:
如果有Error未解决,必须要解决了,再继续操作下文 。
别心急!何况,下文没有你上面Error的解决方法。多点耐心就好。
六、注册 显示
1、启用 lv_conf.h
双击打开 lv_conf.h,对以下内容进行修改,以启用此文件。
- 第15行,原:#if 0,修改为:#if 1
完成后,是这个样子的:
2、启用 lv_port_disp.h
双击打开 lv_port_disp.h,修改以下内容,以启用此文件:
- 第7行,原:#if 0, 修改为:#if 1
- 第22行,原:“lvgl/lvgl.h", 修改为:”lvgl.h"
完成后,是这个样子的:
3、启用 lv_port_disp.c
双击打开 lv_port_disp.c,修改以下内容,以启用此文件:
- 第7行,原:#if 0, 修改为:#if 1
- 第12行,原"lv_port_disp_template.h", 修改为:"lv_port_disp.h"
完成后,是这个样子的:
4、添加 LCD 驱动的头文件
在 lv_port_disp.c中:
- 第14行,插入你的LCD驱动文件,如:#include "bsp_LCD_ILI341.h",写上你的h文件。
- 第20行、第25行,是显示屏的宽、高度。查看你的显示屏参数,填写实际像素即可。
插入LCD的头文件,目的是为了让这个c文件,能调用LCD的: 画点函数;
注意一个:LVGL默认使用横屏的方式,这一点要注意,别写反了。
完成后,是这个样子的
5、选择创建缓存的方式,3选1
还是在 lv_port_disp.c 中,向下滚动,
(会出现很多错误提示,不用管。也可以先编译一次,让刚才启用的h文件生效,错误就会消失)
第86行到101行,LVGL 提供了创建显示缓冲区的3种方式,这里,必须3选1。
绝大多数情况下,使用第1种方法,即:只创建1个缓冲区;
- 注释掉第90~101行,即:不使用第2和第3种方法;
完成后,是这个样子的:
6、关联 画点函数
还是在 lv_port_disp.c 中,向下滚动,找到disp_flush( )函数:
- 第173行,替换你的 LCD 的画点函数; 参数:x坐标、y坐标、16位颜色值。
- 小编用的画点函数:LCD_DrawPoint( x, y, color_p->full) ;
你的画点函数,可能和小篇所用的不一样,照样画瓢即可。
完成后,是这个样子的:
这里给LVGL一个画点函数后, LVGL就能完成需要的显示操作了。
进阶技巧:提高刷屏效率
一般地,画点函数的底层操作:发送X坐标指令、X值、Y坐标指令、Y值、颜色值。
假如要刷320x240的整屏,至少传输14万次指令、14万次坐标值,7万次颜色值。
相当地耗时。
要是你的LCD驱动文件中,有区域填充颜色的函数,就能大量地减少指令、坐标值的发送次数。
下面是使用 魔女开发板 LCD驱动文件中所提供的 区域填充 函数,可以效仿参考。
- LCD_DispFlush(area->x1, area->y1, area->x2, area->y2, (uint16_t*)color_p);
如果没有区域填充函数,不用强求,直接使用画点函数吧,先完成,再完善。
至此,显示部分的修改、注册,已完成。
点击编译:0 Erros。
切记:如果有Error未解决,必须要解决了,再继续操作下文 。
提示:
细心的朋友,如果参考过其它LVGL教程,可能会有疑问。
为什么操作这么少?是不是漏了 disp_init()的那部分?
是的,我们没有为 disp_init()函数填入LCD的初始化函数。
没必要这样做。
在第九部分,将会在main.c的 main( ) 里直接调用LCD初始化函数。
这样更符合开发习惯,也使思路更清晰。
七、注册 触摸屏
1、启用 "lv_port_indev.h"
打开"lv_port_indev.h", 修改以下内容,以启动此文件:
- 第8行,原:#if 0, 修改成:#if 1
- 第20行,原:"lvgl / lvgl.h", 修改成:"lvgl.h"
完成后,是这个样子的:
2、启动 "lv_port_indev.c"
打开"lv_port_indev.c", 修改以下内容,以启动此文件:
- 第 7行,原:#if 0, 修改为:#if 1
- 第12行,原:“lv_port_indev_template.h", 修改为:"lv_port_indev.h"
- 第13行,原:"../../lvgl.h",修改为:"lvgl.h"
完成后,是这个样子的:
3、添加 触屏 的驱动头文件
还是在 "lv_port_indev.c" 中:
- 第14行,插入:#include "触摸屏的头文件",小编这边是:#include "bsp_XPT2046.h"
目的是为了让这个c文件,能调用触屏的:触摸状态检测函数、坐标获取函数;
完成后,是这样子的:
4、注释掉不需要的输入任务注册
还是在 "lv_port_indev.c" 中,
向下滚动至大约70行,找到输入注册函数:lv_port_indev_init( ),
函数内有5种输入方式的任务注册:触屏、鼠标、键盘、编码器、物理按键;
- 保留触摸屏输入的任务注册;
- 其它4种输入任务的注册,注释掉,;
完成后,是这个样子的:
5、添加 触摸检测函数
还是在 "lv_port_indev.c" 中,
向下滚动到大约209行,找到触摸检测函数:touchpad_is_pressed(),
这个是:触屏状态检测函数,函数返回:0-未按下、1-按下;
- 第213行,原 return false, 注释掉;
- 第212行,原来是空行,插入:return XPT2046_IsPressed();
这个是魔女开发板所提供的触屏检测函数,返回值已符合:0-未按下、1-按下;
你可以替换成你的方式,如原子哥的变量值方式。各施各法,只要符合函数要求,都行;
完成后,是这个样子的:
6、添加 坐标获取函数
还是在 "lv_port_indev.c" 中,
在刚才触摸检测函数的下方,大约217行,找到坐标获取函数:touchpad_get_xy();
本函数的作为:使LVGL能够获取到触摸按下时的x、y坐标;
- 第221行, 修改为:(*x) = XPT2046_GetX();
- 第222行, 修改为:(*y) = XPT2046_GetY();
同上,可以各施各法,用各种方法赋值。
完成后,是这个样子的:
7、额外的测试预埋
(这一步,是非必须的,可以选择跳过。)
在后续的按钮测试中,有可能发生触摸坐标与显示坐标不对应的情况。
我们在这里先预埋一个操作,当后面发生问题,不用傻傻的盲猜原因:
触摸发生时,画点!!
这样,当发生错误时,, 能更好地排查问题:
1:触摸时,画的点,与触摸位置不对应,那就是触摸校验参数不对,要重新校准;
2:触摸时,屏上任何位置,都没看到有点出现,那就应该是触摸没发生了;
具体的预埋操作:
首先,在 lv_port_indev.c 内的顶部:#include 屏显的头文件 如:
#include "bsp_LCD_ILI9341.h” // 注意:这里要替换成你的LCD头文件
然后,就在刚才的那个 touchpad_get_xy( ) 函数中,增加加一行画点操作:
- 第222行下方,插入新行,画点操作:LCD_DrawPoint( *x, *y, BLACK);
完成后,是这个样子的:
至此,触摸屏的注册,已经完成。
点击编译:0 Error。
提示:
参考过其它教程的细心的朋友,这里又会发现,相比其它教程,这里又少了步骤!
在其它教程中,会把其它几种输入方式的相关获取函数,都注释掉。
即:大约第230行~408行,鼠标输入、键盘输入、编码器输入....,统统注释掉。
不需要这样操作!
你没有为那些输入方式进行任务注册,也不调用它们,它们就不起任何作用。
而且,编译器聪明着,编译时将自动忽略死代码(即使是Level 0,死代码也不会产生影响)。
八、添加 LVGL 的文件引用
之前的几个部分,已修改完成了LVGL显示、触摸支持。
现在,正式在工程中“应用” LVGL。
1、给工程,添加 LVGL 的头文件
打开 main.c,在顶部, #include 三个头文件(复制下面三行):
#include "lvgl.h" // 它为整个LVGL提供了更完整的头文件引用
#include "lv_port_disp.h" // LVGL的显示支持
#include "lv_port_indev.h" // LVGL的触屏支持
完成后,是这个样子的:
2、初始化LCD、触摸屏
在main函数内、 while 循环之前,调用LCD初始化函数、触摸屏初始化函数。
删除原示例中多余的显示测试代码、触摸测试代码。
下图,是小篇所用开发板的LCD驱动函数:
LCD_Init(); // 初始化 LCD
LCD_SetDir(1); // 设置LCD的显示方向:横屏
XPT2046_Init(xLCD.width, xLCD.height, xLCD.dir); // 初始化触摸屏
记得前几步时,我们没有像其它教程那样,给disp_init() 填入LCD的初始化函数。
现在,随着其它的设备,把初始化放在一起,更符合习惯、更直观。
完成后,是这个样子的:
提示:
在上图的第187行:W25Q128_Init();
它是外部Flash设备W25Q128的的初始化函数。
开发板的触摸屏校准数据,存储在它里面。每次上电,要从它里面读取之前的校准数据。
如果你用的不是魔女开发板,或者,有其它的储存渠道,不用对它初始化。
3、初始化LVGL、显示、触屏
在硬件的初始化代码之后,进行LVGL的初始化(复制下面3行):
lv_init(); // LVGL 初始化
lv_port_disp_init(); // 注册LVGL的显示任务
lv_port_indev_init(); // 注册LVGL的触屏检测任务
完成后,是这个样子的:
4、显示按钮控件、文本控件
在LVGL的初始化之后,添加LVGL控件 ,以测试LVGL的显示:
- 添加一个按钮
- 为按钮添加文本
- 添加一个独立的标签文本
具体代码如下:
// 按钮lv_obj_t *myBtn = lv_btn_create(lv_scr_act()); // 创建按钮; 父对象:当前活动屏幕lv_obj_set_pos(myBtn, 10, 10); // 设置坐标lv_obj_set_size(myBtn, 120, 50); // 设置大小// 按钮上的文本lv_obj_t *label_btn = lv_label_create(myBtn); // 创建文本标签,父对象:上面的btn按钮lv_obj_align(label_btn, LV_ALIGN_CENTER, 0, 0); // 对齐于:父对象lv_label_set_text(label_btn, "Test"); // 设置标签的文本// 独立的标签lv_obj_t *myLabel = lv_label_create(lv_scr_act()); // 创建文本标签; 父对象:当前活动屏幕lv_label_set_text(myLabel, "Hello world!"); // 设置标签的文本lv_obj_align(myLabel, LV_ALIGN_CENTER, 0, 0); // 对齐于:父对象lv_obj_align_to(myBtn, myLabel, LV_ALIGN_OUT_TOP_MID, 0, -20); // 对齐于:某对象
完成后,是这个样子的:
有没有一种感觉:LVGl的代码,与我们平时写的STM32的代码,风格区别相当大?
特别是刚玩嵌入式的兄弟,很不习惯:函数名称长、参数多......。
没事,不用太在意。
多读,多写写,慢慢就会喜欢上它这种“编码思维”:一致性、模块化、可配置性、可维护性!
好了,至此,已经编写好让LVGL显示控件的代码,离开跑只差两步操作!
不急,先编译一下!
0 Error, 35 Warning !!
只要是 0 Error,就可以了!那些35个警告的,不用管它。
如果出现错误:
1:少量的错误,应该是某个操作错了,向上翻动编译信息,找到第1个 error, 按它的描述排查、对比刚才的操作。
2:数量众多的错误,如几十个!向上翻动编译信息,应该会看到一片相同的错误描述,如下图:
错误描述:Error:L640E:No space in execution regions with .ANY....
这种情况就是内存不足了,程序需要的内存大于芯片RAM。
具体解决:下面的裁剪方法,可以参考。
额外的裁剪探讨:程序Flash和RAM的资源占用
编译后,在信息栏会有详细的资源需要统计。
我们这个程序:Flash占用190K, RAM占用80K (可翻看上面的截图)
- FLASH 占用 = Code + RO-data + RW-data = 163172 + 31808 + 592 = 190K
- RAM 占用 = RW-data + ZI-data = 592 + 80504 = 80K
市面常用的几款STM32芯片的Flash和RAM资源大小:
芯片型号 Flash Ram STM32F103RC 256 K 48 K STM32F103VE 512 K 64 K STM32F407VE 512 K 192 K STM32H750VB 128 K 1056 K 如果你使用的是STM32F407VE,Flash和RAM都是妥妥的足够。
但是:
F103RC、F103VE,芯片硬件的RAM,远远达不到程序运行的需要。
H750VB, Flash也是远远的不够。
怎么办?
就两个路:要么对程序进行裁剪,要么直接更换资源更大的芯片!
1:做项目时,在不缺钱的情况下,优先考虑更换芯片。这样方便于后续扩充功能。
2:如果不更换芯片,就要对程序进行裁剪了,主要是减少内存的占用。
下面,是针对RAM和FLASH不足的情况,常用的操作。
第一:程序RAM > 硬件RAM,修改LVGL的内存池大小
- lv_conf.h中,第52行,LV_MEM_SIZE,LVGL管理的内存池大小,48U, 改为12U
修改后,重新编译,一般,程序RAM占用会降至40K内.
如果修改后,还是 >硬件RAM,再来:
- lv_port_disp.c中,第87、88行,显存大小(刷屏用),原10行,修改为2行到5行左右;
修改后,重新编译,一般,程序的RAM占用,会再减小几K;
如果,还是 >硬件RAM,细心检查一下程序中,是不是定义了全局有效的大数组。
第二:程序Flash占用 > 硬件Flash
常用芯片中,F103C8、H750VB这两款,硬件Flash是比较小的。
不过呢,它俩通常都有“隐藏”的Flash。
即在芯片生产中,Flash不止这么小,但各种原因,参数上定为128K了。
注意,这种情况,只是“通常”,而非“肯定”,具体情况,可以上百度八卦一下网友的验证。
我们无视编译统计的大小,只要无Error, 直接烧录! 只要能正常运行程序,基本是有额外的Flash了。
九、LVGL 心跳、任务刷新
按目前的移植进度,我们只差两个时间任务要处理:
-
第1个时间任务:为LVGL提供准确的心跳。必须间隔精准地,调用时基函数:lv_tick_inc(),俗称心跳,让LVGL精准地知道时间的流逝;
-
第2个时间任务:间隔5ms左右,调用周期性任务函数: lv_timer_handler() ,它的作用是检查所有注册任务的时间戳,执行那些已经到期的任务,如:屏显更新、动画更新、触控、定时器事件等;
1、给LVGL一个心跳时基
(概念上,有点类似FreeRTOS的1ms时基。)
LVGL心跳函数(时基函数):lv_tick_inc(),每隔1ms调用一次;
这个函数对于图形界面的流畅运行比较重要,它令 LVGL 知道执行任务时流逝的时间。
如果 lv_tick_inc() 调用间隔不准确,可能会导致显示卡顿、任务处理不及时。
特别地,不建议使用滴答时钟SysTick产生这个时基,因为它常常需要被用于RTOS等。
建议使用TIM产生1ms中断,设置它的中断为高优先级,通过中断函数调用LVGL心跳时基。
可以使用各个TIM、各种方法,产生1ms中断,如寄存器操作、标准库、手撸HAL等等。
本篇,通过CubeMX配置TIM6,产生1ms中断。
( TIM6 1ms中断,操作 5-1:设置TIM6时基 )
- 使能TIM6,打勾 Activated即可。
- 设置分频值(PSC):F103是72-1, F407是84-1。为什么要减1,因为执行时:寄存器值+1。
- 设置周期值(ARR):1000-1。
通过设置这两个参数,可以把周期设置为1ms,具体的计算方法:
周期时长 = PSC/运行时钟*ARR = 84/84000000*1000 = 0.001秒
( TIM6 1ms中断,操作 5-2:设置TIM6的中断 )
设定好了周期,还要使能中断,令每一周期结束时产生一次中断。
- 打勾TIM6的中断,并设置中断抢占级为:0,(默认也是0);
TIM6已设置好了。
(TIM6 1ms中断,操作 5-3:CubeMX 生成)
keil,先点击保存,不要嫌麻烦。如果之前最后的操作是编译,那么,它就已自动保存了。
现在点击CubeMX里的生成按钮,令刚才的配置更新到工程里面。
切换为keil,如果刚才没有关闭,这时它就弹窗提示代码有更新,点击“是”, 当前工程即可更新代码。
( TIM6 1ms中断,操作 5-4:开启TIM6 )
回到main.c中,调用HAL函数:启动TIM6,并使能它的周期更新中断。
- HAL_TIM_Base_Start_IT(&htim6);
注意,这一行,建议插入在LVGL的初始化之后,如下图中位置 。
完成后,是这个样子的:
( TIM6 1ms中断,操作 5-5:中断回调函数 )
编写周期更新中断的回调函数中,在里面调用lv_tick_inc( ),给LVGL提供心跳;
本篇,在main.c的尾部,在/* USER CODE BEGIN 4 */ 的配对注释内,编写这个函数;
完成后,是这个样子的:
回调函数解释:
这是一个TIM的周期更新中断回调函数,它是定义在***_hal_tim.c中的一个弱定义函数。
CubeMX、CubeID生成的工程,TIM发生周期更新中断时,都会统一调用它。
我们需要在期待的位置,重写这个函数。本篇写在了main.c的尾部。
在回调函数中,我们调用:lv_tick_inc(1),参数为1,即让LVGL知道,1ms已经过去了。
如果你设置TIM产生的是2ms的中断,也可以:lv_tick_inc(2),效果是一样的。
另外 :
在这个中断回调函数中,我们额外地添加了LED每0.5ms闪烁的代码。
目的是为了调试时,可以肉眼判断定时器TIM是否按预期正常工作。
只有TIM6按预期正常工作了,才能给 LVGL 一个准确的心跳时基。
编写回调函数后,编译!
0 Error, 35 Warning。
2、每隔5ms左右,调用任务刷新函数: lv_timer_handler()
简单地理解,每5ms让LVGL刷新(干活)一次。
让LVGL定期检查所有已注册任务的时间戳,执行那些已经到期的任务,如刷屏、检测触摸等;
官方的描述:大约5ms左右、在while循环中调用;
- 在msin.c的while中,每隔5ms调用:lv_timer_handler()
特别地:切勿通过TIM中断调用它,因为它的执行时间有点长,避免霸占中断资源。
完成后,是这样子的:
HAL_Delay 的坑
上面代码中,你或许会对 HAL_Delay(1-1)产生疑惑。
HAL_Delay 函数执行时,它会对传入的参数+1进行计时。
具体原因,请细刨 HAL 源代码的注释。
如果间隔周期小,如 5ms内的,建议对传入的参数-1,能更精准;
如果间隔周期大,如 几十ms、几百ms,不用-1,因为延时的执行误差也远大于1ms。
至此,两个时间任务的,也处理完毕!
LVGL的移植,已全部完成 !
点击编译:0 Erros !
开始烧录吧!
一百多K的程序,烧录时间有点长,大约耗时十来秒。
运行效果如下:
显示正常,显示部分已移植成功!
触摸正常,按下时按钮的状态生产了变化,触摸部分也移植成功!
(已移植成功的老乡,先别走!!建议耐心读完下半部分,干货!!)
5、触摸没反应的排查
一次就移植成功的机率太低太低了。
更大可能出现的情况是:显示正常,触摸没反应!
正常情况下:如上图gif 所显示的,点击按钮时,按下、释放,按钮的状态是不一样的。
如果按钮在按下时没有反应、不会产生状态变化 ,主要排查3项:
- 触摸检测:lv_port_indev.c 第209行:touchpad_is_pressed(),返回值是否相符;
- 坐标获取:lv_port_indev.c 第217行:touchpad_get_xy();
- 坐标不符:触摸屏坐标与显示屏坐标不符,需要重新校准;
如果已按上面(七-7)的步骤,在touchpad_get_xy()函数预埋了画点函数,如下图:
配合这个预埋,现在可以这样测试:在显示屏空白的地方,用指甲,慢慢地,划几道线。
- 如果划不出黑线(断断续续的黑线),排查硬件初始化,和上面的1、2两项;
- 如果划出了黑线,但是坐标不对,那就是触摸屏需要重新校准了,即上面的3;
注意:有时候,黑点黑线,是被画在了屏的边上,堆在一起了,要注意细看。
关于第一种错误,检查:触摸检测函数返回值,是否正确。
- 用printf大法!把触摸返回值、坐标获取值,printf出来,在串口助手上观察;
- 必须确认:触摸按下时,有返回值,而且返回值正确(0-未按下、1-按下),
- 如果返回值对了,再确认坐标获取是否正常,查对printf出来的数据。
- 最后,就是从本篇开头,一步步对归照,是哪一步出现漏做了。
关于第二种,重新校准
- 不同的开发板,问一问屏的商家,如何重新校准触摸屏;
- 本篇使用的“魔女开发板”在这个工程中,使用串口助手发送:XPT2046, 即可进入重新校准。进入了校准界面后,用笔尖,点击依次出现的四个十字线,即可完成重新校准。
十、控件的事件添加、响应处理
当上述问题都解决了,按钮能正常触控后,再操作这一步部分。
LVGL的学习,可以大概地为分两部分:界面绘制、事件处理。
1、界面的绘制
我们这里不啰嗦,上面的示范代码,你能看个明白即可,无需深挖。
因为LVGL的界面绘制,更常规的操作:使用可视化工具进行设计,再把界面工程移植回STM32。
2、事件处理
可视化工具,能帮我们处理好:控件生成、布局、屏幕切换等;
但是,不能处理STM32上的事,如按下按钮,发送某CAN通信等;
这些,都是需要回到Keil或者CubeIDE里,自行增加编写的。
所以,特意增加一章,示范:事件的添加、编写响应处理函数。
明白了响应和处理的操作,后面使用可视化工具时,能有更好的理解。
下面,以按钮的点击为例,示范:事件添加、响应处理。
回到main函数,在添加按钮的那三行代码下方,增加一行,为控件添加事件:
- lv_obj_add_event_cb(myBtn, myBtn_event, LV_EVENT_CLICKED, NULL);
这行有点复杂,对参数稍作解释:
myBtn:控件的名称(不限于按钮);
myBtn_event:事件响应时,LVGL调用的处理函数 (等一会儿要手动编写这个函数);
LV_EVENT_CLICKED:点击事件; 不同的控件,有不同的事件类型;
NULL:传递给回调函数的可选用户数据,这里暂时不用;
完成后,是这个样子的:
然后,开始编写刚才说的那个事件回调函数。
在main函数的上方,注释BEGIN 0 与 END 0之间,编写回调函数:myBtn_event();
// 按钮的事件回调函数
static void myBtn_event(lv_event_t *event)
{lv_obj_t *btn = lv_event_get_target(event); // 获得调用这个回调函数的对象if (event->code == LV_EVENT_CLICKED){static uint8_t cnt = 0;cnt++;lv_obj_t *label = lv_obj_get_child(btn, NULL); // 获取第1个子对象(我们在设计时,已安排了它的第1个子对象是一个label对象)lv_label_set_text_fmt(label, "Button: %d", cnt); // 设置标签的文本,写法类似printf}
}
对函数内的代码行,上面已有注释,比较简单,这里也就不啰嗦了。
完成后,是这个样子的:
编译,烧录,运行效果如下:
十 一、几个相关的小事情
上面,LVGL的移植,已经完成的。
备份好这个工程吧,它能作为后续可视化设计移植的基础工程。
本部分,探讨一些轻松一点的、可能对你有用的玩法。
1、在右下角,显示CPU使用率、FPS帧数
- lv_conf.h中第282行,找到:LV_USE_PERF_MONITOR,原值:0, 修改为:1
2、在左下角,显示LVGL的内存使用率
- lv_conf.h中第289行,找到:LV_USE_MEM_MONITOR,原值:0, 修改为:1
3、黑色主题
- lv_conf.h中第579行:找到:LV_THEME_DEFAULT_DARK, 原值:0, 修改为:1
4、获取控件的各种玩法
LVGL的控件玩法,最好的网站,没有之一:
LVGL 中文开发手册 -- https://lvgl.100ask.net/master/index.html
有啥控件,控件的实现效果如何,都可以在这里找到答案。
如,想实现一个下拉列表:
打开上面网址, 找到 EXamples / Widgets / Dropdown(下拉列表)。
点击后,右侧会展示各种下拉列表的效果(有点延时,要稍等),还可以通过鼠标点击它。
先找到我们想要的控件效果,
在效果的下方,点击 “Show C code",可以展开这个效果的代码,复制到工程中,即可测试。
5、查询 LVGL 某个函数、某个变量的解释
直接问AI,没有更快了,下面是Kimi的网址:
https://kimi.moonshot.cn
十 二、显示中文
如何显示中文呢?如果使用代码的方法,不外乎两个方法:
- LVGL源文件中,自带了一个中文字库。
- 网上有各种的字模转换方法、程序。
这两种方法,使用过程都很烦琐,都不建议使用。
小篇的建议是:通过Gui Guider实现。
Gui Guider除了能对界面进行可视化设计,还能轻松实现中文显示,根本无需理会转换的问题。
在我们移植的这个LVGL工程基础上,就能直接使用Gui Guider的移植。
Gui Guider 教程链接:LVGL_可视化设计 (Gui Guider)
相关文章:
【快速入门 LVGL】-- 1、STM32 工程移植 LVGL
目录 一、LVGL 简述 二、复制一个STM32工程 三、下载 LVGL 四、裁剪 源文件 五、工程添加 LVGL 文件 六、注册 显示 七、注册 触摸屏 八、LVGL 心跳、任务刷新 九、开跑 LVGL 十、控件的事件添加、响应处理 十 一、几个好玩小事情 十 二、显示中文 ~~ 约定 ~~ 在…...
Mac使用-快速开始总结(持续更新)
目录 Mac使用-快速开始总结常用快捷键 Mac使用-快速开始总结 第一次使用mac,发现很多细节上和windows不一样,以下是自己遇到的常用总结,帮助自己快速熟悉mac的使用~ 常用快捷键 复制、粘贴 快捷键? 复制:…...
Kubernetes (K8s) 入门指南
Kubernetes (K8s) 入门指南 什么是Kubernetes? Kubernetes,通常简称为 K8s(因为从 “K” 到 “s” 之间有八个字符),是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。它最初由谷歌设…...
归纳webpack
常用配置项 const HtmlWebpackPlugin require(html-webpack-plugin); // 通常用于生成HTML const MiniCssExtractPlugin require(mini-css-extract-plugin);// 用于分离CSS const cssMinimizerWebpackPlugin require("css-minimizer-webpack-plugin"); // 用于压…...
Web APP 阶段性综述
Web APP 阶段性综述 当前,Web APP 主要应用于电脑端,常被用于部署数据分析、机器学习及深度学习等高算力需求的任务。在医学与生物信息学领域,Web APP 扮演着重要角色。在生物信息学领域,诸多工具以 Web APP 的形式呈现ÿ…...
SpringBoot之OriginTrackedPropertiesLoader类源码学习
源码解析 /*** 作用是从给定的资源(如文件或输入流)中加载 .properties 文件,* 并将属性键值对转换为带有来源信息(origin)的 OriginTrackedValue 对象。*/ public class OriginTrackedPropertiesLoader {private fin…...
Flask学习入门笔记
Flask学习入门笔记 前言1. 安装Flask2. 创建一个简单的Flask应用3. 路由与视图函数3.1 基本路由3.2 动态路由3.3 HTTP方法 4. 请求与响应4.1 获取请求数据4.2 返回响应 5. 模板渲染5.1 基本模板渲染5.2 模板继承 6. 静态文件6.1 静态文件的目录结构6.2 在模板中引用静态文件6.2…...
List 接口的实现类
在 Java 中,List 是一个非常常用的接口,提供了有序、可重复的元素集合。List 接口有多个实现类,每个实现类都有其特定的特性和适用场景。以下是 Java 中主要实现了 List 接口的类及其详细介绍。 1. 常见的 List 实现类 1.1 ArrayList 简介&…...
SpringCloud-基于Docker和Docker-Compose的项目部署
一、初始化环境 1. 卸载旧版本 首先,卸载可能已存在的旧版本 Docker。如果您不确定是否安装过,可以直接执行以下命令: sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logro…...
【人工智能】Python中的自动化机器学习(AutoML):如何使用TPOT优化模型选择
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 随着机器学习在各行业的广泛应用,模型选择和优化成为了数据科学家面临的主要挑战之一。自动化机器学习&am…...
Kafka 超级简述
Kafka 就是一个 分布式的消息系统,它帮助不同的系统和应用之间传递信息。可以把它想象成一个超级高效的 “邮局”: 生产者(Producer) 就是把信息(消息)送到这个 “邮局” 的人。消费者(Consume…...
tomcat项目运行后报500
HTTP状态 500 - 内部服务器错误 类型 异常报告消息 实例化Servlet类[com.mdy.servlet_02.LifeDemoServlet]异常描述 服务器遇到一个意外的情况,阻止它完成请求。例外情况jakarta.servlet.ServletException: 实例化Servlet类[com.mdy.servlet_02.LifeDemoServlet]异常…...
Chapter1:初见C#
参考书籍:《C#边做边学》; 1.初见C# 1.1 C#简介 C # {\rm C\#} C#编写了许多完成常用功能的程序放在系统中,把系统中包含的内容按功能分成多个部分,每部分放在一个命名空间中,导入命名空间语法格式如下: /…...
SQL-leetcode—620. 有趣的电影
620. 有趣的电影 表:cinema ------------------------ | Column Name | Type | ------------------------ | id | int | | movie | varchar | | description | varchar | | rating | float | ------------------------ id 是该表的主键(具有唯一值的列)。 每行包含…...
《鸿蒙Next平台:决策树面对噪声数据的鲁棒性逆袭》
在机器学习领域,决策树是一种强大的模型,但训练数据中的噪声往往会影响其性能和鲁棒性。在鸿蒙Next平台上,我们可以采用多种策略来增强决策树模型在面对噪声数据时的鲁棒性。 数据预处理层面 数据清洗:利用鸿蒙Next平台的数据处理…...
minio https配置
如果使用了官网的教程 使用minio-user作为启动用户 准备好:文件名要定死,因为minio只认识key和crt文件 将xxxxx.key重命名为private.key 将xxxxx.pem重命名为public.crt 将这两个文件放到/home/minio-user/.minio/certs/ 目录下 chown -r minio…...
Windows重装后NI板卡LabVIEW恢复正常
在重新安装Windows系统后,NI(National Instruments)板卡能够恢复正常工作,通常是由于操作系统的重新配置解决了之前存在的硬件驱动、兼容性或配置问题。操作系统重装后,系统重新加载驱动程序、清理了潜在的冲突或损坏的…...
Spring Initializr创建springboot项目 “java: 错误: 无效的源发行版:19”
我用的1.8的jdk,排查发现这是jdk和springboot版本冲突导致的。 1、File->Project Structure->Module->source和dependancies改成相应的版本 2、File->Setting->Build,Execution,Deployment->Compiler->Java Compiler->target bytecode ver…...
Vue computed属性原理及其惰性求值特点
1,computed属性 在 Vue.js 中, computed 属性是 Vue 响应式系统的一个核心特性,它允许开发者声明式地描述一个依赖其他响应式属性的计算值。 computed 属性是基于它们的依赖进行缓存的,只有当依赖发生变化时,它们才会…...
关于在 Kotlin DSL 中,ndk 的配置方式
在 Kotlin DSL 中,ndk 的配置方式有所不同,取决于 Android Gradle 插件版本。ndk { abiFilters(…) } 在 Kotlin DSL 中实际上是 externalNativeBuild 的一部分,需要通过正确的上下文调用。 错误代码: ndk {abiFilters("ar…...
机器学习(一)
一, Supervised Machine Learning (监督机器学习) 1,定义:学习X到Y或输入到输出的映射的算法,学习算法从正确答案中学习。即教机器 如何做事情(数据集学习算法模型),根据已有的数据集,知道输入和输出结果 之间的关系,并根据这…...
得物App再迎开放日,全流程体验正品查验鉴别
近日,得物App超级品质保障中心再度迎来了开放日活动。近60位得物App的用户与粉丝齐聚超级品质保障中心,全流程体验正品查验鉴别。开放日当天,参与者有机会近距离观察得物App的商品质检区、鉴别区、收发流转区、实验室和正品库等关键功能区&am…...
《leetcode-runner》【图解】【源码】如何手搓一个debug调试器——表达式计算
前文: 《leetcode-runner》如何手搓一个debug调试器——引言 《leetcode-runner》如何手搓一个debug调试器——架构 《leetcode-runner》如何手搓一个debug调试器——指令系统 《leetcode-runner》【图解】如何手搓一个debug调试器——调试程序【JDI开发】【万字详解…...
Flink概述
一、Flink是什么 二、Flink特点 三、Flink vs SparkStreaming 表 Flink 和 Streaming对比 Flink Streaming 计算模型 流计算 微批处理 时间语义 事件时间、处理时间 处理时间 窗口 多、灵活 少、不灵活(窗口必须是批次的整数倍) 状态 有 …...
【Linux】信号
目录 一、信号的概念二、信号的产生2.1 通过键盘进行信号的产生2.2 通过系统调用进行信号的产生2.2.1 kill函数2.2.2 raise函数2.2.3 abort函数 2.3 通过异常的方式进行信号的产生2.4 通过软件条件的方式进行信号的产生2.4.1 关闭管道读端2.4.2 alarm函数 2.5 Core Dump&#x…...
【漏洞分析】DDOS攻防分析
0x00 UDP攻击实例 2013年12月30日,网游界发生了一起“追杀”事件。事件的主角是PhantmL0rd(这名字一看就是个玩家)和黑客组织DERP Trolling。 PhantomL0rd,人称“鬼王”,本名James Varga,某专业游戏小组的…...
【js进阶】设计模式之单例模式的几种声明方式
单例模式,简言之就是一个类无论实例化多少次,最终都是同一个对象 原生js的几个辅助方式的实现 手写forEch,map,filter Array.prototype.MyForEach function (callback) {for (let i 0; i < this.length; i) {callback(this[i], i, this);} };con…...
【VUE】计算属性+动态样式方法封装
【VUE】父子组件联动实现动态样式控制 【VUE】页面跳转实现动态样式控制 在utils下创建文件夹styleController 编写通用的方法 /*** 样式控制* 本文件主要提供一些动态控制样式的方法*//*** 控制表格表头中的 某些列 是否显示星号** param showStarActions boolean 当值为True时…...
Mac玩Steam游戏秘籍!
Mac玩Steam游戏秘籍! 大家好!最近有不少朋友在用MacBook玩Steam游戏时遇到不支持mac的问题。别担心,我来教你如何用第三方工具Crossover来畅玩这些不支持的游戏,简单又实用! 第一步:下载Crossover 首先&…...
【后端面试总结】tls中.crt和.key的关系
tls中.crt和.key的关系 引言 在现代网络通信中,特别是基于SSL/TLS协议的加密通信中,.crt和.key文件扮演着至关重要的角色。这两个文件分别代表了数字证书和私钥,是确保通信双方身份认证和数据传输安全性的基石。本文旨在深入探讨TLS中.crt和…...
【Axure】配色库
配色库是一个专为设计师和创意工作者打造的在线资源平台,旨在提供丰富的色彩解决方案,帮助用户轻松找到或创造美观和谐的色彩搭配。其中,一个典型的配色库包含了以下几个核心元素: 渐变色:提供多样化的渐变色方案&…...
PL/SQL语言的语法糖
PL/SQL语言的语法糖 引言 PL/SQL(Procedural Language/Structured Query Language)是Oracle公司为其数据库管理系统(DBMS)设计的一种过程化语言。作为一种扩展SQL的语言,PL/SQL不仅支持数据的查询和操作,…...
Pytorch|YOLO
🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊 一、 前期准备 1. 设置GPU 如果设备上支持GPU就使用GPU,否则使用CPU import torch import torch.nn as nn import torchvision.transforms as transforms im…...
doc、pdf转markdown
国外的一个网站可以: Convert A File Word, PDF, JPG Online 这个网站免费的,算是非常厚道了,但是大文件上传多了之后会扛不住 国内的一个网站也不错: TextIn-AI智能文档处理-图像处理技术-大模型加速器-在线免费体验 https://…...
ZooKeeper 常见问题与核心机制解析
Zookeeper集群本身不直接支持动态添加机器。在Zookeeper中,集群的配置是在启动时静态定义的,并且集群中的每个成员都需要知道其他所有成员。当你想要增加一个新的Zookeeper服务器到现有的集群中时,你需要更新所有现有服务器的配置文件&#x…...
期权懂|场内期权合约行权价格是如何设定制度的?
锦鲤三三每日分享期权知识,帮助期权新手及时有效地掌握即市趋势与新资讯! 场内期权合约行权价格是如何设定制度的? 场内期权合约的行权价格是期权合约中的一个关键要素,它决定了期权买方在期权到期日或之前买入(对于…...
处理 SQL Server 中的表锁问题
在 SQL Server 中,表锁是一个常见的问题,尤其是在并发访问和数据更新频繁的环境中。表锁会导致查询性能下降,甚至导致死锁和系统停滞。本文将详细介绍如何识别、分析和解决 SQL Server 中的表锁问题。 什么是表锁? 表锁是 SQL S…...
【Mysql进阶知识】Mysql 程序的介绍、选项在命令行配置文件的使用、选项在配置文件中的语法
目录 一、程序介绍 二、mysqld--mysql服务器介绍 三、mysql - MySQL 命令行客户端 3.1 客户端介绍 3.2 mysql 客户端选项 指定选项的方式 mysql 客户端命令常用选项 在命令行中使用选项 选项(配置)文件 使用方法 选项文件位置及加载顺序 选项文件语法 使用举例&am…...
代码随想录算法训练营总结
本人是一名普普通通的计算机专业的毕业生,在大学学数据结构和算法就感觉非常难,到毕业也没刷过几道题,所幸后来入职的公司也没有考察算法相关的内容。到现在已经工作两年多了,看到过许多聊面试聊算法的文章,也接触到一…...
二进制/源码编译安装mysql 8.0
二进制方式: 1.下载或上传安装包至设备: 2.创建组与用户: [rootopenEuler-1 ~]# groupadd mysql [rootopenEuler-1 ~]# useradd -r -g mysql -s /bin/false mysql 3.解压安装包: tar xf mysql-8.0.36-linux-glibc2.12-x86_64.ta…...
AI 编程工具—Cursor进阶使用 阅读开源项目
AI 编程工具—Cursor进阶使用 阅读开源项目 首先我们打开一个最近很火的项目browser-use ,直接从github 上克隆即可 索引整个代码库 这里我们使用@Codebase 这个选项会索引这个代码库,然后我们再选上这个项目的README.md 文件开始提问 @Codebase @README.md 这个项目是用…...
掌握C语言内存布局:数据存储的智慧之旅
大家好,这里是小编的博客频道 小编的博客:就爱学编程 很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!! 目录 引言正文一、数据类型介绍1.内置类型2.自定义…...
一键化配置java环境
一键化配置java环境 下载javaPathConfig 打开,将java的jdk路径写进去 例如我的路径就是 C:\Program Files\Java\jdk-1.8点击确认设置即可...
【I/O编程】UNIX文件基础
IO编程的本质是通过 API 操作 文件。 什么是 IO I - Input 输入O - Output 输出 这里的输入和输出都是站在应用(运行中的程序)的角度。外部特指文件。 这里的文件是泛指,并不是只表示存在存盘中的常规文件。还有设备、套接字、管道、链接…...
leetcode 面试经典 150 题:汇总区间
链接汇总区间题序号228题型数组解法一次遍历法难度简单熟练度✅✅✅ 题目 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属…...
联想Android面试题及参考答案
请介绍一下 Android 的架构,并谈谈对 Linux 的了解。 Android 架构主要分为四层,从下往上依次是 Linux 内核层、系统运行库层、应用框架层和应用层。 Linux 内核层是 Android 系统的基础。它提供了底层的硬件驱动程序,包括显示驱动、摄像头驱动、音频驱动等多种硬件设备的驱…...
redux 结合 @reduxjs/toolkit 的使用
1,使用步骤 使用React Toolkit 创建 counterStore(store目录下) --> 为React注入store(src下面的index) --> React组件使用store中的数据(组件) 2,例如下面有一个简单加减的…...
【鱼皮大佬API开放平台项目】Spring Cloud Gateway HTTPS 配置问题解决方案总结
问题背景 项目架构为前后端分离的微服务架构: 前端部署在 8000 端口API 网关部署在 9000 端口后端服务包括: api-backend (9001端口)api-interface (9002端口) 初始状态: 前端已配置 HTTPS(端口 8000)后端服务未配…...
PHP反序列化
一、PHP面向对象的基础知识 基本概念 1、面向过程VS面向对象 以做饭为例,面向过程是自己从原材料到成品全部自己做,面向对象相当于去饭店,点菜,等待结果(上菜)。 2、类的定义 类是定义了一件事物的抽象…...
6、原来可以这样理解C语言_函数(7/8)嵌套调⽤和链式访问
目录 七、嵌套调⽤和链式访问 七、(1)、嵌套调⽤ 七、(2)、链式访问 七、嵌套调⽤和链式访问 七、(1)、嵌套调⽤ 嵌套调⽤就是函数之间的互相调⽤,每个函数就⾏⼀个乐⾼零件,正是因…...