Orologio con Arduino

In questo articolo ti mostro come è possibile realizzare un orologio con Arduino.
DigitSpace
Prima di passare al contenuto è doveroso ringraziare il sito DigitSpace per avermi fornito parte del materiale di questo progetto.
Come promesso iniziamo a fare qualcosa di più interessante mettendo assieme quelle che sono state le conoscenze apprese nei precedenti articoli.
Abbiamo visto come collegare un display OLED ad Arduino, quindi abbiamo giocato un pò con un encoder rotativo e scoperto come interfacciare il modulo RTC DS1307 al micro controllore.
Bene!
A questo punto possiamo pensare di mettere insieme queste nozioni per realizzare qualcosa di più intrigante.
Premessa
Di certo lo scopo dell’articolo non è quello di farti risparmiare i costi relativi all’acquisto di un orologio.
Non è costruendoci da soli una sveglia che risolleveremo le nostre economie, ma di certo sarà un ottimo spunto per tutti quei progetti in cui vorremo far comparire a “video” informazioni relative alla data e all’ora con possibilità (ed è questa la parte interessante) di regolazione senza l’ausilio del PC.
In Breve
Mettendo assieme quindi quello che ti ho mostrato nei precedenti articoli, andremo a realizzare un orologio costituito da un display OLED 128x64px per mostrare a video la data e l’ora, un modulo RTC DS1307 per la parte di gestione del tempo, ed un encoder rotativo HW-040/YK-040 per la regolazione ed il salvataggio delle impostazioni.
Codice orologio con Arduino
Il codice è abbastanza semplice, solo che per via dei commenti può sembrare un po complesso. In realtà non lo è, quindi magari ti consiglio di ripulirlo e tenere a fianco la versione commentata per capire riga dopo riga cosa ho fatto e perché.
/* Orologio con Arduino Autore : Andrea Lombardo Web : http://www.lombardoandrea.com Post : https://wp.me/p27dYH-Q2 */ // Inclusione librerie #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <RTClib.h> #include <Bounce2.h> /* Definisco le dimensioni del display, serviranno per creare l'istanza del display e magari riutilizzarle nel codice qualora dovessero servirmi queste informazioni */ #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 // Delay del debounce per il bottone "mode" #define DEBOUNCE_DELAY 15 /* Per alleggerire il numero di operazioni da far eseguire ad Arduino limito la lettura dei dati dal modulo RTC ad ogni secondo piUttosto che ad ogni ciclo del loop, tanto sarebbe inutile. */ #define DELAY_RTC_READINGS 1000 // Definisco pin per encoder rotativo KY-040 / HW-040 const unsigned int pinClk = 2; const unsigned int pinDt = 3; const unsigned int pinSwMode = 5; // Creo un enum per definire due modalità "user-friendly" typedef enum Mode { MODE_NORMALE, MODE_REGOLA }; /* In base allo stato di questa variabile siamo in modalità "Regolazione" o "Normale" */ Mode mode = MODE_NORMALE; // Do per scontato che sono in modalità "Normale" // Creo istanza del display Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT); // Creo istanza modulo DS1307 RTC_DS1307 rtc; // Creo istanza pulsante (incorporato nell'encoder) Bounce btnMode = Bounce(); // Conservo istante ultima lettura dal modulo RTC unsigned long lastRTCReads; // Conservo ultima posizione del pin Clk dell'encoder unsigned int prevClk; /* Contatore dell'encoder che verrà utilizzato in fase di regolazione della data/ora */ unsigned int countEncoder; /* Da quanti step è composta la nostra fase di regolazione? 1 = REGOLO IL GIORNO 2 = REGOLO IL MESE 3 = REGOLO L'ANNO 4 = REGOLO L'ORA 5 = REGOLO I MINUTI 6 = REGOLO I SECONDI */ const unsigned int numStep = 6; // Tengo traccia dello step corrente durante fase di regolazione unsigned int stepRegolazione = 0; /* Creo un array che conterrà i valori numerici durante la reimpostazione della data/ora La classe DateTime prende in considerazione gli anni dal 2000 al 2099. Ce li faremo bastare! */ int newDateTime[numStep] = {1, 1, 20, 0, 0, 0}; void setup() { //Definizione modalità dei pin pinMode(pinClk, INPUT); pinMode(pinDt, INPUT); pinMode(pinSwMode, INPUT_PULLUP); btnMode.attach(pinSwMode); btnMode.interval(DEBOUNCE_DELAY); /* Per essere sicuro che vengano intercettati tutti i cambiamenti di stato dell'encoder sfrutto le potenzialità degli interrupt di Arduino. */ attachInterrupt(digitalPinToInterrupt(pinClk), checkEncoder, CHANGE); //Provo ad inizializzare il display all'indirizzo 0x3C (il tuo potrebbe essere diverso) if (!display.begin( SSD1306_SWITCHCAPVCC, 0x3C)) { /* Se non sono riuscito ad inizializzare il display creo un loop infinito ed impedisco al programma di andare avanti */ while (true); } // Pulisco il buffer display.clearDisplay(); // Applico la pulizia al display display.display(); //Inizializza comunicazione I2C con modulo DS1307 rtc.begin(); /* Quando il modulo viene acceso per la prima volta o risulta essere sconfigurato a causa della perdita della memoria (esempio batteria scarica) il metodo rtc.isrunning() restituisce false Ho scritto un funzione per essere utilizzata sia qui in fase di setup sia durante l'esecuzione del programma. Se il modulo è sconfigurato passa in automatico in modalità "Regolazione" */ checkRTC(); //Inizializzo variabili lastRTCReads = millis(); //Eseguo prima lettura del valore del pin Clk dell'encoder prevClk = digitalRead(pinClk); // Inizializzo la variabile contatore dell'encoder countEncoder = 1; } void loop() { // Aggiorna lettura del bottone "mode" btnMode.update(); if (mode == MODE_REGOLA) { // Se ci troviamo in modalità di regolazione /* Per comodità e pulizia del codice sposto le azioni di regolazione in un altra funzione */ editMode(); } else { // Se ci troviamo in modalità normale /* Per comodità e pulizia del codice sposto le azioni della fase normale in un altra funzione */ normalMode(); }//Fine modalità normale }//Fine loop /* Interpreta i movimenti dell'encoder Maggiori info qui -> https://wp.me/p27dYH-PP Questa funzione viene chiamata tramite interrupt */ void checkEncoder() { int currClk = digitalRead(pinClk); int currDt = digitalRead(pinDt); if (currClk != prevClk) { if (currDt == currClk) { countEncoder --; } else { countEncoder ++; } prevClk = currClk; } } /* Operazioni da eseguire quando siamo in modalità normale */ void normalMode() { /* Se siamo in modalità normale la pressione del bottone mode ci fa passare in modalità "Regolazione" */ if (btnMode.fell()) { mode = MODE_REGOLA; // Resetta contatore encoder e contatore step prima di passare in modalità regolazione countEncoder = 1; stepRegolazione = 0; } if (millis() - lastRTCReads >= DELAY_RTC_READINGS) { // Aggiorna variabile per reimpostare il delay del prossimo ciclo lastRTCReads = millis(); // Ripulisco display ad ogni ciclo display.clearDisplay(); // Mostro a video contenuti statici printStaticContent(); /* Ad ogni passaggio controlla se il modulo RTC è ancora in buone condizioni o si è sconfigurato. Se si è sconfigurato passerò in modalità "Regolazione" */ checkRTC(); // Eseguo lettura valori dal modulo RTC DateTime data = rtc.now(); /* Formati passati al metodo toString() della classe DateTime per ottenere due stringhe da stampare in due sezioni differenti del display. */ char formatoData[] = "DD-MM-YYYY"; char formatoOra[] = "hh:mm:ss"; display.setTextColor(WHITE); display.setTextSize(2); display.setCursor(4, 22); display.print(data.toString(formatoData)); display.setCursor(16, 40); display.print(data.toString(formatoOra)); //Mando in stampa le modifiche display.display(); } } /* Operazioni da eseguire quando siamo in modalità Regolazione */ void editMode() { // Ripulisco display ad ogni ciclo display.clearDisplay(); // Setto dimensione font e colore display.setTextSize(2); display.setTextColor( WHITE); // Mi posiziono nella parte alta del display display.setCursor(0, 0); // In base al numero dello step mostro e regolo una porzione della data/ora switch (stepRegolazione) { case 0: // Se siamo in posizione 0 vogliamo regolare il giorno // E facciamo si che non si vada oltre i valori consentiti per il numero relativo al giorno if (countEncoder > 31 ) { countEncoder = 1; } else if ( countEncoder < 1) { countEncoder = 31; } display.print("SET GIORNO"); if (countEncoder < 10) { display.setCursor((SCREEN_WIDTH / 2 ) - 10, 24); } else { display.setCursor((SCREEN_WIDTH / 2 ) - 21, 24); } display.setTextSize(4); display.print(countEncoder); newDateTime[stepRegolazione] = countEncoder; // Alla pressione del pomello vado avanti con gli step e reimposto il contatore if (btnMode.fell()) { countEncoder = 1; stepRegolazione++; } break; case 1: // Se siamo in posizione 1 vogliamo regolare il mese // E facciamo si che non si vada oltre i valori consentiti per il numero relativo al mese if (countEncoder > 12 ) { countEncoder = 1; } else if ( countEncoder < 1) { countEncoder = 12; } display.print("SET MESE"); if (countEncoder < 10) { display.setCursor((SCREEN_WIDTH / 2 ) - 10, 24); } else { display.setCursor((SCREEN_WIDTH / 2 ) - 21, 24); } display.setTextSize(4); display.print(countEncoder); newDateTime[stepRegolazione] = countEncoder; // Alla pressione del pomello vado avanti con gli step e reimposto il contatore if (btnMode.fell()) { countEncoder = 1; stepRegolazione++; } break; case 2: // Se siamo in posizione 2 vogliamo regolare l'anno // E facciamo si che non si vada oltre i valori consentiti dalla classe DateTime 2000 - 2099 if (countEncoder > 99 ) { countEncoder = 0; } else if ( countEncoder < 0) { countEncoder = 99; } display.print("SET ANNO"); if (countEncoder < 10) { display.setCursor((SCREEN_WIDTH / 2 ) - 10, 24); } else { display.setCursor((SCREEN_WIDTH / 2 ) - 21, 24); } display.setTextSize(4); display.print(countEncoder); newDateTime[stepRegolazione] = countEncoder; // Alla pressione del pomello vado avanti con gli step e reimposto il contatore if (btnMode.fell()) { countEncoder = 1; stepRegolazione++; } break; case 3: // Se siamo in posizione 3 vogliamo regolare l'ora // E facciamo si che non si vada oltre i valori consentiti per il numero relativo all'ora if (countEncoder > 23 ) { countEncoder = 0; } else if ( countEncoder < 0) { countEncoder = 23; } display.print("SET ORA"); if (countEncoder < 10) { display.setCursor((SCREEN_WIDTH / 2 ) - 10, 24); } else { display.setCursor((SCREEN_WIDTH / 2 ) - 21, 24); } display.setTextSize(4); display.print(countEncoder); newDateTime[stepRegolazione] = countEncoder; // Alla pressione del pomello vado avanti con gli step e reimposto il contatore if (btnMode.fell()) { countEncoder = 0; stepRegolazione++; } break; case 4: // Se siamo in posizione 4 vogliamo regolare i minuti // E facciamo si che non si vada oltre i valori consentiti per il numero relativo ai minuti if (countEncoder > 59 ) { countEncoder = 0; } else if ( countEncoder < 0) { countEncoder = 59; } display.print("SET MINUTI"); if (countEncoder < 10) { display.setCursor((SCREEN_WIDTH / 2 ) - 10, 24); } else { display.setCursor((SCREEN_WIDTH / 2 ) - 21, 24); } display.setTextSize(4); display.print(countEncoder); newDateTime[stepRegolazione] = countEncoder; // Alla pressione del pomello vado avanti con gli step e reimposto il contatore if (btnMode.fell()) { countEncoder = 0; stepRegolazione++; } break; case 5: // Se siamo in posizione 5 vogliamo regolare i secondi (anche se francamente me lo eviterei) // E facciamo si che non si vada oltre i valori consentiti per il numero relativo ai secondi if (countEncoder > 59 ) { countEncoder = 0; } else if ( countEncoder < 0) { countEncoder = 59; } display.setTextSize(1); display.print("SET "); display.setTextSize(2); display.print("SECONDI"); if (countEncoder < 10) { display.setCursor((SCREEN_WIDTH / 2 ) - 10, 24); } else { display.setCursor((SCREEN_WIDTH / 2 ) - 21, 24); } display.setTextSize(4); display.print(countEncoder); newDateTime[stepRegolazione] = countEncoder; // Alla pressione del pomello vado avanti con gli step e reimposto il contatore if (btnMode.fell()) { countEncoder = 1; stepRegolazione++; } break; case 6: /* Se siamo in posizione 6 abbiamo finito di regolare l'orologio quindi mostriamo un prompt di conferma ed in caso di risposta affermativa salviamo le modifiche */ if (countEncoder > 1 ) { countEncoder = 0; } else if ( countEncoder < 0) { countEncoder = 1; } display.print("CONFERMI?"); display.setCursor(10, 24); display.setTextSize(2); if (countEncoder) { display.setTextColor(BLACK, WHITE); display.print(" SI "); display.setTextColor(WHITE); display.print(" NO "); } else { display.setTextColor( WHITE); display.print(" SI "); display.setTextColor(BLACK, WHITE); display.print(" NO "); display.setTextColor( WHITE); } /* Alla pressione del pomello: se il valore del contatore è 1 allora salvo altrimenti abbiamo perso solo tempo ;-) */ if (btnMode.fell()) { if (countEncoder) { /* SALVO LE IMPOSTAZIONI Uno dei formati accettai per l'istanza dalla classe DateTime è quello americano DateTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec) */ rtc.adjust(DateTime(newDateTime[2], newDateTime[1], newDateTime[0], newDateTime[3], newDateTime[4], newDateTime[5])); } // Ritorno in modalità normale mode = MODE_NORMALE; } break; } //Mando in stampa le modifiche al display display.display(); } /* Per comodità creo una funzione per disegnare sul display eventuali contenuti statici come linee o scritte fisse che rimarranno inalterate per tutta l'esecuzione del programma (o parte) In questo caso disegno un rettangolo alto 16px e lungo 128px e lo posiziono nella parte più alta del display. Al suo interno inserisco una scritta in negativo e la posiziono al centro. */ void printStaticContent() { display.fillRect(0, 0, SCREEN_WIDTH, 16, WHITE); display.setTextColor(BLACK, WHITE); display.setTextSize(1); display.setCursor(10, 4); display.print("LOMBARDOANDREA.COM"); } /* Controlla se il modulo RTC è configurato. Se non lo è setta la modalità in "Regolazione" */ void checkRTC() { if (!rtc.isrunning()) { mode = MODE_REGOLA; countEncoder = 1; stepRegolazione = 0; } }
Schema orologio con Arduino
Come visto in precedenza sia il display, sia il modulo RTC, sfruttano l’interfaccia I2C per la comunicazione con Arduino, quindi per comodità di collegamento ho collegato “in cascata” al modulo RTC 1307 il display OLED.

