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.
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.
Your board should have come with a little JST connector to access those GPIOs.
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.
Recommended reading: ESP32 Cheap Yellow Display (CYD) Pinout (ESP32-2432S028R).
2) Install ESP32 Boards in Arduino IDE
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.
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).
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 that are currently not installed (usually the Adafruit Bus IO and the Adafruit Unified Sensor libraries).
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/ | https://RandomNerdTutorials.com/esp32-tft-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
}
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;
Related content: ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE)
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“.
Finally, click the upload button.
After a few seconds, it will display the current data on the screen as shown in the picture below.
To update the data on the table, you just need to click on the refresh button.
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”.
The table is scrollable. You can scroll down to check the date and time of the last update.
Troubleshooting
If you are experiencing issues opening the WorldTimeAPI website or if your HTTP request is failing to retrieve time, you can change the following line 252.
String url = String("http://worldtimeapi.org/api/timezone/") + timezone;
From their domain name (worldtimeapi.org) to their IP address as follows:
String url = String("http://213.188.196.246/api/timezone/") + timezone;
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:
- Getting Started with ESP32 Cheap Yellow Display Board – CYD (ESP32-2432S028R)
- ESP32 Touchscreen On/Off Button – Cheap Yellow Display (ESP32-2432S028R)
- ESP32 Cheap Yellow Display (CYD) Pinout (ESP32-2432S028R)
- LVGL with ESP32 Cheap Yellow Display Board (ESP32-2432S028R)
To learn more about the ESP32, make sure to take a look at our resources:
Hi, I would love to do this project. But I keep getting this error code. I can’t seem to get past it. Or find the correct zip file. Any help would be Great!!!
In file included from C:\Users\Aaron\AppData\Local\Temp.arduinoIDE-unsaved2024915-26032-1dvjlgf.gt9i\sketch_oct15a\sketch_oct15a.ino:15:
C:\Users\Aaron\AppData\Local\Temp.arduinoIDE-unsaved2024915-26032-1dvjlgf.gt9i\sketch_oct15a/lvgl.h:21:10: fatal error: src/lv_init.h: No such file or directory
21 | #include “src/lv_init.h”
| ^~~~~~~~~~~~~~~
compilation terminated.
exit status 1
Compilation error: src/lv_init.h: No such file or directory
Hi.
Please double-check the tutorial of the installation of the library.
Double-check your folders and files.
Regards,
Sara
Some of the WorldTimeAPI links do not work for me??
What links?
Can you be more specific?
Hello David!
Troubleshooting
If you are experiecing issues opening the WorldTimeAPI website or if your HTTP request is failing to retrieve time, you can change the following line 252.
String url = String(“http://worldtimeapi.org/api/timezone/”) + timezone;
From their domain name (worldtimeapi.org) to their IP address as follows:
String url = String(“http://213.188.196.246/api/timezone/”) + timezone;
I hope this helps!
If you are experiecing issues opening the WorldTimeAPI website or if your HTTP request is failing to retrieve time, you can change the following line 252.
String url = String(“http://worldtimeapi.org/api/timezone/”) + timezone;
From their domain name (worldtimeapi.org) to their IP address as follows:
String url = String(“http://213.188.196.246/api/timezone/”) + timezone;
Can not open this side. tried it with Firefox, Chrome and Avast browser. No answer. Program not complete working for date and time.
Hi, when verifying the sketch I get this error message below, any thoughts why?
Many thanks and love the work that you guys do, thanks,
Arduino: 1.8.19 (Windows 10), Board: “ESP32 Dev Module, Disabled, Disabled, Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS), 240MHz (WiFi/BT), QIO, 80MHz, 4MB (32Mb), 921600, Core 1, Core 1, None, Disabled, Disabled”
C:\Users\44784\OneDrive\Bryan’s Docs\Arduino\Sketches\ESP32_CYD_with_BME280_sensor\ESP32_CYD_with_BME280_sensor.ino: In function ‘void draw_event_cb(lv_event_t*)’:
ESP32_CYD_with_BME280_sensor:192:46: error: ‘lv_draw_task_get_label_dsc’ was not declared in this scope; did you mean ‘lv_draw_task_state_t’?
192 | lv_draw_label_dsc_t * label_draw_dsc = lv_draw_task_get_label_dsc(draw_task);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
| lv_draw_task_state_t
ESP32_CYD_with_BME280_sensor:196:44: error: ‘lv_draw_task_get_fill_dsc’ was not declared in this scope
196 | lv_draw_fill_dsc_t * fill_draw_dsc = lv_draw_task_get_fill_dsc(draw_task);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
ESP32_CYD_with_BME280_sensor:204:46: error: ‘lv_draw_task_get_label_dsc’ was not declared in this scope; did you mean ‘lv_draw_task_state_t’?
204 | lv_draw_label_dsc_t * label_draw_dsc = lv_draw_task_get_label_dsc(draw_task);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
| lv_draw_task_state_t
ESP32_CYD_with_BME280_sensor:212:44: error: ‘lv_draw_task_get_fill_dsc’ was not declared in this scope
212 | lv_draw_fill_dsc_t * fill_draw_dsc = lv_draw_task_get_fill_dsc(draw_task);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
C:\Users\44784\OneDrive\Bryan’s Docs\Arduino\Sketches\ESP32_CYD_with_BME280_sensor\ESP32_CYD_with_BME280_sensor.ino: In function ‘void get_date_and_time()’:
ESP32_CYD_with_BME280_sensor:249:5: error: ‘HTTPClient’ was not declared in this scope; did you mean ‘HttpClient’?
249 | HTTPClient http;
| ^~~~~~~~~~
| HttpClient
ESP32_CYD_with_BME280_sensor:254:5: error: ‘http’ was not declared in this scope
254 | http.begin(url);
| ^~~~
ESP32_CYD_with_BME280_sensor:260:23: error: ‘HTTP_CODE_OK’ was not declared in this scope
260 | if (httpCode == HTTP_CODE_OK) {
| ^~~~~~~~~~~~
Multiple libraries were found for “HttpClient.h”
Used: C:\Users\44784\OneDrive\Bryan’s Docs\Arduino\libraries\HttpClient
Not used: C:\Program Files (x86)\Arduino\libraries\Bridge
Multiple libraries were found for “lvgl.h”
Used: C:\Users\44784\OneDrive\Bryan’s Docs\Arduino\libraries\lvgl
Not used: C:\Users\44784\OneDrive\Bryan’s Docs\Arduino\libraries\arduino_436457
Multiple libraries were found for “WiFi.h”
Used: C:\Users\44784\AppData\Local\Arduino15\packages\esp32\hardware\esp32\3.0.5\libraries\WiFi
Not used: C:\Program Files (x86)\Arduino\libraries\WiFi
exit status 1
‘lv_draw_task_get_label_dsc’ was not declared in this scope; did you mean ‘lv_draw_task_state_t’?
This report would have more information with
“Show verbose output during compilation”
option enabled in File -> Preferences.
Hi.
Please double-check the installation of the libraries.
Regards,
Sara
HI, Ii have this running OK, but I’m only getting ‘0’ for luminosity.
I believe there may some board variations where the resistors need to be changed.
https://github.com/hexeguitar/ESP32_TFT_PIO?tab=readme-ov-file#ldr
Do you know the values of R15/R19 on your board?
Dave
hi Dave,
I’m also getting ‘0’ for luminosity. I’m using this board amazon.com/dp/B0D83RNNXV from DIYmalls
Manufacturer: DIYmalls
ASIN: B0D83RNNXV
Hello
I have a compilation error
Compilation error: invalid use of incomplete type ‘lv_draw_task_t’ {aka ‘struct lv_draw_task_t’}
Line 185
Hi.
Please double-check the installation of the libraries.
Regards,
Sara
I have the same problem. Could you solve it?
After several attempts, following the installation tutorial step by step, I checked the folders and directories, I received this error when compiling:
grpc: error while marshaling: string field contains invalid UTF-8
Compilation error: invalid use of incomplete type ‘lv_draw_task_t’ {aka ‘struct lv_draw_task_t’}
Hi.
I just compiled the code again and it is working as expected.
Please double-check that you have the right configuration files and they are on the right places.
Also, double-check the versions of the libraries you’re using and the board you’re selecting.
Here’s the installation guide: https://randomnerdtutorials.com/lvgl-cheap-yellow-display-esp32-2432s028r/
Regards,
Sara
After uninstalling the Arduino IDE, removing all libraries, and installing the Arduino IDE again and following the guide: https://randomnerdtutorials.com/lvgl-cheap-yellow-display-esp32-2432s028r/ I managed to compile and write the code, however nothing appears on the screen, only this appears on the serial monitor:
16:53:14.006 -> ets Jul 29 2019 12:21:46
16:53:14.006 ->
16:53:14.006 -> rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
16:53:14.006 -> configsip: 0, SPIWP:0xee
16:53:14.006 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
16:53:14.006 -> mode:DIO, clock div:1
16:53:14.006 -> load:0x3fff0030,len:4832
16:53:14.006 -> load:0x40078000,len:16460
16:53:14.006 -> load:0x40080400,len:4
16:53:14.006 -> load:0x40080404,len:3504
16:53:14.006 -> entry 0x400805cc
16:53:14.459 -> LVGL Library Version: 9.2.0
16:53:14.641 -> Connecting.
16:53:15.095 -> Connected to Wi-Fi network with IP Address: 192.168.90.32
16:53:15.133 -> Could not find a valid BME280 sensor, check wiring!
Your BME280 sensor is not connected properly to the board. Please double-check the GPIOs.
Regards,
Sara
The Sketch compiles OK but I then get the following error:
Sketch uses 1356513 bytes (103%) of program storage space. Maximum is 1310720 bytes.
Global variables use 64220 bytes (19%) of dynamic memory, leaving 263460 bytes for local variables. Maximum is 327680 bytes.
Sketch too big; see https://support.arduino.cc/hc/en-us/articles/360013825179 for tips on reducing it.
text section exceeds available space in board
Compilation error: text section exceeds available space in board
Hi
Please read the tutorial.
We have a red big rectangle showing how to upload the code to avoid that error.
Regards,
Sara
How can the time be updated continuously?
WorldTimeApi website is inactive. Is there any other websites to get date and time?
If you are experiencing issues opening the WorldTimeAPI website or if your HTTP request is failing to retrieve time, you can change the following line 252.
String url = String(“http://worldtimeapi.org/api/timezone/”) + timezone;
From their domain name (worldtimeapi.org) to their IP address as follows:
String url = String(“http://213.188.196.246/api/timezone/”) + timezone;
Regards,
Sara
Thank you, but there is still an error “GET request failed, error: connection lost”. Wi-Fi connection is correct.
Hi Sara,
Where can I purchase the JST lead here in the UK?
Thanks
Alan
Hi.
It should have come together when you buy a CYD display.
Regards,
Sara
It didn’t. Where is the best place to buy them separately?
Thanks
Have purchased a JST cable, unfortunately the connector is to big. Can you advice on exact connector I need?
Thanks
Alan
Hi.
I think if you search for this term, you should get the right one (not 100% sure):
“jst connector 4 pins 1.25mm”
I hope this helps.
Regards,
Sara
Thanks Sara
Hi,
I am getting “0” as luminosity. My boards come from Amazon and Banggood. On both boards I can see the LDRs. Can you provide links where I can find solutions, even if replacing electronics components is required.
Thanks for your support.
What I found was here:
https://github.com/hexeguitar/ESP32_TFT_PIO?tab=readme-ov-file#ldr
Dave
Thanks Dave,
I’ll give it a try soon.
Have a nice one,
Serge
The LDR circuit on almost all the CYD boards is incorrect, no matter where the board came from. Along with the components around the built in Audio amp circuit. I will attach the link to show you where the mods to correct these are found.
github.com/witnessmenow/ESP32-Cheap-Yellow-Display/tree/main/Mods
Thanks for sharing.
Regards,
Sara
I get the following error: Line 189
Bibliothek Adafruit BusIO in Version 1.16.2 im Ordner: C:\Users\Tom\Documents\Arduino\libraries\Adafruit_BusIO wird verwendet
exit status 1
Compilation error: invalid use of incomplete type ‘lv_draw_task_t’ {aka ‘struct lv_draw_task_t’}
Sorry too much verbosity. Can anyone help me? It doesn’t compile.
Hi.
Unfortunately, I cannot reproduce this issue.
Take a look at the suggestions in this discussion: https://github.com/lvgl/lvgl/issues/6812
Let me know if you were able to fix this.
Regards,
Sara
Dear Sara,
thank you for reply and the link you sent me. I am not familiar with lvgl. There are 3 suggestions to overcome the problem.
1. Use a different function, but I don’t know wich funktion to substitute.
2. enable LV_USE_PRIVATE_API in lv_conf.h; so I added
#define LV_USE_PRIVATE_API 1
to lv_conf.h and it compiled your scetch.
If there is a better solution, please let me know.
Thank you very much.
Tom