CYD WEB SERVER ESP32-32E
Cheap Yellow Display // 3.5" ST7796 // Built-in SD Card
KEY FEATURES
HARDWARE SPECS
DISPLAY PREVIEW
DISPLAY INFO
ARDUINO PROJECT SETUP
Create this folder structure for your project:
CYD_WEB_Server/
└── CYD_WEB_Server.ino <-- Main sketch (copy from below)
SD Card (FAT32):
├── index.html <-- Your website homepage
├── style.css <-- Stylesheet (copy template below)
└── images/ <-- Optional image folder
Note: CYD has built-in SD card slot. Web files are served from SD card. Format as FAT32.
COMPONENTS NEEDED
- ESP32-2432S028R (CYD) - Cheap Yellow Display board with built-in 2.8" or 3.5" ST7796 display
- MicroSD Card - FAT32 formatted (up to 32GB recommended)
- USB-C or Micro-USB Cable - For programming (check your board version)
All-in-One: CYD includes display, SD card slot, touch panel, RGB LED, and audio DAC on a single board. No external wiring required!
REQUIRED LIBRARIES
Install via Arduino Library Manager (Sketch → Include Library → Manage Libraries):
Arduino_GFX_Library- Display driver for ST7796SimpleFTPServer- FTP server for wireless file upload
Note: WiFi, WebServer, SD, and SPI are built into ESP32 Arduino core.
SimpleFTPServer Config: Edit FtpServerKey.h in the library folder:
- Set
STORAGE_TYPEtoSTORAGE_SD - Set
STORAGE_SD_FORCE_DISABLE_SEPORATECLASStotrue
ARDUINO IDE 3.x BOARD SETTINGS
Tools menu configuration:
- Board: ESP32 Dev Module
- Upload Speed: 921600
- Flash Frequency: 80MHz
- Flash Mode: QIO
- Flash Size: 4MB (32Mb)
- Partition Scheme: Default 4MB with spiffs
- PSRAM: Disabled
Note: CYD uses standard ESP32-WROOM-32E, not ESP32-S3. Select "ESP32 Dev Module".
TROUBLESHOOTING
- Display stays white/black: Check that you're using ST7796 driver. CYD 2.8" uses different pins than 3.5" version.
- SD Card not mounting: Must be FAT32 formatted. Try cards 32GB or smaller.
- Upload fails: Hold BOOT button during upload. Some CYD boards need this.
- Wrong colors: Display may need BGR mode. Check Arduino_GFX initialization.
- Touch not working: Touch uses separate SPI pins (GPIO 33, 32, 21, 25). Check XPT2046 library.
- FTP connection refused: Verify SimpleFTPServer is configured for SD storage.
- Web files not loading: Check file paths on SD card. Files must be in root or match URL paths.
ARDUINO SKETCH
/* ESP32-32E Web Server + FTP + SD Card Generic Template - Customize for your project Hardware: - ESP32-32E Module (dual-core 240MHz) - ST7796 3.5" TFT Display (320x480) - Built-in Micro SD Card Slot - Resistive Touch (XPT2046) */ #include <WiFi.h> #include <WebServer.h> #include <SD.h> #include <SPI.h> #include <SimpleFTPServer.h> #include <Arduino_GFX_Library.h> #include <time.h> // ============== CONFIGURATION ============== // WiFi credentials - CHANGE THESE const char* ssid = "YOUR_WIFI_SSID"; const char* password = "YOUR_WIFI_PASSWORD"; // FTP credentials - CHANGE THESE const char* ftp_user = "admin"; const char* ftp_pass = "admin"; // NTP Time settings const char* ntpServer = "pool.ntp.org"; const long gmtOffset_sec = -21600; // CST = UTC-6 (adjust for your timezone) const int daylightOffset_sec = 3600; // DST offset // ============== PIN DEFINITIONS ============== // Display pins (ST7796 - shared SPI bus) #define TFT_SCK 14 #define TFT_MOSI 13 #define TFT_MISO 12 #define TFT_CS 15 #define TFT_DC 2 #define TFT_RST -1 // Not connected (use software reset) #define TFT_BL 27 // Backlight control pin! // SD Card pins (built-in slot, shares SPI with display) #define SD_CS 5 // SD card CS per lcdwiki // Touch pins (XPT2046) #define TOUCH_CS 33 #define TOUCH_IRQ 36 // RGB LED pins #define LED_RED 22 #define LED_GREEN 16 #define LED_BLUE 17 // Audio pins #define AUDIO_EN 4 #define AUDIO_DAC 26 // Battery ADC #define BATT_ADC 34 // ============== COLORS (RGB565) ============== #define BLACK 0x0000 #define WHITE 0xFFFF #define CYAN 0x07FF #define MAGENTA 0xF81F #define YELLOW 0xFFE0 #define GREEN 0x07E0 #define RED 0xF800 #define BLUE 0x001F #define DARKGRAY 0x4208 #define DARKCYAN 0x0345 #define DARKMAGENTA 0x4008 #define ORANGE 0xFC00 // ============== DISPLAY DIMENSIONS ============== #define SCREEN_WIDTH 320 #define SCREEN_HEIGHT 480 // ============== OBJECTS ============== // Hardware SPI for display (HSPI bus) Arduino_DataBus *bus = new Arduino_ESP32SPI( TFT_DC, // DC TFT_CS, // CS TFT_SCK, // SCK = 14 TFT_MOSI, // MOSI = 13 TFT_MISO, // MISO = 12 HSPI, // HSPI bus (pins 12-15) 10000000 // 10MHz ); Arduino_GFX *gfx = new Arduino_ST7796(bus, TFT_RST, 0, false); // rotation=0, IPS=false WebServer server(80); FtpServer ftpSrv; // ============== VARIABLES ============== unsigned long lastDisplayUpdate = 0; unsigned long lastTickerUpdate = 0; unsigned long requestCount = 0; int animFrame = 0; bool sdCardMounted = false; int lastDisplayedHour = -1; int lastDisplayedMin = -1; // ============== CONTENT TYPES ============== 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(".gif")) return "image/gif"; else if (filename.endsWith(".ico")) return "image/x-icon"; else if (filename.endsWith(".json")) return "application/json"; return "text/plain"; } // ============== FILE HANDLER ============== bool handleFileRead(String path) { Serial.println("Request: " + path); requestCount++; if (path.endsWith("/")) path += "index.html"; if (!sdCardMounted) { server.send(503, "text/plain", "SD Card not mounted"); return false; } String contentType = getContentType(path); if (SD.exists(path)) { File file = SD.open(path, FILE_READ); if (file) { server.streamFile(file, contentType); file.close(); return true; } } return false; } // ============== DISPLAY HELPER FUNCTIONS ============== void drawCornerBrackets(int x, int y, int w, int h, uint16_t color) { int len = 12; 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); } void drawHorizontalDivider(int y, uint16_t color) { gfx->drawFastHLine(20, y, SCREEN_WIDTH - 40, color); } 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 = 6 + (i * 4); uint16_t color = (i < bars) ? CYAN : DARKGRAY; gfx->fillRect(x + (i * 8), y + (18 - barHeight), 5, barHeight, color); } } // 7-Segment patterns const uint8_t segPatterns[] = { 0b1111110, 0b0110000, 0b1101101, 0b1111001, 0b0110011, 0b1011011, 0b1011111, 0b1110000, 0b1111111, 0b1111011 }; 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); } void drawColon(int x, int y, int h, int t, uint16_t color) { int dotSize = t + 2; gfx->fillRect(x, y + h/3 - dotSize/2, dotSize, dotSize, color); gfx->fillRect(x, y + 2*h/3 - dotSize/2, dotSize, dotSize, color); } void draw7SegTime(int x, int y, int hours, int mins, uint16_t color, uint16_t dimColor) { int digitW = 28, digitH = 44, thick = 5, gap = 6, colonW = 12; 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); } void drawChevrons(int frame) { int y = 445; uint16_t color = (frame % 2 == 0) ? MAGENTA : DARKMAGENTA; uint16_t color2 = (frame % 2 == 0) ? DARKMAGENTA : MAGENTA; gfx->setTextSize(1); gfx->setTextColor(color); gfx->setCursor(80, y); gfx->print(">>"); gfx->setTextColor(color2); gfx->setCursor(96, y); gfx->print(">>"); gfx->setTextColor(WHITE); gfx->setCursor(120, y); gfx->print("WEBSRV 1.0T"); gfx->setTextColor(color2); gfx->setCursor(200, y); gfx->print("<<"); gfx->setTextColor(color); gfx->setCursor(216, y); gfx->print("<<"); } void drawTicker(int frame) { const char* msg = " /// SYSTEM NOMINAL /// WEB SERVER ONLINE /// TF CARD ACTIVE /// ESP32-32E /// "; int msgLen = strlen(msg); int charW = 6; int totalW = msgLen * charW; int tickerY = 420; int scrollX = SCREEN_WIDTH - (millis() / 20) % (totalW + SCREEN_WIDTH); gfx->fillRect(20, tickerY, SCREEN_WIDTH - 40, 16, BLACK); gfx->drawFastHLine(20, tickerY, SCREEN_WIDTH - 40, DARKGRAY); gfx->drawFastHLine(20, tickerY + 15, SCREEN_WIDTH - 40, DARKGRAY); gfx->setTextColor(CYAN); gfx->setTextSize(1); for (int i = 0; i < msgLen; i++) { int charX = scrollX + (i * charW); if (charX >= 20 && charX < SCREEN_WIDTH - 20) { gfx->setCursor(charX, tickerY + 4); gfx->print(msg[i]); } } } // ============== MAIN DISPLAY FUNCTIONS ============== void drawBootScreen() { gfx->fillScreen(BLACK); // Border frame gfx->drawRect(5, 5, SCREEN_WIDTH - 10, SCREEN_HEIGHT - 10, DARKCYAN); gfx->drawRect(10, 10, SCREEN_WIDTH - 20, SCREEN_HEIGHT - 20, CYAN); gfx->drawRect(15, 15, SCREEN_WIDTH - 30, SCREEN_HEIGHT - 30, DARKCYAN); // Title gfx->setTextColor(DARKCYAN); gfx->setTextSize(3); gfx->setCursor(59, 80); gfx->print("WEB SERVER"); gfx->setTextColor(CYAN); gfx->setCursor(60, 81); gfx->print("WEB SERVER"); // Decorative lines drawHorizontalDivider(120, DARKGRAY); gfx->drawFastHLine(40, 125, SCREEN_WIDTH - 80, MAGENTA); drawHorizontalDivider(130, DARKGRAY); // Subtitle gfx->setTextColor(MAGENTA); gfx->setTextSize(2); gfx->setCursor(55, 160); gfx->print("[ ESP32-32E NODE ]"); // Status box drawCornerBrackets(30, 200, SCREEN_WIDTH - 60, 80, CYAN); gfx->setTextColor(YELLOW); gfx->setTextSize(2); gfx->setCursor(60, 230); gfx->print(">> INITIALIZING..."); // Version info gfx->setTextColor(DARKGRAY); gfx->setTextSize(1); gfx->setCursor(100, 320); gfx->print("ST7796 320x480 Display"); gfx->setCursor(115, 335); gfx->print("Firmware v1.0"); } void drawStatusScreen() { gfx->fillScreen(BLACK); // Border frame gfx->drawRect(5, 5, SCREEN_WIDTH - 10, SCREEN_HEIGHT - 10, DARKCYAN); gfx->drawRect(10, 10, SCREEN_WIDTH - 20, SCREEN_HEIGHT - 20, DARKGRAY); // Title bar gfx->fillRect(20, 15, SCREEN_WIDTH - 40, 35, DARKMAGENTA); gfx->drawRect(20, 15, SCREEN_WIDTH - 40, 35, MAGENTA); gfx->setTextColor(WHITE); gfx->setTextSize(3); gfx->setCursor(60, 20); gfx->print("WEB SERVER"); // Status indicators bar drawHorizontalDivider(55, CYAN); // Status indicators int indicatorY = 65; // Power ON indicator gfx->fillCircle(50, indicatorY + 6, 6, GREEN); gfx->drawCircle(50, indicatorY + 6, 8, GREEN); gfx->setTextColor(GREEN); gfx->setTextSize(1); gfx->setCursor(62, indicatorY + 2); gfx->print("PWR"); // SD Card indicator if (sdCardMounted) { gfx->fillCircle(120, indicatorY + 6, 6, YELLOW); gfx->drawCircle(120, indicatorY + 6, 8, YELLOW); gfx->setTextColor(YELLOW); } else { gfx->fillCircle(120, indicatorY + 6, 6, RED); gfx->drawCircle(120, indicatorY + 6, 8, RED); gfx->setTextColor(RED); } gfx->setCursor(132, indicatorY + 2); gfx->print("SD"); // FTP indicator gfx->fillCircle(180, indicatorY + 6, 6, CYAN); gfx->drawCircle(180, indicatorY + 6, 8, CYAN); gfx->setTextColor(CYAN); gfx->setCursor(192, indicatorY + 2); gfx->print("FTP"); // Signal indicator gfx->setTextColor(WHITE); gfx->setCursor(240, indicatorY + 2); gfx->print("SIG"); drawSignalBars(270, indicatorY - 2, WiFi.RSSI()); drawHorizontalDivider(85, DARKGRAY); // Main content area frame drawCornerBrackets(15, 90, SCREEN_WIDTH - 30, 320, CYAN); // Static labels (drawn once) gfx->setTextColor(WHITE); gfx->setTextSize(1); gfx->setCursor(115, 97); gfx->print("NETWORK ADDRESS"); String ip = WiFi.localIP().toString(); gfx->setTextColor(CYAN); gfx->setTextSize(2); int ipWidth = ip.length() * 12; gfx->setCursor((SCREEN_WIDTH - ipWidth) / 2, 120); gfx->print(ip); drawHorizontalDivider(145, DARKGRAY); gfx->setTextColor(WHITE); gfx->setTextSize(1); gfx->setCursor(55, 155); gfx->print("RUNTIME"); gfx->setCursor(210, 155); gfx->print("REQUESTS"); drawHorizontalDivider(195, DARKGRAY); gfx->setTextColor(WHITE); gfx->setTextSize(1); gfx->setCursor(124, 210); gfx->print("CURRENT TIME"); drawHorizontalDivider(360, DARKGRAY); drawHorizontalDivider(390, DARKGRAY); } void updateDisplay() { animFrame++; // Update signal bars drawSignalBars(270, 63, WiFi.RSSI()); // Runtime and Requests section unsigned long uptime = millis() / 1000; int uptimeHours = uptime / 3600; int uptimeMins = (uptime % 3600) / 60; int uptimeSecs = uptime % 60; // Left side - Runtime (clear and redraw value only) gfx->fillRect(40, 170, 110, 18, BLACK); gfx->setTextColor(YELLOW); gfx->setTextSize(2); if (uptimeHours > 0) { gfx->setCursor(40, 170); gfx->printf("%02d:%02d hr", uptimeHours, uptimeMins); } else { gfx->setCursor(52, 170); gfx->printf("%02d min", uptimeMins); } // Right side - Requests (clear and redraw value only) gfx->fillRect(220, 170, 80, 18, BLACK); gfx->setTextColor(GREEN); gfx->setTextSize(2); gfx->setCursor(220, 170); gfx->print(requestCount); // Time display section struct tm timeinfo; if (getLocalTime(&timeinfo)) { int hour12 = timeinfo.tm_hour % 12; if (hour12 == 0) hour12 = 12; // Large 7-segment time display (only redraw when time changes) if (hour12 != lastDisplayedHour || timeinfo.tm_min != lastDisplayedMin) { gfx->fillRect(50, 230, 220, 70, BLACK); draw7SegTime(55, 235, hour12, timeinfo.tm_min, CYAN, DARKGRAY); // AM/PM indicator gfx->fillRect(230, 255, 35, 20, BLACK); gfx->setTextSize(2); gfx->setTextColor(GREEN); gfx->setCursor(233, 270); gfx->print(timeinfo.tm_hour >= 12 ? "PM" : "AM"); lastDisplayedHour = hour12; lastDisplayedMin = timeinfo.tm_min; } // Date display (clear and redraw) gfx->fillRect(95, 320, 130, 18, BLACK); char dateStr[15]; strftime(dateStr, sizeof(dateStr), "%m/%d/%Y", &timeinfo); gfx->setTextColor(YELLOW); gfx->setTextSize(2); gfx->setCursor(95, 320); gfx->print(dateStr); } // Bottom stats (clear entire row once, then redraw) gfx->fillRect(25, 375, 270, 10, BLACK); // Battery voltage int battRaw = analogRead(BATT_ADC); float battVoltage = (battRaw / 4095.0) * 3.3 * 2; gfx->setTextColor(WHITE); gfx->setTextSize(1); gfx->setCursor(25, 375); gfx->print("BATT:"); gfx->setTextColor(battVoltage > 3.5 ? GREEN : RED); gfx->printf("%.1fV", battVoltage); // Free heap memory gfx->setTextColor(WHITE); gfx->setCursor(115, 375); gfx->print("HEAP:"); gfx->setTextColor(CYAN); gfx->printf("%dK", ESP.getFreeHeap() / 1024); // WiFi RSSI value gfx->setTextColor(WHITE); gfx->setCursor(205, 375); gfx->print("RSSI:"); gfx->setTextColor(YELLOW); gfx->printf("%ddBm", WiFi.RSSI()); // Ticker and chevrons drawTicker(animFrame); drawChevrons(animFrame); } // ============== SETUP ============== void setup() { Serial.begin(115200); delay(1000); Serial.println("\n=== ESP32-32E SD Card Web Server ===\n"); // Initialize RGB LEDs pinMode(LED_RED, OUTPUT); pinMode(LED_GREEN, OUTPUT); pinMode(LED_BLUE, OUTPUT); digitalWrite(LED_RED, HIGH); // Red on during boot digitalWrite(LED_GREEN, LOW); digitalWrite(LED_BLUE, LOW); // Set chip selects as outputs and deselect both FIRST pinMode(TFT_CS, OUTPUT); pinMode(SD_CS, OUTPUT); digitalWrite(TFT_CS, HIGH); digitalWrite(SD_CS, HIGH); // Turn on backlight at 40% brightness using PWM ledcAttach(TFT_BL, 5000, 8); // Pin, 5kHz frequency, 8-bit resolution ledcWrite(TFT_BL, 102); // 40% of 255 = 102 Serial.println("Backlight at 40% (IO27)"); // Initialize display FIRST (it sets up HSPI bus) Serial.println("Initializing display..."); gfx->begin(); gfx->fillScreen(BLACK); Serial.println(" Display initialized"); // Initialize SD card (shares HSPI bus with display) Serial.println("Initializing SD card..."); pinMode(SD_CS, OUTPUT); digitalWrite(SD_CS, HIGH); digitalWrite(TFT_CS, HIGH); // Deselect display if (!SD.begin(SD_CS, SPI, 4000000)) { Serial.println(" SD Card mount failed!"); sdCardMounted = false; } else { sdCardMounted = true; Serial.println(" SD Card OK!"); } if (sdCardMounted) { uint64_t cardSize = SD.cardSize() / (1024 * 1024); Serial.printf(" SD Card Size: %lluMB\n", cardSize); digitalWrite(LED_GREEN, HIGH); } // Draw boot screen Serial.println("Drawing boot screen..."); drawBootScreen(); // Show SD status on display if (!sdCardMounted) { gfx->setTextColor(RED); gfx->setTextSize(2); gfx->setCursor(70, 260); gfx->print("SD CARD ERROR!"); digitalWrite(LED_RED, HIGH); delay(2000); } // Connect to WiFi Serial.printf("Connecting to %s", ssid); gfx->setTextColor(CYAN); gfx->setTextSize(1); gfx->setCursor(80, 370); gfx->print("Connecting to WiFi..."); WiFi.begin(ssid, password); int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 30) { delay(500); Serial.print("."); // Blink blue LED during connection digitalWrite(LED_BLUE, attempts % 2); attempts++; } if (WiFi.status() == WL_CONNECTED) { Serial.println(" Connected!"); Serial.print("IP Address: "); Serial.println(WiFi.localIP()); digitalWrite(LED_BLUE, HIGH); // Blue on = WiFi connected // Configure NTP time configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // Start FTP server ftpSrv.begin(ftp_user, ftp_pass); Serial.println("FTP server started"); Serial.printf("FTP User: %s\n", ftp_user); // Hold boot screen for 5 seconds delay(5000); drawStatusScreen(); updateDisplay(); } else { Serial.println(" Failed!"); digitalWrite(LED_BLUE, LOW); digitalWrite(LED_RED, HIGH); // Red = WiFi failed WiFi.softAP("ESP32-Server", "password123"); Serial.print("AP IP: "); Serial.println(WiFi.softAPIP()); gfx->setTextColor(ORANGE); gfx->setTextSize(2); gfx->setCursor(60, 380); gfx->print("AP Mode: 192.168.4.1"); } // Setup web server // API endpoint for live system data server.on("/api/info", []() { String json = "{"; json += "\"ip\":\"" + WiFi.localIP().toString() + "\","; json += "\"ssid\":\"" + String(ssid) + "\","; json += "\"rssi\":" + String(WiFi.RSSI()) + ","; json += "\"port\":80,"; json += "\"uptime\":" + String(millis() / 1000) + ","; json += "\"heap\":" + String(ESP.getFreeHeap()) + ","; json += "\"sdSize\":" + String(sdCardMounted ? SD.cardSize() / (1024 * 1024) : 0) + ","; json += "\"requests\":" + String(requestCount); json += "}"; server.send(200, "application/json", json); }); server.onNotFound([]() { if (!handleFileRead(server.uri())) { server.send(404, "text/plain", "404: Not Found"); } }); server.begin(); Serial.println("Web server started on port 80"); } // ============== LOOP ============== void loop() { server.handleClient(); ftpSrv.handleFTP(); 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 TEMPLATE
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ESP32 Server</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="scanlines"></div> <div class="container"> <header> <div class="logo-frame"> <span class="bracket">[</span> <h1>ESP32<span class="accent">SERVER</span></h1> <span class="bracket">]</span> </div> <p class="status">// SYSTEM ONLINE</p> </header> <main> <section class="card"> <h2>SYSTEM STATUS</h2> <div class="status-grid"> <div class="status-item"> <span class="label">WiFi</span> <span class="value online">CONNECTED</span> </div> <div class="status-item"> <span class="label">SD Card</span> <span class="value online">MOUNTED</span> </div> <div class="status-item"> <span class="label">FTP</span> <span class="value online">ACTIVE</span> </div> <div class="status-item"> <span class="label">Web Server</span> <span class="value online">RUNNING</span> </div> </div> </section> <section class="card"> <h2>ABOUT</h2> <p>ESP32 CYD Web Server with SD Card storage and FTP access.</p> <p>Upload files via FTP or swap the SD card to update content.</p> </section> </main> <footer> <p>© 2025 // ESP32 CYD SERVER</p> </footer> </div> </body> </html>
STYLESHEET TEMPLATE
/* Cyberpunk Theme - ESP32 CYD Server */ :root { --bg-dark: #0a0a0f; --bg-card: #12121a; --neon-cyan: #00ffff; --neon-magenta: #ff00ff; --neon-yellow: #ffff00; --text-primary: #e0e0e0; --text-dim: #666; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Courier New', monospace; background: var(--bg-dark); color: var(--text-primary); min-height: 100vh; overflow-x: hidden; } /* Scanline Effect */ .scanlines { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; background: repeating-linear-gradient( 0deg, rgba(0,0,0,0.1) 0px, rgba(0,0,0,0.1) 1px, transparent 1px, transparent 2px ); z-index: 1000; } .container { max-width: 800px; margin: 0 auto; padding: 20px; } /* Header */ header { text-align: center; padding: 40px 0; border-bottom: 1px solid #333; } .logo-frame { display: inline-flex; align-items: center; gap: 10px; } .bracket { font-size: 3rem; color: var(--neon-magenta); text-shadow: 0 0 20px var(--neon-magenta); } h1 { font-size: 2.5rem; color: var(--neon-cyan); text-shadow: 0 0 30px var(--neon-cyan); letter-spacing: 8px; } .accent { color: var(--neon-magenta); text-shadow: 0 0 30px var(--neon-magenta); } .status { color: var(--text-dim); margin-top: 10px; letter-spacing: 3px; } /* Cards */ .card { background: var(--bg-card); border: 1px solid #333; border-radius: 10px; padding: 25px; margin: 20px 0; } .card h2 { color: var(--neon-magenta); font-size: 1rem; letter-spacing: 3px; margin-bottom: 20px; border-bottom: 1px solid var(--neon-magenta); padding-bottom: 10px; } .card p { color: var(--text-dim); line-height: 1.6; margin-bottom: 10px; } /* Status Grid */ .status-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; } .status-item { display: flex; justify-content: space-between; padding: 10px; background: rgba(0,255,255,0.05); border: 1px solid #333; border-radius: 5px; } .label { color: var(--text-dim); } .value.online { color: #00ff00; text-shadow: 0 0 10px #00ff00; } /* Footer */ footer { text-align: center; padding: 30px 0; border-top: 1px solid #333; color: var(--text-dim); font-size: 0.8rem; } /* Responsive */ @media (max-width: 600px) { .status-grid { grid-template-columns: 1fr; } h1 { font-size: 1.5rem; } }
SETUP INSTRUCTIONS
- WiFi: Replace
YOUR_WIFI_SSIDandYOUR_WIFI_PASSWORDwith your credentials. - FTP: Default credentials are
admin/admin. Change these for security. - SD Card: Format as
FAT32. Uses built-in slot on the CYD board. - Display: ST7796 uses HSPI bus (GPIO 12-15). Shares SPI with SD card.
- Backlight: Controlled via PWM on GPIO 27. Adjust brightness in code (0-255).
- Library: Install
SimpleFTPServerandArduino_GFX_Libraryvia Arduino Library Manager. - Board: Select "ESP32 Dev Module" in Arduino IDE.
SD CARD FILE STRUCTURE
Format your SD card as FAT32 and organize the website files as follows:
- Format SD card as FAT32 (use SD Card Formatter tool for best results)
- Copy
index.htmlandstyle.cssto the root of the SD card - Create an
imagesfolder for any graphics (optional) - Insert SD card into the CYD board before powering on
SD Card folder structure:
SD Card (FAT32)/
├── index.html // Main webpage
├── style.css // Stylesheet
├── script.js // JavaScript (optional)
└── images/ // Image folder (optional)
├── logo.png
└── background.jpg
Tip: Use FTP to upload files wirelessly after initial setup. Connect to the CYD's IP address on port 21 using FileZilla or similar client.
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.
BUILD CHECKLIST
Follow these steps to build your CYD Web Server:
- Create project folder: CYD_WEB_Server/
- Copy Arduino sketch: Save code as CYD_WEB_Server.ino
- Edit WiFi credentials: Replace YOUR_WIFI_SSID and YOUR_WIFI_PASSWORD
- Format SD card: FAT32 format (32GB or smaller recommended)
- Copy web files to SD: Add index.html and style.css to SD card root
- Install libraries: Arduino_GFX_Library + SimpleFTPServer
- Configure SimpleFTPServer: Edit FtpServerKey.h (STORAGE_SD)
- Insert SD card: Place SD card in CYD's built-in slot
- Select board: ESP32 Dev Module (NOT ESP32-S3)
- Upload sketch: Compile and upload (hold BOOT if needed)
- Test: Open browser to ESP32's IP address shown on display