Skip to Content

Orologio ad anello LED con WS2812

Orologio ad anello LED con WS2812

In questo tutorial imparerai come costruire un orologio ad anello LED con la striscia LED WS2812 e un ESP32. Aggiungeremo la regolazione automatica della luminosità e useremo anche un provider di orario via internet per mantenerlo sempre preciso. Infine, l’orologio sarà attivato dal movimento, utilizzando un sensore PIR, per non sprecare energia inutilmente.

Il breve video qui sotto mostra l’orologio in azione. Puoi vedere i segni delle ore (punti bianchi) e i secondi che scorrono (punto bianco in movimento).

LED Ring Clock in action
Orologio ad anello LED in funzione

L’ora attuale è indicata dal punto arancione e i minuti dai punti gialli. Quindi, nell’immagine sopra, l’orologio segna le 11:36.

Sincronizzando l’orologio tramite Wi-Fi con un provider di orario internet, possiamo assicurarci che il nostro orologio mostri sempre l’ora corretta, indipendentemente dai cambiamenti dell’ora legale, interruzioni di corrente o da un orologio interno impreciso.

Iniziamo con i componenti necessari.

Componenti necessari

Ecco i componenti necessari per il progetto. Invece della ESP32-C3 Mini Development Board elencata qui sotto, ho usato una scheda molto simile chiamata ESP32-C3 SuperMini di AliExpress. La mia aveva solo un LED integrato monocromatico, mentre la scheda qui sotto ha un LED RGB. Ma a parte questo dovrebbero essere quasi identiche e funzionare entrambe. Qualsiasi altro ESP32 o ESP8266 andrà bene. Tuttavia, se vuoi usare un Arduino avrai bisogno di una Wi-Fi shield.

ESP32-C3 Mini

Cavo USB C

Anello LED RGB

Dupont wire set

Set di cavi Dupont

Half_breadboard56a

Breadboard

Kit di resistenze & LED

Set di potenziometri

Sensore di movimento

Set di fotoresistenze

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Basi della striscia LED RGB WS2812

Useremo un anello LED RGB WS2812 per il nostro orologio. Il WS2812 è un tipo specifico di LED RGB basato sul package LED 5050. Il LED RGB 5050 si riferisce alle dimensioni del package, che sono 5.0mm x 5.0mm. L’immagine qui sotto mostra un LED 5050 con il suo IC di controllo e i tre LED (verde, rosso, blu).

5050 RBG LED
LED RBG 5050 ( source )

Nota che esistono altri driver simili per strisce LED RGB come WS2811, SK6812 e WS2815. Questo quick guide contiene un bel confronto tra i diversi tipi. Qui useremo il WS2812.

Il WS2812 funziona a 5V, ha un controller PWM integrato per il mixing dei colori e può essere collegato in cascata, usando una singola linea di comunicazione per creare strisce LED più lunghe. Più comunemente troverai il WS2812 usato in strisce LED RGB flessibili come quella mostrata qui sotto.

WS2812 RGB LED Strip
Striscia LED RGB WS2812 ( source )

Queste strisce LED sono disponibili con diversi numeri di LED e densità e possono essere tagliate a misura. Oltre alle strisce flessibili, puoi trovarle anche a forma di anelli rigidi:

WS2812 RGB LED Rings
Anelli LED RGB WS2812 ( source )

Gli anelli più piccoli sono in un unico pezzo, ma l’anello grande con 60 LED che useremo per il nostro orologio è composto da quattro parti che dovrai saldare insieme. L’immagine qui sotto mostra il retro dei quattro pezzi:

WS2812 RGB LED Ring in four parts
Anello LED RGB WS2812 in quattro parti

Come assemblare i quattro segmenti in un unico grande anello è l’argomento della prossima sezione.

Costruzione dell’orologio ad anello LED

Poiché un’ora ha 60 minuti e un minuto 60 secondi, vogliamo usare un anello con 60 LED. Come detto sopra, per via delle dimensioni arriva in 4 sezioni (ognuna con 15 LED) che dobbiamo saldare insieme.

Nota che le strisce e gli anelli LED RGB WS2812 hanno un lato di ingresso (DIN) e uno di uscita (DO) come mostrato nell’immagine qui sotto:

Input and Output of an WS2812 LED Strip
Ingresso e uscita di una striscia LED WS2812

Inizia saldando il filo del segnale (giallo) al pad DIN di uno dei segmenti dell’anello LED. Poi salda un filo di massa (nero) al pad GND e un filo di alimentazione (rosso) al pad 5V. Dovrebbe apparire così:

Collegamento dell’anello LED WS2812

Infine, dobbiamo collegare i quattro segmenti. Collega GND a GND, 5V a 5V e DOUT a DIN per ciascun segmento. Un collegamento tra segmenti dovrebbe apparire così.

Wiring of LED Ring Segments
Collegamento dei segmenti dell’anello LED

In pratica non puoi collegare i pad in modo errato, altrimenti non otterresti un anello perfetto:

Wiring of complete LED Ring
Collegamento dell’anello LED completo

Nota che le strisce LED lunghe soffrono di una caduta di tensione che fa sì che i LED alla fine della striscia siano meno luminosi di quelli all’inizio. A volte quindi trovi strisce LED con alimentazione a entrambe le estremità.

Con l’anello da 60 LED, non ho notato differenze di luminosità tra il primo e l’ultimo LED. Tuttavia, se dovessi notarle, puoi collegare i pad GND e 5V tra l’ultimo e il primo segmento LED. Nel mio caso non è stato necessario.

Potresti anche trovare la saldatura un po’ difficile perché i pad sono molto piccoli. Aiuta posizionare i segmenti dell’anello LED in un supporto per fissarli. Ho usato una stampante 3D per creare il supporto o telaio dell’orologio mostrato nella prossima sezione.

Telaio dell’orologio LED

Il telaio dell’orologio LED è composto da tre parti. Il retro, dove si trova l’anello LED, una parte frontale trasparente e una base che permette di tenere in piedi l’anello LED.

Parts of the LED Clock Frame
Parti del telaio dell’orologio LED

Il telaio completo dell’orologio LED appare così e puoi trovare il STL files here :

