LVGL_Arduino2.4: 2.4-inch ESP32 Touchscreen Environment Monitoring and LED Control¶
1. Course Introduction¶
This lesson uses Arduino IDE 2.3.10 and Arduino-ESP32 3.3.8 to drive a 2.4-inch ESP32 touchscreen. Combined with TFT_eSPI 2.5.43, LVGL 9.1.0, DHT20, and a UI exported from SquareLine Studio 1.6.1, it implements temperature and humidity display and touch-controlled LED. After the program starts, the LCD backlight turns on and displays the background, two sensor readings, and the ON and OFF buttons. When a button is tapped, the UI event modifies the led state, and the main loop then controls GPIO25.
Learners need to complete board setup, project compilation and upload, and then observe the LCD, touch, LED, and serial output. At the end of the lesson, eight independent examples are provided for LED, OLED, speaker, SD card, touch, BLE, Wi-Fi, and GPS, making it convenient to verify on-board or external functions one by one.
Reference materials:
2. Learning Objectives¶
- Be able to select
ESP32-WROOM-DA Modulein Arduino IDE 2.3.10 and complete compilation and upload of the main project. - Be able to explain the data flow among the LVGL display refresh callback, touch input callback, TFT_eSPI, and the SquareLine UI.
- Be able to read DHT20 temperature and humidity and update the integer results to
ui_Label1andui_Label2. - Be able to change
ledvia the ON/OFF buttons and verify that the GPIO25 output changes synchronously with the serial status. - Be able to judge whether the experiment is successful based on the backlight, UI, touch coordinates, sensor readings, and LED response.
3. Preparation¶
- 1 × 2.4-inch ESP32 touchscreen development board, target chip is ESP32.
- 1 × USB data cable that supports both power supply and data transfer.
- 1 × DHT20 temperature and humidity module, connected to GPIO22 (SDA) and GPIO21 (SCL).
- 1 × LED module, connected to GPIO25.
- Arduino-ESP32 3.3.8, with the board set to
ESP32-WROOM-DA Module. - Project built-in libraries: LVGL 9.1.0, TFT_eSPI 2.5.43, Crowbits_DHT20, and the complete SquareLine UI files from the main lesson directory.
- Fully preserve the
ui*.c,ui*.h, and image array files in theLVGL_Arduino2.4directory; do not copy only the.inofile.
4. Software Operation Steps¶
This tutorial demonstrates using Arduino IDE 2.3.10. The IDE version is not strictly required, and other versions also work.
For the first operation, complete the steps in order; do not skip directly to upload after a compilation failure.
- Launch Arduino IDE, open Help > About Arduino IDE, and confirm the version is
2.3.10. Close the About window before continuing.
- Enter the project folder
LVGL_Arduino2.4and openLVGL_Arduino2.4.ino. The window title should display the project name.
- Check the file tabs above the editor area and confirm that
ui.c,ui.h,ui_Screen1.c, and threeui_img_*_png.cfiles are visible in the same project. If these files are missing, do not continue compiling; instead, copy the complete project directory again.
- Open the Board Manager, search for
esp32. Find esp32 by Espressif Systems, select and install3.3.8; if the interface already shows3.3.8 installed, simply close the Board Manager.
- Open File > Preferences and note the "Sketchbook location". Close Arduino IDE, then confirm that the
librariesfolder exists under that directory.
How to add the library files: https://www.elecrow.com/wiki/Arduino_IDE_Library_Import_Guide.html
Note: The course dependency libraries need to be placed in the libraries directory under the Arduino Sketchbook location. After copying, restart Arduino IDE to avoid the IDE still using the old library index.
- Connect the development board using the USB data cable.
- Open Tools > Board > esp32 and select ESP32-WROOM-DA Module. After completion, the board name should appear at the top or in the status bar of the IDE.
- Open Tools > Port and select the newly appeared
COMport.
- Keep the other options in the Tools menu at their default values.
- Click the Upload button and wait for the output window to show the write progress and the upload-complete message. If it stays at
Connecting...for a long time, hold the BOOT button on the board and release it once you see writing begin; if it still fails, recheck the port.
-
Click the Serial Monitor in the upper right corner and set the baud rate to
115200. When touching the screen,Data xandData yshould appear; after clicking ON or OFF, the LED status text will also be shown.
5. Hardware Operation Steps¶
- With the power off, connect the DHT20 module to the I2C interface of the development board, confirming that SDA corresponds to GPIO22 and SCL corresponds to GPIO21. Connect the LED module to the interface marked GPIO_D so that the signal line connects to IO25. Do not connect the signal line to a power pin, and do not plug or unplug modules while powered on.
- Reconnect the USB cable that supports data transfer. After upload completes and the board auto-resets, the screen backlight should turn on, and the interface should show temperature, humidity, and the ON and OFF buttons.
- Use a finger to tap ON and OFF in turn, and observe whether the LED lights up and goes out accordingly, while checking whether the serial monitor outputs the corresponding status. Do not press the screen with sharp or conductive objects.
ON -> LED lights up
OFF -> LED goes out
6. Key Code Explanation¶
6.1 Prepare the display buffer and flush function first¶
/*---------------------------------------------------------------
* Display configuration
* The LVGL canvas matches the physical 320 x 240 landscape display.
* A partial buffer reduces RAM use while TFT_eSPI transfers each area.
*--------------------------------------------------------------*/
static const uint16_t screenWidth = 320;
static const uint16_t screenHeight = 240;
static lv_color_t buf1[screenWidth * screenHeight / 8];
// Controls the TFT display and its touch controller.
TFT_eSPI lcd = TFT_eSPI();
void my_disp_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
// LVGL's area coordinates are inclusive, so both dimensions need +1.
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
// Send the rendered rectangle to the LCD over the TFT_eSPI transaction.
lcd.startWrite();
lcd.setAddrWindow(area->x1, area->y1, w, h);
lcd.pushColors((uint16_t *)px_map, w * h, true);
lcd.endWrite();
// Tell LVGL that the buffer is free for the next partial render.
lv_display_flush_ready(disp);
}
This code is responsible for sending the content rendered by LVGL to the LCD. LVGL does not repaint the entire screen every time; instead, it places only the changed small block into buf1 and then hands it over to my_disp_flush().
area determines the rectangular region to be written to the screen this time, and px_map is the pixel data for that region. The final lv_display_flush_ready() is critical—it tells LVGL: "This block has been flushed, and you can continue using this buffer." If this line is omitted, the interface often updates only once and then freezes.
Here, screenWidth and screenHeight must match the actual screen orientation. This lesson uses a landscape 320×240 display.
6.2 Feed touch coordinates into LVGL¶
// Stores the latest touch position returned by TFT_eSPI.
uint16_t touchX, touchY;
void my_touchpad_read(lv_indev_t *indev, lv_indev_data_t *data)
{
// 600 is the TFT_eSPI pressure threshold used to reject noise and false touches.
bool touched = lcd.getTouch(&touchX, &touchY, 600);
if (!touched)
{
// Report release so LVGL does not keep the previous point pressed.
data->state = LV_INDEV_STATE_RELEASED;
}
else
{
// Forward the calibrated touch state and coordinates to LVGL.
data->state = LV_INDEV_STATE_PRESSED;
data->point.x = touchX;
data->point.y = touchY;
// Serial output makes touch calibration and orientation easy to verify.
Serial.print("Data x ");
Serial.println(touchX);
Serial.print("Data y ");
Serial.println(touchY);
}
}
LVGL calls this function when processing touch input. 600 is the touch pressure threshold—too high makes it hard to register a touch, while too low causes false touches. Only when a press is detected are the coordinates passed to LVGL.
This code also conveniently prints the coordinates to the serial port, making it easy to determine whether the touch is actually being read. If the screen lights up and the interface displays, but the buttons do not respond, first check whether this step outputs normal coordinates, and then check the input device registration that follows.
6.3 Complete the initialization sequence in setup()¶
// Receives the LED state selected by the generated UI event code.
int led;
// Provides temperature and humidity readings over the I2C bus.
Crowbits_DHT20 dht20;
/**
* @brief Initialize the board peripherals, LVGL display, touch input, and UI.
*
* Arduino calls this function once after startup or reset. The generated UI
* is created only after display and input callbacks have been registered.
*
* @param None.
* @return Nothing.
*/
void setup()
{
Serial.begin(115200);
/*---------------------------------------------------------------
* Initialize the LED and DHT20 sensor
* The UI later changes led, while the loop applies that value to GPIO25.
*--------------------------------------------------------------*/
pinMode(25, OUTPUT);
digitalWrite(25, LOW);
Wire.begin(22, 21);
dht20.begin();
lv_init();
lv_tick_set_cb(millis);
/*---------------------------------------------------------------
* Initialize the LCD and backlight
* Keep the backlight off until the controller has been cleared.
*--------------------------------------------------------------*/
lcd.begin();
lcd.fillScreen(TFT_BLACK);
delay(300);
pinMode(27, OUTPUT);
digitalWrite(27, HIGH);
lcd.setRotation(1);
/*---------------------------------------------------------------
* Register the LVGL display and touch devices
* LVGL uses these callbacks to move pixels to the LCD and obtain input.
*--------------------------------------------------------------*/
lv_display_t *disp = lv_display_create(screenWidth, screenHeight);
lv_display_set_flush_cb(disp, my_disp_flush);
lv_display_set_buffers(disp, buf1, NULL, sizeof(buf1), LV_DISPLAY_RENDER_MODE_PARTIAL);
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, my_touchpad_read);
ui_init();
}
setup() runs only once, so it is best suited for "startup preparation." The order also matters: prepare the LCD, backlight, LVGL, and touch first, and finally call ui_init(), so that the UI objects can display directly as soon as they are created.
If ui_init() is removed, the backlight and LCD may still light up, but the background, temperature/humidity, and buttons will not appear. If you forget to turn on the backlight, the screen will often look black, easily giving the false impression that the program is not running.
6.4 Update temperature/humidity and LED in the main loop¶
/**
* @brief Update sensor labels, apply the UI-selected LED state, and service LVGL.
*
* Arduino calls this function repeatedly after setup() finishes. The short
* delay leaves time for the system while keeping touch and UI updates fluid.
*
* @param None.
* @return Nothing.
*/
void loop()
{
char DHT_buffer[6];
int a = (int)dht20.getTemperature();
int b = (int)dht20.getHumidity();
snprintf(DHT_buffer, sizeof(DHT_buffer), "%d", a);
// LVGL labels receive text, so convert the temperature before updating it.
lv_label_set_text(ui_Label1, DHT_buffer);
snprintf(DHT_buffer, sizeof(DHT_buffer), "%d", b);
// Reuse the buffer for humidity after the temperature label has been updated.
lv_label_set_text(ui_Label2, DHT_buffer);
/*---------------------------------------------------------------
* Apply the UI-selected LED state
* The generated event callbacks set led to either 1 or 0.
*--------------------------------------------------------------*/
if (led == 1) {
digitalWrite(25, HIGH);
Serial.print("led_on");
}
if (led == 0) {
digitalWrite(25, LOW);
Serial.print("led_off");
}
// Service LVGL timers, redraws, and pending touch events.
lv_timer_handler();
// Yield briefly so the UI remains responsive while the loop repeats.
delay(10);
}
DHT20 returns numeric values, while lv_label_set_text() expects text, so here the temperature and humidity are first converted to strings and then written to the UI labels.
The value of led is not changed here; instead, the UI button events described later change it to 1 or 0, and the main loop controls GPIO25 based on it each iteration. lv_timer_handler() must also keep running; otherwise, buttons, redraws, and touch input will gradually stop.
If the temperature and humidity display abnormally, first check the I2C connection; if the button does not respond when pressed, then check whether the UI event correctly changed led.
7. UI Resource Creation and Integration¶
This section uses SquareLine Studio 1.6.1 to demonstrate the UI creation process again. The course already provides the exported UI files. Beginners can first read this section to understand the workflow, and then directly use the files in the project to complete compilation. When creating a new project, you must select LVGL 9.1.0;
How to download SquareLine Studio: https://www.elecrow.com/wiki/Create_LVGL_UI_with_SquareLine_Studio.html
- Launch SquareLine Studio 1.6.1, click Create, and select the Arduino with TFT_eSPI template for the Arduino platform. This template is responsible for generating the UI file framework suitable for Arduino and TFT_eSPI projects.
Notes: When you select the Arduino framework, SquareLine Studio only displays the Arduino with TFT_eSPI option. It generates template code suitable for TFT_eSPI, but SquareLine Studio also supports other graphics libraries; when switching to different hardware, you need to modify the display code according to the actual library.
- Enter the project name, set the LVGL version to
9.1.0, set the resolution to width320and height240, and set the color depth to16 bit, then click CREATE. After creation, the canvas should be a landscape 320×240.
Note: A 16 bit color depth can represent 65,536 colors, using an RGB 5:6:5 pixel representation. Keep it consistent with the color configuration of your project.
- After the project opens, select
Screen1in the left Screens, and confirm that the central canvas is blank and that the right-side Inspector shows the Screen properties. All subsequent widgets are added to this page.
- In the Assets area, click ADD FILE TO ASSETS, and import
background.png,on.png, andoff.pngfromLVGL-Assets-320x240/in sequence. After importing, you should see three thumbnails.
Notes: Image assets only support the PNG format; the pixel dimensions of an image should be smaller than the project screen size; a single image should not exceed 100 KB, and it is best to keep it within 30 KB to avoid affecting display smoothness.
- Select
Screen1, then expand STYLE SETTINGS > STYLE (MAIN) > Background on the right side and enable the background image setting.
- Select
backgroundin Bg Image. The canvas should immediately display the course background image, and the background should completely cover the 320×240 page.
- Click Label in the left Widgets to add
Label1toScreen1. This label will be updated by the program to show the temperature value.
- Select
Label1, and adjust the X, Y position and width/height in Transform so that it sits within the temperature display area of the background image. You can also drag it first and then fine-tune it with numeric values.
- In the STYLE (MAIN) of
Label1, change the text color to white and set an appropriate font size. The text in the canvas should be clearly visible and should not extend beyond the background frame.
-
Right-click
Label1and select Duplicate to generateLabel2, then move it to the humidity display area. Keep the namesLabel1andLabel2, because the main program updates them by name. -
Select the two Labels respectively, and enter default numbers in Text for easy preview. These are only placeholder values at design time; after the development board runs, they will be replaced by the DHT20 readings.
-
Click Button in Widgets, add
Button1, and drag it to the ON area on the right side of the interface. -
In Transform, set the size of
Button1and adjust its position so that the button aligns with the ON area in the background. -
Expand
Button1's STYLE (MAIN) > Background, and selecton.pngas the background image. An ON icon should appear in the canvas. -
Duplicate
Button1to getButton2, move it to the OFF area, and change the background image tooff.png. Keep the namesButton1andButton2to make it easier to match them with the exported event functions. -
In the STATE settings of the two buttons, check the
DEFAULTandPRESSEDstates. The pressed state can use an obvious color change, and you should be able to tell whether the button is pressed in preview. In "Inspector" -> "Style Settings" -> "State", set the displayed background color to white, and when in the "Fixed State" it displays red. Set the same parameters for the "OFF" button. -
Select
Button1and click ADD EVENT. -
Select "CLICKED" as the trigger condition, and select the trigger event in "Action". The LED control function will be implemented by modifying it later in the generated program.
Notes: Since the button ultimately controls the on/off of the LED, you can add any event here first to let the exported UI file generate the button event code framework; the LED control code will be modified in a later step.
-
Complete this event. Here, I choose to switch screens, that is, switch to the Screen1 screen.
-
Add the event to Button2 in the same way (state is "OFF").
-
Click Run.
-
Open File > Project Settings, then make the relevant settings for the exported files.
-
Set the export directory to an easy-to-find pure English path, create a new output folder, fill in and confirm that the LVGL Include Path is
lvgl.h, then click APPLY CHANGES after confirming.Tip: After selecting Flat export, all output files are placed in the same folder, and the program does not need to modify the file paths. If Flat export is not selected, files will be scattered into different folders, and the compiler may not be able to recognize them automatically — usually requiring manual path modification — so it is recommended to keep it checked.
-
Click Export > Export UI Files. After the export is complete,
ui.c,ui.h,ui_Screen1.c, event files, helper files, and image array files should appear in the target directory. -
Close the Arduino IDE, and copy all exported
.cand.hfiles to the directory whereLVGL_Arduino2.4.inois located. -
Reopen the project, keep the event type judgment in the two button event functions in
ui_Screen1.c, and let the ON event executeled = 1;and the OFF event executeled = 0;. Then return to the main program and click Verify to confirm that there are no errors aboutledor UI objects not being found.Then, replace the function calls in the Button1 and Button2 functions with the functions used to turn the LED on and off.
The project's ui_Screen1.c finally creates two white numeric labels and two image buttons. If you modify widget names or resource names in SquareLine Studio, you must simultaneously check the ui.h declaration, the ui_Screen1.c references, and the ui_Label1 and ui_Label2 in the main program.
8. Experimental Phenomenon¶
After the firmware is uploaded and reset, the LCD is first cleared to black, and about 300 ms later the backlight turns on and loads the 320×240 UI. The background image should be displayed completely, with two white integers shown in the middle-left of the screen and the ON and OFF icons shown on the right side. When the DHT20 is connected normally, the two integers are updated respectively according to the temperature and humidity readings.
When you press the screen with a finger, the serial monitor outputs the coordinates as Data x and Data y. After clicking the ON icon, led becomes 1, GPIO25 outputs a high level, and led_on appears in the serial monitor; after clicking the OFF icon, led becomes 0, GPIO25 outputs a low level, and led_off appears in the serial monitor. During continuous touch, the interface should remain responsive, and the device should not reset or freeze.
9. Code Download¶
- Arduino project: Arduino_2.4
Example Demo of ESP32 HMI Function¶
Example1: LED blinking.¶
Connect the LED to the GPIO_D(IO25) port, and upload the following code to the board. The LED will blink.
/*---------------------------------------------------------------
* LED hardware configuration
* GPIO25 drives the on-board LED used by this example.
*--------------------------------------------------------------*/
#define D_PIN 25
/**
* @brief Prepare the serial port and LED output.
*
* Arduino calls this function once after the board starts or resets.
*
* @param None.
* @return Nothing.
*/
void setup() {
Serial.begin(115200);
pinMode(D_PIN, OUTPUT);
}
/**
* @brief Blink the LED with equal on and off intervals.
*
* Arduino calls this function repeatedly after setup() finishes. The
* two 500 ms delays produce a complete one-second blink cycle.
*
* @param None.
* @return Nothing.
*/
void loop() {
digitalWrite(D_PIN, HIGH);
delay(500);
digitalWrite(D_PIN, LOW);
delay(500);
}
Example2: Control an external OLED screen through I2C.¶
Connect the OLED screen to the IIC port.
#include <U8g2lib.h>
#include <Wire.h>
/*---------------------------------------------------------------
* OLED hardware configuration
* The software I2C bus uses the board's GPIO22 and GPIO21 pins.
*--------------------------------------------------------------*/
#define I2C_SDA 22
#define I2C_SCL 21
// Controls the 128 x 64 SSD1306 OLED without a dedicated reset pin.
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /*clock=*/I2C_SCL, /*data=*/I2C_SDA, /*reset=*/U8X8_PIN_NONE);
/**
* @brief Initialize the OLED and scroll the title across the screen.
*
* Arduino calls this function once after startup or reset. The page loop
* is required by U8g2 so the complete frame is rendered before the next
* horizontal text position is drawn.
*
* @param None.
* @return Nothing.
*/
void setup() {
Serial.begin(115200);
/*---------------------------------------------------------------
* Configure text rendering
* Enable UTF-8 printing and select the font and drawing direction.
*--------------------------------------------------------------*/
u8g2.begin();
u8g2.enableUTF8Print();
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.setFontDirection(0);
/*---------------------------------------------------------------
* Animate the title
* Move the text from the right edge to the left in 20-pixel steps.
*--------------------------------------------------------------*/
for (int i = 128; i > -78; i -= 20)
{
u8g2.firstPage();
do {
u8g2.drawStr(i, 25, "ELECROW");
delay(2);
} while (u8g2.nextPage());
}
}
/**
* @brief Keep the sketch idle after the one-time OLED animation.
*
* Arduino calls this function repeatedly after setup() finishes.
*
* @param None.
* @return Nothing.
*/
void loop() {
}
Example3: Speaker¶
#include <driver/dac.h>
/*---------------------------------------------------------------
* Speaker hardware configuration
* GPIO26 is connected to ESP32 DAC channel 2.
*--------------------------------------------------------------*/
#define SPEAKER_PIN 26
// One cycle of an 8-bit sine wave, centered at the DAC midpoint.
/** * @brief Enable the DAC output used by the speaker.
*
* Arduino calls this function once after the board starts or resets.
*
* @param None.
* @return Nothing.
*/
void setup() {
Serial.begin(115200);
dac_output_enable(DAC_CHANNEL_2);
}
/**
* @brief Generate a timed sine wave through the ESP32 DAC.
*
* Each waveform cycle uses all 256 table entries. The function is called
* from loop() whenever the example needs to play a tone.
*
* @param frequency Tone frequency in hertz.
* @param durationMs Playback duration in milliseconds.
* @return Nothing.
*/
void playSineWave(int frequency, int durationMs) {
int sampleRate = frequency * 256;
int totalSamples = (sampleRate * durationMs) / 1000;
int delayUs = 1000000 / sampleRate;
for (int i = 0; i < totalSamples; i++) {
dac_output_voltage(DAC_CHANNEL_2, sineWave[i % 256]);
delayMicroseconds(delayUs);
}
// Returning to the midpoint avoids leaving a DC offset on the speaker.
dac_output_voltage(DAC_CHANNEL_2, 128);
}
/**
* @brief Play a one-second tone followed by two seconds of silence.
*
* Arduino calls this function repeatedly after setup() finishes.
*
* @param None.
* @return Nothing.
*/
void loop() {
playSineWave(1000, 1000);
delay(2000);
}
Behavior: The speaker sounds for 1 second, stops for 2 seconds, and repeats this cycle continuously.
Example4: Initialize SD Card slot¶
Please insert an SD card formatted as FAT16 or FAT32. Other file system formats may fail to be read.
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <FS.h>
/*---------------------------------------------------------------
* SD card SPI configuration
* These pins connect the ESP32 SPI bus to the card socket.
*--------------------------------------------------------------*/
#define SD_MOSI 23
#define SD_MISO 19
#define SD_SCK 18
#define SD_CS 5
/**
* @brief Start the serial monitor, SPI bus, and SD card.
*
* Arduino calls this function once after startup or reset. The return code
* from SD_init() selects the status message shown to the learner.
*
* @param None.
* @return Nothing.
*/
void setup() {
Serial.begin(9600);
SPI.begin(SD_SCK, SD_MISO, SD_MOSI);
delay(100);
if (SD_init() == 1)
{
Serial.println("Card Mount Failed");
}
else
Serial.println("initialize SD Card successfully");
}
/**
* @brief Leave the sketch idle after the one-time card inspection.
*
* Arduino calls this function repeatedly after setup() finishes.
*
* @param None.
* @return Nothing.
*/
void loop() {
}
/**
* @brief Mount the SD card and print its capacity and file list.
*
* setup() calls this function once after the SPI bus is ready. A nonzero
* result tells setup() that mounting or card detection failed.
*
* @param None.
* @return 0 when the card is ready, or 1 when initialization fails.
*/
int SD_init()
{
if (!SD.begin(SD_CS))
{
Serial.println("Card Mount Failed");
return 1;
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE)
{
Serial.println("No TF card attached");
return 1;
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("TF Card Size: %lluMB\n", cardSize);
listDir(SD, "/", 2);
// These optional operations are retained as reference exercises.
// listDir(SD, "/", 0);
// createDir(SD, "/mydir");
// listDir(SD, "/", 0);
// removeDir(SD, "/mydir");
// listDir(SD, "/", 2);
// writeFile(SD, "/hello.txt", "Hello ");
// appendFile(SD, "/hello.txt", "World!\n");
// readFile(SD, "/hello.txt");
// Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
// Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
// Serial.println("SD init over.");
return 0;
}
/**
* @brief Print files from a directory and optionally visit subdirectories.
*
* SD_init() calls this function after mounting the card. Recursive calls
* reduce levels so traversal cannot continue beyond the requested depth.
*
* @param fs Mounted file system to inspect.
* @param dirname Directory path to open.
* @param levels Maximum number of subdirectory levels to visit.
* @return Nothing.
*/
void listDir(fs::FS & fs, const char *dirname, uint8_t levels)
{
// Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root)
{
//Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory())
{
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
// i = 0;
while (file)
{
if (file.isDirectory())
{
// Serial.print(" DIR : ");
// Serial.println(file.name());
if (levels)
{
listDir(fs, file.name(), levels - 1);
}
}
else
{
Serial.print("FILE: ");
Serial.print(file.name());
// lcd.setCursor(0, 2 * i);
// lcd.printf("FILE:%s", file.name());
Serial.print("SIZE: ");
Serial.println(file.size());
// lcd.setCursor(180, 2 * i);
// lcd.printf("SIZE:%d", file.size());
// i += 16;
}
file = root.openNextFile();
}
}
Behavior: The serial monitor displays the SD card capacity and sequentially shows the file names and file sizes on the card.
Example5: Initialize the touch¶
Please check if you have install the library TFT_eSPI Upload the following code to ESP display, and open the serial monitor to check the touch information.
#include <TFT_eSPI.h>
// Controls both the LCD panel and its resistive touch interface.
TFT_eSPI lcd = TFT_eSPI();
// Stores the most recent calibrated touch position.
uint16_t touchX, touchY;
// Converts raw touch measurements into coordinates for this display.
uint16_t calData[5] = { 557, 3263, 369, 3493, 3 };
/**
* @brief Initialize serial output, the LCD, and touch calibration data.
*
* Arduino calls this function once after startup or reset. The saved
* calibration values are applied so touches can be reported immediately.
*
* @param None.
* @return Nothing.
*/
void setup() {
Serial.begin(9600);
lcd.begin();
lcd.setRotation(1);
/*---------------------------------------------------------------
* Select the touch calibration method
* Run touch_calibrate() to measure a new panel, or apply the saved
* values for normal use. Only the saved-value path is active here.
*--------------------------------------------------------------*/
// touch_calibrate();
lcd.setTouch(calData);
}
/**
* @brief Report valid touch coordinates to the serial monitor.
*
* Arduino calls this function repeatedly after setup() finishes. A touch
* is accepted only when TFT_eSPI passes the configured pressure threshold.
*
* @param None.
* @return Nothing.
*/
void loop() {
bool touched = lcd.getTouch(&touchX, &touchY, 600);
if (touched)
{
Serial.print("Data x ");
Serial.println(touchX);
Serial.print("Data y ");
Serial.println(touchY);
}
}
/**
* @brief Guide the user through touch calibration and print the result.
*
* Call this function from setup() when the panel requires new calibration
* values. The printed array can then replace the saved calData values.
*
* @param None.
* @return Nothing.
*/
void touch_calibrate()
{
uint16_t calData[5];
uint8_t calDataOK = 0;
Serial.println("Touch-screen calibration");
Serial.println("Please touch the corners as directed");
// lv_timer_handler();
lcd.calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15);
Serial.println("calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15)");
Serial.println(); Serial.println();
Serial.println("//Use this calibration code in setup():");
Serial.print("uint16_t calData[5] = ");
Serial.print("{ ");
for (uint8_t i = 0; i < 5; i++)
{
Serial.print(calData[i]);
if (i < 4) Serial.print(", ");
}
Serial.println(" };");
Serial.print(" tft.setTouch(calData);");
Serial.println(); Serial.println();
}
Behavior: When the screen is touched with a finger, the serial monitor continuously displays the current X and Y coordinates of the touch position.
Example6: BLE¶
Upload the following code to the board, and use the phone to search the Bluetooth device.
#include "BLEDevice.h"
#include "BLEServer.h"
#include "BLEUtils.h"
#include "BLE2902.h"
#include <BLECharacteristic.h>
/*---------------------------------------------------------------
* BLE service configuration
* The advertised service exposes one readable, writable, and notifiable
* characteristic under fixed UUIDs.
*--------------------------------------------------------------*/
#define bleServerName "ESP32SPI-BLE"
#define SERVICE_UUID "6479571c-2e6d-4b34-abe9-c35116712345"
#define CHARACTERISTIC_UUID "826f072d-f87c-4ae6-a416-6ffdcaa02d73"
// References the advertising controller created during setup().
BLEAdvertising* pAdvertising = NULL;
// References the BLE server that accepts central-device connections.
BLEServer* pServer = NULL;
// References the custom service published by this example.
BLEService *pService = NULL;
// References the data endpoint contained in the custom service.
BLECharacteristic* pCharacteristic = NULL;
// Records whether a BLE central is currently connected.
bool connected_state = false;
/**
* @brief Track BLE connection state changes reported by the server.
*/
class MyServerCallbacks: public BLEServerCallbacks
{
/**
* @brief Record a successful central-device connection.
*
* The BLE stack invokes this callback whenever a central connects.
*
* @param pServer Server that accepted the connection.
* @return Nothing.
*/
void onConnect(BLEServer *pServer)
{
connected_state = true;
}
/**
* @brief Record that the central device has disconnected.
*
* The BLE stack invokes this callback when the active link closes.</arg_value:6124c78e><think:6124c78e></think:6124c78e>The BLE stack invokes this callback when the active link closes.
*
* @param pServer Server whose connection closed.
* @return Nothing.
*/
void onDisconnect(BLEServer *pServer)
{
connected_state = false;
}
};
/**
* @brief Create and advertise the BLE service and characteristic.
*
* Arduino calls this function once after startup or reset. Advertising
* makes the board discoverable under bleServerName.
*
* @param None.
* @return Nothing.
*/
void setup() {
Serial.begin(115200);
/*---------------------------------------------------------------
* Build the GATT server
* The characteristic supports the three operations demonstrated by
* common BLE scanner applications.
*--------------------------------------------------------------*/
BLEDevice::init(bleServerName);
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY);
pCharacteristic->setValue("ELECROW");
/*---------------------------------------------------------------
* Publish the service
* Advertising includes the service UUID so a scanner can identify the
* example before opening a connection.
*--------------------------------------------------------------*/
pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->start();
pService->start();
// pAdvertising->stop();
// pService->stop();
}
/**
* @brief Keep the sketch idle while the BLE stack handles events.
*
* Arduino calls this function repeatedly after setup() finishes.
*
* @param None.
* @return Nothing.
*/
void loop() {
}
Example7: Initialize the WIFI¶
Upload the following code to ESP display. Note: Please change the Wi-Fi SSID and password to your own.
#include <WiFi.h>
/*---------------------------------------------------------------
* Wi-Fi credentials
* Replace these example values with the local 2.4 GHz network details.
*--------------------------------------------------------------*/
const char *ssid = "elecrow888";
const char *password = "elecrow2014";
/**
* @brief Connect the board to Wi-Fi and print its assigned address.
*
* Arduino calls this function once after startup or reset. Execution waits
* here until the access point accepts the connection.
*
* @param None.
* @return Nothing.
*/
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
WiFi.setAutoReconnect(true);
// Do not continue until network-dependent code can use a valid link.
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.println("connecting");
}
Serial.println("WiFi is connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// WiFi.disconnect();
}
/**
* @brief Keep the sketch idle after the one-time connection test.
*
* Arduino calls this function repeatedly after setup() finishes. Automatic
* reconnection remains active in the Wi-Fi stack.
*
* @param None.
* @return Nothing.
*/
void loop() {
}
Example8: Connect Crowtail-GPS module via UART to Get Location¶
Connect the GPS module to the UART interface. After running the program, you can view the returned GPS data in the serial monitor. It is best to be outdoors for better GPS signal reception.
GPS module purchase link: https://www.elecrow.com/crowtailgps-p-1515.html
/*---------------------------------------------------------------
* GPS UART configuration
* The module uses ESP32 hardware UART2, leaving the USB serial port free
* for monitoring and sending commands from the computer.
*--------------------------------------------------------------*/
#define GPS_RX 16
#define GPS_TX 17
// Provides the dedicated hardware serial channel connected to the GPS.
HardwareSerial gpsSerial(2);
// Temporarily stores a block of bytes received from the GPS module.
unsigned char buffer[256];
// Tracks the number of valid bytes currently stored in buffer.
int count = 0;
/**
* @brief Start the GPS and USB serial ports with matching settings.
*
* Arduino calls this function once after startup or reset. UART2 uses
* 9600 baud, eight data bits, no parity, and one stop bit.
*
* @param None.
* @return Nothing.
*/
void setup()
{
gpsSerial.begin(9600, SERIAL_8N1, GPS_RX, GPS_TX);
Serial.begin(9600);
}
/**
* @brief Bridge data between the GPS module and the serial monitor.
*
* Arduino calls this function repeatedly after setup() finishes. GPS data
* is forwarded in blocks, while bytes entered on the computer are sent
* directly to the module.
*
* @param None.
* @return Nothing.
*/
void loop()
{
if (gpsSerial.available())
{
while (gpsSerial.available())
{
buffer[count++] = gpsSerial.read();
// Stop before the next byte could exceed the fixed buffer capacity.
if (count == 256) break;
}
Serial.write(buffer, count);
clearBufferArray();
count = 0;
}
if (Serial.available())
gpsSerial.write(Serial.read());
}
/**
* @brief Clear the portion of the receive buffer that was used.
*
* loop() calls this function after forwarding a GPS data block. Only the
* valid range is cleared because the remaining bytes were not modified.
*
* @param None.
* @return Nothing.
*/
void clearBufferArray()
{
for (int i = 0; i < count; i++)
{
buffer[i] = 0;
}
}































































