Dfrobot的firebeetle -E是一款经典的ESP32开发板,搭配专门的扩展板后就可以开启物联网世界。本项目使用Dfrobot firebeetle -E搭配了2.8寸的ili9391触控屏,结合LVGL图形库实现了板载led灯控制和接收显示普通电脑传输过来运行状态信息。
成功的嵌入式设备需要一个极具吸引力的用户界面,才能给用户留下良好的第一印象。传统的图形绘制库(例如经典TFT_eSPI,Adafruit_GFX,Lovyan_GFX等)可以帮助用户快速的完成方案,但是完成一个精美用户界面却不是那么容易的,通常都是一些资深的大佬才能做到的。而lvgl的出现可以一定程度上改变这个现状,它的存在就是为用户提供友好的界面,使用嵌入式开发在最终产品呈现提供了一种解决方案,做出来的界面完全可以和修炼多年传统老师傅做的用户界面媲美,所以要尽情拥抱lvgl吧。
- lvgl 图形界面的移植
但是lvgl的版本一直在演替,带来的问题是版本间的差异会给刚入门的小伙伴造成很大的困扰,即便是想运行一个benchmark测试,就要折腾好久,甚至弃坑,都是很正常的现象。
经过多日的探索终于找到了解决这个问题最优雅的办法。可以同时运行examples 和 demos 的方法。具体呈现是:在项目文件夹lib目录下建lvgl_lib文件夹,把lvgl官网下载的文件夹放这,把lv_conf.h考到lvgl_lib 目录下,即与lvgl平级目录下。在lv_conf.h 打开demos 和编译example 选项,即可运行example 和demos,测试后可以重新关闭,是为了节省编译空间和时间。
在主程序 main.cpp里采用如下引用方式,以上每步设计均为必须。更改lv-conf.h的参数需要重新从头编译,耗时较久,需要去泡个茶再回来。
关于定时器,在esp32环境下,不需要设置lvgl的心跳定时器,项目依然是可以正常运行的。
|-- main.cpp ......
|-- lvgl_lib
| |-- lvlg @ 8.3.2
| |-- lv_conf.h
.....
#include "lvgl/lvgl.h"
#include "lvgl/examples/lv_examples.h"
#include "lvgl/demos/lv_demos.h"
在所有的引用 lvgl.h的头文件中 加入 下列定义,即可保证代码的移植性。
#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
在lv_conf.h 开启下列基本选项。
我们用的TFT_eSPI作为背后的刷图工具,这个绘图工具,主要优点是使用的人群较多,虽然性能非常杰出。DISP_BUF_SIZE 超过 LV_HOR_RES_MAX * 30 fps提升效果不明显。开启双缓冲buf后加启动pushImageDMA后,fps可以达到55-60帧。PushPixelDMA性能优于pushImageDMA 大约有3帧的差距。就这样一个良好的lvgl学习平台就搭建好了。
2.首页设计(动态时钟功能)
一直想自己做个手机界面,这次过一把瘾吧。我在屏幕的中央放一个gif动图,然后在动图上增加时针、分钟、秒针,他们会随着真实的时间转动,转动过程通过lvgl的动画功能实现完美过渡,时间则是在开机的时候通过联网获取的。其他的小控件只起到装饰效果,并没有实际作用。
struct tm timeinfo;
if (getLocalTime(&timeinfo))
{
lv_img_set_angle(hour_img, (int)((timeinfo.tm_hour + float(timeinfo.tm_min) / 60) * 300) % 3600);
/*错误“invalid operands of types 'float' and 'int' to binary 'operator %”
因为求余运算符%要求左边的数是整数,但是把一个float类型的数放在%左边进行运算,就会报以上错误。*/
lv_img_set_angle(min_img, timeinfo.tm_min * 60 + timeinfo.tm_sec);
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, sec_img);
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_img_set_angle);
lv_anim_set_values(&a, (timeinfo.tm_sec * 60) % 3600,
(timeinfo.tm_sec + 1) * 60);
lv_anim_set_time(&a, 1000);
lv_anim_start(&a);
}
}
2、板载灯光控制
这页面的功能是模拟通过触屏来控制外部的执行器,传说中的可以用开控制家电,例如通过红外、蓝牙、网络的功能来控制家里的电器。实现起来也是非常简单,在屏幕上新建几个按钮,在按钮的回调函数里进行开关的逻辑。然而为了增加气氛,增加了背景动画效果,可以实现转场过程背景彩色圆点会自动重绘,这里有用到比较底层的绘图机理。 lvgl的绘图有一个复杂的流程,在不同阶段实施不同的工作,这里我在“code == LV_EVENT_DRAW_MAIN” 这个时机在背景上随机绘制了100个随机颜色的实心圆,使得界面更加活跃。这个底层绘图机理比较有意思,但是通常我们比较少用到,通常都是通过使用经典控件来完成我们的业务功能。
static void circle_draw_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_DRAW_MAIN)
{ // draw the ampitute with bars and the value peaks with the horizontal line above it .
lv_obj_t *obj = lv_event_get_target(e);
lv_draw_ctx_t *draw_ctx = lv_event_get_draw_ctx(e); // the whole place we draw the map.
lv_draw_rect_dsc_t draw_rect_dsc;
lv_draw_rect_dsc_init(&draw_rect_dsc);
draw_rect_dsc.bg_opa = LV_OPA_90;
draw_rect_dsc.radius = LV_RADIUS_CIRCLE;
for (int i = 0; i < 100; i++)
{
draw_rect_dsc.bg_color = lv_color_hex(rand() % 0xffffff); //背景没有发生变化。
lv_area_t circle_rect;
lv_coord_t r = random(2, 15);
circle_rect.x1 = random(310); // x_step is the width of the bar and the gap between those bars.
circle_rect.x2 = circle_rect.x1 + r; // THE WIDTH OF THE BAR IS 5 PIXEL
circle_rect.y1 = random(230); // THE HEIGTH OF THE BAR ,UP SIDE OF THE CENTER.
circle_rect.y2 = circle_rect.y1 + r;
lv_draw_rect(draw_ctx, &draw_rect_dsc, &circle_rect);
}
}
}
3.电脑信息监控功能
这个功能的实现原理是esp32通过蓝牙和电脑的蓝牙连接,在电脑上运行一个python 程序,间隔一定时间采集电脑的各种运行信息数据发送到和esp32连接的串口上去。 那么这里需要的东西就有点多了,esp32这边需要驱动蓝牙的接口等待接收数据,收到数据后发送给后台工作的lvgl. 信息显示页面使用一张包含了需要展示相关信息图标的静态图,覆盖整个页面,在对应的图标下方用标签来显示对应的数值。那么这样好像比较单调,这里采用了图片旋转动画将“散热风扇”图标转动起来,其他图标不具有运动属性,就先不管了。
lv_anim_t fan_ani;
lv_anim_init(&fan_ani);
lv_anim_set_var(&fan_ani, fan_img);
lv_anim_set_exec_cb(&fan_ani, (lv_anim_exec_xcb_t)lv_img_set_angle); // 绑定旋转回调函数,系统自带。
lv_anim_set_values(&fan_ani, 0, 3600); // 旋转1周
lv_anim_set_repeat_count(&fan_ani, LV_ANIM_REPEAT_INFINITE); // 一直转一直爽。
lv_anim_set_time(&fan_ani, 500); // 旋转一周用500ms
lv_anim_start(&fan_ani);
好了,最后只差电脑端发送数据了,通过调用了几个采集电脑运输信息的库,然后将数据打包整理重串口发送出来,还需要一些python 调试知识。在数据包里找 Host_python 这个文件夹,用工具将这文件作为运行目录打开command 命令行,或者运行电脑的command命令,然后再指向这个目录,然后启动数据采集程序。 启动前,需要通过蓝牙连接界面找到esp32发射的蓝牙信号连接上,然后查找到它对应的蓝牙串口,并修改windows_host.py 里连接端口的数据。
然后连接成功后,会在电脑终端上看到发送出去的信息。 整个业务流程就是这样子。 难点在于电脑终端python程序的调试,如果有些信息没采集到,需要自己打印一下每步的系信息,查找问题的根源。lvgl内容则使用了 “lv_msg_send” 这个函数巧妙的实现了外部数据和lvgl控件的信息交流。
(tips,如果重新开机连接不上,需要在电脑上删除已有的连接,重新连接一下)
4.信息页
最后一页,采集了一些firebeetle的基本信息,如果接入电池也可以检测电量大小。非常简单,在一张图片上,平铺标签数据即可。这些页面总体上是通过 “平铺视图部件”(lv_tileview)来实现页面之间的切换的,理论上是可以无限添加页面,只要你存储空间和脑洞足够大,它就能够承载你的欲望。总体使用下来,lvgl应对嵌入式开发的方方面面游刃有余。
5.小问题
(1)中文字体使用。
准备需要用到字体font.ttf字体类文件,准备需要包括基本中英文标点符号。
转到 lvgl 字体制作页面。https://lvgl.io/tools/fontconverter
选择字号,输入生成字体文件名,该文件名在程序中直接作为字体变量名使用。把需要的字体粘贴在 字符框处。点击转化即可以下载字体,将字体文件放到 main.cpp 平级目录下,或者放include 等文件也可以识别到的。
具体使用方式是:
声明字符变量:LV_FONT_DECLARE(my_font)
局部使用:lv_obj_set_style_text_font(obj,&my_font,0);
风格使用:lv_style_set_text_font(&style_obj,&my_font);
(2)内存消耗太大
编译失败提示:section .dram0.bss' will not fit in regiondram0_0_seg
这不是固件太大,是内存消耗太大,因为一直追求更高的性能把显示的缓存开太大引起的。只要将lvgl的disp_buf_size调低到*30就可以了。
总结
UI界面的设计是很有用,业务逻辑固然重要,优质内容也需要友好的用户界面来包装才能让大众感受和体现得出来。 因此,UI的使用才场景非常多,即使是一个单一功能的程序经过包装后或许就可以成为一个网红爆品,lvgl优越的性能为创客提供了一个实现概念到产品的捷径,使得即使新手也可以经过短时间的学习同样可以做出出色的产品,这是其他图形库没法做到的。
经过一个季度的学习取得较为满意的成果,学习永远在路上,只要每天都进步一点,才能配得上这个创新和颠覆节奏越来越快的时代。
谨以此小作总结奋斗的2022年!
源码配套资源:
链接:https://pan.baidu.com/s/1NXrySwbTrVU0ECm6TX2_KQ?pwd=84hj
提取码:84hj