Arduino_LVGL_2.8: 2.8-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.8-inch ESP32 touchscreen. Combined with the UI exported from TFT_eSPI 2.5.43, LVGL 9.1.0, DHT20, and 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 values, and the ON and OFF buttons. When a button is clicked, 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 course, eight independent examples are also provided—LED, OLED, speaker, SD card, touch, BLE, Wi-Fi, and GPS—to make it easy to verify onboard or external functions step by step.
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 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 in sync with the serial status. - Be able to determine whether the experiment was successful based on the backlight, UI, touch coordinates, sensor values, and LED response.
3. What You Need to Prepare¶
- 1 × 2.8-inch ESP32 touchscreen development board, target chip being 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. - Libraries bundled with the project: LVGL 9.1.0, TFT_eSPI 2.5.43, Crowbits_DHT20, and the complete SquareLine UI files from the main course directory.
- Fully preserve the
ui*.c,ui*.h, and image array files in theLVGL_Arduino2.8directory; do not copy only the.inofile.
4. Software Operation Steps¶
This tutorial uses Arduino IDE 2.3.10 for demonstration. The IDE version is not strictly required, and other versions work equally well.
For the first operation, please 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.
- Select File > Open, navigate to the project folder
LVGL_Arduino2.8, and openLVGL_Arduino2.8.ino. The window title should display the project name.
- Check the file tabs above the editor area to confirm that
ui.c,ui.h,ui_Screen1.c, and the 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 Boards 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 Boards Manager.
- Open File > Preferences and note the "Sketchbook location". Close Arduino IDE, then confirm that the
librariesfolder exists in that directory.
How to add the library files: https://www.elecrow.com/wiki/Arduino_IDE_Library_Import_Guide.html
Note: The course dependencies must be placed in the libraries directory under the Arduino Sketchbook folder. After copying, restart Arduino IDE to prevent the IDE from 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 of the IDE or in the status bar.
- 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 board's BOOT button and release it once writing begins; if it still fails, re-check the port.
-
Click 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 displayed.
5. Hardware Operation Steps¶
- With the power off, connect the DHT20 module to the board's IIC interface, confirming that SDA maps to GPIO22 and SCL maps to GPIO21. Connect the LED module to the interface labeled 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 resets automatically, the screen backlight should turn on, and the temperature, humidity, and the ON and OFF buttons should appear in the interface.
- Use your 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 LVGL Partial Refresh to LCD¶
/*---------------------------------------------------------------
* LVGL display configuration
* The landscape UI uses a partial buffer containing one eighth of the frame.
*--------------------------------------------------------------*/
static const uint16_t screenWidth = 320;
static const uint16_t screenHeight = 240;
static lv_color_t buf1[screenWidth * screenHeight / 8];
/**
* @brief Transfer a rendered LVGL area to the LCD.
*
* LVGL calls this function whenever a rectangular area has been rendered
* into the partial buffer. The function must notify LVGL after the LCD
* transfer finishes, otherwise the buffer cannot be reused.
*/
void my_disp_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;
lcd.startWrite();
lcd.setAddrWindow(area->x1, area->y1, w, h);
lcd.pushColors((uint16_t *)px_map, w * h, true);
lcd.endWrite();
lv_display_flush_ready(disp);
}
Execution timing and effect: LVGL places only the rectangular area that needs updating into buf1, then calls my_disp_flush(). area determines the LCD write window, and px_map is the 16-bit color data of that area. Do not omit the final lv_display_flush_ready(), otherwise the interface will usually stall after the first refresh.
6.2 Handing Touch State to LVGL¶
// Stores the latest calibrated coordinates returned by TFT_eSPI.
uint16_t touchX, touchY;
/**
* @brief Convert one TFT_eSPI touch sample into an LVGL pointer event.
*
* LVGL polls this callback from lv_timer_handler(). The x-axis mirror
* matches the physical panel direction used by the 320 x 240 UI.
*/
void my_touchpad_read(lv_indev_t *indev, lv_indev_data_t *data)
{
bool touched = lcd.getTouch(&touchX, &touchY, 600);
if (!touched) {
data->state = LV_INDEV_STATE_RELEASED;
} else {
data->state = LV_INDEV_STATE_PRESSED;
data->point.x = screenWidth - touchX;
data->point.y = touchY;
}
}
Execution timing and effect: When lv_timer_handler() runs, LVGL repeatedly reads this callback. 600 is the touch pressure threshold accepted by TFT_eSPI; in the landscape direction the X coordinate uses the screenWidth - touchX mirror to match this project's display orientation.
Troubleshooting focus: When the UI displays but the buttons do not respond, first check whether lv_indev_set_read_cb() has registered this function, then check whether the serial port continuously outputs reasonable coordinates in the range 0–319 and 0–239.
6.3 Registering Display, Input Device, and UI¶
/*---------------------------------------------------------------
* Initialize LVGL and register display/input devices
* The UI can be created only after these callbacks are ready.
*--------------------------------------------------------------*/
lv_init();
lv_tick_set_cb(millis);
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();
Execution timing and effect: This code runs in setup(). The LVGL time base, display callback, and input callback must be ready first, and only then can ui_init() be used to create the page objects. If ui_init() is removed, the LCD and backlight may still work normally, but the background, labels, and buttons will not appear.
6.4 Temperature/Humidity Labels and LED Linkage¶
/**
* @brief Update sensor labels, apply the LED state, and service LVGL.
*
* Arduino calls this function repeatedly after setup(). DHT20 values are
* converted to integer text before being written into the SquareLine labels.
*/
void loop()
{
char DHT_buffer[6];
int a = (int)dht20.getTemperature();
int b = (int)dht20.getHumidity();
snprintf(DHT_buffer, sizeof(DHT_buffer), "%d", a);
lv_label_set_text(ui_Label1, DHT_buffer);
snprintf(DHT_buffer, sizeof(DHT_buffer), "%d", b);
lv_label_set_text(ui_Label2, DHT_buffer);
// The UI event code changes led; loop() owns the physical GPIO25 output.
if (led == 1) {
digitalWrite(25, HIGH);
}
if (led == 0) {
digitalWrite(25, LOW);
}
lv_timer_handler();
delay(10);
}
Execution timing and effect: The Arduino framework calls loop() continuously. The DHT20 readings are converted to integers and then written to ui_Label1 and ui_Label2; led is updated by the UI event, and the main loop is only responsible for converting the state into the GPIO25 output.
6.5 ON/OFF Button Events¶
// Shares the LED state with the Arduino application loop.
extern int led;
/**
* @brief Set the shared LED state when the ON button is clicked.
*/
void ui_event_Button1(lv_event_t * e)
{
lv_event_code_t event_code = lv_event_get_code(e);
if(event_code == LV_EVENT_CLICKED) {
led = 1;
}
}
/**
* @brief Clear the shared LED state when the OFF button is clicked.
*/
void ui_event_Button2(lv_event_t * e)
{
lv_event_code_t event_code = lv_event_get_code(e);
if(event_code == LV_EVENT_CLICKED) {
led = 0;
}
}
Execution timing and effect: This code is located in ui_Screen1.c exported by SquareLine Studio. The button events only modify the shared variable led and do not write to GPIO directly; this cleanly separates UI logic from hardware output, and makes it easier to later determine whether a problem comes from the touch event or the GPIO25 wiring.
7. UI Asset Creation and Integration¶
This section uses SquareLine Studio 1.6.1 to re-demonstrate how to create the interface. The course already provides the exported UI files; beginners can first read this section to understand the process, 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.
Note: When selecting 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 other 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, the resolution to width320and height240, and the color depth to16 bit, then click CREATE. After creation, the canvas should be landscape 320×240.
-
Note: A
16 bitcolor depth can represent 65,536 colors, using an RGB 5:6:5 pixel representation. Keep it consistent with the project's color configuration. -
After the project opens, select
Screen1in the left Screens panel and confirm that the center canvas is blank and 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 turn. After importing, you should see three thumbnails.
Note: Image assets only support PNG format; the pixel size of images should be smaller than the project screen size; a single image should not exceed 100 KB, and is best kept within 30 KB, to avoid affecting display smoothness.
- Select
Screen1, expand STYLE SETTINGS > STYLE (MAIN) > Background on the right side, and enable the background image setting.
6.In Bg Image, select background.The canvas should immediately display the course background image, and the background should fully cover the 320×240 page. 
- In the Widgets panel on the left, click Label to add
Label1toScreen1. This label will be updated by the program with the temperature value.
- Select
Label1, and in Transform adjust the X and Y position as well as the width and height so that it sits within the temperature display area of the background image. You can also drag it first and then fine-tune using the numeric values.
- In
Label1's STYLE (MAIN), change the text color to white and set an appropriate font size. The text on the canvas should be clearly visible and must not overflow the background frame.
-
Right-click
Label1and choose Duplicate to generateLabel2, then move it to the humidity display area. Keep the namesLabel1andLabel2, because the main program updates them by name. -
Select each of the two Labels in turn, and in Text enter a default number for easy preview. These are only placeholder values at design time; once the development board runs, they will be replaced by the DHT20 readings.
-
In Widgets, click Button to 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. The ON icon should appear on the canvas. -
Duplicate
Button1to getButton2, move it to the OFF area, and change the background image tooff.png. Keep the namesButton1andButton2so that they correspond to the exported event functions. -
In the STATE settings of both buttons, check the
DEFAULTandPRESSEDstates. The pressed state may use a noticeable color change so that you can tell in preview whether a button is pressed. In "Inspector" -> "Style Settings" -> "State", set the displayed background color to white, while the "Fixed State" displays red. Set the same parameters for the "OFF" button. -
Select
Button1and click ADD EVENT -
Select "CLICKED" as the trigger condition, and in "Action" choose the trigger event. The LED control functionality will be implemented later by modifying the generated program.
Note: Because the button ultimately controls the LED on/off, you can add any event here first so that the exported UI file generates 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 (the 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 path consisting of English characters only, 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 will be placed in the same folder, so the program will not need to modify file paths. If Flat export is not selected, files will be scattered across different folders and the compiler may fail to recognize them automatically, usually requiring manual path changes, so it is recommended to keep it checked.
-
Click Export > Export UI Files. After the export completes, the
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.8.inois located. -
Reopen the project. In the two button event functions in
ui_Screen1.c, keep the event type check, and make the ON event executeled = 1;while the OFF event executesled = 0;. Then return to the main program and click Verify to confirm there are no errors aboutledor UI objects not being found.Then, replace the function calls inside the Button1 and Button2 functions with the functions used to turn the LED on and off.
The ui_Screen1.c in the project ultimately creates two white numeric labels and two image buttons. If you modify the control names or resource names in SquareLine Studio, you must synchronously check the ui.h declarations, the ui_Screen1.c references, and the ui_Label1 and ui_Label2 in the main program.
8. Experimental Phenomena¶
After the firmware is uploaded and reset, the LCD is first cleared to black, and after about 300 ms the backlight turns on and loads the 320×240 UI. The background image should be displayed in full, with two white integers shown toward the middle-left of the screen and the ON and OFF icons shown on the right. When the DHT20 connection is normal, the two integers update respectively with the temperature and humidity readings.
When you press the screen with your 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 the serial port shows led_on; after clicking the OFF icon, led becomes 0, GPIO25 outputs a low level, and the serial port shows led_off. During continuous touch, the interface should remain responsive, and the device should not reset or freeze.
9. Code Download¶
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.
const uint8_t sineWave[256] = {
128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 161, 164, 167, 170, 173,
176, 179, 182, 185, 187, 190, 193, 195, 198, 201, 203, 206, 208, 210, 213, 215,
217, 219, 222, 224, 226, 228, 230, 231, 233, 235, 236, 238, 240, 241, 242, 244,
245, 246, 247, 248, 249, 250, 251, 251, 252, 253, 253, 254, 254, 254, 254, 254,
255, 254, 254, 254, 254, 254, 253, 253, 252, 251, 251, 250, 249, 248, 247, 246,
245, 244, 242, 241, 240, 238, 236, 235, 233, 231, 230, 228, 226, 224, 222, 219,
217, 215, 213, 210, 208, 206, 203, 201, 198, 195, 193, 190, 187, 185, 182, 179,
176, 173, 170, 167, 164, 161, 158, 155, 152, 149, 146, 143, 140, 137, 134, 131,
128, 124, 121, 118, 115, 112, 109, 106, 103, 100, 97, 94, 91, 88, 85, 82,
79, 76, 73, 70, 68, 65, 62, 60, 57, 54, 52, 49, 47, 45, 42, 40,
38, 36, 33, 31, 29, 27, 25, 24, 22, 20, 19, 17, 15, 14, 13, 11,
10, 9, 8, 7, 6, 5, 4, 4, 3, 2, 2, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 2, 2, 3, 4, 4, 5, 6, 7, 8, 9,
10, 11, 13, 14, 15, 17, 19, 20, 22, 24, 25, 27, 29, 31, 33, 36,
38, 40, 42, 45, 47, 49, 52, 54, 57, 60, 62, 65, 68, 70, 73, 76,
79, 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 124
};
/**
* @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.</arg_value:6124c78e><think:6124c78e>I notice the last code block in the source ends with the comment `// Returning to the midpoint avoids leaving a DC offset on the speaker.` without a closing brace. The original source actually cuts off there — it says "the speaker." and then `</user_query>`. So the code block is incomplete in the source. I should preserve it exactly as given, including the truncated ending without the closing `}`. Since I must not add information not in the source, I'll keep it as-is (the function body is open, no closing brace). Actually wait — should I add the closing brace? The rule says do not add info not in source. The source ends mid-function. I'll reproduce exactly as given, without a closing brace. That's faithful to the source.
Good. The translation is complete.</think:6124c78e>// 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);
}
Observation: The speaker plays for 1 second, then goes silent for 2 seconds, and this pattern repeats in a continuous loop.
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();
}
}
Observation: The serial monitor displays the SD card capacity, and then shows the file names and file sizes on the card in sequence.
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();
}
Observation: When you touch the screen with your finger, the serial monitor continuously displays the X and Y coordinates of the current 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.
*
* @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.
Note: It is best to use the GPS outdoors for better satellite signal reception; after the program runs, the returned data can be viewed in the serial monitor.
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;
}
}




























