LED Clock Frame
Telaio dell’orologio LED

La prossima cosa da fare è collegare l’anello LED all’ESP32.

Collegamento dell’orologio ad anello LED

Collegare l’anello LED WS2812 all’ESP32 è semplice. Prima collega il GND dell’ESP32 al GND dell’anello LED (filo blu). Poi collega il 5V dell’ESP32 all’ingresso 5V dell’anello LED WS2812. Infine collega GPIO3 dell’ESP32 tramite una resistenza da 220Ω al DIN del WS2812.

Connecting ESP32 to WS2812 LED Ring
Collegamento ESP32 ad anello LED WS2812

Puoi anche provare senza la resistenza da 220Ω per fare delle prove, ma è più sicuro averla. Protegge il GPIO dell’ESP32 da sovracorrenti. Invece di GPIO3 puoi usare qualsiasi altro pin GPIO purché supporti il PWM.

Nota che l’anello LED può assorbire molta corrente! Un singolo LED RGB è composto da tre LED (rosso, verde, blu) e ognuno consuma fino a 20mA quando è completamente acceso. Questo significa che, se un singolo LED RGB è completamente attivato (luce bianca), assorbe fino a 60mA (io ho misurato 45mA). Moltiplica 60mA per 60 LED e otteniamo una corrente di 3.6A , se tutti i LED dell’anello sono completamente accesi!

Di solito è troppa corrente da prelevare dall’ESP32 o dalla porta USB. Il codice di test nella prossima sezione quindi imposta la luminosità dell’anello LED su un valore basso di 10, che è più che sufficiente per le prove.

A proposito, se ti serve più info sulla scheda SuperMini che sto usando qui, dai un’occhiata al ESP32-C3 SuperMini Board tutorial.

Codice di test per l’anello LED

Prima di provare a implementare qualcosa di complesso, è meglio testare il funzionamento dell’anello LED con un codice semplice. Il codice qui sotto accende sequenzialmente tutti i 60 LED e, quando sono tutti accesi, li spegne di nuovo. Questo ci permette di verificare che tutti i LED funzionino e che i segmenti dell’anello siano collegati correttamente.

#include "Adafruit_NeoPixel.h"

#define DINPIN 3
#define NUMPIXELS 60

Adafruit_NeoPixel pixels(NUMPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

void loop() {
  for (int i = 0; i < NUMPIXELS; i++) {  // Switch all LEDs on
    pixels.setPixelColor(i, pixels.Color(255, 255, 255)); // White
    pixels.show();
    delay(200);
  }
  for (int i = 0; i < NUMPIXELS; i++) {  // Switch all LEDs off
    pixels.setPixelColor(i, pixels.Color(0, 0, 0));
    pixels.show();
    delay(200);
  }
}

Nel frammento di codice sopra, stiamo usando la Adafruit NeoPixel library . Se non l’hai già fatto, dovrai install the library prima, prima di poter usare questo codice.

Analizziamo il codice di test per capirne meglio il funzionamento.

Costanti e variabili

Per prima cosa definiamo la costante DINPIN che specifica il pin di ingresso dati a cui è collegato l’anello LED, e NUMPIXELS che definisce il numero totale di Pixel/LED nella striscia. Nel nostro caso abbiamo 60 LED e usiamo GPIO 3.

#define DINPIN 3
#define NUMPIXELS 60

Funzione setup

Nella funzione setup() inizializziamo la striscia dell’anello LED chiamando pixels.begin() , cancelliamo eventuali colori pixel esistenti con pixels.clear() , impostiamo il livello di luminosità a 10 con pixels.setBrightness(10) , e infine mostriamo lo stato dei pixel usando pixels.show() .

void setup() {
  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

Come ho già detto, fai attenzione quando cambi la luminosità! A luminosità massima l’anello LED potrebbe assorbire troppa corrente per il tuo ESP32, la porta USB o qualsiasi alimentatore tu stia usando.

Funzione loop

La funzione loop() contiene un ciclo che prima accende tutti i LED di bianco iterando su ogni pixel con un ciclo for. La funzione pixels.setPixelColor() viene usata per impostare il colore di ogni pixel su bianco (255, 255, 255) e poi viene chiamata pixels.show() per mostrare lo stato dei LED. Viene aggiunto un ritardo di 200ms tra ogni aggiornamento del pixel.

Dopo che tutti i pixel sono impostati su bianco, un altro ciclo viene usato per spegnere tutti i LED impostando il loro colore su nero (0, 0, 0) e aggiornando la striscia con pixels.show() di nuovo con un ritardo di 200ms tra ogni aggiornamento.

void loop() {
  for (int i = 0; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(255, 255, 255)); // White
    pixels.show();
    delay(200);
  }

  for (int i = 0; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(0, 0, 0)); // Black
    pixels.show();
    delay(200);
  }
}

Se funziona, congratulazioni! Nella prossima sezione proviamo qualcosa di un po’ più complesso simulando l’orario da visualizzare sull’anello LED e migliorando anche un po’ il nostro circuito.

Collegamento migliorato dell’orologio ad anello LED

Come detto sopra, l’anello LED può assorbire fino a 3.6A quando tutti i LED sono completamente accesi. Non vogliamo prelevare tutta questa corrente dall’ESP32. Dobbiamo invece collegare l’anello LED a un’alimentazione esterna. Il circuito qui sotto mostra come fare:

Connecting WS2812 LED Ring to external power supply
Collegamento dell’anello LED WS2812 ad alimentazione esterna

Il WS2812 funziona a 5V, quindi avrai bisogno di un alimentatore a 5V con abbastanza corrente per l’anello LED alla massima luminosità. Con una luminosità di 5, ho misurato una corrente di 80mA e con una luminosità di 50 la corrente era di 400mA.

La bassa luminosità di 5 va bene in una stanza buia e la luminosità di 50 è più che sufficiente in una stanza luminosa. Con valori di luminosità più alti l’orologio può illuminare una stanza, cosa che non è lo scopo di un orologio. Quindi, nel mio caso, un alimentatore da 500mA sarebbe sufficiente.

