1484|2

26

帖子

12

TA的资源

一粒金砂(中级)

楼主
 

【得捷电子Follow me第2期】基于lvgl构建的多任务程序框架(综合贴) [复制链接]

  本帖最后由 genvex 于 2023-10-13 10:04 编辑

 


 

 

项目目录
一、编程环境搭建
二、屏幕驱动及Lvgl移植

三、开机动画设计

四、主要功能展示

五、中文字体处理

六、任务5 手机远程遥控板载led灯

总结:心得体会
 

 

前言:

      本项目内容使用Feather TFT 及 LIS3DH加速度传感器、AHT20 温湿度传感器 硬件,开发了一套用户UI,包括了时钟、硬件信息、温湿度、天气预报、加速度传感器使用,最后,使用blinker库完成手机远程遥控板载led灯的功能。 


 

 

 

 

 

一、编程环境搭建

    项目是基于Arduino在vscode的PlatformIO开发平台完成的。Arduio用来管理小型单功能项目还是可以的,主要是它编程环境准备相对简单,大众创客的挚爱。进阶的使用可以将战场转移到vscode,利用vscodeIDE提供的自带技能及其他优秀插件带来的便利,来提升编程能力。

(1)新建一个Feather的项目

 

 

(2)对项目的配置进行微调

    vscode 编程环境就相对复杂一点,同样的内容在别人机器上好好的,在你的电脑上就可能玩不转了,因为很难保证大家的环境细节完全一致,所以调整配置文件也是件技术活,也没啥技巧就是多看别人的配置。如果编译通过但是不能上传,就需要指定 esptoolspy的版本,才能够解决这个问题。FeatherTFT 带有PSRAM在这里作了相关声明,后面才可以正常使用。另外,对flash分区也作了调整,默认情况只有1.多M的空间,把所有项目相关内容都一次放进去还是比较紧张的,这里我调整到3M左右的空间,跑起来就没有顾忌。

 

 


