Community project

Ho Un Esp32-2432s028 Vorrei Fare Un Dispositivo

Gian Luca Battini

Published July 23, 2026

ESP320 components4 assembly steps
Remix this project
Photo of Ho Un Esp32-2432s028 Vorrei Fare Un Dispositivo

This ESP32-2432S028 project transforms the development board into a file synchronization device with a touchscreen interface. It connects to WiFi networks, communicates with WebDAV servers, and manages file transfers from a microSD card, all controlled through the integrated 2.4-inch display and touch input.

The guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions. Builders will configure network settings, upload the provided firmware, insert a microSD card, and use the touchscreen to monitor and control synchronization between local storage and remote servers.

Wiring diagram

Interactive · read-only

Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.

Assembly

4 steps
  1. Inserisci la microSD

    A scheda spenta, inserisci una microSD formattata FAT32 nello slot integrato. Per il primo test, copia uno o due piccoli file direttamente nella cartella principale della scheda.

    • Tip: Usa nomi file semplici, senza caratteri speciali, per il primo test.
    • Tip: Il firmware sincronizza i file nella cartella principale; non trasferisce le sottocartelle.
    • Non rimuovere la microSD mentre una sincronizzazione è in corso.
  2. Alimenta la scheda

    Collega l'ESP32-2432S028R al computer o a un alimentatore USB stabile tramite la sua porta USB.

    • Tip: Non sono necessari moduli esterni: display, touchscreen e slot microSD sono integrati nella scheda.
    • Usa esclusivamente alimentazione USB a 5 V.
  3. Configura la rete e il server

    Dopo il deploy, se la rete Wi-Fi non è ancora configurata, collega telefono o computer alla rete Wi-Fi temporanea "SD-WebDAV-Setup". Apri http://192.168.4.1, quindi inserisci la rete Wi-Fi e l'URL completo della cartella WebDAV del NAS, con utente e password.

    • Tip: L'URL deve indicare una cartella WebDAV esistente e terminare preferibilmente con /.
    • Tip: Dopo la connessione Wi-Fi, l'indirizzo della pagina web appare sul display.
    • La pagina web non usa HTTPS locale: usala soltanto sulla tua rete fidata. Le credenziali WebDAV vengono memorizzate nella memoria interna della scheda.
  4. Avvia e controlla la sincronizzazione

    Premi il pulsante "Sincronizza" dalla pagina web oppure tocca il grande pulsante centrale sul display. Il display mostra il file in invio e il risultato finale.

    • Tip: Un file con lo stesso nome già presente nella cartella WebDAV viene sostituito.
    • Tip: Per file grandi, mantieni la scheda alimentata e con Wi-Fi stabile fino alla fine.
    • Non spegnere la scheda durante il caricamento di un file.

Firmware

ESP32
main.cppDeploy to device
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include <SD.h>
#include <SPI.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>
#include <Preferences.h>


// Forward declarations
String esc(String value);
String encodePath(const String &value);
void drawScreen();
bool beginSD();
void beginTouch();
void loadSettings();
void saveSettings();
void connectWifi();
void startAccessPoint();
bool webDavPut(File &file, const String &name);
void synchronize();
String page();
void startServer();
void handleTouch();

constexpr int SD_CS = 5;
constexpr int SD_SCK = 18;
constexpr int SD_MISO = 19;
constexpr int SD_MOSI = 23;
constexpr int TOUCH_CS = 33;
constexpr int TOUCH_IRQ = 36;
constexpr int TOUCH_SCK = 25;
constexpr int TOUCH_MISO = 39;
constexpr int TOUCH_MOSI = 32;

TFT_eSPI tft;
SPIClass vspi(VSPI);
XPT2046_Touchscreen touch(TOUCH_CS, TOUCH_IRQ);
WebServer server(80);
Preferences prefs;

String ssid, wifiPassword, davUrl, davUser, davPassword;
String statusLine = "Avvio";
String lastResult = "Nessuna sincronizzazione";
bool sdReady = false;
bool syncing = false;
unsigned long lastTouch = 0;

String esc(String value) {
  value.replace("&", "&amp;"); value.replace("<", "&lt;");
  value.replace(">", "&gt;"); value.replace("\"", "&quot;");
  return value;
}