Nel circuito sopra, ho anche aggiunto un condensatore consigliato da 100μ fino a 1000μ sulla linea di alimentazione. Serve a stabilizzare l’alimentazione in caso di fluttuazioni di corrente causate dall’accensione o spegnimento simultaneo di molti LED.

Nella prossima sezione, passiamo dal codice di test a un orologio simulato che ci permette di provare diversi modi e colori per visualizzare l’ora su un anello LED.

Codice per un orologio ad anello LED simulato

Ci sono molti modi diversi per visualizzare l’ora su un anello LED. L’immagine qui sotto mostra la versione che ho scelto:

Showing time on an LED Ring
Visualizzazione dell’ora su un anello LED

L’ora attuale (punto arancione) è indicata da un singolo LED nella posizione corrispondente dell’orologio. I minuti (punti gialli) sono visualizzati illuminando tutti i LED fino al minuto attuale. Nell’immagine sopra l’orario è 11:32 e quindi il LED arancione nella posizione delle 11 è acceso, e i 32 LED gialli mostrano i 32 minuti.

I segni del quadrante sono indicati da LED bianchi deboli e il secondo attuale è mostrato aumentando la luminosità del LED nella posizione del secondo attuale. È un po’ difficile da vedere nell’immagine sopra, ma il LED nella posizione delle 3 secondi è leggermente più luminoso. Come viene fatto, lo vediamo nel codice seguente.

Nota che questo codice non mostra l’ora reale ma simula solo un orario accelerato per testare la visualizzazione sull’anello LED. Dai un’occhiata rapida al codice completo prima di discuterne i dettagli.

#include "Adafruit_NeoPixel.h"

#define DINPIN 3
#define NPIXELS 60
#define OFFSET 29

Adafruit_NeoPixel pixels(NPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);

int index(int i) {
  return (i + OFFSET) % NPIXELS;
}

void setPixel(int i, uint32_t color) {
  pixels.setPixelColor(index(i), color);
}

uint32_t getPixel(int i) {
  return pixels.getPixelColor(index(i));
}

void setTicks(int m) {
  uint32_t tickColor = pixels.Color(50, 50, 50);
  for (int h = 0; h < 12; h++) {
    setPixel(h * 5, tickColor);
  }
}

void setHours(int h) {
  uint32_t hourColor = pixels.Color(200, 50, 0);
  setPixel(h * 5, hourColor);  // h: 0..11 = 12 Hours
}

void setMinutes(int m) {
  uint32_t minColor = pixels.Color(100, 100, 0);
  for (int i = 0; i <= m; i++) {
    setPixel(i, minColor);
  }
}

void setSeconds(int s) {
  uint32_t color = getPixel(s);
  setPixel(s, color + 0x373737);
}

void showTime(int h, int m, int s) {
  pixels.clear();
  setMinutes(m);
  setTicks(m);
  setHours(h);
  setSeconds(s);
  pixels.show();
}

void simulateClock() {
  for (int h = 0; h < 12; h++) {
    for (int m = 0; m < 60; m++) {
      for (int s = 0; s < 60; s++) {
        showTime(h, m, s);
        delay(100);
      }
    }
  }
}

