ESP32 CYD with LVGL: Display BME280 Sensor Data on a Table

In this guide, you’ll learn how to create a responsive table using LVGL. We’ll display temperature, humidity, and pressure from the BME280 sensor, and luminosity from the LDR on the display. We’ll also display the timestamp associated with the readings (date and time). The ESP32 will be programmed using Arduino IDE.

ESP32 Cheap Yellow Display CYD Board BME280 Table Readings LVGL Arduino

New to the ESP32 Cheap Yellow Display? Start here: Getting Started with ESP32 Cheap Yellow Display Board – CYD (ESP32-2432S028R).

Project Overview

In this project, we’ll create a table with sensor readings from the BME280 sensor and the LDR on the ESP32 CYD board. We’ll also display the date and time, and your board IP address.

To get an accurate date and time for your timezone, we’ll use the WorldTimeAPI. To get the time from the API, the ESP32 needs to connect to the internet, so you need to have a router in your surroundings so that the ESP32 can connect to it.

If you don’t have an internet connection where the ESP32 will operate, you can omit the date and time section or use an RTC module.

We’ll also display a floating button with the refresh icon that when clicked will update the values on the table.

Prerequisites

Before proceeding, make sure you follow the next prerequisites. You must follow all steps, otherwise, your project will not work.

1) Parts Required and Wiring the Circuit

For this project, you need the following parts:

We’ll use I2C communication protocol to get data from the BME280 sensor. On the ESP32 CYD board there is an extended IO socket that allows you to connect external peripherals. We’ll connect the sensor to the CN1 connector.

ESP32 CYD Cheap Yellow Display Board JST Connector Connecting BME280 Sensor

Your board should have come with a little JST connector to access those GPIOs.

ESP32 CYD Cheap Yellow Display Board JST Connector

If you’re not familiar with the BME280 sensor, we recommend that you read our getting started guide: ESP32 with BME280 Sensor using Arduino IDE (Pressure, Temperature, Humidity)

The CYD board should have a built-in LDR (light-dependent resistor) next to the screen and connected to GPIO 34.

ESP32 Cheap Yellow Display LDR (Light Dependent ResistorESP32-Cheap-Yellow-)

Recommended reading: ESP32 Cheap Yellow Display (CYD) Pinout (ESP32-2432S028R).

2) Install ESP32 Boards in Arduino IDE

Arduino IDE 2 Logo

We’ll program the ESP32 using Arduino IDE. Make sure you have the ESP32 boards installed. Follow the next tutorial:

3) Get familiar with the ESP32 Cheap Yellow Display

The ESP32-2432S028R development board has become known in the maker community as the “Cheap Yellow Display” or CYD for short. This development board, whose main chip is an ESP32-WROOM-32 module, comes with a 2.8-inch TFT touchscreen LCD, a microSD card interface, an RGB LED, and all the required circuitry to program and apply power to the board.

ESP32 Cheap Yellow Display CYD Board ESP32-2432S028R front

If this is your first time using the ESP32 Cheap Yellow Display, make sure to follow our getting started guide:

4) Install TFT and LVGL Libraries

LVGL (Light and Versatile Graphics Library) is a free and open-source graphics library that provides a wide range of easy-to-use graphical elements for your microcontroller projects that require a graphical user interface (GUI).

LVGL new logo

Follow the next tutorial to install and configure the required libraries to use LVGL for the ESP32 Cheap Yellow Display using Arduino IDE.

5) Install ArduinoJson and BME280 Libraries

For this project, you need to install the ArduinoJSON library to handle the JSON response when you make a request to the WorldTimeAPI.

In the Arduino IDE, go to Sketch > Include Library > Manage Libraries. Search for ArduinoJSON and install the library by Benoit Blanchon. We’re using version 7.0.4. We recommend using the same version.

We’ll use the Adafruit BME280 library to get data from the BME280. In the Arduino IDE, go to Sketch > Include Library > Manage Libraries. Search for Adafruit BME280 Library on the search box and install the library. Also, install any dependencies currently not installed (usually the Adafruit Bus IO and the Adafruit Unified Sensor libraries).