Orologio con Arduino funzionamento
Cosa accadrà quindi una volta messo in moto?
- Controllo stato di configurazione modulo RTC all’avvio.
- Se modulo OK tutti contenti.
- Se modulo non configurato entra in modalità REGOLAZIONE.
- Ruotando l’encoder definisco il numero da assegnare alla porzione della data che sto configurando.
- Ad ogni pressione del pomello passo alla porzione di data successiva.
- Se ho completato tutti gli step di configurazione, mi verrà chiesta conferma per il salvataggio.
- Se tutto ok torna in modalità NORMALE.
- Per entrare in modalità REGOLAZIONE manualmente durante la fase NORMALE, premo il pomello.
Non abbiamo di certo scoperto l’acqua calda, ne tantomeno reinventato la ruota, abbiamo semplicemente visto un sistema, a mio avviso semplice, per gestire e regolare un orologio all’interno dei nostri progetti con Arduino.
Inoltre ho giocato un po con il display per creare il prompt di conferma con i “bottoni” SI e NO che si evidenziano quando selezionati.
A fine pagina trovi il link per scaricare il pacchetto con codici e schemi di collegamento. Come sempre ti ricordo che acquistando prodotti Amazon passando attraverso i link del mio sito, io percepisco una piccola commissione (parliamo di centesimi) in buoni regalo. Questi buoni sommati alle eventuali donazioni PayPal, servono a mantenere attivo il sito web e ad acquistare nuovi componenti.
Commentati Recentemente