void setup() {
  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

void loop() {
  simulateClock();
}

Nel codice sopra, usiamo la libreria Adafruit NeoPixel per simulare un orologio usando una striscia LED con 60 pixel. L’orologio visualizzerà ore, minuti e secondi cambiando i colori di specifici pixel sulla striscia LED.

Costanti e variabili

Come prima, definiamo prima le costanti e le variabili necessarie per la simulazione dell’orologio. Specifichiamo il pin dati per la striscia LED, il numero totale di pixel e un valore di offset per l’indicizzazione dei pixel.

#include "Adafruit_NeoPixel.h"

#define DINPIN 3
#define NPIXELS 60
#define OFFSET 29

Adafruit_NeoPixel pixels(NPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);

Parliamo un attimo della costante OFFSET e del perché è necessaria. Se monti l’orologio ad anello LED nel suo telaio, il primo LED dell’anello (dove sono collegati i fili) si troverà un pixel a sinistra rispetto alla posizione delle 6 (indice 0).

Offset for pixel index
Offset per l’indice dei pixel

Quindi, se abbiamo un orario di 12 (= 0) non possiamo accendere il LED/pixel in posizione 0, perché si trova in basso. Invece dobbiamo accendere il LED in posizione 29, che corrisponde alle 12. Questo significa che dobbiamo sempre aggiungere un offset di 29 a qualsiasi indice pixel, se vogliamo accendere il LED nella posizione corrispondente dell’orologio.

Questo è il motivo della costante OFFSET . A seconda del tuo telaio e della posizione meccanica del primo LED dell’anello, potresti dover usare un valore di OFFSET diverso.

Funzione index

La costante OFFSET viene usata nella funzione index() per mappare un indice orario i a una posizione LED sull’anello. Oltre all’offset, dobbiamo anche calcolare il modulo (%) per assicurarci che l’indice LED sia nell’intervallo 0..59, dato che abbiamo solo 60 LED.

int index(int i) {
  return (i + OFFSET) % NPIXELS;
}

Supponiamo di avere 36 minuti. Il LED sull’anello che dobbiamo illuminare per mostrare il minuto sarà all’indice: 36 + 29 % 60 = 5.

Funzioni setPixel e getPixel

Le funzioni setPixel() e getPixel() usano la funzione index() per impostare o ottenere il colore per un dato indice i sull’anello LED:

void setPixel(int i, uint32_t color) {
  pixels.setPixelColor(index(i), color);
}

uint32_t getPixel(int i) {
  return pixels.getPixelColor(index(i));
}

Funzione setTick

La funzione setTick() segna i tick delle ore sull’anello LED. Poiché un giorno su un orologio ha 12 ore ma abbiamo 60 LED, dobbiamo illuminare ogni 5° LED sull’anello per segnare le ore.

void setTicks(int m) {
  uint32_t tickColor = pixels.Color(50, 50, 50);
  for (int h = 0; h < 12; h++) {
    setPixel(h * 5, tickColor);
  }
}

Se guardi la funzione, itera sulle 12 ore, moltiplica un’ora h per 5 e poi usa setPixel() e quindi index() per convertire l’ora in un indice LED, dove viene impostato il colore.

Io uso un colore bianco debole (50, 50, 50) ma puoi scegliere qualsiasi colore ti piaccia. Assicurati solo che il valore colore non sia superiore a 200, per via del modo in cui vengono visualizzati i secondi. Ne parliamo più avanti.

Funzione setHours

La funzione setHours() funziona come la funzione setTicks() , ma visualizza una specifica ora invece di tutte le ore e usa un colore diverso. Ho scelto un colore arancione-rosso caldo per il segno dell’ora.

void setHours(int h) {
  uint32_t hourColor = pixels.Color(200, 50, 0);
  setPixel(h * 5, hourColor);  // h: 0..11 = 12 Hours
}

Funzione setMinutes

Mentre la funzione setHours() illumina solo un singolo LED per l’ora attuale, la funzione setMinutes() illumina tutti i LED fino al minuto attuale. Ecco perché c’è il ciclo for .

void setMinutes(int m) {
  uint32_t minColor = pixels.Color(100, 100, 0);
  for (int i = 0; i <= m; i++) {
    setPixel(i, minColor);
  }
}

Funzione setSeconds

Infine, vogliamo mostrare il secondo attuale. Non volevo visualizzare il secondo sopra ore e minuti già mostrati e ho preferito rendere il LED del secondo attuale un po’ più luminoso – qualunque sia il colore che mostra in quel momento.

void setSeconds(int s) {
  uint32_t color = getPixel(s);
  setPixel(s, color + 0x373737);
}

La funzione prima ottiene il colore del LED al secondo attuale s chiamando getPixel() . Poi rende il colore più luminoso aggiungendo 55 a ciascun valore colore (rosso, verde, blu). 55 in esadecimale è 0x37 . È da qui che viene questo valore di 0x373737 . Ed è il motivo per cui il colore base non può essere superiore a 200 , dato che 200 + 55 = 255 , che è il valore massimo per un colore.

Funzione showTime

Per mostrare un orario ( h, m, s ) sull’orologio basta chiamare le funzioni sopra in questa sequenza e aggiornare lo stato dei LED tramite pixels.show() alla fine.

void showTime(int h, int m, int s) {
  pixels.clear();
  setMinutes(m);
  setTicks(m);  
  setHours(h);
  setSeconds(s);
  pixels.show();
}

Funzione simulateClock

Per testare la visualizzazione dell’orario, uso una semplice simulazione che scorre ore, minuti e secondi 10 volte più velocemente ( delay(100) ).

void simulateClock() {
  for (int h = 0; h < 12; h++) {
    for (int m = 0; m < 60; m++) {
      for (int s = 0; s < 60; s++) {
        showTime(h, m, s);
        delay(100);
      }
    }
  }
}

Funzioni setup e loop

Le funzioni setup e loop ora sono molto semplici. Nella funzione setup inizializziamo l’anello LED. E nella funzione loop simuliamo il funzionamento dell’orologio.

void setup() {
  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

void loop() {
  simulateClock();
}

A parte la visualizzazione dell’orario reale, ora abbiamo tutto per mostrare l’ora sul nostro anello LED. Puoi usare questo codice simulatore per trovare i colori e le visualizzazioni che preferisci, senza doverti preoccupare dell’orario reale da mostrare.

Per maggiori informazioni sui LED WS2812 e sugli effetti che puoi ottenere dai un’occhiata al nostro tutorial How To Control WS2812B Individually Addressable LEDs using Arduino . Nota però che a maggio 2024 non sono riuscito a far funzionare il FastLED library , che viene usato in questo tutorial, con un ESP32.

Nella prossima sezione otteniamo l’orario reale da un provider di orario internet e usiamo il codice sopra per visualizzarlo sul nostro orologio.

Codice per un orologio ad anello LED con orario da web

Potremmo usare l’orologio interno dell’ESP32 per ottenere il segnale orario. Ma questo significa che ogni volta che l’orologio perde alimentazione dovremmo impostare l’orario. Inoltre dovremmo regolare l’orario due volte l’anno quando cambia l’ora legale.

Questo significa che avremmo bisogno di pulsanti o software extra (interfaccia web, app per telefono) per poter impostare l’orario. Tutto ciò è piuttosto scomodo. Preferisco invece avere un orologio completamente automatico ottenendo sempre l’orario esatto da un provider di orario internet.

Se vuoi sapere come funziona in dettaglio dai un’occhiata al nostro tutorial Automatic Daylight Savings Time Clock . In pratica ho copiato e incollato il codice da lì nel codice qui sotto.

#include "WiFi.h"
#include "HTTPClient.h"
#include "ArduinoJson.h"
#include "TimeLib.h"
#include "Adafruit_NeoPixel.h"

#define WIFI_SSID "YOUR_SSID"
#define WIFI_PASSPHRASE "YOUR_PASSWORD"
#define URL "http://worldtimeapi.org/api/ip"

#define DINPIN 3
#define NPIXELS 60
#define OFFSET 29

Adafruit_NeoPixel pixels(NPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);
StaticJsonDocument<2048> doc;

int index(int i) {
  return (i + OFFSET) % NPIXELS;
}

void setPixel(int i, uint32_t color) {
  pixels.setPixelColor(index(i), color);
}

uint32_t getPixel(int i) {
  return pixels.getPixelColor(index(i));
}

void setTicks(int m) {
  uint32_t tickColor = pixels.Color(50, 50, 50);
  for (int h = 0; h < 12; h++) {
    setPixel(h * 5, tickColor);
  }
}

void setHours(int h) {
  uint32_t hourColor = pixels.Color(200, 50, 0);
  setPixel(h * 5, hourColor);  // h: 0..11 = 12 Hours
}

void setMinutes(int m) {
  uint32_t minColor = pixels.Color(100, 100, 0);
  for (int i = 0; i <= m; i++) {
    setPixel(i, minColor);
  }
}

void setSeconds(int s) {
  uint32_t color = getPixel(s);
  setPixel(s, color + 0x373737);
}

void showTime(int h, int m, int s) {
  pixels.clear();
  setMinutes(m);
  setTicks(m);
  setHours(h);
  setSeconds(s);
  pixels.show();
}

void updateDisplay() {
  time_t t = now();
  showTime(hourFormat12(t), minute(t), second(t));
}

bool shouldSyncTime() {
  time_t t = now();
  bool wifi_on = WiFi.status() == WL_CONNECTED;
  bool should_sync = (minute(t) == 0 && second(t) == 3) || (year(t) == 1970);
  return wifi_on && should_sync;
}

void syncTime() {
  delay(1000);
  HTTPClient http;
  http.begin(URL);
  if (http.GET() > 0) {
    String json = http.getString();
    auto error = deserializeJson(doc, json);
    if (!error) {
      int Y, M, D, h, m, s, ms, tzh, tzm;
      sscanf(doc["datetime"], "%d-%d-%dT%d:%d:%d.%d+%d:%d",
             &Y, &M, &D, &h, &m, &s, &ms, &tzh, &tzm);
      setTime(h, m, s, D, M, Y);
    }
  }
  http.end();
}

void setup() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSPHRASE);
  while (WiFi.status() != WL_CONNECTED)
    delay(500);

  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

void loop() {
  if (shouldSyncTime())
    syncTime();
  updateDisplay();
  delay(100);
}

Prima di poter usare questo codice dovrai installare due librerie aggiuntive, ovvero ” ArduinoJson.h ” e ” TimeLib.h “. ” WiFi.h ” e ” HTTPClient.h ” fanno parte della libreria standard ESP32/Arduino e non vanno installate separatamente.

Dovrai anche impostare SSID e password della tua rete WiFi. Questo permetterà all’ESP32 di connettersi al provider di orario internet all’URL indicato:

#define WIFI_SSID "YOUR_SSID"
#define WIFI_PASSPHRASE "YOUR_PASSWORD"
#define URL "http://worldtimeapi.org/api/ip"

Detto ciò, diamo un’occhiata più da vicino alle nuove funzioni aggiunte al codice.

Funzione syncTime

La funzione syncTime() si connette al provider di orario internet, legge i dati in formato JSON, estrae le informazioni di orario rilevanti (h, m, s, D, M, Y) e imposta di conseguenza l’orologio interno dell’ESP32.

void syncTime() {
  delay(1000);
  HTTPClient http;
  http.begin(URL);
  if (http.GET() > 0) {
    String json = http.getString();
    auto error = deserializeJson(doc, json);
    if (!error) {
      int Y, M, D, h, m, s, ms, tzh, tzm;
      sscanf(doc["datetime"], "%d-%d-%dT%d:%d:%d.%d+%d:%d",
             &Y, &M, &D, &h, &m, &s, &ms, &tzh, &tzm);
      setTime(h, m, s, D, M, Y);
    }
  }
  http.end();
}

Funzione shouldSyncTime

Non vogliamo interrogare troppo spesso il provider di orario internet e rischiare di essere bloccati. Per questo limito la sincronizzazione dell’orologio interno dell’ESP32 con il provider di orario internet a una volta all’ora. In particolare, sincronizziamo al terzo secondo dopo ogni ora. La funzione shouldSyncTime() ci dice quando è il momento.

bool shouldSyncTime() {
  time_t t = now();
  bool wifi_on = WiFi.status() == WL_CONNECTED;
  bool should_sync = (minute(t) == 0 && second(t) == 3) || (year(t) == 1970);
  return wifi_on && should_sync;
}

Funzione updateDisplay

La funzione updateDisplay() sostituisce la funzione simulateClock() che usavamo prima. Legge l’orologio interno dell’ESP32, che abbiamo sincronizzato con l’orario internet, e chiama showTime() per visualizzarlo sull’anello LED.

void updateDisplay() {
  time_t t = now();
  showTime(hourFormat12(t), minute(t), second(t));
}

Funzione setup

Nella funzione setup() aggiungiamo la funzionalità per connettersi alla rete WiFi ma per il resto inizializziamo l’anello LED come prima.

void setup() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSPHRASE);
  while (WiFi.status() != WL_CONNECTED)
    delay(500);

  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