二、屏幕驱动及Lvgl移植

      成功的嵌入式设备需要一个极具吸引力的用户界面,才能给用户留下良好的第一印象。传统的图形绘制库(例如经典TFT_eSPI,Adafruit_GFX,Lovyan_GFX等)可以帮助用户快速的入门,lvgl的出现给嵌入式开发在最终产品呈现提供了一种解决方案。但是lvgl的版本一直在演替,带来的问题是版本间的差异会给刚入门的小伙伴造成很大的困扰,即便是想运行一个helloworld测试,就要折腾好久,甚至弃坑,都是很正常的现象。

   Lvgl的驱动核心驱动代码见下图所示。核心思想是用常规的作图库为lvgl提供一个刷屏的工具函数。我这里用了LovyanGFX来驱动Feather的屏幕,因为这个图形库有专门针对FeatherS3的驱动支持,得来全不费工夫,直接使用就好,到驱动底层内容去观摩了下,作者对屏幕的x,y方向做个偏移设置,就是这两个参数,自己上手就够有一阵折腾的。构建好这个工具函数后,把它传递到lvgl驱动核心,在配置上主要是把屏幕的尺寸、刷屏的缓存大小,触控驱动(本次没用上)的参数,看似长篇大论,但是一个环节也不能少,这个架构也是经过多次模仿学习,本人认为是一个较为优雅的解决方案。这里特别强调一下,这个驱动把刷屏的内存放在PSRAM里面,目前PSRAM最常见使用就是用来刷屏,这样做可以节省些常规内存,保障程序和图形界面的正常运行,这样设置下来,屏幕能保持在50~60fps的刷新率,也是相当流畅的。

 

  • static void lvgl_begin(void)
  • {
  • #define LVGL_HOR_RES (240)
  • #define LVGL_VER_RES (135)
  • // #define DISP_BUF_SIZE (LVGL_HOR_RES * 100)
  • lcd.init();
  • lcd.initDMA();
  • lcd.setRotation(2);
  • // lcd.setBrightness(100);
  • lv_init();
  • static lv_disp_draw_buf_t draw_buf;
  • size_t DISP_BUF_SIZE = sizeof(lv_color_t) * (LVGL_HOR_RES * LVGL_VER_RES); // best operation.
  • static lv_color_t *buf1 = (lv_color_t *)heap_caps_malloc(
  • DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
  • static lv_color_t *buf2 = (lv_color_t *)heap_caps_malloc(
  • DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
  • // static lv_color_t *buf1 = (lv_color_t *)heap_caps_malloc( // most fast operation.
  • // DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
  • // static lv_color_t *buf2 = (lv_color_t *)heap_caps_malloc(
  • // DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
  • lv_disp_draw_buf_init(&draw_buf, buf1, buf2, DISP_BUF_SIZE); /*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 = LVGL_HOR_RES;
  • disp_drv.ver_res = LVGL_VER_RES;
  • /*Used to copy the buffer's content to the display*/
  • disp_drv.flush_cb = my_disp_flush;
  • /*Set a display buffer*/
  • disp_drv.draw_buf = &draw_buf;
  • /*Finally register the driver*/
  • lv_disp_drv_register(&disp_drv);
  • }
  • static void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area,
  • lv_color_t *color_p)
  • {
  • int w = (area->x2 - area->x1 + 1);
  • int h = (area->y2 - area->y1 + 1);
  • /* Start new TFT transaction */
  • lcd.startWrite();
  • /* set the working window */
  • lcd.setAddrWindow(area->x1, area->y1, w, h);
  • /* Write the buffer to the display */
  • // lcd.writePixels((lgfx::rgb565_t *)&color_p->full, w * h);
  • lcd.writePixelsDMA((lgfx::rgb565_t *)&color_p->full, w * h);
  • // lcd.pushImageDMA(area->x1, area->y1, w, h,&color_p->full);
  • // lcd.pushPixelsDMA((uint16_t *)color_p, w * h);
  • // lcd.pushImage(area->x1,area->y1,w,h,&color_p->full);
  • /* terminate TFT transaction */
  • lcd.endWrite();
  • /* tell lvgl that flushing is done */
  • lv_disp_flush_ready(disp);
  • }

 

 


三、开机动画设计

 

 

    模仿Epressif demo的开机动画制作了一个开机动画,这个动画的运行机理和Lvgl的动画机制不一样。这个动画使用了一个时间计数器(count)来推动动画的前进,随便变量count的步进,三个大小不一的圆弧产生旋转的效果最后消失。主要脑洞比较大的地方是设计圆弧的起始角度和结束角度,这两个数值决定了圆弧的长度,用三角函数的数值变化区间来实现圆弧的从无到有再缩短,视觉效果就产生旋转效应。为了便于理解对计数器变化引起图形数值的变化做了一幅曲线图,观察起始角度和结束角度的变化曲线,蓝色线条是起始角度在整个周期的数值变化曲线,在开始一段时间(count<0)的阶段保持在0度(即默认的起始位置,这里需要复习一下arc的绘图语法,见代码块),在这个阶段结束角度在慢慢变大(橙色线),呈现了圆弧在伸长。下一阶段(count>0),开始角度(蓝色线)也开始递增,同时,结束角度(橙色线)也继续保持上升状态,这阶段呈现了弧形在旋转的效果,因为开始角度和结束角度同时移动;但由于开始角度增长速度比较快(斜率大),很快就与结束角度重合,这个阶段呈现了弧形在不断地缩短直到最后消失,至此,弧形的动画结束,最后是乐鑫和Adafruit公司的logo的淡入(fadein)和淡出(fadeout),利用的cos函数从第四象限到第一象限数值呈钟形的曲线效果,实现淡入淡出。红色虚线表开始角度和结束角度的距离,也就是弧形的长度变化曲线, 而真正产生旋转效果的参数是 rotation一直在匀速增加,推进了3调弧形的位置。

  • lv_arc_set_bg_angles(arc[i], arc_start, arc_end);
  • // 要设置背景弧的开始和结束角度,请使用 lv_arc_set_bg_angles(arc, start_angle, end_angle)
  • //  函数或 lv_arc_set_bg_start/end_angle(arc, angle)。
  • // 零度位于对象的中间右侧(3 点钟方向),并且度数沿顺时针方向增加。 角度应在 [0;360] 范围内。
  • lv_arc_set_rotation(arc[i], (count + 120 * (i + 1)) % 360);

同时,板载的led灯也跟随count变量的变化产生渐变,跟动画互相辉映,产生出来灯火绚烂的效果。最后动画结束后用回调函数钩子把主程序带动起来。

 

 

 


四、主要功能展示

    本项目的主要功能包括时钟、主板性能测试、传感器数值显示面板(模拟),天气预报,泡泡5个板块。各板块使用相同的类模板进行构造,通过板载按键实现循环切换,理论上可以无限添加,只要flash空间够大

 

 

 

(1)主程序:

 

主程序模块

 

(2)监控模块

     用于检测和展示主板的硬件信息。

 

(3)温湿度监控模块

      采集了使用AHT20 温湿度传感器的数据并采用仪表盘的形式展示出来。 

 

 

  

 

 

  • void MonitorModel::init(){
  • aht.begin();
  • getAlldata();
  • }
  • void MonitorModel::getAlldata(){
  • sensors_event_t humid, temp;
  • aht.getEvent(&humid, &temp); // populate temp and humidity objects with fresh data℃
  • Serial.print(temp.temperature);
  • Serial.print(humid.relative_humidity);
  • this->temperature = temp.temperature;
  • this->humidity = humid.relative_humidity;
  • }
  • float MonitorModel::getTemperature() {
  • return this->temperature;
  • }
  • long MonitorModel::getHumidity() {
  • return this->humidity;
  • }

 

 

(4)时钟模块

 

(5)天气模块

 

(6)加速度传感器及泡泡球屏保

   读取了Adafruit_LIS3DH加速度传感器数据并显示在屏幕上。 

 

 

 

  • void BubbleModel::init()
  • {
  • if (!lis.begin(0x18))
  • { // change this to 0x19 for alternative i2c address
  • Serial.println("Couldnt start");
  • while (1)
  • yield();
  • }
  • lis.setRange(LIS3DH_RANGE_4_G); // 2, 4, 8 or 16 G!
  • Serial.print("Range = "); Serial.print(2 << lis.getRange());
  • Serial.println("G");
  • lis.setDataRate(LIS3DH_DATARATE_50_HZ);
  • }
  • void BubbleModel::getAlldata()
  • {
  • lis.read(); // get X Y and Z data at once
  • sensors_event_t event;
  • lis.getEvent(&event);
  • /* Display the results (acceleration is measured in m/s^2) */
  • Serial.print("\tX: "); Serial.print(event.acceleration.x);
  • Serial.print("\tY: "); Serial.print(event.acceleration.y);
  • Serial.print("\tZ: "); Serial.print(event.acceleration.z);
  • this->accX = event.acceleration.x;
  • this->accY = event.acceleration.y;
  • this->accZ = event.acceleration.z;
  • }

 

五、中文字体处理

    转中文字体虽然没有太多的技术难度,就是有点麻烦例如选什么字体(自己觉得好看,才是真的好看,纠结),字号(要跟屏幕大小匹配,来回要调整几次),字数(要转哪些字,多了文件大,少了不够用,导致某些不常见情况会出现乱码)。在没有其他选择的时候,大家会用lvgl官方提供的在线转化工具,稍微有点懵,用几次也可以掌握。国内民间也有好些转图转字体的好用的工具,本项目采用的就是好心人制作转字体工具LvglFontTools0.4,虽然界面有点丑,但是转出来的字体能正常使用,还要什么单车呢。

教程和参见下方链接和导图(不好用免费退款

 

中文字体制作:

https://blog.csdn.net/kelleo/article/details/122686644

 

 


六、任务5 手机远程遥控板载led灯

    实现遥控有很多解决方案,例如本地局域网的控制,乐鑫rainmaker方案,mqtt方案。前期测试了rainmaker的方案,没有成功,而且需要flash空间比较大,运行还不太稳定。局域网控制方案,需要设计一个网页,网页元素包括三条滑动条,用来控制灯光的三种颜色,也设计好了,就是过程较为繁琐。最后,选用了比较优雅的方案,使用了点灯科技的blinker库来实现项目要求的功能,该库的背后原理是mqtt机制,但是经过封装后,开发出来给用户使用的api非常简单,另外,还贴心提供手机客户端的app,使用起来更加方便,不足之处是只提供一个硬件的接入,但是算得上良心商家了。

 

 

 

    

 

 

  • void rgb1_callback(uint8_t r_value, uint8_t g_value, uint8_t b_value, uint8_t bright_value)
  • {
  • BLINKER_LOG("R value: ", r_value);
  • colorR = r_value;
  • BLINKER_LOG("G value: ", g_value);
  • colorG = g_value;
  • BLINKER_LOG("B value: ", b_value);
  • colorB = b_value;
  • BLINKER_LOG("Rrightness value: ", bright_value);
  • pixels.setPixelColor(0, r_value, g_value, b_value);
  • if (bright_value > 100)
  • bright_value = 100; // too high will damage the led.
  • brightness = bright_value;
  • pixels.setBrightness(brightness);
  • pixels.show();
  • lv_led_set_brightness(blinker_led, brightness);
  • lv_led_set_color(blinker_led, lv_color_make(colorR, colorG, colorB));
  • lv_label_set_text_fmt(rlabel, "R:%d", colorR);
  • lv_label_set_text_fmt(glabel, "G:%d", colorG);
  • lv_label_set_text_fmt(blabel, "B:%d", colorB);
  • }

  

rgb灯的响应回调函数


本项目的一些心得体会:

    以往参加活动,针对主办方提出的要求逐一完成任务,把任务分解开来,这样比较轻松的完成单个内容。这次尝试把所有的任务整合在一起,在一次启动就可以展示所有(必做任务)内容,这是一次积极的挑战,通过面向对象的编程思想,把功能用类(class)进行统一的封装,使得不同的功能可以自由切换,而且还能保持流畅运行。理论上只要Flash够大,可以按照类的模板不断的扩展应用程序,相当做个了一个项目的架构,体现了做应用项目的思维,这种思维应该继续使用下去,因为经过简单功能程序学习,最终是需要把这些简单的功能综合起来(综合功能产品,手机),这样才能成为一个有用的工具,一个有趣的产品。

   感谢主办方给予我极大的创作空间,祝愿该系列活动完满成功并持续举办下去!

 

主任务代码  ,主要修改自己wifi信息,高德天气api账号,用到 LIS3DH加速度传感器、AHT20 温湿度传感器 硬件。

附加任务代码  需要使用点灯科技blinker app 及硬件密钥。 预祝食用愉快,有问题,欢迎来聊。  汤半泛!

 

 

最新回复

屏幕能保持在50~60fps的刷新率,也是相当流畅的。不错不错!   详情 回复 发表于 2023-10-14 13:13
点赞(1) 关注(1)
 
 

回复
举报

115

帖子

8

TA的资源

一粒金砂(高级)

沙发
 

Arduino玩起来,六六六!

 
 
 

回复

7145

帖子

11

TA的资源

版主

板凳
 

屏幕能保持在50~60fps的刷新率,也是相当流畅的。不错不错!

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 2/10 下一条
福禄克有奖直播:高精度测温赋能电子制造 报名中!
直播时间:2025年2月28日(周五)上午10:00
直播主题:高精度测温赋能电子制造
小伙伴们儿快来报名直播吧~好礼等你拿!

查看 »

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表