Installing Adafruit BME280 Sensor Library Arduino IDE

ESP32 CYD: Display BME280 Sensor Readings on a Table – Arduino Code

The following code will create the table and display the sensor readings. Before uploading the code to your board, you need to insert your network credentials so that the ESP32 can connect to the internet to get the time. You also need to insert your timezone.

/*  Rui Santos & Sara Santos - Random Nerd Tutorials - https://RandomNerdTutorials.com/esp32-cyd-lvgl-display-bme280-data-table/
    THIS EXAMPLE WAS TESTED WITH THE FOLLOWING HARDWARE:
    1) ESP32-2432S028R 2.8 inch 240×320 also known as the Cheap Yellow Display (CYD): https://makeradvisor.com/tools/cyd-cheap-yellow-display-esp32-2432s028r/
      SET UP INSTRUCTIONS: https://RandomNerdTutorials.com/cyd-lvgl/
    2) REGULAR ESP32 Dev Board + 2.8 inch 240x320 TFT Display: https://makeradvisor.com/tools/2-8-inch-ili9341-tft-240x320/ and https://makeradvisor.com/tools/esp32-dev-board-wi-fi-bluetooth/
      SET UP INSTRUCTIONS: https://RandomNerdTutorials.com/esp32-tft-lvgl/
    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/

/*  Install the "lvgl" library version 9.X by kisvegabor to interface with the TFT Display - https://lvgl.io/
    *** IMPORTANT: lv_conf.h available on the internet will probably NOT work with the examples available at Random Nerd Tutorials ***
    *** YOU MUST USE THE lv_conf.h FILE PROVIDED IN THE LINK BELOW IN ORDER TO USE THE EXAMPLES FROM RANDOM NERD TUTORIALS ***
    FULL INSTRUCTIONS AVAILABLE ON HOW CONFIGURE THE LIBRARY: https://RandomNerdTutorials.com/cyd-lvgl/ or https://RandomNerdTutorials.com/esp32-tft-lvgl/   */
#include <lvgl.h>

/*  Install the "TFT_eSPI" library by Bodmer to interface with the TFT Display - https://github.com/Bodmer/TFT_eSPI
    *** IMPORTANT: User_Setup.h available on the internet will probably NOT work with the examples available at Random Nerd Tutorials ***
    *** YOU MUST USE THE User_Setup.h FILE PROVIDED IN THE LINK BELOW IN ORDER TO USE THE EXAMPLES FROM RANDOM NERD TUTORIALS ***
    FULL INSTRUCTIONS AVAILABLE ON HOW CONFIGURE THE LIBRARY: https://RandomNerdTutorials.com/cyd-lvgl/ or https://RandomNerdTutorials.com/esp32-tft-lvgl/   */
#include <TFT_eSPI.h>

// Install the "XPT2046_Touchscreen" library by Paul Stoffregen to use the Touchscreen - https://github.com/PaulStoffregen/XPT2046_Touchscreen - Note: this library doesn't require further configuration
#include <XPT2046_Touchscreen.h>

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Specify the timezone you want to get the time for
const char* timezone = "Europe/Lisbon";

// Store date and time
String current_date;
String current_time;

// Install Adafruit Unified Sensor and Adafruit BME280 Library
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#define I2C_SDA 27
#define I2C_SCL 22
#define SEALEVELPRESSURE_HPA (1013.25)
TwoWire I2CBME = TwoWire(0);
Adafruit_BME280 bme;

// SET VARIABLE TO 0 FOR TEMPERATURE IN FAHRENHEIT DEGREES
#define TEMP_CELSIUS 1    

#define LDR_PIN 34

// Touchscreen pins
#define XPT2046_IRQ 36   // T_IRQ
#define XPT2046_MOSI 32  // T_DIN
#define XPT2046_MISO 39  // T_OUT
#define XPT2046_CLK 25   // T_CLK
#define XPT2046_CS 33    // T_CS