Funzione loop

Nella funzione loop() prima controlliamo se dobbiamo sincronizzare l’orario. Se sì, chiamiamo syncTime() per farlo. In ogni caso chiamiamo updateDisplay() per visualizzare l’orario attuale sull’anello LED.

void loop() {
  if (shouldSyncTime())
    syncTime();
  updateDisplay();
  delay(100);
}

Poiché abbiamo un delay(100) nel loop, questo aggiornamento avviene ogni 100ms – ovvero 10 volte al secondo.

E questo è tutto! Così hai un orologio completamente automatico, sempre preciso, che mostra l’orario su un anello LED. Nelle prossime sezioni aggiungiamo la regolazione automatica della luminosità e l’attivazione tramite movimento per renderlo ancora migliore.

Regolazione automatica della luminosità dell’orologio ad anello LED

I LED WS2812 sono molto luminosi. Di solito non li vuoi così luminosi di notte. D’altra parte, se regoli la luminosità per una stanza buia, l’orologio sarà difficile da leggere di giorno. Sarebbe bello regolare automaticamente la luminosità dei LED in base alle condizioni di luce attuali.

A questo scopo, aggiungiamo una resistenza fotosensibile (LDR) al nostro circuito, leggiamo il suo valore tramite un ingresso analogico sull’ESP32 e chiamiamo setBrightness() per regolare la luminosità complessiva dell’anello LED.

Circuito dell’orologio ad anello LED con LDR

L’immagine qui sotto mostra come collegare il sensore di luce (LDR) al circuito esistente. Un pin dell’LDR è collegato a 5V e l’altro pin a un potenziometro da 10KΩ, che a sua volta è collegato a massa.

Wiring of LDR with LED Ring Clock
Collegamento dell’LDR con l’orologio ad anello LED

L’uscita dell’LDR (filo verde) è collegata a GPIO0 dell’ESP32. Qualsiasi altro pin GPIO andrà bene purché possa leggere un segnale analogico.

L’LDR e il potenziometro da 10KΩ formano un partitore di tensione. Per maggiori dettagli su come funziona dai un’occhiata al nostro tutorial How to detect light using an Arduino , da cui proviene questo circuito.

