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

👉 【Funpack2-3】基于FireBeetle 2 ESP32-E开发板的LVGL移植及传感器显示
👉 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测距折线图的纵坐标数据,但是实现起来确实不是想的那么容易,因此目前无法实现折线图纵坐标的灵活切换。当测量数据较大时,数据会绘制在折线图范围之外。

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值