String encodePath(const String &value) {
  const char *hex = "0123456789ABCDEF";
  String result;
  for (size_t i = 0; i < value.length(); ++i) {
    const char c = value[i];
    if (isalnum(c) || c == '/' || c == '.' || c == '-' || c == '_') result += c;
    else { result += '%'; result += hex[(c >> 4) & 15]; result += hex[c & 15]; }
  }
  return result;
}

void drawScreen() {
  tft.fillScreen(TFT_NAVY);
  tft.setTextColor(TFT_WHITE, TFT_NAVY);
  tft.setTextSize(2);
  tft.drawString("SD WebDAV Sync", 10, 10);
  tft.drawFastHLine(10, 35, 220, TFT_CYAN);
  tft.setTextSize(1);
  String network = WiFi.status() == WL_CONNECTED ? "Wi-Fi: " + WiFi.SSID() : "Wi-Fi: da configurare";
  tft.drawString(network, 10, 52);
  tft.drawString(sdReady ? "microSD: pronta" : "microSD: non trovata", 10, 70);
  tft.drawString("Stato:", 10, 96);
  tft.setTextColor(TFT_YELLOW, TFT_NAVY);
  tft.drawString(statusLine.substring(0, 36), 10, 112);
  uint16_t colour = syncing ? TFT_DARKGREY : TFT_GREEN;
  tft.fillRoundRect(12, 148, 216, 50, 6, colour);
  tft.setTextColor(TFT_BLACK, colour);
  tft.setTextDatum(MC_DATUM);
  tft.setTextSize(2);
  tft.drawString(syncing ? "ATTENDI" : "TOCCA: SINCRONIZZA", 120, 173);
  tft.setTextDatum(TL_DATUM);
  tft.setTextColor(TFT_WHITE, TFT_NAVY);
  tft.setTextSize(1);
  String address = WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "192.168.4.1";
  tft.drawString("Pagina web: http://" + address, 10, 222);
  tft.drawString("Configura Wi-Fi e Nextcloud dal browser", 10, 240);
}

bool beginSD() {
  vspi.end();
  vspi.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
  sdReady = SD.begin(SD_CS, vspi, 20000000);
  return sdReady;
}

void beginTouch() {
  vspi.end();
  vspi.begin(TOUCH_SCK, TOUCH_MISO, TOUCH_MOSI, TOUCH_CS);
  touch.begin(vspi);
  touch.setRotation(1);
}

void loadSettings() {
  prefs.begin("webdavsync", true);
  ssid = prefs.getString("ssid", ""); wifiPassword = prefs.getString("wp", "");
  davUrl = prefs.getString("url", ""); davUser = prefs.getString("usr", ""); davPassword = prefs.getString("pwd", "");
  prefs.end();
}

void saveSettings() {
  prefs.begin("webdavsync", false);
  prefs.putString("ssid", ssid); prefs.putString("wp", wifiPassword);
  prefs.putString("url", davUrl); prefs.putString("usr", davUser); prefs.putString("pwd", davPassword);
  prefs.end();
}

void connectWifi() {
  if (ssid.isEmpty()) return;
  statusLine = "Connessione Wi-Fi..."; drawScreen();
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid.c_str(), wifiPassword.c_str());
  unsigned long started = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - started < 15000) delay(250);
  statusLine = WiFi.status() == WL_CONNECTED ? "Wi-Fi connesso" : "Wi-Fi non raggiungibile";
}

void startAccessPoint() {
  WiFi.mode(WIFI_AP_STA);
  WiFi.softAP("SD-WebDAV-Setup");
  statusLine = "Rete setup: SD-WebDAV-Setup";
}

bool webDavPut(File &file, const String &name) {
  String url = davUrl;
  if (!url.endsWith("/")) url += "/";
  HTTPClient request;
  request.setTimeout(30000);
  if (!request.begin(url + encodePath(name))) return false;
  if (!davUser.isEmpty()) request.setAuthorization(davUser.c_str(), davPassword.c_str());
  request.addHeader("Content-Type", "application/octet-stream");
  int response = request.sendRequest("PUT", &file, file.size());
  request.end();
  return response >= 200 && response < 300;
}