A seconda della luminosità della luce ambiente, il circuito LDR produrrà una tensione proporzionale nell’intervallo da 0V a 5V. In pratica, non si ottiene mai l’intero intervallo, poiché non si avrà mai buio completo o massima luminosità. Il potenziometro da 10KΩ ti permette di impostare un punto di lavoro per la tensione di uscita. Mapperemo le variazioni di tensione in un intervallo adatto nel codice qui sotto.

Codice di test per regolare l’intervallo di luminosità

Prima di aggiungere la funzione di regolazione automatica della luminosità al nostro orologio ad anello LED, dovremo prima regolare i parametri. A seconda della luce ambiente, vogliamo un valore di luminosità tra 5, quando è molto buio, e magari 50, quando è molto luminoso.

Quello che leggiamo dal sensore di luce sull’ingresso analogico, però, saranno valori nell’intervallo 0-4095. E questo dipenderà dalla resistenza di default dell’LDR, dalla regolazione del potenziometro e dalle condizioni di luce ambiente.

Il seguente codice di test ti permette di trovare la giusta mappatura tra i valori del sensore LDR e i valori di luminosità che vogliamo impostare.

#define LDRPIN 0

void setup() {
  Serial.begin(112500);
  pinMode(LDRPIN, INPUT);
}

void loop() {
  int ldrValue = analogRead(LDRPIN);
  Serial.print(ldrValue);

  int brightness = map(ldrValue, 1400, 3000, 5, 50);
  Serial.print(" -> ");
  Serial.println(brightness);

  delay(1000);
}

Legge il valore dell’LDR ( ldrValue ), lo stampa, lo mappa su un valore di luminosità ( brightness ) e stampa anche quello. Se carichi ed esegui questo codice, dovresti vedere qualcosa di simile sul Serial Monitor.

Serial output of test code to adjust brightness
Output seriale del codice di test per regolare la luminosità

Se copri il sensore di luce (buio), vuoi vedere stampato un valore di luminosità vicino a 5, e se esponi il sensore a luce molto intensa (es. luce solare), vuoi ottenere un valore vicino a 50. Per ottenere questo dovrai regolare i parametri fromLow , fromHigh della funzione map:

map(value, fromLow, fromHigh, toLow, toHigh)

Per il mio sensore LDR, la regolazione del potenziometro e le condizioni di luce, sono arrivato a fromLow=1400 e fromHigh=3000 , come puoi vedere nel codice sopra. I tuoi valori saranno diversi ma puoi usare i miei come punto di partenza.

Allo stesso modo, se vuoi un intervallo di luminosità diverso da 5..50 , puoi scegliere valori diversi per toLow e toHigh . Potresti anche spegnere l’orologio di notte scegliendo toLow=0 .

Aggiunta del codice per la regolazione automatica della luminosità

Una volta trovate le impostazioni dei parametri che ti piacciono, puoi aggiungere la seguente funzione di regolazione automatica della luminosità al codice dell’orologio.

void updateBrightness() {
  if (millis() % 1000 < 100) {
    int ldrValue = analogRead(LDRPIN);
    int brightness = map(ldrValue, 1400, 3000, 5, 50);
    pixels.setBrightness(constrain(brightness, 5, 50));
  }
}

Imposta la luminosità dell’intero anello LED in base al valore letto su LDRPIN . Tuttavia, non vogliamo regolare la luminosità ogni volta che aggiorniamo l’orologio, cosa che avviene ogni 100ms. Questo potrebbe causare un fastidioso effetto di sfarfallio, poiché ogni piccolo cambiamento di luce ambiente potrebbe cambiare la luminosità dei LED.

Perciò aggiorniamo la luminosità solo ogni secondo, cosa ottenuta tramite millis() % 1000 < 100. Se vuoi aggiornamenti meno o più frequenti di ogni 1000ms, basta cambiare il 1000 con quello che preferisci. Nota che la soglia di < 100 è legata al ritardo di 100ms nel loop principale.

Nota anche la chiamata extra a onstrain(brightness, 5, 50), che assicura che la luminosità sia nell’intervallo 5..50 , cosa non garantita dalla funzione map() .

Per integrare la regolazione della luminosità nel codice dell’orologio, basta aggiungere una chiamata alla funzione updateBrightness() nel loop principale:

void loop() {
  if (shouldSyncTime())
    syncTime();
  updateBrightness();  
  updateDisplay();
  delay(100);
}

Ovviamente, c’è anche la definizione della costante LDRPIN e l’impostazione di LDRPIN come input. Il codice completo per l’orologio ad anello LED con regolazione automatica della luminosità è mostrato qui sotto:

#include "WiFi.h"
#include "HTTPClient.h"
#include "ArduinoJson.h"
#include "TimeLib.h"
#include "Adafruit_NeoPixel.h"

#define WIFI_SSID "YOUR_SSID"
#define WIFI_PASSPHRASE "YOUR_PASSWORD"
#define URL "http://worldtimeapi.org/api/ip"


#define LDRPIN 0
#define DINPIN 3
#define NPIXELS 60
#define OFFSET 29

Adafruit_NeoPixel pixels(NPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);
StaticJsonDocument<2048> doc;

int index(int i) {
  return (i + OFFSET) % NPIXELS;
}

void setPixel(int i, uint32_t color) {
  pixels.setPixelColor(index(i), color);
}

uint32_t getPixel(int i) {
  return pixels.getPixelColor(index(i));
}

void setTicks(int m) {
  uint32_t tickColor = pixels.Color(50, 50, 50);
  for (int h = 0; h < 12; h++) {
    setPixel(h * 5, tickColor);
  }
}

void setHours(int h) {
  uint32_t hourColor = pixels.Color(200, 50, 0);
  setPixel(h * 5, hourColor);  // h: 0..11 = 12 Hours
}

void setMinutes(int m) {
  uint32_t minColor = pixels.Color(100, 100, 0);
  for (int i = 0; i <= m; i++) {
    setPixel(i, minColor);
  }
}

void setSeconds(int s) {
  uint32_t color = getPixel(s);
  setPixel(s, color + 0x373737);
}

void showTime(int h, int m, int s) {
  pixels.clear();
  setMinutes(m);
  setTicks(m);
  setHours(h);
  setSeconds(s);
  pixels.show();
}

