Ρολόι με Ξυπνητήρι με το Arduino

arduino_alarm_clock_sm2
arduino_alarm_clock_sm2arduino_alarm_clock_smarduino_clock_sm
4.25 5 4 Product


Περιγραφή

Εδώ παρουσιάζουμε μία απλή εφαρμογή με το αναπτυξιακό σύστημα “Arduino Uno”. Πρόκειται για ένα ηλεκτρονικό ρολόι με ξυπνητήρι που χρησιμοποιεί αλφαριθμητική οθόνη LCD και 6 πλήκτρα. Στην οθόνη απεικονίζεται η τρέχουσα ώρα σε πραγματικό χρόνο, η τρέχουσα ημέρα της εβδομάδας και η προγραμματισμένη ώρα αφύπνισης (ώρα που ηχεί το ξυπνητήρι). Χρησιμοποιούμε 5 πλήκτρα (buttons) για τη ρύθμιση της ημέρας, της ώρας και των λεπτών του ρολογιού καθώς και για τη ρύθμιση της ώρας και των λεπτών του ξυπνητηριού, αντίστοιχα. Χρησιμοποιούμε επίσης ένα ακόμη πλήκτρο για την απενεργοποίηση του ξυπνητηριού. Το ξυπνητήρι ηχεί για ένα λεπτό καθημερινά, στην προγραμματισμένη ώρα αφύπνισης.

Χρησιμοποιούμε μία απλή οθόνη LCD 2x16 (2 γραμμές/16 χαρακτήρες ανά γραμμή). Στην πρώτη γραμμή της οθόνης απεικονίζεται η τρέχουσα ημέρα της εβδομάδας και στη δεύτερη γραμμή απεικονίζονται η τρέχουσα ώρα και η προγραμματιζόμενη ώρα αφύπνισης (βλ. φωτογραφία 1). Η τρέχουσα ώρα απεικονίζεται με ακρίβεια δευτερολέπτου και η ώρα αφύπνισης απεικονίζεται με ακρίβεια ενός λεπτού της ώρας. Το ρολόι λειτουργεί σε 24ωρη βάση και ο διαχωρισμός της ώρας - λεπτών και των λεπτών - δευτερολέπτων γίνεται με χρήση του χαρακτήρα «:». Επίσης, χρησιμοποιούμε το πρόθεμα «AL» για την απεικόνιση της προγραμματισμένης ώρας αφύπνισης. Όταν απενεργοποιηθεί το ξυπνητήρι, αντί της προγραμματισμένης ώρας αφύπνισης απεικονίζεται η ένδειξη “ OFF ”. 

Η οθόνη του ρολογιού
Φωτογραφία 1. Η οθόνη του ρολογιού

Το ξυπνητήρι υλοποιείται με έναν βομβητή (buzzer) που ενεργοποιείται – απενεργοποιείται από έναν ακροδέκτη I/O (ψηφιακής εισόδου-εξόδου) του μικροελεγκτή. Επίσης, το ρολόι χρησιμοποιεί και ένα απλό LED, το οποίο αναβοσβήνει ρυθμικά κάθε δευτερόλεπτο, δεικνύοντας με αυτό τον τρόπο την ακριβή απαρίθμηση του χρόνου. 
 

Συνδεσμολογία

Χρησιμοποιούμε μία αλφαριθμητική οθόνη LCD, συμβατή με HD44780. Επιλέγουμε μεταφορά δεδομένων στην οθόνη μέσω nibbles (4αδες bit). Έτσι, δεσμεύουμε 4εις ακροδέκτες I/O για τη μεταφορά δεδομένων και 2 ακροδέκτες I/O για τις γραμμές ελέγχου RS και Ε της οθόνης. Η γραμμή ελέγχου RW συνδέεται μόνιμα σε λογικό 0 (γείωση).

Για το δίαυλο δεδομένων χρησιμοποιούμε τους ακροδέκτες Ι/Ο 5,4,3,2 που συνδέονται στους ακροδέκτες D4, D5, D6 και D7 της οθόνης, αντίστοιχα. Για τις γραμμές ελέγχου RS και E, χρησιμοποιούμε τους ακροδέκτες I/O 12 και 11, αντίστοιχα. Η συνδεσμολογία που περιγράφουμε εδώ, απεικονίζεται αναλυτικά στην εικόνα 1: 

Η σύνδεση της οθόνης με το Arduino Uno
Εικόνα 1. Η σύνδεση της οθόνης με το Arduino Uno

Χρησιμοποιούμε επίσης 6 πλήκτρα για τις ρυθμίσεις του ρολογιού. Τα πλήκτρα είναι τύπου button που είναι ανοικτά (normally open) και ενεργοποιούνται (κλείνουν) σε κάθε πάτημα. Έκαστο πλήκτρο είναι ένας απλός διακόπτης με δύο ακροδέκτες. Σε έκαστο πλήκτρο, ο ένας ακροδέκτης συνδέεται μόνιμα με τη γη (ground) και ο άλλος ακροδέκτης συνδέεται σε κάποιον ακροδέκτη I/O του μικροελεγκτή. Τα 6 πλήκτρα χρησιμοποιούν τους ακροδέκτες I/O 8, 9, 10 και Α0, Α1, Α2, αντίστοιχα. Για το buzzer (βομβητή) και το ενδεικτικό LED χρησιμοποιούμε τους ακροδέκτες I/O Α5 και 13, αντίστοιχα. Η συνδεσμολογία των πλήκτρων ρύθμισης, του buzzer και του ενδεικτικού LED, απεικονίζεται αναλυτικά στην εικόνα 2.

Σε σειρά με το LED συνδέουμε μία αντίσταση των 220Ω για τον περιορισμό του ρεύματος, ενώ ο βομβητής (buzzer) λειτουργεί με 5V και συνδέεται απευθείας, δίχως αντίσταση.

Οι γραμμές I/O των πλήκτρων βρίσκονται κανονικά σε λογικό 1, μέσω των εσωτερικών αντιστάσεων pull-up του μικροελεγκτή που τις ενεργοποιούμε κατάλληλα μέσω κώδικα. Σε κάθε πάτημα έκαστου πλήκτρου, η αντίστοιχη γραμμή I/O γειώνεται μέσω του διακόπτη του πλήκτρου και μεταβαίνει σε λογικό 0.    

 Η σύνδεση των πλήκτρων ρύθμισης, του buzzer και του ενδεικτικού LED
Εικόνα 2. Η σύνδεση των πλήκτρων ρύθμισης, του buzzer και του ενδεικτικού LED

Ο κώδικας

Το ρολόι πραγματικού χρόνου

Το ρολόι πραγματικού χρόνου υλοποιείται μέσω της τεχνικής απαρίθμησης χρόνου μέσω ρουτίνας διακοπής (interrupt driven clock). Χρησιμοποιούμε μία μεταβλητή τύπου long ως μετρητή δευτερολέπτων. Η τιμή της μεταβλητής αυξάνεται κατά 1 σε κάθε κτύπο του ρολογιού που προέρχεται από μία ρουτίνα διακοπής. Η ρουτίνα διακοπής εκτελείται κάθε ένα δευτερόλεπτο ακριβώς και η ακρίβεια του χρόνου προέρχεται από τον κρύσταλλο χρονισμού του μικροελεγκτή (κρύσταλλος 16MHz). 

Το ρολόι LCD σε πλακέτα πρωτοτύπων (breadboard)
Φωτογραφία 2. Το ρολόι LCD σε πλακέτα πρωτοτύπων (breadboard)

Για την υλοποίηση του απαριθμητή δευτερολέπτων χρησιμοποιούμε μία ρουτίνα διακοπής που «τρέχει» σε κάθε υπερχείλιση του timer 1 του μικροελεγκτή. Επιλέγουμε, ο timer1 να απαριθμεί παλμούς που προέρχονται από την έξοδο ενός προδιαιρέτη (prescaler) που διαιρεί τη συχνότητα των 16MHz (του ρολογιού του επεξεργαστή) δια 256. Τόσο ο timer 1 όσο και ο προδιαιρέτης (prescaler) αποτελούν εσωτερικά module του μικροελεγκτή.

Ρολόι πραγματικού χρόνου με χρήση χρονιστή και προδιαιρέτη
Εικόνα 3. O timer1 απαριθμεί παλμούς που προέρχονται από την έξοδο ενός προδιαιρέτη

Η ρουτίνα διακοπής εκτελείται κάθε 1 δευτερόλεπτο, σε κάθε υπερχείλιση του timer1 και η ακρίβεια του 1 δευτερολέπτου εξασφαλίζεται ως εξής:

Με αναφορά στην εικόνα 3, παρατηρούμε ότι στην έξοδο του προδιαιρέτη έχουμε συχνότητα F που είναι ίση με:

F=Fclk/N        (1)

Όπου Fclk =16ΜΗz (η συχνότητα του κυρίως ρολογιού του μικροελεγκτή, προερχόμενη από τον κρύσταλλο χρονισμού) και N=256 (o επιλεγμένος λόγος διαίρεσης του prescaler).  

Σε συχνότητα F, o κάθε κύκλος διαρκεί χρόνο Τ=1/F:

T=N/Fclk        (2)

Για την απαρίθμηση 1 δευτερολέπτου χρειάζονται M κύκλοι, όπου M= 1/T=F. Ο timer1 είναι απαριθμητής των 16bit και ξεκινώντας την απαρίθμηση από το 0, υπερχειλίζει κάθε 216=65536 παλμούς. Για να απαριθμήσει 1 δευτερόλεπτο ακριβώς, θα πρέπει ο timer1 να υπερχειλίζει κάθε M παλμούς και αυτό μπορεί να επιτευχθεί αν ο timer1 φορτωθεί αρχικά με την τιμή Κ:

Κ=216-Μ      (3)

Αντικαθιστώντας Fclk=16 MHz και N=256 στις εξισώσεις (2) και (3), βρίσκουμε ότι:

K=3036       (4)

Αυτό σημαίνει, ότι προκειμένου να επιτύχουμε μία ρουτίνα διακοπής που να εκτελείται κάθε 1 δευτερόλεπτο θα πρέπει:

  1. Να επιλέξουμε ως είσοδο για τον timer1, το κύριο ρολόι των 16MHz.
  2. Να επιλέξουμε λόγο διαίρεσης Ν=256 στον εσωτερικό προδιαιρέτη (prescaler).
  3. Σε κάθε ρουτίνα διακοπής θα πρέπει να αρχικοποιούμε τον timer1 στην αρχική τιμή Κ=3036.

Τα βήματα 1 και 2 πραγματοποιούνται με τον παρακάτω κώδικα που εκτελείται μόνο μία φορά εντός του κώδικα αρχικοποίησης (setup):

 // initialize timer1

  noInterrupts();           // disable all interrupts

  TCCR1A = 0;

  TCCR1B = 0;

// Set timer1_counter to the correct value for our interrupt interval

  timer1_counter = 3036;

  TCNT1 = timer1_counter;   // preload timer

  TCCR1B |= (1 << CS12);    // 256 prescaler

  TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt

  interrupts();             // enable all interrupts 

Σε κάθε ρουτίνα διακοπής, που διαρκεί ακριβώς 1sec, ο μετρητής δευτερολέπτων (seconds) αυξάνεται κατά 1, το LED (ledpin) αναβοσβήνει (toggle) και ο timer 1 αρχικοποιείται (preloaded) στην τιμή M=3036. Επομένως, ο κώδικας της ρουτίνας διακοπής, έχει ως εξής:

ISR(TIMER1_OVF_vect)        // interrupt service routine

  TCNT1 = timer1_counter;   // preload timer

  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);

  seconds = seconds+1;

  }

O κώδικας του ατέρμονα βρόχου loop

Το ρολόι μας είναι interrupt driven, επομένως ο χρόνος απαριθμείται αυτόματα μέσω της ρουτίνας διακοπής του timer1 και όλη η πληροφορία που χρειαζόμαστε για το χρόνο εμπεριέχεται στη μεταβλητή seconds. Βάσει αυτής της παρατήρησης μπορούμε να περιγράψουμε πολύ εύκολα τον κώδικά που «τρέχει» εντός του ατέρμονα βρόχου (loop). Ας περιγράψουμε λοιπόν αρχικά το διάγραμμα ροής του ατέρμονα βρόχου μέσω ψευδοκώδικα:

  1. Αρχή
  2. Έλεγξε αν έχει πατηθεί κάποιο πλήκτρο και πραγματοποίησε την αντίστοιχη λειτουργία του πλήκτρου
  3. Εμφάνισε την τρέχουσα ημέρα στην πρώτη γραμμή του LCD
  4. Εμφάνισε την τρέχουσα ώρα στην αρχή της δεύτερης γραμμής του LCD
  5. Εμφάνισε την ώρα αφύπνισης (ξυπνητήρι) στο τέλος της δεύτερης γραμμής του LCD
  6. Αν συμπληρωθούν 604800 δευτερόλεπτα μηδένισε το μετρητή seconds διότι συμπληρώθηκε μία εβδομάδα
  7. Αύξησε κατά 1 το μετρητή κύκλων επανάληψης του βρόχου (προκειμένου να καταγράφονται οι κύκλοι επανάληψης για debouncing των πλήκτρων)
  8. Έλεγξε αν πρέπει να ηχήσει το ξυπνητήρι (ο βομβητής)
  9. Επέστρεψε στην αρχή

Μετατρέπουμε τώρα τον ψευδοκώδικα σε πραγματικό κώδικα που εκτελείται δομημένα με την κλήση συναρτήσεων:

 void loop()

{

  checkDaysButton();

  checkHoursButton();

  checkMinButton();

  checkAlHoursButton();

  checkAlMinButton();

  checkAlButton();

  printDay(getDay(seconds));

  printHour(getHour(seconds));

  printMin(getMin(seconds));

  printSec(getSec(seconds));

  printAlHour(AlHour);

  printAlMin(AlMin);

if(seconds > 604799)seconds = 0;

  cnt = cnt + 1;

  checkAlarm();

 }
 

Επεξήγηση των συναρτήσεων που εκτελούνται στο loop

Συνάρτηση getDay

Επιστρέφει την τρέχουσα ημέρα (0,1,2,3,4,5,6 για Κυριακή έως Σάββατο, αντίστοιχα). Λαμβάνει ως όρισμα το μετρητή seconds.

Όταν ο μετρητής seconds έχει την τιμή 0, σημαίνει ότι είναι Κυριακή μεσάνυχτα (Su 00:00:00). Μία ημέρα (ένα εικοσιτετράωρο) διαρκεί 86400 δευτερόλεπτα. Επομένως, η τρέχουσα μέρα προκύπτει από το ακέραιο πηλίκο seconds/86400:

 int getDay(long sec)

{

 int day;

 day = sec / 86400;

 return day;

}
 

Συνάρτηση PrintDay

Εμφανίζει την τρέχουσα ημέρα στην πρώτη γραμμή της οθόνης LCD.

void printDay(int days)

{

   lcd.setCursor(0, 0);

   switch (days)

  {

    case 0:

      lcd.print(sun);

    break;

    case 1:

      lcd.print(mon);

    break;

    case 2:

     lcd.print(tue);

    break;

    case 3:

     lcd.print(wed);

    break;

    case 4:

     lcd.print(thu);

    break;

    case 5:

     lcd.print(fri);

    break;

    case 6:

     lcd.print(sat);

    break;

  }

}

Οι σταθερές sun, mon, …., sat είναι συμβολοσειρές (string) των 16 χαρακτήρων που έχουν δηλωθεί ως καθολικές μεταβλητές: 

String sun = "Su              ";

String mon = "  Mo            ";

String tue = "    Tu          ";

String wed = "      We        ";

String thu = "        Th      ";

String fri = "          Fr    ";

String sat = "            Sa  ";

Συνάρτηση getΗour

Επιστρέφει την τρέχουσα ώρα (0,1,2,3…..23) σε εικοσιτετράωρη βάση. Λαμβάνει ως όρισμα το μετρητή seconds.

Κάθε ημέρα διαρκεί 86400 δευτερόλεπτα και κάθε ώρα διαρκεί 3600 δευτερόλεπτα. Επομένως, η τρέχουσα ώρα υπολογίζεται ως εξής:

  1. Υπολογίζεται το ακέραιο υπόλοιπο της διαίρεσης seconds/86400. Το αποτέλεσμα αντιστοιχεί στο πλήθος δευτερολέπτων που έχουν περάσει από τα μεσάνυχτα της τρέχουσας ημέρας.
  2. Υπολογίζεται το ακέραιο πηλίκο του προηγούμενου υπολοίπου δια 3600. Το αποτέλεσμα αντιστοιχεί στην τρέχουσα ώρα.

long getHour(long sec)

{

  long mod;

  long hour;

  mod = sec % 86400;

  hour = mod / 3600;

  return hour;

}

Συνάρτηση PrintΗour

Εμφανίζει την τρέχουσα ώρα στην αρχή της δεύτερης γραμμής της οθόνης LCD. Ή ώρα εμφανίζεται με 2 ψηφία (π.χ 08 ή 21) και ακολουθεί το διαχωριστικό σύμβολο «:».

void printHour(int hr)

{

    lcd.setCursor(0,1);

    

    if(hr < 10){

      lcd.print("0");

      lcd.print(hr);

    }else

      lcd.print(hr);

 

    lcd.print(":");

}
 

Συνάρτηση getMin

Επιστρέφει τα τρέχοντα λεπτά της ώρας (0,1,2,3…..59). Λαμβάνει ως όρισμα το μετρητή seconds.

Κάθε ημέρα διαρκεί 86400 δευτερόλεπτα, κάθε ώρα διαρκεί 3600 δευτερόλεπτα και κάθε λεπτό διαρκεί 60 δευτερόλεπτα. Επομένως, τα λεπτά της ώρας υπολογίζονται ως εξής:

  1. Υπολογίζεται το ακέραιο υπόλοιπο της διαίρεσης seconds/86400. Το αποτέλεσμα αντιστοιχεί στο πλήθος των δευτερολέπτων που έχουν περάσει από τα μεσάνυχτα της τρέχουσας ημέρας.
  2. Υπολογίζεται το υπόλοιπο του προηγούμενου υπολοίπου δια 3600. Το αποτέλεσμα αντιστοιχεί στο πλήθος των δευτερολέπτων που έχουν περάσει από την πιο πρόσφατη αλλαγή ώρας (τα δευτερόλεπτα της τελευταίας ώρας).
  3. Υπολογίζεται το ακέραιο πηλίκο του προηγούμενου υπολοίπου δια 60. Το αποτέλεσμα αντιστοιχεί στα λεπτά της τρέχουσας ώρας.

long getMin(long sec)
{

  long mod;

  long hour;

  long minu;

  mod = sec % 86400;

  hour = mod % 3600;

  minu =  hour / 60;

  return minu;

}
 

Συνάρτηση PrintMin

Εμφανίζει τα λεπτά της τρέχουσας ώρας αμέσως μετά από την τρέχουσα ώρα, στη δεύτερη γραμμή της οθόνης LCD. Τα λεπτά εμφανίζονται με 2 ψηφία (π.χ 09 ή 58) και ακολουθεί το διαχωριστικό σύμβολο «:».

void printMin(int mi){

    lcd.setCursor(3,1);

    if(mi < 10){

      lcd.print("0");

      lcd.print(mi);

    } else lcd.print(mi);

    lcd.print(":");

}
 

Συνάρτηση getSec

Επιστρέφει τα δευτερόλεπτα του τρέχοντος λεπτού της ώρας (0,1,2,3…..59). Λαμβάνει ως όρισμα το μετρητή seconds.

Τα δευτερόλεπτα του τρέχοντος λεπτού της ώρας είναι το ακέραιο υπόλοιπο της διαίρεσης seconds δια 60. 

long getSec(long sec){

  return sec % 60;

}
 

Συνάρτηση PrintSec

Εμφανίζει τα δευτερόλεπτα του τρέχοντος λεπτού της ώρας, αμέσως μετά από τα τρέχοντα λεπτά, στη δεύτερη γραμμή της οθόνης LCD. Τα δευτερόλεπτα εμφανίζονται με 2 ψηφία (π.χ 01 ή 59)
 

void printSec(int se)
{

    lcd.setCursor(6,1);

    if(se < 10){

      lcd.print("0");

      lcd.print(se);

    }else

      lcd.print(se);

}

Συνάρτηση PrintAlHour

Εμφανίζει την τρέχουσα ώρα αφύπνισης (ώρα ξυπνητηριού) στο τέλος της δεύτερης γραμμής της οθόνης LCD, αμέσως μετά από την ένδειξη του ρολογιού. Ή ώρα εμφανίζεται με 2 ψηφία (π.χ 08 ή 21) μετά το προθεμα «AL» και ακολουθεί το διαχωριστικό σύμβολο «:». Η τρέχουσα ώρα αφύπνισης περιέχεται σε μία καθολική μεταβλητή (καθολική μεταβλητή AlHour) που δέχεται η συνάρτηση ως όρισμα. Τυχόν αρνητική τιμή στην καθολική μεταβλητή της ώρας σημαίνει ότι το ξυπνητήρι είναι OFF και τότε εμφανίζεται η ένδειξη «AL ΟFF» έναντι της τρέχουσας ώρας αφύπνισης.

void printAlHour(long hr){

    lcd.setCursor(9,1);

     lcd.print("AL");

    if (hr>-1)

    {    

        if(hr < 10){

          lcd.print("0");

          lcd.print(hr);

        }else

          lcd.print(hr);

        lcd.print(":");

    }

    else lcd.print(" OFF ");

}
 

Συνάρτηση PrintAlMin

Εμφανίζει τα λεπτά της ώρας αφύπνισης (ώρα ξυπνητηριού) στο τέλος της δεύτερης γραμμής της οθόνης LCD, αμέσως μετά από την ένδειξη της ώρας αφύπνισης. Τα λεπτά της ώρας αφύπνισης περιέχονται σε μία μεταβλητή που δέχεται η συνάρτηση ως όρισμα. Τα λεπτά της ώρας αφύπνισης εμφανίζονται μόνο αν το ξυπνητήρι είναι ενεργό (on) και αυτό συμβαίνει όταν η καθολική μεταβλήτή AlHour (που περιέχει την ώρα αφύπνισης)  είναι θετική ή μηδέν. 

void printAlMin(long mi){

    lcd.setCursor(14,1);

    if (AlHour>-1)

    {

       if(mi < 10){

      lcd.print("0");

      lcd.print(mi);

    }else

      lcd.print(mi);

    }

}

Οι συναρτήσεις checkΧButton

Υπάρχουν έξι συναρτήσεις checkΧButton, όπου X το όνομα έκαστου πλήκτρου. Αυτές είναι οι 

checkDaysButton();

checkHoursButton();

checkMinButton();

checkAlHoursButton();

checkAlMinButton();

checkAlButton(); 

που ελέγχουν αν πατήθηκε κάποιο πλήκτρο και εκτελούν την αντίστοιχη λειτουργία του πλήκτρου καλώντας μία αντίστοιχη συνάρτηση για έκαστο πλήκτρο.

Οι συναρτήσεις checkΧButton είναι πανομοιότυπες και υλοποιούν debouncing (καταστολή αναπήδησης) των πλήκτρων μέσω κώδικα. Έκαστη συνάρτηση checkΧButton ανιχνεύει αν πατήθηκε το αντίστοιχο πλήκτρο στο οποίο αναφέρεται. Αν ανιχνευτεί πάτημα του πλήκτρο, καλείται η αντίστοιχη ρουτίνα της λειτουργίας έκαστου πλήκτρου που προβαίνει στις απαραίτητες ενέργειες. Όλες οι συναρτήσεις checkΧButton ελέγχουν τα αντίστοιχα πλήκτρα σε κάθε κύκλο του main loop. 

Κάθε συνάρτηση checkΧButton χρησιμοποιεί κάποιες καθολικές μεταβλητές που συγκρατούν τη τρέχουσα και την προγενέστερη κατάσταση του πλήκτρου καθώς και τον ακριβή χρόνο ενεργοποίησης του πλήκτρου, προκειμένου να γίνεται debouncing. Επίσης, όλες οι συναρτήσεις checkΧButton μετρούν χρονικές στιγμές και διαστήματα αξιοποιώντας τη βάση χρόνου cnt, που είναι μία καθολική μεταβλητή τύπου long που αυξάνεται κατά 1 σε κάθε κύκλο του main loop και χρησιμοποιείται ως βάση χρόνου για το debouncing.    

Για περισσότερες πληροφορίες σχετικά με την τεχνική debouncing που χρησιμοποιούμε ανατρέξτε στο σύνδεσμο https://playground.arduino.cc/Learning/SoftwareDebounce, στην επίσημη ιστοσελίδα του Arduino.

Παρακάτω παραθέτουμε μία συνάρτηση checkΧButton, την checkDaysButton, που ελέγχει αν πατήθηκε το πλήκτρο που αλλάζει τις ημέρες. Αν ανιχνευτεί πάτημα του αντίστοιχου πλήκτρου, τότε καλείται η συνάρτηση dButtonPressed() που προβαίνει στις απαραίτητες ενέργειες. 

void checkDaysButton(){

  int reading1 = digitalRead(dButton); 

 

   // If the switch changed, due to noise or pressing:

  if (reading1 != lastButton1State) {

    // reset the debouncing timer

    lastDebounce1Time = cnt;

  }

  if ((cnt - lastDebounce1Time) > debounceDelay) {

    // whatever the reading is at, it's been there for longer

    // than the debounce delay, so take it as the actual current state:

    // if the button state has changed:

    if (reading1 != button1State) {

      button1State = reading1;

 

      // only toggle the LED if the new button state is HIGH

      if (button1State == LOW) {

        //Serial.println("button pressed");

        dButtonPressed();

      }

    }

  }
 

Συνάρτηση checkAlarm

Ελέγχει την τρέχουσα ώρα και τη συγκρίνει με την ώρα αφύπνισης.  ν η ώρα αφύπνισης είναι ίδια με την τρέχουσα ώρα του ρολογιού τότε ενεργοποιείται ο βομβητής (το ξυπνητήρι). Ο έλεγχος της ισότητας γίνεται με ακρίβεια λεπτού (τα δευτερόλεπτα αγνοούνται). Επομένως, όταν ενεργοποιείται το ξυπνητήρι, ενεργοποιείται μόνο για ένα λεπτό.

void checkAlarm(){

  if((getHour(seconds) == AlHour) && (getMin(seconds) == AlMin) ){

      Serial.print("Alarm ON");

      digitalWrite(buzzPin, HIGH); 

  }else{

      Serial.print("Alarm OFF");

     digitalWrite(buzzPin, LOW); 

  }

 }

Οι λειτουργίες των πλήκτρων

Οι συναρτήσεις που υλοποιούν τις λειτουργίες των πλήκτρων δεν καλούνται άμεσα εντός του κυρίως loop αλλά εμμέσως σε κάθε πάτημα έκαστου πλήκτρου, μέσω των συναρτήσεων checkΧButton.

Συνάρτηση dButtonPressed 

Αυξάνει την τρέχουσα ημέρα κατά 1, σε κάθε πάτημα του πλήκτρου της αντίστοιχης λειτουργίας. Αύξηση της τρέχουσας ημέρας κατά 1 ισοδυναμεί με αύξηση του μετρητή seconds κατά 86400 δευτερόλεπτα. Προσοχή όμως! Αν είναι Σάββατο (δηλαδή seconds>518399) πρέπει να ανακτηθεί η τρέχουσα ώρα για να μεταβούμε στην Κυριακή διότι το άνω όριο του μετρητή seconds είναι το 604799. Σε κάθε περίπτωση, καλό είναι να απενεργοποιείται η ρουτίνα διακοπής όταν επεμβαίνουμε στη μεταβλητή seconds, προκειμένου να αποφύγουμε αναπήδηση από ταυτόχρονη πρόσβαση του κώδικα και της ρουτίνας διακοπής στη μεταβλητή seconds ταυτόχρονα.  

void dButtonPressed(){

  long buff;

  buff = seconds;

  if (buff>86400) buff=buff % 86400;

  noInterrupts();

  if (seconds>518399)

    seconds = buff;

  else seconds=seconds+86400;

  //delay(300);

  interrupts(); 

}

Συνάρτηση hButtonPressed

Αυξάνει την τρέχουσα ώρα κατά 1, σε κάθε πάτημα του πλήκτρου της αντίστοιχης λειτουργίας. Αύξηση της τρέχουσας ώρας κατά 1 ισοδυναμεί με αύξηση του μετρητή seconds κατά 3600 δευτερόλεπτα. Προσοχή όμως! Αν είναι μία ώρα πριν τα μεσάνυχτα της Κυριακής (δηλαδή seconds>518399) πρέπει να ανακτηθεί η τρέχουσα ώρα για να μεταβούμε στην Κυριακή διότι το άνω όριο του μετρητή seconds είναι το 604799. Σε κάθε περίπτωση, καλό είναι να απενεργοποιείται η ρουτίνα διακοπής όταν επεμβαίνουμε στη μεταβλητή seconds, προκειμένου να αποφύγουμε αναπήδηση από ταυτόχρονη πρόσβαση του κώδικα και της ρουτίνας διακοπής στη μεταβλητή seconds ταυτόχρονα.  

void hButtonPressed(){

  long buff;

  buff = seconds;

  if (buff>86400) buff=buff % 86400;

  noInterrupts();

  if (seconds>518399)

    seconds = buff;

  else seconds=seconds+3600;

  interrupts(); 

}


Συνάρτηση mButtonPressed 

Αυξάνει τα λεπτά της ώρας κατά 1, σε κάθε πάτημα του πλήκτρου της αντίστοιχης λειτουργίας. Αύξηση των λεπτών κατά 1 ισοδυναμεί με αύξηση του μετρητή seconds κατά 60 δευτερόλεπτα. Χρειάζεται κι εδώ κάποια προσοχή λίγο πριν τα μεσάνυχτα της Κυριακής προκειμένου να μην υπερβούμε το άνω όριο του μετρητή seconds που είναι το 604799. Επιπλέον, οι γραμμές 

seconds = seconds / 60;

seconds = seconds * 60;

μηδενίζουν τα τρέχοντα δευτερόλεπτα σε κάθε αύξηση των λεπτών κατά 1. Αυτό κρίνεται σκόπιμο προκειμένου να μπορούμε να συγχρονίσουμε το ρολόι μας με κάποιο άλλο πρότυπο ρολόι με ακρίβεια δευτερολέπτου.

Σε κάθε περίπτωση, απενεργοποιείται η ρουτίνα διακοπής όταν επεμβαίνουμε στη μεταβλητή seconds, προκειμένου να αποφύγουμε αναπήδηση από ταυτόχρονη πρόσβαση του κώδικα και της ρουτίνας διακοπής στη μεταβλητή seconds ταυτόχρονα.  

void mButtonPressed(){

   long buff;

  buff = seconds;

  if (buff>86400) buff=buff % 86400;

  noInterrupts();

  if (seconds>518399)

    seconds = buff;

  else seconds=seconds+60;

    seconds = seconds / 60;

    seconds = seconds * 60 ;

  //delay(300);

  interrupts(); 

 }
 

Συνάρτηση AlHourPressed

Αυξάνει την ώρα αφύπνισης (ξυπνητηριού) κατά 1, σε κάθε πάτημα του πλήκτρου της αντίστοιχης λειτουργίας. Η αύξηση γίνεται με απλή επέμβαση στην καθολική μεταβλητή AlHour που περιέχει την τρέχουσα ώρα αφύπνισης και λαμβάνει τιμές στα όρια 0 – 23.

void AlHourPressed()

{

  AlHour++;

  if (AlHour>23) AlHour=0;

}

Συνάρτηση AlMinPressed

Αυξάνει τα λεπτά της ώρας αφύπνισης (ξυπνητηριού) κατά 1, σε κάθε πάτημα του πλήκτρου της αντίστοιχης λειτουργίας. Η αύξηση γίνεται με απλή επέμβαση στην καθολική μεταβλητή AlMin που περιέχει τα λεπτά της ώρας αφύπνισης και λαμβάνει τιμές στα όρια 0 – 59.

void AlMinPressed()

{

  AlMin++;

  if (AlMin>59) AlMin=0;

}


Συνάρτηση AlButtonPressed

Απενεργοποιεί το ξυπνητήρι σε κάθε πάτημα του αντίστοιχου πλήκτρου. Η απενεργοποίηση του ξυπνητηριού γίνεται με απόδοση αρνητικής τιμής στην καθολική μεταβλητή AlHour. Για να ενεργοποιηθεί και πάλι το ξυπνητήρι χρειάζεται να πατηθεί το πλήκτρο που ρυθμίζει την ώρα αφύπνισης.

void AlButtonPressed(){

 

  AlHour=-1;

  AlMin=0;

}
 

Σχετικά με αυτό το άρθρο

Το ρολόι με ξυπνητήρι με το Arduino Uno φτιάχτηκε το Γενάρη του 2016. Η κατασκευή και ο προγραμματισμός έγιναν για λόγους εκπαιδευτικούς. Η εντολή που "φορτώνει" τον timer1 με μία προκαθορισμένη αρχική τιμή σε κάθε εκτέλεση της ρουτίνας διακοπής προκαλεί μία απειροελάχιστη χρονική καθυστέρηση που επηρεάζει την ακρίβεια του ρολογιού μακροπρόθεσμα. Η συγκεκριμένη καθυστέρηση προκαλεί ένα συστηματικό σφάλμα της τάξης των 2 δευτερολέπτων ανά ώρα που χρήζει διόρθωσης μέσω κατάλληλου κώδικα. Στον κώδικα της κατασκευής που παρουσιάσαμε στο άρθρο μας και και που παραθέτουμε παρακάτω σε συνημμένο αρχείο, δεν έχουμε συμπεριλάβει κώδικα για τη διόρθωση του συστηματικού σφάλματος των 2 δευτερολέπτων ανά ώρα. Επομένως, αν θέλετε να επιτύχετε απόλυτη ακρίβεια, θα πρέπει να συμπληρώσετε τον κώδικα και να "προσθέσετε τη δική σας πινελιά" στο έργο! Μία εναλλακτική λύση στο πρόβλημα θα ήταν να αλλάξετε τον κρύσταλλο χρονισμού και να χρησιμοποιήσετε κάποιον που έχει συχνότητα που είναι ακέραιο πολλαπλάσιο του 216 Hz, ή να φτιάξετε ένα δικό σας κρυσταλλικό ταλαντωτή με έναν κατάλληλο κρύσταλλο ρολογιού (32.768ΚΗz) και να "οδηγήσετε" τον timer1 με αυτόν.

Ο Κώστας και ο Γιώργος στον προγραμματισμό του Ρολογιού
Ο προγραμματισμός του Ρολογιού στο γραφείο (υπάρχει και πίτσα ... αλλά δεν φαίνεται στη φωτογραφία!)

Εάν έχετε οποιεσδήποτε νέες ιδέες, να προτείνεται κάποιες προσθήκες, να κάνετε διορθώσεις ή οτιδήποτε άλλο παρακαλώ να αποστείλετε τα σχόλια σας. Όλοι θα εκτιμήσουν την περαιτέρω συνεισφορά. Μπορείτε επίσης αν θέλετε να δημοσιεύσετε στο site μας και οποιαδήποτε δική σας κατασκευή με Arduino. 

Συνημμένα

Εδώ παραθέτουμε τον κώδικα της ηλεκτρονικής κατασκευής. Συναρμολογήστε το υλικό, φορτώστε τον κώδικα στο περιβάλλον προγραμματισμού του Arduino, προγραμματίστε την πλακέτα και.. voila το ρολόι με ξυπνητήρι! Καλή διασκέδαση και καλή.. εκμάθηση!  

Ο κώδικας της κατασκευής