1078|2

21

帖子

9

TA的资源

一粒金砂(中级)

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

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

 


 

 

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

三、开机动画设计

四、主要功能展示

五、中文字体处理

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

总结:心得体会
 

 

前言:

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


 

 

image-20231008105514-1.png  

 

 

一、编程环境搭建

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

(1)新建一个Feather的项目

image-20231008105514-2.png  

 

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

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

 

image-20231008105514-3.png  


二、屏幕驱动及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);
}

 

image-20231008105514-4.png  


三、开机动画设计

 

7893f8bae3c5510c3882dafceda9760a202310600461.gif  

    模仿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变量的变化产生渐变,跟动画互相辉映,产生出来灯火绚烂的效果。最后动画结束后用回调函数钩子把主程序带动起来。

image-20231008105514-6.png  

 

image-20231008105514-7.png  


四、主要功能展示

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

 

WeChat_2023100811262520231081127162.gif  

image-20231008105514-8.png  

(1)主程序:

image-20231008105514-9.png  

主程序模块

image-20231008105514-10.png  

(2)监控模块

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

image-20231008105514-11.png  

(3)温湿度监控模块

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

微信图片_20231013000203.jpg  

 

   image-20231008105514-12.png  

 

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)时钟模块

image-20231008105514-13.png  

(5)天气模块

image-20231008105514-14.png  

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

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

 

c8b1160d3f290aeedd47ea6d63728f322023101306583.gif  

 

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

image-20231008105514-15.png  

 


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

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

 

blinker0.png  

 

     image-20231008105514-18.png  

 

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

回复
举报

103

帖子

7

TA的资源

一粒金砂(高级)

Arduino玩起来,六六六!


回复

6763

帖子

9

TA的资源

版主

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


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

猜你喜欢
随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

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

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

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

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