SPIClass touchscreenSPI = SPIClass(VSPI);
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);

#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320

// Touchscreen coordinates: (x, y) and pressure (z)
int x, y, z;

#define DRAW_BUF_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];

// If logging is enabled, it will inform the user about what is happening in the library
void log_print(lv_log_level_t level, const char * buf) {
  LV_UNUSED(level);
  Serial.println(buf);
  Serial.flush();
}

// Get the Touchscreen data
void touchscreen_read(lv_indev_t * indev, lv_indev_data_t * data) {
  // Checks if Touchscreen was touched, and prints X, Y and Pressure (Z)
  if(touchscreen.tirqTouched() && touchscreen.touched()) {
    // Get Touchscreen points
    TS_Point p = touchscreen.getPoint();

    // Advanced Touchscreen calibration, LEARN MORE » https://RandomNerdTutorials.com/touchscreen-calibration/
    float alpha_x, beta_x, alpha_y, beta_y, delta_x, delta_y;

    // REPLACE WITH YOUR OWN CALIBRATION VALUES » https://RandomNerdTutorials.com/touchscreen-calibration/
    alpha_x = -0.000;
    beta_x = 0.090;
    delta_x = -33.771;
    alpha_y = 0.066;
    beta_y = 0.000;
    delta_y = -14.632;

    x = alpha_y * p.x + beta_y * p.y + delta_y;
    // clamp x between 0 and SCREEN_WIDTH - 1
    x = max(0, x);
    x = min(SCREEN_WIDTH - 1, x);

    y = alpha_x * p.x + beta_x * p.y + delta_x;
    // clamp y between 0 and SCREEN_HEIGHT - 1
    y = max(0, y);
    y = min(SCREEN_HEIGHT - 1, y);

    // Basic Touchscreen calibration points with map function to the correct width and height
    //x = map(p.x, 200, 3700, 1, SCREEN_WIDTH);
    //y = map(p.y, 240, 3800, 1, SCREEN_HEIGHT);

    z = p.z;

    data->state = LV_INDEV_STATE_PRESSED;

    // Set the coordinates
    data->point.x = x;
    data->point.y = y;

    // Print Touchscreen info about X, Y and Pressure (Z) on the Serial Monitor
    Serial.print("X = ");
    Serial.print(x);
    Serial.print(" | Y = ");
    Serial.print(y);
    Serial.print(" | Pressure = ");
    Serial.print(z);
    Serial.println();
  }
  else {
    data->state = LV_INDEV_STATE_RELEASED;
  }
}

static void float_button_event_cb(lv_event_t * e) {
  update_table_values();
}

static lv_obj_t * table;
static void update_table_values(void) {
  // Get the latest temperature reading in Celsius or Fahrenheit
  #if TEMP_CELSIUS
    float bme_temp = bme.readTemperature();
    const char degree_symbol[] = "\u00B0C";
  #else
    float bme_temp = 1.8 * bme.readTemperature() + 32;
    const char degree_symbol[] = "\u00B0F";
  #endif
  
  String bme_temp_value = String(bme_temp) + degree_symbol;
  String bme_humi_value = String(bme.readHumidity()) + "%";
  String bme_press_value = String(bme.readPressure() / 100.0F) + " hPa";
  String ldr_value = String(analogRead(LDR_PIN));
  
  // Get the time from WorldTimeAPI
  get_date_and_time();
  //Serial.println("Current Date: " + current_date);
  //Serial.println("Current Time: " + current_time);

  // Fill the first column
  lv_table_set_cell_value(table, 0, 0, "Data");
  lv_table_set_cell_value(table, 1, 0, "Temperature");
  lv_table_set_cell_value(table, 2, 0, "Humidity");
  lv_table_set_cell_value(table, 3, 0, "Pressure");
  lv_table_set_cell_value(table, 4, 0, "Luminosity");
  lv_table_set_cell_value(table, 5, 0, "Date");
  lv_table_set_cell_value(table, 6, 0, "Time");
  lv_table_set_cell_value(table, 7, 0, "IP Address");

  // Fill the second column
  lv_table_set_cell_value(table, 0, 1, "Value");
  lv_table_set_cell_value(table, 1, 1, bme_temp_value.c_str());
  lv_table_set_cell_value(table, 2, 1, bme_humi_value.c_str());
  lv_table_set_cell_value(table, 3, 1, bme_press_value.c_str());
  lv_table_set_cell_value(table, 4, 1, ldr_value.c_str());
  lv_table_set_cell_value(table, 5, 1, current_date.c_str());
  lv_table_set_cell_value(table, 6, 1, current_time.c_str());
  lv_table_set_cell_value(table, 7, 1, WiFi.localIP().toString().c_str());
}

