LITTLE WEB SERVER CODE
Arduino Sketch + LittleFS Web Page
DISPLAY PREVIEW
LIVE STATS
ARDUINO SKETCH
/* ESP32-S3 Web Server + Display ESP32-S3 SuperMini + GC9A01 Round Display */ #include <WiFi.h> #include <WebServer.h> #include <LittleFS.h> #include <Arduino_GFX_Library.h> #include <time.h> // WiFi credentials const char* ssid = "YOUR_WIFI_SSID"; const char* password = "YOUR_WIFI_PASSWORD"; // NTP Time settings const char* ntpServer = "pool.ntp.org"; const long gmtOffset_sec = -21600; // CST = UTC-6 const int daylightOffset_sec = 3600; // DST offset // Display pins #define TFT_SCK 12 #define TFT_MOSI 11 #define TFT_CS 8 #define TFT_DC 9 #define TFT_RST 10 #define TFT_BL 7 // Colors (RGB565) #define BLACK 0x0000 #define WHITE 0xFFFF #define CYAN 0x07FF #define MAGENTA 0xF81F #define YELLOW 0xFFE0 #define GREEN 0x07E0 #define RED 0xF800 #define DARKGRAY 0x4208 #define DARKCYAN 0x0345 #define DARKMAGENTA 0x4008 #define ORANGE 0xFC00 // Display objects Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, TFT_SCK, TFT_MOSI, -1); Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RST, 0, true); WebServer server(80); unsigned long lastDisplayUpdate = 0; unsigned long lastTickerUpdate = 0; unsigned long requestCount = 0; int animFrame = 0; String getContentType(String filename) { if (filename.endsWith(".html")) return "text/html"; else if (filename.endsWith(".css")) return "text/css"; else if (filename.endsWith(".js")) return "application/javascript"; else if (filename.endsWith(".png")) return "image/png"; else if (filename.endsWith(".jpg")) return "image/jpeg"; else if (filename.endsWith(".ico")) return "image/x-icon"; return "text/plain"; } bool handleFileRead(String path) { Serial.println("Request: " + path); requestCount++; if (path.endsWith("/")) path += "index.html"; String contentType = getContentType(path); if (LittleFS.exists(path)) { File file = LittleFS.open(path, "r"); server.streamFile(file, contentType); file.close(); return true; } return false; } // Draw corner brackets for HUD effect void drawCornerBrackets(int x, int y, int w, int h, uint16_t color) { int len = 8; gfx->drawFastHLine(x, y, len, color); gfx->drawFastVLine(x, y, len, color); gfx->drawFastHLine(x + w - len, y, len, color); gfx->drawFastVLine(x + w - 1, y, len, color); gfx->drawFastHLine(x, y + h - 1, len, color); gfx->drawFastVLine(x, y + h - len, len, color); gfx->drawFastHLine(x + w - len, y + h - 1, len, color); gfx->drawFastVLine(x + w - 1, y + h - len, len, color); } // Draw tick marks around circle edge void drawTickMarks(int cx, int cy, int r, uint16_t color) { for (int i = 0; i < 12; i++) { float angle = i * 30 * PI / 180; int x1 = cx + cos(angle) * (r - 5); int y1 = cy + sin(angle) * (r - 5); int x2 = cx + cos(angle) * r; int y2 = cy + sin(angle) * r; gfx->drawLine(x1, y1, x2, y2, color); } } // Draw signal strength bars void drawSignalBars(int x, int y, int rssi) { int bars = 0; if (rssi > -50) bars = 4; else if (rssi > -60) bars = 3; else if (rssi > -70) bars = 2; else if (rssi > -80) bars = 1; for (int i = 0; i < 4; i++) { int barHeight = 4 + (i * 3); uint16_t color = (i < bars) ? CYAN : DARKGRAY; gfx->fillRect(x + (i * 6), y + (12 - barHeight), 4, barHeight, color); } } // 7-Segment digit patterns const uint8_t segPatterns[] = { 0b1111110, 0b0110000, 0b1101101, 0b1111001, 0b0110011, 0b1011011, 0b1011111, 0b1110000, 0b1111111, 0b1111011 }; // Draw a single 7-segment digit void draw7Seg(int x, int y, int digit, int w, int h, int t, uint16_t color, uint16_t dimColor) { if (digit < 0 || digit > 9) return; uint8_t seg = segPatterns[digit]; int hh = h / 2; gfx->fillRect(x + t, y, w - t*2, t, (seg & 0b1000000) ? color : dimColor); gfx->fillRect(x + w - t, y + t, t, hh - t, (seg & 0b0100000) ? color : dimColor); gfx->fillRect(x + w - t, y + hh, t, hh - t, (seg & 0b0010000) ? color : dimColor); gfx->fillRect(x + t, y + h - t, w - t*2, t, (seg & 0b0001000) ? color : dimColor); gfx->fillRect(x, y + hh, t, hh - t, (seg & 0b0000100) ? color : dimColor); gfx->fillRect(x, y + t, t, hh - t, (seg & 0b0000010) ? color : dimColor); gfx->fillRect(x + t, y + hh - t/2, w - t*2, t, (seg & 0b0000001) ? color : dimColor); } // Draw colon for clock void drawColon(int x, int y, int h, int t, uint16_t color) { int dotSize = t + 1; gfx->fillRect(x, y + h/3 - dotSize/2, dotSize, dotSize, color); gfx->fillRect(x, y + 2*h/3 - dotSize/2, dotSize, dotSize, color); } // Draw 7-segment time display void draw7SegTime(int x, int y, int hours, int mins, uint16_t color, uint16_t dimColor) { int digitW = 16, digitH = 24, thick = 3, gap = 3, colonW = 6; draw7Seg(x, y, hours / 10, digitW, digitH, thick, color, dimColor); draw7Seg(x + digitW + gap, y, hours % 10, digitW, digitH, thick, color, dimColor); int colonX = x + 2*(digitW + gap) + gap; drawColon(colonX, y, digitH, thick, color); int minX = colonX + colonW + gap; draw7Seg(minX, y, mins / 10, digitW, digitH, thick, color, dimColor); draw7Seg(minX + digitW + gap, y, mins % 10, digitW, digitH, thick, color, dimColor); } // Draw scrolling ticker void drawTicker(int frame) { const char* msg = " /// SYSTEM NOMINAL /// WEB SERVER ONLINE /// ESP32-S3 ACTIVE /// "; int msgLen = strlen(msg); int charW = 6; int totalW = msgLen * charW; int scrollX = 240 - (millis() / 20) % (totalW + 240); gfx->fillRect(35, 183, 170, 12, BLACK); gfx->drawFastHLine(35, 183, 170, DARKGRAY); gfx->drawFastHLine(35, 194, 170, DARKGRAY); gfx->setTextColor(CYAN); gfx->setTextSize(1); for (int i = 0; i < msgLen; i++) { int charX = scrollX + (i * charW); if (charX >= 35 && charX < 200) { gfx->setCursor(charX, 185); gfx->print(msg[i]); } } } // Draw pulsing chevrons void drawChevrons(int frame) { int y = 198; uint16_t color = (frame % 2 == 0) ? MAGENTA : DARKMAGENTA; uint16_t color2 = (frame % 2 == 0) ? DARKMAGENTA : MAGENTA; gfx->setTextSize(1); gfx->setTextColor(color); gfx->setCursor(60, y); gfx->print(">>"); gfx->setTextColor(color2); gfx->setCursor(72, y); gfx->print(">>"); gfx->setTextColor(DARKGRAY); gfx->setCursor(93, y); gfx->print("WEBSRV 1.0"); gfx->setTextColor(color2); gfx->setCursor(156, y); gfx->print("<<"); gfx->setTextColor(color); gfx->setCursor(168, y); gfx->print("<<"); } // Draw seconds tick around edge void drawSecondsTick(int secs) { int cx = 120, cy = 120, r = 112; float angle = (secs * 6 - 90) * PI / 180; int x1 = cx + cos(angle) * (r - 4); int y1 = cy + sin(angle) * (r - 4); int x2 = cx + cos(angle) * (r + 2); int y2 = cy + sin(angle) * (r + 2); gfx->drawLine(x1, y1, x2, y2, WHITE); int prevSec = (secs == 0) ? 59 : secs - 1; float prevAngle = (prevSec * 6 - 90) * PI / 180; int px1 = cx + cos(prevAngle) * (r - 4); int py1 = cy + sin(prevAngle) * (r - 4); int px2 = cx + cos(prevAngle) * (r + 2); int py2 = cy + sin(prevAngle) * (r + 2); gfx->drawLine(px1, py1, px2, py2, DARKGRAY); } // Draw boot screen void drawBootScreen() { gfx->fillScreen(BLACK); gfx->drawCircle(120, 120, 119, DARKCYAN); gfx->drawCircle(120, 120, 118, CYAN); gfx->drawCircle(120, 120, 117, DARKCYAN); gfx->drawCircle(120, 120, 110, DARKGRAY); drawTickMarks(120, 120, 115, MAGENTA); gfx->drawCircle(120, 120, 85, DARKMAGENTA); gfx->drawCircle(120, 120, 60, DARKCYAN); gfx->setTextColor(DARKCYAN); gfx->setTextSize(2); gfx->setCursor(59, 49); gfx->print("WEBSERVER"); gfx->setTextColor(CYAN); gfx->setCursor(60, 50); gfx->print("WEBSERVER"); gfx->drawFastHLine(40, 72, 160, DARKGRAY); gfx->drawFastHLine(50, 74, 140, MAGENTA); gfx->drawFastHLine(40, 76, 160, DARKGRAY); gfx->setTextColor(MAGENTA); gfx->setTextSize(1); gfx->setCursor(48, 85); gfx->print("[ ESP32-S3 NODE ]"); drawCornerBrackets(35, 105, 170, 40, CYAN); gfx->setTextColor(YELLOW); gfx->setCursor(52, 120); gfx->print(">> INITIALIZING..."); } // Draw main status screen void drawStatusScreen() { gfx->fillScreen(BLACK); gfx->drawCircle(120, 120, 119, DARKCYAN); gfx->drawCircle(120, 120, 118, CYAN); gfx->drawCircle(120, 120, 117, DARKCYAN); drawTickMarks(120, 120, 115, MAGENTA); gfx->drawCircle(120, 120, 108, DARKGRAY); gfx->fillRect(30, 22, 180, 22, DARKMAGENTA); gfx->drawRect(30, 22, 180, 22, MAGENTA); gfx->setTextColor(WHITE); gfx->setTextSize(2); gfx->setCursor(60, 26); gfx->print("WEBSERVER"); gfx->drawFastHLine(35, 48, 170, CYAN); gfx->fillCircle(60, 58, 4, GREEN); gfx->drawCircle(60, 58, 6, GREEN); gfx->setTextColor(GREEN); gfx->setTextSize(1); gfx->setCursor(70, 54); gfx->print("ONLINE"); gfx->setTextColor(DARKGRAY); gfx->setCursor(130, 54); gfx->print("SIGNAL"); drawCornerBrackets(28, 68, 184, 110, CYAN); gfx->drawFastHLine(35, 69, 170, DARKGRAY); gfx->drawFastHLine(35, 177, 170, DARKGRAY); } // Update display with live data void updateDisplay() { animFrame++; gfx->fillRect(30, 70, 180, 106, BLACK); gfx->drawFastHLine(35, 69, 170, DARKGRAY); gfx->drawFastHLine(35, 177, 170, DARKGRAY); int rssi = WiFi.RSSI(); drawSignalBars(165, 52, rssi); int scanY = 70 + (animFrame % 20) * 5; if (scanY < 175) gfx->drawFastHLine(32, scanY, 176, DARKCYAN); gfx->setTextColor(DARKGRAY); gfx->setTextSize(1); gfx->setCursor(90, 76); gfx->print("NET.ADDR"); String ip = WiFi.localIP().toString(); int ipWidth = ip.length() * 6; gfx->setTextColor(CYAN); gfx->setCursor((240 - ipWidth) / 2, 88); gfx->print(ip); gfx->drawFastHLine(40, 100, 160, DARKGRAY); unsigned long uptime = millis() / 1000; int hours = uptime / 3600; int mins = (uptime % 3600) / 60; int secs = uptime % 60; gfx->setTextColor(DARKGRAY); gfx->setCursor(69, 106); gfx->print("RUNTIME"); gfx->setCursor(135, 106); gfx->print("REQ"); char uptimeStr[12]; sprintf(uptimeStr, "%02d:%02d:%02d", hours, mins, secs); gfx->setTextColor(YELLOW); gfx->setCursor(63, 118); gfx->print(uptimeStr); gfx->setTextColor(GREEN); gfx->setCursor(135, 118); gfx->print(requestCount); gfx->drawFastHLine(40, 130, 160, DARKGRAY); struct tm timeinfo; if (getLocalTime(&timeinfo)) { int hour12 = timeinfo.tm_hour % 12; if (hour12 == 0) hour12 = 12; drawSecondsTick(timeinfo.tm_sec); draw7SegTime(78, 138, hour12, timeinfo.tm_min, CYAN, BLACK); gfx->setTextSize(1); gfx->setTextColor(MAGENTA); gfx->setCursor(168, 148); gfx->print(timeinfo.tm_hour >= 12 ? "PM" : "AM"); char dateStr[12]; strftime(dateStr, sizeof(dateStr), "%m/%d/%Y", &timeinfo); gfx->setTextColor(YELLOW); gfx->setCursor(90, 168); gfx->print(dateStr); } else { gfx->setTextColor(DARKGRAY); gfx->setCursor(60, 150); gfx->print("SYNCING TIME..."); } drawTicker(animFrame); drawChevrons(animFrame); } void setup() { Serial.begin(115200); delay(1000); Serial.println("\n=== ESP32-S3 Web Server ===\n"); pinMode(TFT_BL, OUTPUT); digitalWrite(TFT_BL, HIGH); gfx->begin(); gfx->setRotation(0); drawBootScreen(); if (!LittleFS.begin(true)) { Serial.println("LittleFS Mount Failed!"); return; } Serial.printf("\nConnecting to %s", ssid); WiFi.begin(ssid, password); int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 30) { delay(500); Serial.print("."); attempts++; } if (WiFi.status() == WL_CONNECTED) { Serial.println(" Connected!"); Serial.print("IP Address: "); Serial.println(WiFi.localIP()); Serial.print("MAC Address: "); Serial.println(WiFi.macAddress()); configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); drawStatusScreen(); updateDisplay(); } else { Serial.println(" Failed! Starting AP mode..."); WiFi.softAP("ESP32-WebServer", "password123"); Serial.print("AP IP: "); Serial.println(WiFi.softAPIP()); } server.onNotFound([]() { if (!handleFileRead(server.uri())) { server.send(404, "text/plain", "404: Not Found"); } }); server.begin(); Serial.println("Web server started!"); } void loop() { server.handleClient(); if (millis() - lastDisplayUpdate > 1000) { lastDisplayUpdate = millis(); if (WiFi.status() == WL_CONNECTED) updateDisplay(); } if (millis() - lastTickerUpdate > 50) { lastTickerUpdate = millis(); if (WiFi.status() == WL_CONNECTED) drawTicker(0); } }
WEB PAGE
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ESP32 Web Server</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Courier New', monospace; background: #0a0a0f; color: #e0e0e0; min-height: 100vh; display: flex; align-items: center; justify-content: center; } /* Scanlines */ body::before { content: ''; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: repeating-linear-gradient( 0deg, transparent, transparent 2px, rgba(0,0,0,0.1) 2px, rgba(0,0,0,0.1) 4px ); pointer-events: none; z-index: 1000; } /* Grid background */ body::after { content: ''; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-image: linear-gradient(rgba(0,255,255,0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(0,255,255,0.03) 1px, transparent 1px); background-size: 50px 50px; z-index: -1; } .container { background: rgba(10,10,20,0.9); border: 1px solid #0ff; padding: 30px; max-width: 400px; box-shadow: 0 0 30px rgba(0,255,255,0.2); } h1 { color: #0ff; text-align: center; font-size: 1.5rem; letter-spacing: 4px; text-shadow: 0 0 20px #0ff; margin-bottom: 20px; } .divider { height: 1px; background: linear-gradient(90deg, transparent, #f0f, transparent); margin: 15px 0; } .info-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #222; } .label { color: #666; font-size: 0.8rem; } .value { color: #0ff; } .status { text-align: center; margin-top: 20px; color: #0f0; font-size: 0.8rem; } .dot { display: inline-block; width: 8px; height: 8px; background: #0f0; border-radius: 50%; margin-right: 8px; box-shadow: 0 0 10px #0f0; animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } </style> </head> <body> <div class="container"> <h1>[ WEB SERVER ]</h1> <div class="divider"></div> <div class="info-row"> <span class="label">MCU</span> <span class="value">ESP32-S3</span> </div> <div class="info-row"> <span class="label">DISPLAY</span> <span class="value">GC9A01 240x240</span> </div> <div class="info-row"> <span class="label">FRAMEWORK</span> <span class="value">Arduino + LittleFS</span> </div> <div class="divider"></div> <div class="status"> <span class="dot"></span>SYSTEM ONLINE </div> </div> </body> </html>
SETUP INSTRUCTIONS
- WiFi: Replace
YOUR_WIFI_SSIDandYOUR_WIFI_PASSWORDwith your WiFi credentials. - Library: Install
Arduino_GFX_Libraryvia Arduino Library Manager. - Board: Select "ESP32S3 Dev Module" in Arduino IDE.
- Upload Sketch: Click Upload to flash the .ino file.
- Upload Data: Create a
datafolder next to your .ino file. Putindex.htmlinside. Use LittleFS upload tool (Ctrl+Shift+P → "Upload LittleFS"). - Serial Monitor: Open at 115200 baud to see IP address after connection.
- Access: Open the IP address in your browser to see your page.
DISPLAY ROTATION
To rotate the display, add gfx->setRotation(n); in setup() after gfx->begin();
gfx->setRotation(0); // 0° - Default orientation
gfx->setRotation(1); // 90° - Rotate clockwise
gfx->setRotation(2); // 180° - Upside down
gfx->setRotation(3); // 270° - Rotate counter-clockwise
Note: This rotates the entire display output. Use this if your display is mounted upside down or at an angle.
ARDUINO PROJECT SETUP
- Create Project Folder: Create a folder named
LittleWebServerin your Arduino sketches directory (usuallyDocuments/Arduino/) - Main Sketch: Save the code as
LittleWebServer.ino- filename MUST match folder name - Single File Project: This project uses only one .ino file - no additional .h files required
LittleWebServer/
└── LittleWebServer.ino // Complete sketch (copy the code above)
COMPONENTS NEEDED
| ESP32-S3 Super Mini | Main microcontroller | AliExpress |
| GC9A01 Round Display | 240x240 1.28" TFT | Amazon |
| Dupont Wires | For connections | Amazon |
WIRING DIAGRAM
| GC9A01 Display | ESP32-S3 |
|---|---|
| VCC | 3.3V |
| GND | GND |
| SCL (SCK) | GPIO 12 |
| SDA (MOSI) | GPIO 11 |
| RES (RST) | GPIO 10 |
| DC | GPIO 9 |
| CS | GPIO 8 |
| BLK (Backlight) | GPIO 7 |
REQUIRED LIBRARIES
Install via Arduino Library Manager (Sketch → Include Library → Manage Libraries):
| Arduino_GFX_Library | by moononournation - Display driver |
Note: WiFi and WebServer libraries are built into ESP32 Arduino core - no install needed.
ARDUINO IDE 3.x BOARD SETTINGS
| Board | ESP32S3 Dev Module |
| USB CDC On Boot | Enabled |
| Flash Size | 4MB (32Mb) |
| Partition Scheme | Default 4MB with spiffs |
| Upload Speed | 921600 |
ESP32 Board URL: Add this in File → Preferences → Additional Board Manager URLs:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
TROUBLESHOOTING
- Upload fails / Guru Meditation Error: Hold BOOT button while clicking Upload, release after "Connecting..." appears.
- Display is white/blank: Check wiring, especially CS, DC, and RST pins. Verify 3.3V power.
- WiFi won't connect: Double-check SSID and password. ESP32 only supports 2.4GHz networks.
- Can't access web page: Check Serial Monitor for IP address. Ensure device is on same network.
- Display shows wrong IP: WiFi may have assigned different IP. Check router's DHCP list.
- Serial Monitor empty: Set baud rate to 115200. Enable "USB CDC On Boot" in board settings.
BUILD CHECKLIST
- □ Install Arduino IDE 3.x
- □ Add ESP32 board package URL and install ESP32 boards
- □ Install required library: Arduino_GFX_Library
- □ Create project folder:
LittleWebServer/ - □ Copy sketch as
LittleWebServer.ino - □ Edit code: Set WiFi SSID and password
- □ Wire GC9A01 display to ESP32-S3
- □ Connect ESP32-S3 via USB-C
- □ Select ESP32S3 Dev Module and correct COM port
- □ Upload sketch (hold BOOT if needed)
- □ Check Serial Monitor for IP address
- □ Open IP address in web browser - Done!