基于FireBeetle 2 ESP32-E开发板的LVGL移植及传感器显示(Arduino+TFT_eSPI+LVGL)

news2024/9/25 13:24:46

目录

    • 项目介绍
    • 硬件介绍
    • 硬件结构说明
    • LVGL移植
    • 综合实现
    • 功能展示
    • 项目总结

👉 【Funpack2-3】基于FireBeetle 2 ESP32-E开发板的LVGL移植及传感器显示
👉 CSDN-工程源文件
👉 Github-KafCoppelia/FireBeetle2_lvgl_sensors

项目介绍

本项目基于FireBeetle2 ESP32-E开发板,使用VSCode+Arduino插件开发,通过TFT_eSPI库使用硬件SPI驱动LCD屏幕,并移植LVGL。搭配SHT40温湿度传感器与VL53L0激光测距传感器,将检测的数据较为美观地显示在屏幕上。

👉 Github-lvgl/lvgl

硬件介绍

FireBeetle ESP32-E是一款基于ESP-WROOM-32E双核芯片的主控板,它专为IoT设计。
它支持WIFI和蓝牙双模通信并具有体积小巧、超低功耗、板载充电电路、接口易用等特性。可灵活的用于家庭物联网改装、工业物联网改装、可穿戴设备等等。

硬件资源:

  1. Type-C: USB接口:4.75v-5.5v
  2. pH2.0锂电池接口:3.5-4.2v
  3. 2/D9 LED灯:使用2/09号脚控制的LED灯
  4. 充电指示灯:指示充电方式的红色LED,通过三种方式指示充电状态:1充满电或末充电时熄灭2、充电时常亮3、USB供电,末连接锂电池时高频闪烁
  5. RST复位引脚:单击复位按钮,将程序复位
  6. 5/D8 WS2812指示灯:使用5/D8引脚控制的WS2812RGB灯珠
  7. 低功耗焊盘:此焊盘专为低功耗设计,默认为连接状态,使用小刀轻轻刮粉中间的细线即可断开,断开后可降低500u4静态功耗,通过程序控制主控进入睡眠模式后可将功耗降低至131A。注意:焊盘断开后仅UsE方式供电可驱动RGE灯
  8. GDI显示接口:DFRobot专用显示屏接口,详情后文GD显示接口
  9. ESP32模组:乐鑫公司推出的最新ESP32-E模组
  10. 按钮:连接27/D4的按钮

引脚布局图
👉 更多介绍

  1. Funpack第二季第三期:FireBeetle 2 ESP32-E IoT 开发板
  2. DFRobot-FireBeetle Board ESP32 E

此外,介绍本工程内的两款传感器。MIKROE TEMP&HUM 15 CLICK 为一款SHT40温湿度传感器,I2C模式通信,可以获取温度、湿度信息。本工程通过Adafruit SHT4x库实现数据的读取,可直接在Arduino搜索下载该库
SHT40外观图
👉 MIKROE-TEMP&HUM 15 CLICK产品介绍
👉 Github-adafruit/Adafruit_SHT4X

DFRobot的VL53L0激光测距传感器是一款基于意法半导体(STMicroelectronics)新出的基于飞行时间测距 (ToF) 原理设计的高精度测距传感器,通过I2C通信,其Arduino库文件通过官网帮助页面可直接获得,也可直接在Arduino内搜索下载该库
VL53L0外观图
👉 DFRobot-VL53L0

硬件结构说明

SHT40及VL53L0直接连在FireBeetle2的两路硬件I2C上,如此可直接调用库函数实现对传感器数据的读取,十分方便。两个传感器通过I2C地址查询,所以无所谓I2C1、I2C2的区分。

此外,TFT LCD 240x320通过硬件SPI驱动,具体连接关系如下(除电源与地外):