static void draw_event_cb(lv_event_t * e) {
  lv_draw_task_t * draw_task = lv_event_get_draw_task(e);
  lv_draw_dsc_base_t * base_dsc = (lv_draw_dsc_base_t*) draw_task->draw_dsc;
  // If the cells are drawn
  if(base_dsc->part == LV_PART_ITEMS) {
    uint32_t row = base_dsc->id1;
    uint32_t col = base_dsc->id2;

    // Make the texts in the first cell center aligned
    if(row == 0) {
      lv_draw_label_dsc_t * label_draw_dsc = lv_draw_task_get_label_dsc(draw_task);
      if(label_draw_dsc) {
        label_draw_dsc->align = LV_TEXT_ALIGN_CENTER;
      }
      lv_draw_fill_dsc_t * fill_draw_dsc = lv_draw_task_get_fill_dsc(draw_task);
      if(fill_draw_dsc) {
        fill_draw_dsc->color = lv_color_mix(lv_palette_main(LV_PALETTE_BLUE), fill_draw_dsc->color, LV_OPA_20);
        fill_draw_dsc->opa = LV_OPA_COVER;
      }
    }
    // In the first column align the texts to the right
    else if(col == 0) {
      lv_draw_label_dsc_t * label_draw_dsc = lv_draw_task_get_label_dsc(draw_task);
      if(label_draw_dsc) {
        label_draw_dsc->align = LV_TEXT_ALIGN_RIGHT;
      }
    }

    // Make every 2nd row gray color
    if((row != 0 && row % 2) == 0) {
      lv_draw_fill_dsc_t * fill_draw_dsc = lv_draw_task_get_fill_dsc(draw_task);
      if(fill_draw_dsc) {
        fill_draw_dsc->color = lv_color_mix(lv_palette_main(LV_PALETTE_GREY), fill_draw_dsc->color, LV_OPA_10);
        fill_draw_dsc->opa = LV_OPA_COVER;
      }
    }
  }
}

void lv_create_main_gui(void) {
  table = lv_table_create(lv_screen_active());

  // Inserts or updates all table values
  update_table_values();

  // Set a smaller height to the table. It will make it scrollable
  lv_obj_set_height(table, 200);
  lv_obj_center(table);

  // Add an event callback to apply some custom drawing
  lv_obj_add_event_cb(table, draw_event_cb, LV_EVENT_DRAW_TASK_ADDED, NULL);
  lv_obj_add_flag(table, LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS);

  // Create floating button
  lv_obj_t * float_button = lv_button_create(lv_screen_active());
  lv_obj_set_size(float_button, 50, 50);
  lv_obj_add_flag(float_button, LV_OBJ_FLAG_FLOATING);
  lv_obj_align(float_button, LV_ALIGN_BOTTOM_RIGHT, -15, -15);
  lv_obj_add_event_cb(float_button, float_button_event_cb, LV_EVENT_CLICKED, NULL);
  lv_obj_set_style_radius(float_button, LV_RADIUS_CIRCLE, 0);
  lv_obj_set_style_bg_image_src(float_button, LV_SYMBOL_REFRESH, 0);
  lv_obj_set_style_text_font(float_button, lv_theme_get_font_large(float_button), 0);
  lv_obj_set_style_bg_color(float_button, lv_palette_main(LV_PALETTE_GREEN), LV_PART_MAIN);
}

void get_date_and_time() {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;

    // Construct the API endpoint
    String url = String("http://worldtimeapi.org/api/timezone/") + timezone;

    http.begin(url);

    int httpCode = http.GET(); // Make the GET request

    if (httpCode > 0) {
      // Check for the response
      if (httpCode == HTTP_CODE_OK) {
        String payload = http.getString();
        //Serial.println("Time information:");
        //Serial.println(payload);
        
        // Parse the JSON to extract the time
        JsonDocument doc;
        DeserializationError error = deserializeJson(doc, payload);

        if (!error) {
          const char* datetime = doc["datetime"];
          //Serial.println("Datetime: " + String(datetime));
          
          // Split the datetime into date and time
          String datetimeStr = String(datetime);
          int splitIndex = datetimeStr.indexOf('T');
          current_date = datetimeStr.substring(0, splitIndex);
          current_time = datetimeStr.substring(splitIndex + 1, splitIndex + 9); // Extract time portion

        } else {
          Serial.print("deserializeJson() failed: ");
          Serial.println(error.c_str());
        }
      }
    } else {
      Serial.printf("GET request failed, error: %s\n", http.errorToString(httpCode).c_str());
    }

    http.end(); // Close connection
  } else {
    Serial.println("Not connected to Wi-Fi");
  }
}

void setup() {
  String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
  Serial.begin(115200);
  Serial.println(LVGL_Arduino);

  // Set analog read resolution
  analogReadResolution(12);

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.print("\nConnected to Wi-Fi network with IP Address: ");
  Serial.println(WiFi.localIP());

  I2CBME.begin(I2C_SDA, I2C_SCL, 100000);
  bool status;
  // Passing a &Wire2 to set custom I2C ports
  status = bme.begin(0x76, &I2CBME);
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
  
  // Start LVGL
  lv_init();
  // Register print function for debugging
  lv_log_register_print_cb(log_print);

  // Start the SPI for the touchscreen and init the touchscreen
  touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
  touchscreen.begin(touchscreenSPI);
  // Set the Touchscreen rotation in landscape mode
  // Note: in some displays, the touchscreen might be upside down, so you might need to set the rotation to 0: touchscreen.setRotation(0);
  touchscreen.setRotation(2);

  // Create a display object
  lv_display_t * disp;
  // Initialize the TFT display using the TFT_eSPI library
  disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
  lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);
  
  // Initialize an LVGL input device object (Touchscreen)
  lv_indev_t * indev = lv_indev_create();
  lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
  // Set the callback function to read Touchscreen input
  lv_indev_set_read_cb(indev, touchscreen_read);

  // Function to draw the GUI
  lv_create_main_gui();
}

void loop() {
  lv_task_handler();  // let the GUI do its work
  lv_tick_inc(5);     // tell LVGL how much time has passed
  delay(5);           // let this time pass
}

View raw code

How Does the Code Work?

Let’s see how to get the time from the internet and display all data on a table. Alternatively, you can skip to the Demonstration section.

Including Libraries

You need to include the lvgl, TFT_eSPI, and the XPT2046_Touchscreen libraries to communicate and display text on the screen.

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

You need to include the WiFi, HTTPClient, and the ArduinoJson libraries to make HTTP requests and handle JSON data.

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

To use the BME280, we need to include the following libraries.

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

Insert Your Credentials and Timezone

In the following lines, you must insert your network credentials so that the ESP32 can connect to your router.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Set your timezone in the timezone variable at the beginning of the code (list of all available timezones).

const char* timezone = "Europe/Lisbon";

Declaring Other Variables

We need to create an I2C instance on custom I2C pins and an Adafruit_BME280 object to refer to the sensor.

#define I2C_SDA 27
#define I2C_SCL 22
#define SEALEVELPRESSURE_HPA (1013.25)
TwoWire I2CBME = TwoWire(0);
Adafruit_BME280 bme;

