前言:
学习路线:先跟着正点原子的应用视频了解一下LVGL编程思想(然后看怎么创建部件,怎么优化),看个2天。然后就是跟着本文的文件移植(3天),然后gui guider模拟器(2天)。所以在一个星期内就可以对LVGL有轻量的应用,移植能力了。
硬件:stm32f407vet6,屏幕2.8寸电阻屏
文件移植(无系统,外部RAM)
本文根据正点原子视频移植,配合视频阅读本文章效果更好。视频连接
文件准备
首先准备三个文件
- 第一个就是LVGL官方库文件了,获取地址。
- 第二个是驱动,给显示屏实验是提供LCD的输出驱动(显示)和输入驱动(触摸)。
- 第三个是给LVGL提供心跳时基的。
接下来给出官方库文件获取方式:
选择带relese的,然后下载
裁剪LVGL
裁剪的文件夹是从官方获取的LVGL库文件
就保留这五个文件。
文件详情:
把lv_conf_template.h文件改成lv_conf.h文件(更改名字)然后按照下图配置文件
- GUI:LVGL相关库文件
- GUI_APP:官方示例,或者自己添加的功能
文件目录:
LGVL:
- GUI
- GUI_APP
----------------------
GUI:
- lvgl
lvgl:
- examples
- src
- lv_conf.h
- lvgl.h
------------------------
GUI_APP:
- demos
GUI的:
GUI_APP的:
注意这里只添加一个demos就行了。
examples剪裁:里面就包含porting就行了。(带porting的一般都是移植文件的意思)
然后官方文件就按照上图这样放置。
紧接着就是把lv_conf.h的#if 0改成#if 1(已更改)。其目的是使能下面的代码
配置定时器
接下来以显示屏实验为基础,将定时器驱动TIMER文件添加到显示屏实验中去。
配置keil工程
按照这样把刚才裁剪的LVGL文件添加到这里。(只要裁剪的LVGL文件进到工程就行,目录不重要)
接着把这些文件夹添加到keil中
像这样:
紧接着在keil中配置刚才裁剪的LVGL库文件里面的.c文件:
像这样:
然后添加头文件。
选择C99(C语言标准)
编译:(警告是因为数据类型不兼容)
配置LCD驱动
输出(显示屏显示)
配置官方的接口显示模版,把 lv_port_disp_template.c/h 的条件编译指令 #if 0 修改成 #if 1
目的同样是使能下面的代码
.c文件:
.h文件 :
接下来都是在lv_port_disp_templ.c文件里操作
配置LCD
接下来就是在这个接口显示模版里面接入我们自己的的 LCD驱动程序了:
那么第一步就是接入自己LCD的头文件:
第二步就是接入LCD初始函数了:
数据缓冲模式选择
官方设置了三个模式:
假如屏幕像素是1000*100
- 则第一种就是:开辟一个1000*10的数据缓冲区(10可以修改,改大一点也可以)
- 第二种就是:开辟两个1000*10的数据缓冲区(10同样可以修改),是一遍缓存一遍读出。有点类似CPU的流水线
- 第三种就是数据DMA,开辟两个1000*100的数据缓冲区(硬件要求高,因为一个单位是2个字节(16位),然后如果像素是1000*100的话,内存占用就是1000*100*2*2=4*10的5次方字节=0.4MB,(开辟两个所以又再次乘2))
选择第一种模式:
宏定义像素大小:
如下图所示依次是上面的三种模式:选择第一种(则把剩下两种屏蔽即可)
设置屏幕尺寸
如果直接设置大小要注意 :
横屏时假如是1000*100
则竖屏时如果没有更改,则会变成水平像素是1000,纵向是100了,则会显示出错。
如下图:竖屏的时候会显示右边红框和黑框交界的地方,则显示错误
配置填充颜色函数
disp_flush()函数功能就是一个点填充颜色
配置输入(触摸屏)
接下来对lv_port_indev_template.c/h文件进行操作 lv_port_indev_template是接口输入设备模版
首先把 lv_port_indev_template.c/h 的条件编译指令 #if 0 修改成#if 1目的:使能代码
裁剪输入设备
输入设备可以有很多个,比如键盘,按键,编码器,鼠标等。这里选择触摸,故需要裁剪触摸部分就行
头文件修改:也可以在裁剪LVGL中把GUI文件夹里面的LVGL4个官方库文件在存入到GUI文件的新建子文件lvgl(小写)中
只保留触摸屏相关的接口函数:
在lv_port_indev_init中只保留
还有像这种别的输入设备驱动函数也给删了:
导入头文件
初始化触摸屏
在 touchpad_init 函数中初始化触摸屏
配置检测函数
配置坐标检测函数
配置时基
添加定时器驱动
把配置定时器步骤中的定时器驱动添加到keil工程当中:
在btim.c文件中添加#include "lvgl.h"
添加心跳
添加相关函数:
在定时器回调函数里面调用lv_tick_inc(1);
- 具体来说,lv_tick_inc(1); 函数的作用是增加 LVGL 内部的时间计数器。在嵌入式系统中,LVGL 需要定期更新内部的时间计数器,以便正确地管理动画、定时器、事件处理等功能。通过调用 lv_tick_inc(1); 函数,LVGL 将内部的时间计数器增加 1 个时间单位,通常是毫秒(ms)。
- 这个函数通常在系统的主循环或定时器中断服务函数中被调用,以确保 LVGL 内部的时间计数器保持同步。在调用该函数后,LVGL 将会处理任何过期的定时器事件、动画更新或其他需要基于时间的操作。
- 所以说lv_tick_inc(1);作用就是为LVGL提供心跳,类似单片机的基准时钟(借用单片机的时钟来为自己提供时钟)
- 需保证LVGL里面的基准时钟是多少ms,定时器进入中断就是多少ms
如果要配置配置自己的时钟源,如FreeRTOS里面的tick,则可以不在中断中配置,而是在下面函数中修改相应宏为1,且在第二个箭头里添加FreeRTOS的心跳。
修改main函数
因为是按照正点原子的驱动代码修改,所以这些步骤可以自行判断哪里需要改
删除除了main函数以外的函数(根据自己代码判断)
添加头文件:(必须)
#include "./BSP/TIMER/btim.h"
#include "lvgl.h"
#include "lv_port_disp_template.h"
#include "lv_port_indev_template.h"
添加下面函数 (必须)
btim_timx_int_init(10-1, 9000-1);//初始化定时器,需要考虑自己开发板时钟频率,1ms进一次中断即可
lv_init(); //初始化LVGL库
lv_port_disp_init(); //初始化输出设备
lv_port_indev_init();//初始化输入设备
调用 lv_timer_handler(必须)
在while(1)中每隔5ms调用一次lv_timer_handler();(必须)
while(1)
{
delay_ms(5);
lv_timer_handler();
}
lv_timer_handler()和lv_tick_inc()关系
lv_timer_handler() 函数和lv_tick_inc() 函数在 LittlevGL(LVGL)图形库中通常一起使用,它们之间存在密切的关系。
-
lv_tick_inc() 函数:
- lv_tick_inc()函数用于增加 LVGL 内部的时间计数器,通常以毫秒(ms)为单位。
- 它是用来告诉 LVGL 时间已经流逝了多少,从而更新内部的时间状态,例如动画、定时器等需要根据时间变化的功能。
- lv_tick_inc()函数通常由系统的主循环或定时器中断服务函数中调用,以保持 LVGL 内部时间计数器的同步。
-
lv_timer_handler() 函数:
- lv_timer_handler()函数用于处理定时器事件,包括检查定时器是否到期,并执行相应的回调函数或任务。
- 它通常在应用程序的主循环中被调用,以处理与时间相关的任务,例如动画更新、定时事件处理等。
- 在处理定时器事件之前,通常需要先调用lv_timer_handler() 函数来更新 LVGL 内部的时间状态,以确保定时器事件的准确处理。
因此,这两个函数通常是配合使用的:首先调用 lv_tick_inc() 函数来更新 LVGL 内部的时间状态,然后再调用lv_timer_handler() 函数来处理定时器事件,从而实现图形界面的正常运行和响应
最后填写测试代码(可修改)
lv_obj_t* switch_obj = lv_switch_create(lv_scr_act());
lv_obj_set_size(switch_obj, 120, 60);
lv_obj_align(switch_obj, LV_ALIGN_CENTER, 0, 0);
整体main函数代码:
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TOUCH/touch.h"
#include "./BSP/TIMER/btim.h"
#include "lvgl.h"
#include "lv_port_disp_template.h"
#include "lv_port_indev_template.h"
int main(void)
{
HAL_Init();
sys_stm32_clock_init(336, 8, 2, 7);
delay_init(168);
usart_init(115200);
led_init();
// lcd_init(); //因为之前在lvgl哪里调用过了
key_init();
tp_dev.init();
btim_timx_int_init(10-1, 9000-1);//初始化定时器,需要考虑自己开发板时钟频率,1ms进一次中断即可
lv_init(); //初始化LVGL库
lv_port_disp_init(); //初始化输出设备
lv_port_indev_init();//初始化输入设备
lv_obj_t* switch_obj = lv_switch_create(lv_scr_act());
lv_obj_set_size(switch_obj, 120, 60);
lv_obj_align(switch_obj, LV_ALIGN_CENTER, 0, 0);
while(1)
{
delay_ms(5);
lv_timer_handler();
}
}
错误可能
最后编译可能f103系列的可能会出现报错,可能是因为内存分配问题所导致的。此时可以打开lv_conf.h文件把内存大小改小
也可能是启动文件的栈内存分配太小:
..\..\Output\atk_f407.axf: Error: L6406E: No space in execution regions with
原因是:keil默认把所有代码,包括没有用到的代码链接起来放进flash,但是我们不用这么多,所以选择这个:(根据等级越高程序的可调试性逐渐变差)
当你屏幕点不了的时候可能是在touch.c文件里面的tp_init函数里最后屏蔽tp_get_adjust_data();
原因:不详
测试
成功Run起来
VID_20240421_145916
Gui Guider-实现模拟+生成代码
下载
操作步骤
设备-模拟器
空模版:
填写尺寸(太大了,flash可能受不了),名称:
选择代码
把文件生成的lv_anim.h和lv_anim.c替换原本keil工程目录下(Middlewares\LVGL\GUI\lvgl\src\misc)的的lv_anim.h和lv_anim.c
把lv_anim.h头文件添加到lvgl.h中
然后.c文件同理.h文件一样
添加宏定义
然后main.c文件
添加头文件
#include "gui_guider.h"
#include "events_init.h"
在main函数外定义:
lv_ui guider_ui;
在main函数调用:
setup_ui(&guider_ui);
events_init(&guider_ui);
整体main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TOUCH/touch.h"
#include "./BSP/TIMER/btim.h"
#include "lvgl.h"
#include "lv_port_disp_template.h"
#include "lv_port_indev_template.h"
#include "gui_guider.h"
#include "events_init.h"
lv_ui guider_ui;
int main(void)
{
HAL_Init();
sys_stm32_clock_init(336, 8, 2, 7);
delay_init(168);
usart_init(115200);
led_init();
// lcd_init(); //因为之前在lvgl哪里调用过了
key_init();
tp_dev.init();
btim_timx_int_init(10-1, 9000-1);//初始化定时器,需要考虑自己开发板时钟频率,1ms进一次中断即可
lv_init(); //初始化LVGL库
lv_port_disp_init(); //初始化输出设备
lv_port_indev_init();//初始化输入设备
// lv_obj_t* switch_obj = lv_switch_create(lv_scr_act());
// lv_obj_set_size(switch_obj, 120, 60);
// lv_obj_align(switch_obj, LV_ALIGN_CENTER, 0, 0);
setup_ui(&guider_ui);
events_init(&guider_ui);
while(1)
{
delay_ms(5);
lv_timer_handler();
}
}
运行
然后烧录成功run :
注意事项
- 若出现 ..\..\Output\atk_f407.axf: Error: L6406E: No space in execution regions with
- 解决方法:类似错误则参考目录中 移植->修改main函数->错误可能(看目录)
- 若出现..\..\Output\atk_f407.axf: Error: L6218E: Undefined symbol_btn_list_pause_alpha_28x28 (referred from custom.o).
- 解决方法:这种错误可能是你再更换不同的这三个文件时(比如说,这次你选择尺寸是300*200但是你下次新建文件选择的是320*240,这时候导出的这三个文件夹里面的.c/.h不一样),没更换keil中三个文件所对应的.c文件所导致的
以下编程未整理可跳过(根据正点原子)可以去看对应视频
编程
编程思想
部件基本属性
第一个的LV_ALIGN_.....(模式):只能选择在内部的模式(阴影部分)
第二个的LV_ALIGN_.....(模式):能选择所有的
第一个能共享,第二个只能用于单个部件。第一个变量只能是静态全局,使动态分配的,不然会出错。
轮廓是在边框的外面。
相关枚举参数可以去官网查找,也可以去原子,百问网去。
cb --Callback回调函数
同一回调函数,同一部件,不同触发方式
同一回调函数,不同部件(里面函数是上面那个类型)
状态切换必须要开启
当设置颜色不对时,考虑覆盖问题。
lv_obj_add_state(switch1,LV_STATE_CHECKED|LV_STATE_DISABLED)表示打开且不可修改
lv_obj_add_state(switch1,LV_STATE_CHECKED表示打开可
lv_lear_clear_state()同理
lv_obj_add_state(switch1,LV_STATE_CHECKED|LV_STATE_DISABLED)
lv_obj_clear_state(switch1,LV_STATE_CHECKED|LV_STATE_DISABLED)
表示默认关,且可以修改。
上面三个示例由于LVGL版本不一样所导致
下拉部件:
e那个是另外添加到d的后面
滚轮部件:
滑块部件:
可以有三个照片 C语言数组(照片转数组,从官网),二进制,官网定义图标。