void updateDisplay() {
  time_t t = now();
  showTime(hourFormat12(t), minute(t), second(t));
}

bool shouldSyncTime() {
  time_t t = now();
  bool wifi_on = WiFi.status() == WL_CONNECTED;
  bool should_sync = (minute(t) == 0 && second(t) == 3) || (year(t) == 1970);
  return wifi_on && should_sync;
}

void syncTime() {
  delay(1000);
  HTTPClient http;
  http.begin(URL);
  if (http.GET() > 0) {
    String json = http.getString();
    auto error = deserializeJson(doc, json);
    if (!error) {
      int Y, M, D, h, m, s, ms, tzh, tzm;
      sscanf(doc["datetime"], "%d-%d-%dT%d:%d:%d.%d+%d:%d",
             &Y, &M, &D, &h, &m, &s, &ms, &tzh, &tzm);
      setTime(h, m, s, D, M, Y);
    }
  }
  http.end();
}

void updateBrightness() {
  if (millis() % 1000 < 100) {
    int ldrValue = analogRead(LDRPIN);
    int brightness = map(ldrValue, 1400, 3000, 5, 50);
    pixels.setBrightness(constrain(brightness, 5, 50));
  }
}

void setup() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSPHRASE);
  while (WiFi.status() != WL_CONNECTED)
    delay(500);

  pinMode(LDRPIN, INPUT);

  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

void loop() {
  if (shouldSyncTime())
    syncTime();
  updateBrightness();
  updateDisplay();
  delay(100);
}

Uff, ci siamo quasi. Come ultimo miglioramento, accendiamo automaticamente l’orologio se viene rilevato un movimento.

Attivazione automatica dell’orologio ad anello LED

L’orologio ad anello LED consuma parecchia energia e c’è il rischio di bruciare i LED se l’orologio resta acceso tutto il tempo. Ma non ha senso mostrare l’ora se non c’è nessuno a guardarla. Perciò aggiungiamo un sensore di movimento (PIR) al circuito. Accenderà l’orologio per un periodo specificato (es. 10 secondi) se viene rilevato movimento e poi spegnerà l’anello LED.

L’immagine seguente mostra come aggiungere il sensore di movimento PIR al circuito esistente. Basta collegare 5V e GND ai pin corrispondenti del sensore PIR (fili rosso e blu). L’uscita del sensore (filo viola) è collegata a GPIO10 dell’ESP32.

Wiring of PIR sensor with LED Ring Clock
Collegamento del sensore PIR con l’orologio ad anello LED

Ecco una foto del circuito completo su breadboard. Come puoi vedere, sono riuscito a far funzionare l’orologio ad anello LED per i test anche con una batteria da 9V (con un regolatore di tensione a 5V).

LED Ring Clock with PIR sensor and Battery
Orologio ad anello LED con sensore PIR e batteria

Tuttavia, se vuoi davvero far funzionare l’orologio a batteria ti consiglio di usare un power bank USB. Potresti anche voler mettere l’ESP32 in deep-sleep mentre l’orologio è inattivo. Per maggiori dettagli su questo dai un’occhiata al nostro tutorial su How to Build a Motion Activated Night Light .

Prima di integrare l’attivazione automatica nel codice dell’orologio, scriviamo prima un codice di test per il sensore PIR.

Codice di test per il sensore PIR

Il codice seguente legge il segnale dal sensore PIR e stampa “motion detected” se viene rilevato movimento o “nothing” altrimenti.

#define PIRPIN 10

unsigned long lastMotion= 0;

bool motionDetected() {
  if (digitalRead(PIRPIN))
    lastMotion = millis();
  unsigned long diff = millis() - lastMotion;
  return diff < 10000 && diff >= 0;
}

void setup() {
  Serial.begin(115200);
  pinMode(PIRPIN, INPUT);
}

void loop() {
  if (motionDetected()) {
    Serial.println("motion detected");
  } else {
    Serial.println("nothing");
  }
  delay(100);
}

Il sensore di movimento che sto usando qui è il AM312 , che emetterà un segnale alto solo per circa 2 secondi dopo il primo movimento rilevato. Per maggiori dettagli sul AM312 e su come usarlo dai un’occhiata al nostro tutorial How to Build a Motion Activated Night Light .

Se usiamo direttamente il segnale dell’AM312 nel nostro codice, l’orologio si attiverebbe solo per 2 secondi e poi si spegnerebbe di nuovo se non ci sono altri movimenti. Questo comporta un frequente accendi/spegni che non è molto gradevole. Il codice sopra quindi aggiunge un timer che fa sì che motionDetected() restituisca true per almeno 10000ms = 10 secondi. Puoi allungare o accorciare questo periodo come preferisci.

Se costruisci il circuito, carichi ed esegui il codice, e funziona come previsto, sei pronto per integrare il codice di attivazione automatica nel codice dell’orologio.

Aggiunta del codice per l’attivazione automatica

Per l’integrazione, aggiungeremo due funzioni e cambieremo anche un po’ il loop principale.

Funzione motionDetected

La funzione motionDetected() si basa sul codice di test sopra e restituisce true per almeno 10 secondi dopo il primo movimento rilevato. Il timer di 10 secondi si resetta ogni volta che viene rilevato nuovo movimento durante questo periodo. Così l’orologio resta acceso finché c’è qualcuno nei paraggi che si muove.

bool motionDetected() {
  if (digitalRead(PIRPIN))
    lastMotion = millis();
  unsigned long diff = millis() - lastMotion;
  return diff < 10000 && diff >= 0;
}

Nota la condizione diff >= 0 nell’istruzione return. Serve perché il valore del timer restituito da millis() wrap dopo alcuni giorni (49 per un Arduino UNO) e la differenza di tempo diff sarebbe negativa.

Funzione clearDisplay

Se non viene rilevato movimento, spegniamo i LED dell’orologio chiamando clearDisplay() . Questa funzione semplicemente cancella tutti i valori dei pixel impostati e poi aggiorna lo stato dei LED chiamando show() .

void clearDisplay() {
  pixels.clear();
  pixels.show();
}

Funzione loop