Our code is prepared to display the temperature in Celsius or Fahrenheit degrees. To choose your desired Unit, you can set the value of the TEMP_CELSIUS variable. It is set to 1 by default to display the temperature in Celsius degrees.

#define TEMP_CELSIUS 1

If you want to display in Fahrenheit degrees instead, set it to 0.

#define TEMP_CELSIUS 0

Create a LVGL table object, so that we can access it inside all functions later on.

static lv_obj_t * table;

setup()

In the setup(), include the following lines for debugging. These will print the version of LVGL that you’re using. You must be using version 9.

String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.begin(115200);
Serial.println(LVGL_Arduino);

Connect to the Internet

To connect the ESP32 to the internet we use the following code.

// Connect to Wi-Fi
WiFi.begin(ssid, password);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}
Serial.print("\nConnected to Wi-Fi network with IP Address: ");
Serial.println(WiFi.localIP());

Init BME280

The following lines initialize the sensor on the I2C bus created:

I2CBME.begin(I2C_SDA, I2C_SCL, 100000);
bool status;
// Passing a &Wire2 to set custom I2C ports
status = bme.begin(0x76, &I2CBME);
if (!status) {
  Serial.println("Could not find a valid BME280 sensor, check wiring!");
  while (1);
}  

Initialize the LVGL Library

Initialize the LVGL Library by calling the lv_init() function in the setup().

// Start LVGL
lv_init();

Register Debugging Function

Register your log_print() function declared previously as a function associated with debugging LVGL.

// Register print function for debugging
lv_log_register_print_cb(log_print);

Preparing the Touchscreen

Initialize the touchscreen.

// Start the SPI for the touchscreen and init the touchscreen
touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
touchscreen.begin(touchscreenSPI);
// Set the Touchscreen rotation in landscape mode
// Note: in some displays, the touchscreen might be upside down
// so you might need to set the rotation to 0: touchscreen.setRotation(0);
touchscreen.setRotation(2);

Touchscreen Calibration

On the touchscreen_read() function, we adjust the touchscreen points to calibrate them with the display. You must insert the calibration values on the following lines. Check this tutorial to get the calibration values for your display.

// REPLACE WITH YOUR OWN CALIBRATION VALUES » https://RandomNerdTutorials.com/touchscreen-calibration/
alpha_x = -0.000;
beta_x = 0.090;
delta_x = -33.771;
alpha_y = 0.066;
beta_y = 0.000;
delta_y = -14.632;

Create a Display Object

To write to the display, you must create a display object first. You need to do this in all your LVGL sketches. The following lines will create an LVGL display object called disp with the screen width, screen height, and drawing buffer defined earlier.

// Create a display object
lv_display_t * disp;
// Initialize the TFT display using the TFT_eSPI library
disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);

Drawing the GUI

The LVGL library works asynchronously. You must call the function to draw on the display in the setup(). Then, everything works with events and callbacks. The code will always be listening for events in the background. When something happens, it will run the callback function associated with the event. You don’t need to check for any events in the loop().

Throughout most of our examples, the function that will draw to the screen will be called lv_create_main_gui(). Then, inside that function, we’ll add the instructions to build the interface.

// Function to draw the GUI
lv_create_main_gui();

get_date_and_time()

We create two global variables current_date and current_time at the beginning of the code to save the current date and time.

String current_date;
String current_time;

Create a function called get_date_and_time() that makes a request to the WorldTimeAPI and updates the current_date and current_time variables with the current date and time.

