【Funpack2-3】基于FireBeetle 2 ESP32-E开发板的LVGL移植及传感器显示
本项目基于FireBeetle 2 ESP32-E开发板,使用VSCode+Arduino开发,通过TFT_eSPI驱动LCD屏幕,并移植LVGL。搭配SHT40温湿度传感器与VL53L0激光测距传感器,将检测的数据较为美观地显示在屏幕上。
标签
嵌入式系统
Arduino
Funpack活动
lvgl
FireBeetle2
葉SiR
更新2023-01-04
北京大学
642

项目介绍

本项目基于FireBeetle 2 ESP32-E开发板,使用VSCode+Arduino插件开发,通过TFT_eSPI驱动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搜索下载该库

👉  MIKROE-TEMP&HUM 15 CLICK产品介绍
👉 Github-adafruit/Adafruit_SHT4X

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

👉 DFRobot-VL53L0

硬件结构说明

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

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

引脚 含义 引脚编号
I2C SCL SHT40的数据信号 开发板左侧22
I2C SDA SHT40的时钟信号 开发板左侧21
I2C SCL VL53L0的数据信号 开发板右侧22
I2C SDA VL53L0的时钟信号 开发板右侧21
SCL LCD的时钟信号 18,SPI SCK
MOSI LCD的数据信号 23,SPI MOSI
MISO 不用 19
CS LCD的片选信号 16,普通IO
DC LCD的D/C信号 17,普通IO
RST LCD的复位信号 连接至开发板RESET
BL LCD的背光信号 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 measuring”:

项目总结

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

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

附件下载
工程源文件.zip
团队介绍
北京大学 软微学院 电子信息专业就读
团队成员
葉SiR
二次元の开发者;👉 GitHub: https://github.com/KafCoppelia
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号