引脚含义引脚编号
I2C SCLSHT40的数据信号开发板左侧22
I2C SDASHT40的时钟信号开发板左侧21
I2C SCLVL53L0的数据信号开发板右侧22
I2C SDAVL53L0的时钟信号开发板右侧21
SCLLCD的时钟信号18,SPI SCK
MOSILCD的数据信号23,SPI MOSI
MISO不用19
CSLCD的片选信号16,普通IO
DCLCD的D/C信号17,普通IO
RSTLCD的复位信号连接至开发板RESET
BLLCD的背光信号4,普通IO

上述LCD与开发板的连接关系需要在配置TFT_eSPI驱动时再次用到。
硬件连接图

LVGL移植

首先在Arduino内,安装TFT_eSPI库,再在TFT_eSPI库内找到User_Setup.h,并根据屏幕实际配置做修改,注意看注释的说明。主要需要修改的地方有:

👉 Github-Bodmer/TFT_eSPI

  1. 屏幕驱动IC
  2. 对于ST7789,可能出现RGB颜色交换的问题
  3. 屏幕尺寸
  4. 颜色反色的问题, 根据实际问题开启或关闭反色
  5. SPI等引脚对应
  6. 如果有背光控制,可设置
  7. 启动的字体,如果内存大可全部启用
  8. SPI速率,根据实际需求即可

例如,本工程的LCD屏幕为ST7789,对应保留的宏定义有:

#define ST7789_DRIVER
#define TFT_RGB_ORDER TFT_BGR 
#define TFT_WIDTH  240
#define TFT_HEIGHT 320
#define TFT_INVERSION_OFF
#define TFT_BL   4            // LED back-light control pin
#define TFT_BACKLIGHT_ON HIGH  // Level to turn ON back-light (HIGH or LOW)
#define TFT_MISO 19
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS   16  // Chip select control pin
#define TFT_DC   17  // Data Command control pin
#define TFT_RST  -1  // Set TFT_RST to -1 if display RESET is connected to ESP32 board RST
#define SPI_FREQUENCY  27000000

之后,下载LVGL release 源码,并通过zip包安装至Arduino库内。主要步骤可参考LVGL on Arduino帮助页面,主要进行:

  1. 转到安装的Arduino库的目录
  2. 转到lvgl并复制lv_conf_template.h到与Arduino 库目录中lvgl同级的地方,改名为lv_conf.h
  3. 打开lv_conf.h并将第一个更改为启用文件的内容#if 1
  4. 设置显示的颜色深度LV_COLOR_DEPTH 16(根据屏幕实际,本工程为16)
  5. 设置LV_TICK_CUSTOM 1
  6. 如果需要开启LVGL 日志功能,则修改对应宏开关LV_USE_LOG 1
  7. 如果想启用LVGL的demo 或 exmaples,除了修改对应宏开关LV_BUILD_EXAMPLES 1外,参见LVGL on Arduino进行一些额外的步骤

在代码上,需要为LVGL实现一个my_disp_flush()函数,使用TFT_eSPI库的函数进行包装,即可将绘图功能打包给LVGL实现。Arduino实现的最简单的例子(LVGL库内可找到该例):

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <lv_demo.h>

/*Change to your screen resolution*/
static const uint16_t screenWidth  = 480;
static const uint16_t screenHeight = 320;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[ screenWidth * 10 ];

TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight); /* TFT instance */

/* Display flushing */
void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )
{
    uint32_t w = ( area->x2 - area->x1 + 1 );
    uint32_t h = ( area->y2 - area->y1 + 1 );

    tft.startWrite();
    tft.setAddrWindow( area->x1, area->y1, w, h );
    tft.pushColors( ( uint16_t * )&color_p->full, w * h, true );
    tft.endWrite();

    lv_disp_flush_ready( disp );
}