void get_date_and_time() {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;

    // Construct the API endpoint
    String url = String("http://worldtimeapi.org/api/timezone/") + timezone;
    http.begin(url);
    int httpCode = http.GET(); // Make the GET request

    if (httpCode > 0) {
      // Check for the response
      if (httpCode == HTTP_CODE_OK) {
        String payload = http.getString();
        //Serial.println("Time information:");
        //Serial.println(payload);
        // Parse the JSON to extract the time
        JsonDocument doc;
        DeserializationError error = deserializeJson(doc, payload);
        if (!error) {
          const char* datetime = doc["datetime"];          
          // Split the datetime into date and time
          String datetime_str = String(datetime);
          int splitIndex = datetime_str.indexOf('T');
          current_date = datetime_str.substring(0, splitIndex);
          current_time = datetime_str.substring(splitIndex + 1, splitIndex + 9); // Extract time portion
          hour = current_time.substring(0, 2).toInt();
          minute = current_time.substring(3, 5).toInt();
          second = current_time.substring(6, 8).toInt();
        } else {
          Serial.print("deserializeJson() failed: ");
          Serial.println(error.c_str());
        }
      }
    } else {
      Serial.printf("GET request failed, error: %s\n", http.errorToString(httpCode).c_str());
      sync_time_date = true;
    }
    http.end(); // Close connection
  } else {
    Serial.println("Not connected to Wi-Fi");
  }
}

We won’t cover how HTTP requests work. If you want to learn more, you can check the following tutorial: ESP32 HTTP GET with Arduino IDE.

Preparing the GUI

We create the table inside the lv_create_main_gui() function.

