This example demonstrates the complete process of setting up a web server on the ESP32‑WROOM‑32E development board in WiFi‑STA (Station) mode. It displays WiFi connection status via the TFT LCD screen, shows SSID, IP address, and MAC address after successful connection, and starts a web server. LAN browsers can access the ESP32's IP address to control RGB LEDs through webpage buttons. The screen synchronously displays client connection/disconnection status and LED operation feedback.
Hardware prerequisite: LEDs are common‑anode – IO output LOW turns on the LED, HIGH turns it off; the screen uses an ILI9341 SPI display.
wifi_WebServers_test.ino fileconst char *ssid = "ZN"; // Change to your WiFi name
const char *password = "12345678"; // Change to your WiFi password
#define RED_PIN 22
#define GREEN_PIN 16
#define BLUE_PIN 17
#define LED_ON LOW
#define LED_OFF HIGH
http://192.168.1.100) and press Enteroff, the button shows OFF (grey background)on, the button shows ON (green background)The TFT screen (portrait 240×320) displays the following information from top to bottom:
| Area | Y Range | Description |
|---|---|---|
| Connection Status | y=55 | Shows WiFi connection progress and result |
| SSID | y=75 | Shows the connected WiFi SSID |
| IP Address | y=95 | Shows the ESP32's IP address |
| MAC Address | y=115 | Shows the ESP32's MAC address |
| Client Status | y=135 | Shows "Waiting for client connection", "Client connected.", or "Client Disconnected." |
| LED Status | y=160 | Red LED operation message (e.g., "RED LED ON") |
| LED Status | y=185 | Green LED operation message |
| LED Status | y=210 | Blue LED operation message |
⚠️ Important Notes:
LED_ON = LOW and LED_OFF = HIGH. If using a common‑cathode LED, swap these macro definitions.while loop). If WiFi is unavailable, the program will be stuck here.WiFiServer library and handles one client at a time, with a connection timeout of 2000ms.This example code is based on the ESP32‑WROOM‑32E. The operation logic applies to all ESP32 series development boards.
The program header includes library imports, hardware macro definitions, WiFi credentials, global variables, and object instantiation.
TFT_eSPI.h TFT screen driver library, WiFi.h WiFi library – used for screen display and WiFi web server.ssid and password – fill in your 2.4G router's WiFi name and password; the user must modify these.t_buf[100] character array, used with sprintf for formatted string output to the screen (IP, MAC); header string stores the HTTP request text sent by the browser; red_status, green_status, blue_status track the current LED on/off states; millisecond timing variables are used for client idle timeout detection.WiFiServer server(80) creates a web server object listening on port 80; TFT_eSPI my_lcd is the screen operation object.#include <TFT_eSPI.h>
#include <WiFi.h>
#define RED_PIN 22
#define GREEN_PIN 16
#define BLUE_PIN 17
#define LED_ON LOW
#define LED_OFF HIGH
const char *ssid = "ZN";
const char *password = "12345678";
char t_buf[100] = {0};
WiFiServer server(80);
WiFiClient client;
String header;
String red_status = "off";
String green_status = "off";
String blue_status = "off";
unsigned long currentTime = millis();
unsigned long previousTime = 0;
const long timeoutTime = 2000;
TFT_eSPI my_lcd = TFT_eSPI();
The setup() function runs only once after power‑on or reset. It initializes the serial port, LED pins, and TFT screen; performs WiFi configuration and connection; prints network information after successful connection; and starts the web server.
void setup()
{
Serial.begin(115200);
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
digitalWrite(RED_PIN, LED_OFF);
digitalWrite(GREEN_PIN, LED_OFF);
digitalWrite(BLUE_PIN, LED_OFF);
my_lcd.begin();
my_lcd.setRotation(0);
my_lcd.fillScreen(TFT_WHITE);
my_lcd.setTextColor(TFT_RED);
WiFi.mode(WIFI_STA);
WiFi.setSleep(false);
my_lcd.drawString("Start connecting to WiFi...", 10,55,2);
WiFi.begin(ssid, password);
while(WiFi.status()!= WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
my_lcd.fillRect(10, 55, my_lcd.width()-1, 25,TFT_WHITE);
my_lcd.setTextColor(TFT_GREEN);
my_lcd.drawString("Connection WIFI successful!!!", 10,55,2);
my_lcd.setTextColor(TFT_BLUE);
sprintf(t_buf, "SSID : %s", ssid);
my_lcd.drawString(t_buf, 10,75,2);
sprintf(t_buf, " IP : %s", WiFi.localIP().toString().c_str());
my_lcd.drawString(t_buf, 10,95,2);
sprintf(t_buf, " MAC : %s", WiFi.macAddress().c_str());
my_lcd.drawString(t_buf, 10,115,2);
server.begin();
my_lcd.drawString("Waiting for client connection", 10,135,2);
}
Serial.begin(115200);
pinMode(RED_PIN, OUTPUT);
digitalWrite(RED_PIN, LED_OFF);
Serial.begin(115200) sets the serial baud rate to 115200 for debugging via the serial monitor.pinMode() sets the LED pins as output; all LEDs are turned off on power‑up.my_lcd.begin();
my_lcd.setRotation(0);
my_lcd.fillScreen(TFT_WHITE);
my_lcd.setTextColor(TFT_RED);
my_lcd.drawString("Start connecting to WiFi...", 10,55,2);
my_lcd.begin() initializes the TFT screen hardware; setRotation(0) sets portrait mode; fillScreen fills with a white background.drawString() prints a prompt on the screen: connecting to WiFi.WiFi.mode(WIFI_STA);
WiFi.setSleep(false);
WiFi.begin(ssid, password);
while(WiFi.status()!= WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
WiFi.mode(WIFI_STA) sets the ESP32 to STA (Station) mode, connecting to a router as a client.WiFi.setSleep(false) disables WiFi sleep to improve communication stability.WiFi.begin() initiates the router connection; the while loop blocks the program, waiting until WiFi connection is successful.sprintf(t_buf, "SSID : %s", ssid);
my_lcd.drawString(t_buf, 10,75,2);
server.begin();
my_lcd.drawString("Waiting for client connection", 10,135,2);
sprintf() formats text, IP, and MAC information into the t_buf character array, which is then printed to the screen.server.begin() starts the HTTP web server on port 80, waiting for browser client connections.loop() is the Arduino main loop that runs infinitely after setup() finishes.
In simple terms: when a browser accesses the ESP32, it sends a block of text called an HTTP request. loop() receives every character sent by the browser; when it detects consecutive newlines (an empty line), it knows the request is complete and calls web_page() to process button actions and generate the webpage to send back to the browser. This example uses a single‑client model: only one browser can be served at a time.
void loop()
{
client = server.available();
if (client)
{
currentTime = millis();
previousTime = currentTime;
my_lcd.fillRect(10, 135, my_lcd.width()-1, 25,TFT_WHITE);
my_lcd.drawString("Client connected.", 10,135,2);
String currentLine = "";
while (client.connected() && currentTime - previousTime <= timeoutTime)
{
currentTime = millis();
if (client.available())
{
char c = client.read();
Serial.write(c);
header += c;
if (c == '\n')
{
if (currentLine.length() == 0)
{
web_page();
break;
}
else
{
currentLine = "";
}
}
else if (c != '\r')
{
currentLine += c;
}
}
}
header = "";
client.stop();
my_lcd.fillRect(10, 135, my_lcd.width()-1, 25,TFT_WHITE);
my_lcd.drawString("Client Disconnected.", 10,135,2);
}
}
client = server.available();
if (client)
{
my_lcd.drawString("Client connected.", 10,135,2);
String currentLine = "";
}
server.available() checks for incoming browser TCP connections; if a connection exists, it returns a valid client object.currentLine is a temporary string used to parse individual HTTP lines and identify newline characters.char c = client.read();
Serial.write(c);
header += c;
if (c == '\n')
{
if (currentLine.length() == 0)
{
web_page();
break;
}
}
client.read() reads one character from the browser each time.Serial.write(c) outputs the character to the serial monitor for debugging, allowing you to see the complete HTTP message.header, storing the complete request text from the browser.\n represents a newline; when two consecutive newlines (an empty line) are read, the browser's HTTP request is complete, and web_page() is called.\r (carriage return used in Windows) is discarded and not included in string concatenation to avoid parsing issues.while (client.connected() && currentTime - previousTime <= timeoutTime)
client.stop() closes the TCP network connection, and the screen displays a client disconnection message.When the browser's HTTP request is fully received, web_page() is called. It performs two tasks:
- Parse the access path in the header, control the LED levels, and refresh the TFT screen with operation prompts;
- Assemble the complete HTTP response headers and HTML webpage text, and send them back to the browser for page rendering.
void web_page(void)
{
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println("Connection: close");
client.println();
if (header.indexOf("GET /red/on") >= 0)
{
my_lcd.fillRect(10, 160, my_lcd.width()-1, 25,TFT_WHITE);
my_lcd.drawString("RED LED ON", 10,160,2);
red_status = "on";
digitalWrite(RED_PIN, LED_ON);
}
else if (header.indexOf("GET /red/off") >= 0)
{
my_lcd.fillRect(10, 160, my_lcd.width()-1, 25,TFT_WHITE);
my_lcd.drawString("RED LED OFF", 10,160,2);
red_status = "off";
digitalWrite(RED_PIN, LED_OFF);
}
else if (header.indexOf("GET /green/on") >= 0)
{
my_lcd.fillRect(10, 185, my_lcd.width()-1, 25,TFT_WHITE);
my_lcd.drawString("GREEN LED ON", 10,185,2);
green_status = "on";
digitalWrite(GREEN_PIN, LED_ON);
}
else if (header.indexOf("GET /green/off") >= 0)
{
my_lcd.fillRect(10, 185, my_lcd.width()-1, 25,TFT_WHITE);
my_lcd.drawString("GREEN LED OFF", 10,185,2);
green_status = "off";
digitalWrite(GREEN_PIN, LED_OFF);
}
else if (header.indexOf("GET /blue/on") >= 0)
{
my_lcd.fillRect(10, 210, my_lcd.width()-1, 25,TFT_WHITE);
my_lcd.drawString("BLUE LED ON", 10,210,2);
blue_status = "on";
digitalWrite(BLUE_PIN, LED_ON);
}
else if (header.indexOf("GET /blue/off") >= 0)
{
my_lcd.fillRect(10, 210, my_lcd.width()-1, 25,TFT_WHITE);
my_lcd.drawString("BLUE LED OFF", 10,210,2);
blue_status = "off";
digitalWrite(BLUE_PIN, LED_OFF);
}
client.println("<!DOCTYPE html><html>");
client.println("<head><meta name="viewport" content="width=device-width, initial-scale=1">");
client.println("<link rel="icon" href="data:,">");
client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
client.println(".button { background-color: #555555; border: none; color: white; padding: 16px 40px;");
client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
client.println(".button2 {background-color: #4CAF50;}</style></head>");
client.println("<body><h1>ESP32 Web Server LED </h1>");
client.println("<p>RED LED - State " + red_status + "</p>");
if (red_status=="off")
{
client.println("<p><a href="/red/on"><button class="button">OFF</button></a></p>");
}
else
{
client.println("<p><a href="/red/off"><button class="button button2">ON</button></a></p>");
}
client.println("<p>GREEN LED - State " + green_status + "</p>");
if (green_status=="off")
{
client.println("<p><a href="/green/on"><button class="button">OFF</button></a></p>");
}
else
{
client.println("<p><a href="/green/off"><button class="button button2">ON</button></a></p>");
}
client.println("<p> BLUE LED - State " + blue_status + "</p>");
if (blue_status=="off")
{
client.println("<p><a href="/blue/on"><button class="button">OFF</button></a></p>");
}
else
{
client.println("<p><a href="/blue/off"><button class="button button2">ON</button></a></p>");
}
client.println("</body></html>");
client.println();
}
HTTP/1.1 200 OK is the standard HTTP success status code; the blank line is required by the HTTP protocol to separate the response headers from the HTML body.header.indexOf("GET /red/on") searches for the access path in the HTTP request string; when matched, it executes the corresponding LED toggle logic, refreshes the screen text, and updates the global state variables.red_status, green_status, blue_status variables: when an LED is off, a grey OFF button is displayed (linking to the on path); when an LED is on, a green ON button is displayed (linking to the off path).Complete execution steps of this example program:
Key functions used:
WiFi.mode(): Set WiFi operation mode;WiFi.begin(): Initiate WiFi STA connection;WiFi.status(): Read WiFi connection status;WiFi.localIP(): Get ESP32 LAN IP address;WiFi.macAddress(): Get hardware MAC address;WiFiServer::available(): Detect browser TCP client connection;WiFiClient::read(): Read single‑byte data from the browser;header.indexOf(): Search for a target substring in the HTTP request string;sprintf(): Format strings for screen printing;my_lcd.drawString(): Draw text on the TFT screen;my_lcd.fillRect(): Draw a filled rectangle to erase old text and prevent overlapping.const char *ssid = "Your2.4GWiFiName";
const char *password = "YourWiFiPassword";
For open (no password) WiFi, set password to an empty string:
const char *password = "";
#define RED_PIN 22
#define GREEN_PIN 16
#define BLUE_PIN 17
#define LED_ON HIGH
#define LED_OFF LOW
const long timeoutTime = 2000;
Modify webpage buttons and styles
Edit all client.println() HTML/CSS output within the web_page() function to customise the webpage UI.
Enable webpage auto‑refresh
Add a meta tag in the HTML head section to enable auto‑refresh:
<meta http-equiv="refresh" content="5">
WiFiServer only supports single‑client. For concurrent access, use the third‑party library ESPAsyncWebServer.WiFi connection hangs
ESP32 only supports 2.4G WiFi, not 5G; passwords are case‑sensitive; check router signal; use a mobile 2.4G hotspot to eliminate router issues.
Browser cannot open the webpage after entering IP
The computer or phone must be on the same 2.4G LAN as the ESP32; disable AP isolation on the router; verify the IP address printed on the screen; turn off firewall and VPN.
LED not turning on/off or webpage buttons have no effect
Confirm whether the LED hardware is common‑anode or common‑cathode, and verify the LED_ON / LED_OFF macro definitions; check the LED hardware wiring.
Webpage buttons have no effect
Open the serial monitor to view the raw HTTP messages; URL paths are case‑sensitive; with the single‑client model, wait for the timeout to release the previous client connection.
Old text remains on the TFT screen causing text overlap
Before updating screen text, you must call fillRect() to draw a white rectangle to erase the old text on that line.
After visiting the webpage once, subsequent visits fail
This is due to the native WiFiServer single‑client limitation – wait for the timeoutTime to expire for automatic socket resource release.
TFT screen shows garbage or no display
Check SPI hardware connections (CS, DC, MOSI, SCK); verify that the TFT_eSPI library User_Setup.h is configured for the ILI9341 screen pins; ensure the BL backlight pin outputs a high level and the screen is powered with 5V.
No output on the serial monitor
Set the serial monitor baud rate to 115200, select the correct COM port, and press the reset button on the board.