Infine, dobbiamo modificare la funzione loop per reagire al sensore di movimento. Se rileviamo movimento tramite motionDetected() , eseguiamo il solito aggiornamento dell’orologio. Altrimenti spegniamo la visualizzazione dell’orologio tramite clearDisplay() . Tutto questo avviene ogni 1/10 di secondo grazie al delay(100) .

void loop() {
  if (motionDetected()) {
    if (shouldSyncTime())
      syncTime();
    updateBrightness();
    updateDisplay();
  } else {
    clearDisplay();
  }
  delay(100);
}

E questo è tutto. Ora abbiamo tutti i pezzi necessari. Qui sotto trovi il codice completo per l’orologio attivato dal movimento.

Codice completo per orologio attivato dal movimento

#include "WiFi.h"
#include "HTTPClient.h"
#include "ArduinoJson.h"
#include "TimeLib.h"
#include "Adafruit_NeoPixel.h"

#define WIFI_SSID "YOUR_SSID"
#define WIFI_PASSPHRASE "YOUR_PASSWORD"
#define URL "http://worldtimeapi.org/api/ip"

#define PIRPIN 10
#define LDRPIN 0
#define DINPIN 3
#define NPIXELS 60
#define OFFSET 29

Adafruit_NeoPixel pixels(NPIXELS, DINPIN, NEO_GRB + NEO_KHZ800);
StaticJsonDocument<2048> doc;
unsigned long lastMotion = 0;

int index(int i) {
  return (i + OFFSET) % NPIXELS;
}

void setPixel(int i, uint32_t color) {
  pixels.setPixelColor(index(i), color);
}

uint32_t getPixel(int i) {
  return pixels.getPixelColor(index(i));
}

void setTicks(int m) {
  uint32_t tickColor = pixels.Color(50, 50, 50);
  for (int h = 0; h < 12; h++) {
    setPixel(h * 5, tickColor);
  }
}

void setHours(int h) {
  uint32_t hourColor = pixels.Color(200, 50, 0);
  setPixel(h * 5, hourColor);  // h: 0..11 = 12 Hours
}

void setMinutes(int m) {
  uint32_t minColor = pixels.Color(100, 100, 0);
  for (int i = 0; i <= m; i++) {
    setPixel(i, minColor);
  }
}

void setSeconds(int s) {
  uint32_t color = getPixel(s);
  setPixel(s, color + 0x373737);
}

void showTime(int h, int m, int s) {
  pixels.clear();
  setMinutes(m);
  setTicks(m);
  setHours(h);
  setSeconds(s);
  pixels.show();
}

void updateDisplay() {
  time_t t = now();
  showTime(hourFormat12(t), minute(t), second(t));
}

bool shouldSyncTime() {
  time_t t = now();
  bool wifi_on = WiFi.status() == WL_CONNECTED;
  bool should_sync = (minute(t) == 0 && second(t) == 3) || (year(t) == 1970);
  return wifi_on && should_sync;
}

void syncTime() {
  delay(1000);
  HTTPClient http;
  http.begin(URL);
  if (http.GET() > 0) {
    String json = http.getString();
    auto error = deserializeJson(doc, json);
    if (!error) {
      int Y, M, D, h, m, s, ms, tzh, tzm;
      sscanf(doc["datetime"], "%d-%d-%dT%d:%d:%d.%d+%d:%d",
             &Y, &M, &D, &h, &m, &s, &ms, &tzh, &tzm);
      setTime(h, m, s, D, M, Y);
    }
  }
  http.end();
}

void updateBrightness() {
  if (millis() % 1000 < 100) {
    int ldrValue = analogRead(LDRPIN);
    int brightness = map(ldrValue, 1400, 3000, 5, 50);
    pixels.setBrightness(constrain(brightness, 5, 50));
  }
}

bool motionDetected() {
  if (digitalRead(PIRPIN))
    lastMotion = millis();
  unsigned long diff = millis() - lastMotion;
  return diff < 10000 && diff >= 0;
}

void clearDisplay() {
  pixels.clear();
  pixels.show();
}

void setup() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSPHRASE);
  while (WiFi.status() != WL_CONNECTED)
    delay(500);

  pinMode(PIRPIN, INPUT);
  pinMode(LDRPIN, INPUT);

  pixels.begin();
  pixels.clear();
  pixels.setBrightness(10);
  pixels.show();
}

void loop() {
  if (motionDetected()) {
    if (shouldSyncTime())
      syncTime();
    updateBrightness();
    updateDisplay();
  } else {
    clearDisplay();
  }
  delay(100);
}

Conclusioni

In questo tutorial hai imparato come costruire un orologio ad anello LED attivato dal movimento, con regolazione automatica della luminosità e sincronizzazione dell’orario via internet. Ha il vantaggio di essere sempre preciso e completamente automatico. Non servono pulsanti o manopole per accenderlo o spegnerlo, impostare l’orario o regolare la luminosità.

Oltre a costruire un telaio migliore per l’orologio e cambiare i colori dei LED a tuo piacimento, ci sono altri miglioramenti che puoi aggiungere. Si potrebbe aggiungere un sensore di temperatura che regola il colore dei LED per indicare la temperatura ambiente. Ad esempio, più blu quando fa freddo e più rosso quando fa caldo.

Poiché l’orologio è attivato dal movimento, sarebbe possibile farlo funzionare a batteria, soprattutto mettendo l’ESP32 in deep-sleep mentre il display è inattivo. Dai un’occhiata al nostro tutorial su How to Build a Motion Activated Night Light come esempio.

Infine, potresti anche alternare ogni tot secondi tra la visualizzazione dell’orario e una rappresentazione della data attuale, dato che stiamo già scaricando anche le informazioni sulla data dal provider di orario internet. Invece di worldtimeapi.org, potresti anche usare un server SNTP che ti permette di sincronizzare l’orario più spesso. Vedi il nostro tutorial su How to synchronize ESP32 clock with SNTP server .

E se proprio non ti piace l’orologio ad anello, dai un’occhiata a questo tutorial Digital Clock with CrowPanel 3.5″ ESP32 Display , che spiega come mostrare ora e data su un bel display, proprio come un orologio digitale tradizionale.

Tante cose con cui divertirsi!

Se hai domande sentiti libero di scriverle. Felice di aiutare ; )