void setup()
{
    Serial.begin( 115200 ); /* prepare for possible serial debug */

    String LVGL_Arduino = "Hello Arduino! ";
    LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();

    Serial.println( LVGL_Arduino );
    Serial.println( "I am LVGL_Arduino" );
    lv_init();

#if LV_USE_LOG != 0
    lv_log_register_print_cb( my_print ); /* register print function for debugging */
#endif

    tft.begin();          /* TFT init */
    tft.setRotation( 3 ); /* Landscape orientation, flipped */

    lv_disp_draw_buf_init( &draw_buf, buf, NULL, screenWidth * 10 );

    /*Initialize the display*/
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init( &disp_drv );
    /*Change the following line to your display resolution*/
    disp_drv.hor_res = screenWidth;
    disp_drv.ver_res = screenHeight;
    disp_drv.flush_cb = my_disp_flush;
    disp_drv.draw_buf = &draw_buf;
    lv_disp_drv_register( &disp_drv );
    
    lv_demo_widgets();
    Serial.println( "Setup done" );
}

void loop()
{
    lv_timer_handler(); /* let the GUI do its work */
    delay( 5 );
}

综合实现

首先,实现两款传感器数据的读取函数,便于之后在lvgl事务内直接获取数据。对于SHT40,在初始化后,读取温度及湿度信息

Adafruit_SHT4x sht4 = Adafruit_SHT4x(); /* Global instance */

void sht40_get_measured(sensors_event_t *humidity, sensors_event_t *temp) {
	sht4.getEvent(humidity, temp);// populate temp and humidity objects with fresh data

	Serial.print("Temperature: "); Serial.print(temp->temperature); Serial.println(" degrees C");
	Serial.print("Humidity: "); Serial.print(humidity->relative_humidity); Serial.println("% rH");
}

对于VL53L0,TOF测距,在初始化后,读取测量的距离信息:

DFRobot_VL53L0X sensor; /* Global instance */
float vl53l0x_get_measured() {
    float distance = sensor.getDistance();
    Serial.print("Distance: ");Serial.println(distance);
    return distance;
}

屏幕主要分为三块地方:

  1. 左上角显示温湿度信息,并且每秒刷新
  2. 右上角通过判断开发板按键是否按下,实现TOF测距的开/关
  3. 下方将TOF测距以折线图显示,并且每250ms更新动态显示

主要地,通过lvgl实现三个定时器事务,温湿度定时器,每秒获取并刷新显示文字:

lv_timer_t *sht4x_update_timer;
sht4x_update_timer = lv_timer_create(temperature_update, 1000, NULL); /* Always working */
static void temperature_update(lv_timer_t *timer) {
    sht40_get_measured(&sht4x_humidity, &sht4x_temp);
    lv_label_set_text_fmt(temp_label, "Temp: %.1f °C", sht4x_temp.temperature);
    lv_label_set_text_fmt(humd_label, "Humidity: %.1f% rH", sht4x_humidity.relative_humidity);
}

对应的两个标签对象:

lv_obj_t *temp_label = lv_label_create(lv_scr_act());
lv_label_set_text(temp_label, "Temp: 0 °C");
lv_obj_set_style_text_color(lv_scr_act(), lv_color_black(), LV_PART_MAIN);
lv_obj_align(temp_label, LV_ALIGN_TOP_LEFT, 10, 10);

lv_obj_t *humd_label = lv_label_create(lv_scr_act());
lv_label_set_text(humd_label, "Humidity: 0% rH");
lv_obj_set_style_text_color(lv_scr_act(), lv_color_black(), LV_PART_MAIN);
lv_obj_align(humd_label, LV_ALIGN_TOP_LEFT, 10, 30);

按键检测定时器,每30毫秒检测按键是否按下,其中还需要有改变开关控件的交互,当按键按下,开关切换状态,若为打开,则旁边标签显示“TOF measuring”,且TOF开始测距,vl53l0x_update_timer 定时器继续工作;若为关闭,则边标签显示“TOF idle”,且TOF停止工作,vl53l0x_update_timer 定时器暂停:

int last_btn_state = HIGH;
lv_timer_t *btn_state_timer;
btn_state_timer = lv_timer_create(btn_pressed_check, 30, NULL);
static void btn_pressed_check(lv_timer_t *timer) {
    int reading = digitalRead(BTN_PIN);

    if (reading != last_btn_state) {
        if (reading == LOW) {
            if (lv_obj_has_state(tof_measuring_sw, LV_STATE_CHECKED)) {
                lv_obj_clear_state(tof_measuring_sw, LV_STATE_CHECKED);
                vl53l0x_stop();
                lv_timer_pause(vl53l0x_update_timer);
                lv_label_set_text(btn_label, "TOF idle");
            }
            else {
                lv_obj_add_state(tof_measuring_sw, LV_STATE_CHECKED);
                vl53l0x_start();
                lv_timer_resume(vl53l0x_update_timer);
                lv_label_set_text(btn_label, "TOF measuring");
            } 
        }
    }
    last_btn_state = reading;
}

对应的开关控件及标签对象:

lv_obj_t *btn_label = lv_label_create(lv_scr_act());
lv_label_set_text(btn_label, "TOF measuring");
lv_obj_set_style_text_color(lv_scr_act(), lv_color_black(), LV_PART_MAIN);
lv_obj_align(btn_label, LV_ALIGN_CENTER, 50, -100);

lv_obj_t *tof_measuring_sw = lv_switch_create(lv_scr_act());
lv_obj_set_size(tof_measuring_sw, 40, 20);
lv_obj_align(tof_measuring_sw, LV_ALIGN_TOP_RIGHT, -10, 10);
lv_obj_add_state(tof_measuring_sw, LV_STATE_CHECKED); /* switch on in default */

VL530L0定时任务,每250毫秒更新测量,将下一个数据显示在折线图右侧:

lv_timer_t *vl53l0x_update_timer;
vl53l0x_update_timer = lv_timer_create(tof_update, 250, NULL);

static void tof_update(lv_timer_t *timer) {
    float tof_new_data = vl53l0x_get_measured();

    lv_chart_set_next_value(tof_data_chart, tof_data_series, (int)tof_new_data);
    lv_chart_refresh(tof_data_chart);

    lv_label_set_text_fmt(y_next_value_label, "%.1f", tof_new_data);
    if (tof_new_data > 1000) {
        lv_obj_align_to(y_next_value_label, tof_data_chart, LV_ALIGN_OUT_RIGHT_MID, -40, 10);
    }
    else {
        lv_obj_align_to(y_next_value_label, tof_data_chart, LV_ALIGN_OUT_RIGHT_MID, -40, -10);
    }
}

折线图的更新显示,通过LVGL可以很简单的实现,一些标签对象的位置摆放,请参考LVGL帮助文档,查看具体函数的用法即可。

功能展示

上电后,两个传感器即刻开始工作。
上电
当按下按键,关闭TOF测量开关时,折线图停止更新,开关控件关闭且显示“TOF idle”:
关闭TOF测量
当按下按键,开启TOF测量开关时,折线图继续动态更新,开关控件开启且显示“TOF measuring”:
恢复TOF测量

项目总结

此次成功移植了LVGL至LCD上,实现了对SHT40、VL53L0温湿度信息、ToF测距信息的简单显示,使用LVGL显示各种效果都很方便、很好看,代码上也很有条理,通过事件、定时器等功能可以实现多种控件的交互。

美中不足的是,限于时间,原本想实现动态显示ToF测距折线图的纵坐标数据,但是实现起来确实不是想的那么容易,因此目前无法实现折线图纵坐标的灵活切换。当测量数据较大时,数据会绘制在折线图范围之外。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/131482.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯寒假集训第三天《灌溉》

没有白走的路&#xff0c;每一步都算数&#x1f388;&#x1f388;&#x1f388; 题目描述&#xff1a; 一个长方形块&#xff0c;在方形块的中间位置有给定的水管&#xff0c;这些水管在单位时间会往上下左右四个方向进行灌溉。问在给定的方块中&#xff0c;一定时间后&#…

PyCharm 发布了新版,支持最新 Python 3.11 和 PyScript 框架

通常而言&#xff0c;使用新潮的或者快速发展的技术&#xff0c;可能会挺有挑战性&#xff0c;你可能得经常阅读文档&#xff0c;才能熟悉新的语法、API 和协议。 PyCharm 2022.2 通过提供对 Python 3.11 的语言特性和新的 PyScript 框架的支持&#xff0c;能够帮助你完成这一…

代码随想录算法训练营day59|503.下一个更大元素II,42. 接雨水

503.下一个更大元素II 503. 下一个更大元素 II - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;单调栈 1. 单调递增栈&#xff1b;在遍历的过程中模拟走两边nums&#xff1b; class Solution {public int[] nextGreaterElements(int[] nums) {if(numsnull || num…

【MySQL】八,角色管理

创建角色 引入角色的目的是方便管理拥有相同权限的用户。恰当的权限设定&#xff0c;可以确保数据的安全性。 语法 CREATE ROLE role_name[host_name] [,role_name[host_name]]...创建一个经理的角色 create role managerlocalhost;给角色赋予权限 创建角色之后&#xff0…

使用 npm link 测试本地编写的 node 模块 / 引入全局安装的 node 模块

目录 1. npm install VS npm install -g 2. npm install -g 的本质&#xff1f;映射脚本的作用&#xff1f; 3. 如何测试使用未发布的 npm 包&#xff1f;npm link 原理&#xff1f; 4. link 到项目 4.1 全局 link 4.2 解除 link 4.3 link 到项目有两种情况&#xff08;…

ansible 第二天

要求&#xff1a; 安装并且配置ansible 1)安装和配置ansible以及ansible控制节点server.example.com如下&#xff1a; 2)创建一个名为/home/student/ansible/inventory的静态库存文件如下所示&#xff1a; 2.1)node1 是dev主机组的成员 2.2)node2是test主机组的成员 2.3)node1和…

什么是 Loader、手写 Webpack Loader

目录 1. 什么是 Loader 1.1 Loader 工作原理 1.2 Loader 执行顺序 1.3 内联 Loader 前缀​​​​​​​ 2. 如何开发 Loader 2.1 Loader 长什么样子 2.2 配置本地 Loader 的四种方法 2.2.1 在配置 rules 时&#xff0c;指定 Loader 的绝对路径 2.2.2 在 resolveLoader…

Windows配置万德(Wind)量化接口

原理&#xff1a;wind会在python的第三方库中安装一个属于wind的库 文章目录步骤1:确定python的路径步骤2:配置wind的接口步骤3:检查配置步骤4:使用python提取任意的wind数据步骤1:确定python的路径 如果是默认安装&#xff0c;一般路径是&#xff1a;C:\Users\用户名\Anacond…

磨金石教育摄影技能干货分享|优秀作品欣赏—技巧十足的艺术摄影

想要赏析艺术类的摄影&#xff0c;就得立足于画面身后的意蕴&#xff0c;想作者所想&#xff0c;思作者所思。 这有一定的难度&#xff0c;但也不乏趣味。 今天我们就再来看一组艺术类摄影作品&#xff0c;看看作者如何用高明的技巧表达自己心中的感受吧。 1、江苏省-李玉龙-《…

表白墙 -- 前后端代码详解

表白墙 -- 前后端代码详解一、前端二、后端实现2.1 需求2.2 创建项目及初始化2.3 实现提交数据 (存档)2.3.1 实现 doPost2.3.2 构造请求 (修改 html 文件)2.3.3 验证2.4 实现获取数据 (读档)2.4.1 实现 doGet2.4.2 构造请求 (修改 html 文件)2.4.3 验证三、JDBC 版本 (MySQL)3.…

回味2022

回味20221.前言2.过去的十二个月3.我期望的20231.前言 2021年写给自己的总结&#xff1a;回味2021 一年又一年飞逝的光阴&#xff0c;我想唯有时间留给人的印象最为深刻吧。春去秋来&#xff0c;四季轮回间都是时光的印记。2022年12月30日&#xff0c;25岁的我依旧在这间写下2…

从socket开始讲解网络模式

从socket开始讲解网络模式 windows采用IOCP网络模型&#xff0c;而linux采用epoll网络模型&#xff08;Linux得以实现高并发&#xff0c;并被作为服务器首选的重要原因&#xff09;&#xff0c;接下来讲下epoll模型对网络编程高并发的作用 简单的socket连接 socket连接交互的…

LaoCat带你认识容器与镜像(一)

准备更新一个容器与镜像相关的系列&#xff0c;从Docker到K8s的入门再到实际项目进阶应用&#xff0c;这里感谢好朋友泽鹏&#xff0c;是他让我结识容器与镜像&#xff1b;也感谢上家公司菲恩曼&#xff0c;是它给了我去学习、实践的机会&#xff1b;最后感谢翼哥&#xff0c;一…

Linux系统下at任务调度机制

Linux系统下at任务调度机制 基本介绍 at命令是一次性定时计划任务&#xff0c;at 的守护进程 atd 会以后台模式运行&#xff0c;检查作业队列来运行。默认情况下&#xff0c;atd 守护进程每60秒检查作业队列&#xff0c;有作业时&#xff0c;会检查作业运行时间&#xff0c;如果…

深入理解计算机系统_可重定位目标文件的格式---elf格式

本篇笔记记录可重定位目标文件的格式— elf格式&#xff0c;也是《深入理解计算机系统》第7章的内容。了解这些内容&#xff0c;对我们很有帮助&#xff0c;比如代码排错&#xff0c;可以深入了解C/C 实现原理。 分别介绍如何得到可重定位目标文件及其格式。 2.1 如何得到可重…

操作系统~Linux~线程的互斥,mutex互斥锁的使用及其原理

1.一些基本概念 1&#xff0e;临界资源&#xff1a;凡是被线程共享访问的资源都是临界资源&#xff08;多线程、多进程打印数据到显示器&#xff0c;显示器就是临界资源&#xff09; 2&#xff0e;临界区&#xff1a;代码中访问临界资源的代码&#xff08;在代码中&#xff0c;…

kotlin学习笔记之注解与反射

一、声明并应用注解 一个注解允许你把额外的元数据关联到一个声明上。然后元数据就可以被相关的源代码工具访问&#xff0c;通过编译好的类文件或是在运行时&#xff0c;取决于这个注解是如何配置的。 1、应用注解 在kotlin中使用注解的方法和java一样。要应用一个注解&#xf…

如何通过3个月自学成为网络安全工程师!

前言&#xff1a; 趁着今天下班&#xff0c;我花了几个小时整理了下&#xff0c;非常不易&#xff0c;希望大家可以点赞收藏支持一波&#xff0c;谢谢。 我的经历&#xff1a; 我 19 年毕业&#xff0c;大学专业是物联网工程&#xff0c;我相信很多人在象牙塔里都很迷茫&…

Pycharm配置关于pyside6的外部工具

文章目录一、前言二、Pycharm配置1、designer.exe&#xff08;1&#xff09;打开Pycharm的设置&#xff08;2&#xff09;相关参数&#xff08;可复制粘贴&#xff09;2、Pyside6-uic.exe&#xff08;1&#xff09;设置&#xff08;2&#xff09;相关参数&#xff08;可复制粘贴…

Java--抽象类和接口的区别

今天是22年最后一天了, 写篇博客记录一下吧, 这一年发生了很多事情, 也学到了很多知识, 后面要继续加油啊, 大家也要加油啊, 米娜桑. 目录 概述 区别 1. 定义关键字不同 2. 继承或实现的关键字不同 3. 子类扩展的数量不同 4. 属性访问控制符不同 5. 方法控制符不同 6.…