void synchronize() {
  if (syncing) return;
  syncing = true; statusLine = "Sincronizzazione..."; drawScreen();
  if (WiFi.status() != WL_CONNECTED || davUrl.isEmpty() || !beginSD()) {
    lastResult = "Configura Wi-Fi, WebDAV e microSD";
  } else {
    File root = SD.open("/");
    int done = 0, errors = 0;
    File item;
    while ((item = root.openNextFile())) {
      if (!item.isDirectory()) {
        statusLine = "Invio " + String(item.name()); drawScreen();
        if (webDavPut(item, String(item.name()))) ++done; else ++errors;
      }
      item.close();
      delay(5);
    }
    root.close();
    lastResult = "Inviati " + String(done) + ", errori " + String(errors);
  }
  syncing = false; statusLine = lastResult;
  beginTouch();
  drawScreen();
}

String page() {
  String ip = WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : WiFi.softAPIP().toString();
  String result = "<!doctype html><html><head><meta name='viewport' content='width=device-width,initial-scale=1'><style>body{font-family:Arial;max-width:650px;margin:20px auto;padding:0 14px;background:#f4f6f8}.card{background:#fff;padding:16px;margin:12px 0;border-radius:8px}input{width:100%;box-sizing:border-box;padding:9px;margin:5px 0 12px}button{background:#087ea4;color:#fff;border:0;border-radius:5px;padding:11px 15px;font-weight:bold}</style></head><body>";
  result += "<h1>SD WebDAV Sync</h1><div class='card'><b>Indirizzo:</b> " + ip + "<br><b>Stato:</b> " + esc(statusLine) + "<br><b>Risultato:</b> " + esc(lastResult) + "</div>";
  result += "<div class='card'><form method='post' action='/save'><h2>Wi-Fi e cartella Nextcloud</h2>Nome rete Wi-Fi<input name='ssid' value='" + esc(ssid) + "'>Password Wi-Fi<input type='password' name='wp'>URL WebDAV della cartella Nextcloud<input name='url' value='" + esc(davUrl) + "' placeholder='https://cloud.example.it/remote.php/dav/files/NOME_UTENTE/Cartella/'>Utente Nextcloud<input name='usr' value='" + esc(davUser) + "'>Password o password per app Nextcloud<input type='password' name='pwd'><button>Salva e connetti</button></form><p>Per una cartella condivisa, usa il suo percorso dentro ai tuoi File Nextcloud. L&apos;utente deve avere permesso di creare e modificare file nella condivisione.</p></div>";
  result += "<div class='card'><form method='post' action='/sync'><button>Carica ora microSD su Nextcloud</button></form><p>Carica i file presenti nella cartella principale della microSD nella cartella Nextcloud scelta. Un file remoto con lo stesso nome viene sostituito; le sottocartelle non sono incluse.</p></div></body></html>";
  return result;
}

void startServer() {
  server.on("/", HTTP_GET, []() { server.send(200, "text/html; charset=utf-8", page()); });
  server.on("/save", HTTP_POST, []() {
    ssid = server.arg("ssid"); davUrl = server.arg("url"); davUser = server.arg("usr");
    if (server.arg("wp").length()) wifiPassword = server.arg("wp");
    if (server.arg("pwd").length()) davPassword = server.arg("pwd");
    saveSettings(); connectWifi(); if (WiFi.status() != WL_CONNECTED) startAccessPoint();
    drawScreen(); server.sendHeader("Location", "/"); server.send(303);
  });
  server.on("/sync", HTTP_POST, []() { server.send(200, "text/html", "Sincronizzazione avviata. <a href='/'>Torna allo stato</a>"); synchronize(); });
  server.begin();
}

void handleTouch() {
  if (syncing || millis() - lastTouch < 600 || !touch.touched()) return;
  TS_Point point = touch.getPoint();
  if (point.x > 500 && point.x < 3600 && point.y > 500 && point.y < 3600) {
    lastTouch = millis();
    synchronize();
  }
}

void setup() {
  Serial.begin(115200);
  tft.init(); tft.setRotation(1);
  loadSettings();
  beginSD(); beginTouch();
  connectWifi();
  if (WiFi.status() != WL_CONNECTED) startAccessPoint();
  startServer();
  drawScreen();
}

void loop() {
  server.handleClient();
  handleTouch();
}

“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik