Orologio con Arduino

Orologio con Arduino
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;
  }
}
Componenti Orologio con Arduino
Componenti Orologio con Arduino

Orologio con Arduino montato
Orologio con Arduino montato

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.

Schema di collegamento Orologio con Arduino
Schema di collegamento Orologio con Arduino

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.





Potrebbero interessarti anche...

Available for Amazon Prime