void lv_create_main_gui(void) {

We can create a table object using the lv_table_create() function as follows.

table = lv_table_create(lv_screen_active());

Next, we write a function called update_table_values() that fills the table with the readings. We’ll take a look at that function in just a moment.

update_table_values();

Set the table height and center it on the screen.

// Set a smaller height to the table. It will make it scrollable
lv_obj_set_height(table, 200);
lv_obj_center(table);                                                                          

Add a callback and a flag to the table. Those will allow us to draw the table with a specific style (different colors for the headers and alternating grey and white rows).

// Add an event callback to apply some custom drawing
lv_obj_add_event_cb(table, draw_event_cb, LV_EVENT_DRAW_TASK_ADDED, NULL);
lv_obj_add_flag(table, LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS);

Creating the Floating Button

Still, inside the lv_create_main_gui() function, we create the floating button. This is a regular button, but it’s circular and with a specific style.

// Create floating button
lv_obj_t * float_button = lv_button_create(lv_screen_active());
lv_obj_set_size(float_button, 50, 50);
lv_obj_add_flag(float_button, LV_OBJ_FLAG_FLOATING);
lv_obj_align(float_button, LV_ALIGN_BOTTOM_RIGHT, -15, -15);
lv_obj_add_event_cb(float_button, float_button_event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_set_style_radius(float_button, LV_RADIUS_CIRCLE, 0);
lv_obj_set_style_bg_image_src(float_button, LV_SYMBOL_REFRESH, 0);
lv_obj_set_style_text_font(float_button, lv_theme_get_font_large(float_button), 0);
lv_obj_set_style_bg_color(float_button, lv_palette_main(LV_PALETTE_GREEN), LV_PART_MAIN);

The button when clicked will trigger the float_button_event_cb function.

lv_obj_add_event_cb(float_button, float_button_event_cb, LV_EVENT_CLICKED, NULL);

To add the refresh symbol to the button, we use the lv_obj_set_style_bg_image_src() function. To add the refresh symbol we need to use LV_SYMBOL_REFRESH.

lv_obj_set_style_bg_image_src(float_button, LV_SYMBOL_REFRESH, 0);

Adding Data to the Table

In the update_table_values() is where we’ll add some data to the table.

static void update_table_values(void) {

This function is called when we create the GUI in the lv_create_main_gui() function, and when you click the button on the float_button_event_cb() function.

First, we start by getting readings from the BME280 sensor as well as luminosity from the LDR.

// Get the latest temperature reading in Celsius or Fahrenheit
#if TEMP_CELSIUS
  float bme_temp = bme.readTemperature();
  const char degree_symbol[] = "\u00B0C";
#else
  float bme_temp = 1.8 * bme.readTemperature() + 32;
  const char degree_symbol[] = "\u00B0F";
#endif
  
String bme_temp_value = String(bme_temp) + degree_symbol;
String bme_humi_value = String(bme.readHumidity()) + "%";
String bme_press_value = String(bme.readPressure() / 100.0F) + " hPa";
String ldr_value = String(analogRead(LDR_PIN));

The readings are saved on the bme_temp_value, bme_humi_value, bme_pres_value and ldr_value variables. We update the time variables by calling the get_date_and_time() function that we’ve seen previously.

// Get the time from WorldTimeAPI
get_date_and_time();

After getting all the data, we can finally start adding rows to the table. For that, we can use the lv_table_set_cell_value() function. This function accepts as argument the table you’re referring to, the row number, the column number, and the data you want to display.

The following lines fill the first column.

// Fill the first column
lv_table_set_cell_value(table, 0, 0, "Data");
lv_table_set_cell_value(table, 1, 0, "Temperature");
lv_table_set_cell_value(table, 2, 0, "Humidity");
lv_table_set_cell_value(table, 3, 0, "Pressure");
lv_table_set_cell_value(table, 4, 0, "Luminosity");
lv_table_set_cell_value(table, 5, 0, "Date");
lv_table_set_cell_value(table, 6, 0, "Time");
lv_table_set_cell_value(table, 7, 0, "IP Address");

Finally, we fill the second column with the values from the sensors, the date and time, and the ESP32 IP address.

// Fill the second column
lv_table_set_cell_value(table, 0, 1, "Value");
lv_table_set_cell_value(table, 1, 1, bme_temp_value.c_str());
lv_table_set_cell_value(table, 2, 1, bme_humi_value.c_str());
lv_table_set_cell_value(table, 3, 1, bme_press_value.c_str());
lv_table_set_cell_value(table, 4, 1, ldr_value.c_str());
lv_table_set_cell_value(table, 5, 1, current_date.c_str());
lv_table_set_cell_value(table, 6, 1, current_time.c_str());
lv_table_set_cell_value(table, 7, 1, WiFi.localIP().toString().c_str());

loop()

In the loop(), you can add any other tasks that you need your ESP32 to do like in any regular Arduino sketch.

void loop() {
  lv_task_handler();   // let the GUI do its work
  lv_tick_inc(5);         // tell LVGL how much time has passed
  delay(5);                 // let this time pass
}

Demonstration

Upload the code to your board. Go to Tools > Board and select ESP32 > ESP32 Dev Module. Then, select the right COM port in Tools > Port.

If you see an Error like this: “Sketch too big” during the uploading process, in Arduino IDE go to Tools > Partition scheme > choose anything that has more than 1.4MB APP, for example: “Huge APP (3MB No OTA/1MB SPIFFS“.

Select Huge App Partion Scheme Arduino IDE Tools Menu

Finally, click the upload button.

Arduino IDE 2 Upload Button

After a few seconds, it will display the current data on the screen as shown in the picture below.

ESP32 CYD BME280 Sensor Data Table Demonstration Testing

To update the data on the table, you just need to click on the refresh button.

ESP32 CYD BME280 Sensor Data Table Demonstration Refresh Update Values

You can cover the LDR with your finger to see the luminosity values changing.

Note: on many CYD boards, the LDR will not work as expected without modifying the internal circuit. So, if you’re only getting 0 that’s “normal”.

ESP32 CYD BME280 Sensor Data Table Demonstration LDR Sensor

The table is scrollable. You can scroll down to check the date and time of the last update. 

Wrapping Up

In this tutorial, you learned to display sensor data on a responsive table on the Cheap Yellow Display (CYD) board using the LVGL library.

We hope you found this tutorial useful. We’re preparing more guides about this board, so stay tuned. If you would like to learn more about creating graphical user interfaces using the LVGL library with the ESP32, check out our latest eBook:

Other guides you might like reading:

To learn more about the ESP32, make sure to take a look at our resources:



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

1 thought on “ESP32 CYD with LVGL: Display BME280 Sensor Data on a Table”

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.