In this guide, you’ll learn how to create a responsive table using LVGL with an ESP32 and a TFT display. 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.
Are you using a CYD board? Read this guide: ESP32 CYD with LVGL – Display BME280 Sensor Data on a Table
Project Overview
In this project, we’ll create a table with sensor readings from the BME280 sensor and the LDR on a 2.8-inch ILI9341 TFT LCD Touchscreen (240×320). 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
For this project, you need the following parts:
- TFT LCD Touchscreen Display – 2.8 inch ILI9341 240×320
- ESP32 board with enough pins to wire the display (for example an ESP32 DOIT V1 board)
- Breadboard
- Jumper wires
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 ILI9341 TFT LCD Touchscreen Display
The display we’re using in this guide is the 2.8. inch TFT LCD that also comes with a touchscreen. The display communicates via SPI communication protocol and uses the ILI9341 driver. The touchscreen also uses the SPI communication protocol.
The TFT LCD touchscreen also comes with an SD card interface if you need to load files for your specific project. This display is also available with different screen sizes, but we’ll use the one with 240 x 320 pixels).
If this is your first time using this display, make sure to follow our getting started guide:
4) Wire the Display to the ESP32
Wire the TFT LCD and touchscreen pins to the ESP32 GPIOs according to the next table (you must use these exact pins, otherwise the project will not work).
TFT LCD Touchscreen | ESP32 |
T_IRQ | GPIO 36 |
T_OUT | GPIO 39 |
T_DIN | GPIO 32 |
T_CS | GPIO 33 |
T_CLK | GPIO 25 |
SDO(MISO) | GPIO 12 |
LED | GPIO 21 |
SCK | GPIO 14 |
SDI(MOSI) | GPIO 13 |
D/C | GPIO 2 |
RESET | EN/RESET |
CS | GPIO 15 |
GND | GND |
VCC | 5V (or 3.3V)* |
* In the VCC pin, you can either use 5V or 3.3V depending if your J1 connection is open or closed (by default it’s usually open as you can see in the figure below).
VCC = 5V | J1=OPEN
VCC = 3.3V | J1=CLOSE
5) 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 with the 2.8 inch ILI9341 240×320 TFT LCD Touchscreen using Arduino IDE.
6) 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).
ESP32 TFT: 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;
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.
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 in a responsive LVGL table on a TFT with ESP32 board.
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:
- ESP32 with LVGL: Display DS18B20 Sensor Readings on TFT LCD (Text and Arc)
- ESP32 with TFT: Display Image using LVGL – 2.8 inch ILI9341 240×320 (Arduino)
- ESP32 TFT with LVGL: Weather Station (Description, Temperature, Humidity)
To learn more about the ESP32, make sure to take a look at our resources:
You are awesome with this tutorial. I have modified the code to simply show Temperature, Humidity, Pressure, Date and Time. I adjusted the table and removed the heading at the top. I increased the table size to 220, removed the refresh button and added a refresh every 1 minute. I just want to use it for a table top display, but I sure did learn a lot.
I would like the font to be a little bigger, how would I do that and where would it be in the code?
Hi, everythings works fine besides the date and time. On the display both boxes are empty and I get a ‘GET request failure’. The content of the httpCode integer is for both api addresses -5. I have no idea what to do. I installed all libs as required. Can you please support me ? Kind regards Dieter
Newbie here, followed guide installed everything as far I know correctly.. Thought it was odd that for the User and lvgl library d/l links the files didn’t have .h after the file.. I renamed them.. But when I verify the script I keep getting
C:\Users###\AppData\Local\Temp.arduinoIDE-unsaved2024107-28132-5fi99r.gn2y3\sketch_nov7a\sketch_nov7a.ino:15:10: fatal error: lvgl.h: No such file or directory
15 | #include <lvgl.h>
| ^~~~~~~~
compilation terminated.
exit status 1
Compilation error: lvgl.h: No such file or directory
Any info on where I went wrong would be greatly appreciated..
Hi.
Please double-check the installation instructions in this tutorial: https://randomnerdtutorials.com/lvgl-esp32-tft-touchscreen-display-ili9341-arduino/
Regards,
Sara