Χρονόμετρο αντίστροφης μέτρησης

Atmel ATmega2560

 

Η εφαρμογή που παρουσιάζεται υλοποιεί ένα χρονόμετρο αντίστροφης μέτρησης σε αναπτυξιακή πλακέτα Arduino MEGA2560 και ο σκοπός της παρουσίασής της είναι εκπαιδευτικός. Για την υλοποίηση χρησιμοποιήθηκαν επιπλέον εξωτερικά εξαρτήματα: οθόνη LCD 2x16, ROT (detents=pulses) και buzzer. Η ανάπτυξη του λογισμικού έγινε μέσα από την πλατφόρμα Atmel Studio 7.0 σε γλώσσα προγραμματισμού C (πληροφορίες σχετικά: www.porlidas.gr/AVR/DevBrdGr.htm).

 

Συγγραφέας: Πορλιδάς Δημήτριος

Βιογραφικό Σημείωμα

electronics@porlidas.gr

Facebook

Linkedin


 

Το χρονόμετρο μετράει αντίστροφα σε δευτερόλεπτα εμφανίζοντας ταυτόχρονα τον υπόλοιπο χρόνο στην οθόνη σε ώρες/λεπτά/δευτερόλεπτα. Υπάρχει δυνατότητα προρύθμισης του χρόνου έναρξης σε επίπεδο ωρών και λεπτών. Στο τέλος του χρόνου ακούγεται ένα ηχητικό σήμα λήξης και αλλάζει κατάσταση μια ψηφιακή έξοδος. Επίσης, υπάρχει δυνατότητα παρακολούθησης από τον ADC του μικροελεγκτή μιας αναλογικής τάσης και διακοπή της χρονομέτρησης με ταυτόχρονη αλλαγή κατάστασης της ψηφιακής εξόδου αν ξεπεράσει ένα συγκεκριμένο κατώφλι (το οποίο ρυθμίζεται). Διακοπή της χρονομέτρησης μπορεί να γίνει και με αλλαγή κατάστασης σε μια ψηφιακή είσοδο. Ιδιαίτερη έμφαση έχει δοθεί στην ασφαλή λειτουργία του συστήματος και την αποφυγή σφαλμάτων, ώστε να αποφευχθεί τυχόν κατάσταση ενεργοποιημένης εξόδου από αστοχία. Η εφαρμογή μπορεί να χρησιμοποιηθεί σε φορτιστή μπαταριών, σε φούρνους ξήρανσης ή έκθεσης υλικών σε ακτινοβολία κλπ. Στο σχηματικό διάγραμμα που ακολουθεί παρουσιάζεται το θεωρητικό κύκλωμα με τον μικροελεγκτή να περιλαμβάνει μόνο τα απαραίτητα ports.

Όλες οι λειτουργίες του χρονόμετρου εκτελούνται μέσα από τα operation states, τα οποία είναι 10:

  • 0, ready for countdown
  • 1, reset timer
  • 2, set hours
  • 3, set minutes
  • 4, over voltage
  • 5, cut off
  • 6, over voltage set up
  • 7, (intermediate state)
  • 8, error
  • 21, countdown on

Η μετάβαση μεταξύ των operation states γίνεται από τον χρήστη με το ROT ή από εσωτερικές διεργασίες του προγράμματος ή από την ψηφιακή είσοδο. Με το ROT γίνονται επίσης όλες οι ρυθμίσεις, ενώ απαραίτητες πληροφορίες και μηνύματα εμφανίζονται στην οθόνη.

Αρχικό operation state είναι το 0, όπου η ψηφιακή έξοδος είναι σε λογικό 0, το ενδεικτικό LED της πλακέτας σβηστό και το χρονόμετρο έτοιμο για αντίστροφη μέτρηση με αρχικό χρόνο 12:00:00, ο οποίος εμφανίζεται στην οθόνη στην πρώτη σειρά μαζί με την τάση στον ADC. Στη δεύτερη σειρά εμφανίζεται το μήνυμα: PRESS START/ROT> που σημαίνει ότι με πάτημα του ROT ξεκινάει η αντίστροφη μέτρηση (PRESS START), ενώ με περιστροφή CW πηγαίνει στην επόμενη λειτουργία (ROT>). Στη συνέχεια:

  • Αν πατηθεί το ROT ακούγεται ένα σύντομο διπλό ηχητικό σήμα, γίνεται μετάβαση στο operation state 21, αρχίζει η αντίστροφη μέτρηση, η ψηφιακή έξοδος περνάει σε λογικό 1, το ενδεικτικό LED ανάβει και στη δεύτερη σειρά εμφανίζεται το μήνυμα: PRESS STOP που σημαίνει ότι με πάτημα του ROT κατά τη διάρκεια της χρονομέτρησης σταματάει η αντίστροφη μέτρηση επαναφέροντας το χρονόμετρο σε ετοιμότητα δηλαδή στο operation state 0 (στο operation state 0 γίνεται επίσης μετάβαση όταν εξαντληθεί ο χρόνος μέτρησης).
  • Αν περιστραφεί CW το ROT γίνεται μετάβαση στο operation state 1, η ψηφιακή έξοδος παραμένει στο 0 και το LED σβηστό και στην οθόνη εμφανίζεται στη δεύτερη σειρά το μήνυμα: PRESS RESET/ROT> που σημαίνει ότι με πάτημα του ROT επαναφέρει το χρονόμετρο στον αρχικό χρόνο 12:00:00 (PRESS RESET), ενώ με περιστροφή CW πηγαίνει στην επόμενη λειτουργία η οποία είναι το μενού των ρυθμίσεων χρόνου operation state 2 (περιστροφή CCW επαναφέρει στο operation state 0).

Στο operation state 2 γίνεται η ρύθμιση των ωρών. Η ψηφιακή έξοδος παραμένει στο 0 και το LED σβηστό και στην οθόνη εμφανίζεται στη δεύτερη σειρά το μήνυμα: ROT SET h/PRESS> που σημαίνει ότι με περιστροφή ρυθμίζονται οι ώρες (ROT SET h) -CW αυξάνουν, CCW ελαττώνουν-, ενώ με πάτημα του ROT πηγαίνει στην επόμενη ρύθμιση (PRESS>) η οποία είναι το operation state 3.

Στο operation state 3 γίνεται η ρύθμιση των λεπτών (για τα δευτερόλεπτα δεν υπάρχει ρύθμιση). Ομοίως, ψηφιακή έξοδος στο 0 και LED σβηστό, στην οθόνη εμφανίζεται στη δεύτερη σειρά το μήνυμα: ROT SET m/PRESS> που σημαίνει ότι με περιστροφή ρυθμίζονται τα λεπτά, ενώ με πάτημα του ROT βγαίνει από το μενού των ρυθμίσεων χρόνου, επαναφέροντας το χρονόμετρο σε ετοιμότητα δηλαδή στο operation state 0.

Σε οποιοδήποτε operation state, αν πατηθεί το ROT για χρονικό διάστημα μεγαλύτερο του ενός δευτερολέπτου γίνεται μετάβαση στο operation state 6, όπου μπορεί να γίνει ρύθμιση του κατωφλιού της αναλογικής τάσης διακοπής της χρονομέτρησης. Ακούγεται ένα σύντομο τριπλό ηχητικό σήμα, αν γινόταν χρονομέτρηση διακόπτεται και η ψηφιακή έξοδος περνάει στο 0 και το LED σβήνει, στην οθόνη εμφανίζεται στην πρώτη σειρά η τάση κατωφλιού αντί της τάσης στον ADC και στη δεύτερη σειρά το μήνυμα: ROT SET V/PRESS> που σημαίνει ότι με περιστροφή γίνεται η ρύθμιση της τάσης (με βήμα 0.01V), ενώ με πάτημα, επίσης για χρονικό διάστημα μεγαλύτερο του ενός δευτερολέπτου, βγαίνει από το μενού ρύθμισης της τάσης, επαναφέροντας το χρονόμετρο σε ετοιμότητα δηλαδή στο operation state 0.

Σε οποιοδήποτε operation state εκτός από το 6, αν η τάση στον ADC ξεπεράσει το κατώφλι γίνεται μετάβαση στο operation state 4. Αν γινόταν χρονομέτρηση διακόπτεται και η ψηφιακή έξοδος περνάει στο 0 και το LED σβήνει, στην οθόνη εμφανίζεται στη δεύτερη σειρά το μήνυμα: H! V/PRESS RESET που σημαίνει ότι έγινε υπέρβαση τάσης (H! V) και με πάτημα του ROT μπορεί να γίνει επαναφορά (PRESS RESET). Όσο η τάση είναι μεγαλύτερη από το κατώφλι δεν μπορεί να γίνει καμία λειτουργία εκτός από τη ρύθμιση της τάσης κατωφλιού. Αν η τάση πέσει κάτω από κατώφλι δεν γίνεται αυτόματα επαναφορά, αλλά πρέπει να πατηθεί το ROT ώστε να επανέλθει το χρονόμετρο σε ετοιμότητα δηλαδή στο operation state 0.

Σε οποιοδήποτε operation state, αν η ψηφιακή είσοδος γίνει 1 γίνεται μετάβαση στο operation state 5. Αν γινόταν χρονομέτρηση διακόπτεται και η ψηφιακή έξοδος περνάει στο 0 και το LED σβήνει, στην οθόνη εμφανίζεται στη δεύτερη σειρά το μήνυμα: C.O./PRESS RESET που σημαίνει ότι έγινε cut off από εξωτερικό ερέθισμα (C.O.) και με πάτημα του ROT μπορεί να γίνει επαναφορά. Όσο η ψηφιακή είσοδος είναι 1 δεν μπορεί να γίνει καμία λειτουργία. Αν η ψηφιακή είσοδος γίνει 0 δεν γίνεται αυτόματα επαναφορά, αλλά πρέπει να πατηθεί το ROT ώστε να επανέλθει το χρονόμετρο σε ετοιμότητα δηλαδή στο operation state 0. Πρέπει να τονιστεί ότι, αν η ψηφιακή είσοδος αποσυνδεθεί, το σύστημα θα τη δει ως 1 λόγω του set up του port. Αυτό γίνεται για ασφάλεια ώστε, αν για κάποιο λόγο προκύψει αστοχία στη συνδεσμολογία, να σταματήσει η χρονομέτρηση, προκειμένου να αποφευχθεί ο κίνδυνος μην ανταποκριθεί το σύστημα σε ενδεχόμενο cut off.

Επίσης για την ασφάλεια του συστήματος, προκειμένου να αποφευχθεί ο κίνδυνος μην ανταποκριθεί σε ενδεχόμενη ανάγκη διακοπής της χρονομέτρησης εξ αιτίας κάποιας ρουτίνας που καταχράται την ομαλή ροή του προγράμματος, έχει υλοποιηθεί ένας timer, ώστε αν «κρεμάσει» το πρόγραμμα, γίνεται μετά από δύο δευτερόλεπτα μετάβαση στο operation state 8. Αν γινόταν χρονομέτρηση διακόπτεται και η ψηφιακή έξοδος περνάει στο 0 και το LED σβήνει, στην οθόνη εμφανίζεται στη δεύτερη σειρά το μήνυμα: ERR /PRESS RESET που σημαίνει ότι παρουσιάστηκε κάποιο σφάλμα στη ροή του προγράμματος (ERR) και με πάτημα του ROT μπορεί να γίνει επαναφορά. Κατάχρηση μπορεί να γίνει αν μείνει το ROT πατημένο ή σε ενδιάμεση θέση.

Στο ηλεκτρονικό κύκλωμα, οι περιστρεφόμενοι διακόπτες του ROT A/B είναι συνδεδεμένοι στα PE4, PE5 τα οποία είναι προγραμματισμένα ως είσοδοι με ενεργοποιημένες τις pull up αντιστάσεις, και ενεργοποιούν τα INT4, INT5 με το κατερχόμενο μέτωπο. Το button του ROT είναι συνδεδεμένο στο PB0 το οποίο είναι προγραμματισμένο ως είσοδος με ενεργοποιημένη την pull up αντίσταση. Η ψηφιακή είσοδος είναι συνδεδεμένη στο PD0 το οποίο είναι προγραμματισμένο ως είσοδος με ενεργοποιημένη την pull up αντίσταση, και ενεργοποιεί το INT0 με το ανερχόμενο μέτωπο. Το buzzer είναι συνδεδεμένο στο PB5, η ψηφιακή έξοδος στο PB6 και το ενδεικτικό LED[1] στο PB7 τα οποία είναι προγραμματισμένα ως έξοδοι. Η αναλογική τάση είναι συνδεδεμένη στο PF0 το οποίο είναι προγραμματισμένο ως μονή αναλογική είσοδος ADC0 (με αναφορά τη γείωση της πλακέτας). Τέλος, η οθόνη LCD 2x16 είναι συνδεδεμένη στα PL7:1 τα οποία είναι προγραμματισμένα ως έξοδοι. Το PL0 είναι ελεύθερο για μελλοντική χρήση, επίσης μπορεί να απελευθερωθεί και το PL1, το οποίο είναι σκόπιμα συνδεδεμένο στο R/W της οθόνης ώστε τα δύο ελεύθερα ports να προκύψουν συνεχόμενα[2]. Μπορεί επίσης να χρησιμοποιηθεί οθόνη 4x20 χωρίς καμία τροποποίηση του κώδικα.

 

Το κυρίως πρόγραμμα βρίσκεται στο αρχείο main.c. Μετά την include του header file ξεκινάει ο κώδικας του προγράμματος. Το πρόγραμμα αρχικά κάνει τα απαραίτητα initialization: οθόνη, ROT, Timer, ADC (τα οποία γίνονται με ρουτίνες που θα αναλυθούν στην πορεία), ενεργοποίηση των interrupts, προγραμματισμός των ports, αρχικοποιήσεις των μεταβλητών όπου αρχικός χρόνος ορίζονται οι 12 ώρες, τάση κατωφλιού 1.4V και, εφόσον δεν υπάρχει ενεργό cut off, αρχική κατάσταση είναι operation state 0, διαφορετικά είναι το 5. Στη συνέχεια εισέρχεται στον βρόχο της while(1). Μέσα στο βρόχο, αρχικά καλείται η ρουτίνα εμφάνισης του κυρίως logo στην οθόνη, το οποίο αφορά την τρίτη και τέταρτη σειρά για οθόνες 4x20. Στη συνέχεια καλούνται οι ρουτίνες υπολογισμού υπόλοιπου χρόνου, εμφάνισης του χρόνου στην οθόνη, ανάγνωσης ADC, υπολογισμού τάσης (ADC ή κατωφλιού), εμφάνιση της τάσης στην οθόνη και εμφάνισης του operation state στην οθόνη. Ακολουθεί η συνθήκη που εξετάζεται κατά το πάτημα του κουμπιού του ROT. Η πρώτη if με το delay είναι για εξουδετέρωση της αναπήδησης του διακόπτη. H δεύτερη if και η if-else μέσα της περιέχουν τις συνθήκες που εξετάζονται κατά το πάτημα του κουμπιού και καθορίζουν τις διάφορες λειτουργίες του. Η if-else εξετάζει αν υπάρχει ενεργό cut off[3]. Αν υπάρχει (else), ενεργοποιείται το buzzer παράγοντας ήχο διάρκειας 500ms και δεν γίνεται καμία απολύτως λειτουργία. Για την ασφάλεια του συστήματος[4], γίνονται η ψηφιακή έξοδος και η έξοδος του LED 0, το operation state γίνεται 5 και καλείται η ρουτίνα εμφάνισής του. Αν δεν υπάρχει cut off (if), ενεργοποιείται το buzzer με σύντομο ήχο και στη συνέχεια μηδενίζεται ο μετρητής που χρησιμοποιείται για το παρατεταμένο πάτημα. Με την while αμέσως μετά ελέγχεται το κουμπί για παρατεταμένο πάτημα και αν το πάτημα ξεπεράσει το ένα δευτερόλεπτο εισέρχεται το πρόγραμμα στο operation state 6 (ή αν είναι σε αυτό, το εγκαταλείπει). Αυτό επιτυγχάνεται με τις εντολές αύξησης της μεταβλητής Vsi και delay 5ms και την if που ακολουθεί. Η if εκτελείται αν ο Vsi γίνει μεγαλύτερος από 200 (δηλαδή μετά από 200·5ms=1s παρατεταμένου πατήματος) και το operation state είναι διάφορο του 8[5]. Με την εκτέλεσή της κάνει toggle το PB5 για το τριπλό ηχητικό σήμα εισόδου στο (ή εξόδου από το) operation state 6 και με την if-else που ακολουθεί: Αν ήταν σε οποιοδήποτε άλλο operation state εκτός του 6 μεταβαίνει σε αυτό και καλείται η ρουτίνα εμφάνισης του operation state στην οθόνη, αφού πρώτα η ψηφιακή έξοδος περάσει στο 0 και το LED σβήσει. Αν ήταν στο 6, γίνεται μετάβαση στο operation state 0, καλείται η ρουτίνα εμφάνισης του operation state στην οθόνη και στη συνέχεια γίνεται μετάβαση στο ενδιάμεσο operation state 7. Η μετάβαση στο 7 γίνεται γιατί ακολουθεί η switch με τη συνθήκη για το πάτημα του κουμπιού, η οποία εκτελείται πάντα. Αν δεν γινόταν μετάβαση στο 7 αλλά παρέμενε στο 0, η case που στη συνέχεια θα το έβλεπε 0 θα το έκανε 1 και έτσι θα ξεκινούσε η χρονομέτρηση αμέσως κάθε φορά που το πρόγραμμα έβγαινε από το μενού ρύθμισης τάσης. Με τη μετάβαση στο 7, η case που θα το δει 7 το κάνει 0 και έτσι, μετά την έξοδο από το μενού ρύθμισης τάσης, το χρονόμετρο μεταβαίνει σε ετοιμότητα. Με τις επόμενες εντολές ελέγχονται η ψηφιακή έξοδος και η έξοδος του LED, όπου γίνονται 1 μόνο όταν το πρόγραμμα βρίσκεται στο operation state 21. Τελευταία, μηδενίζεται ο timer σφάλματος της ροής του προγράμματος.

 

Ο έλεγχος της χρονομέτρησης βρίσκεται στο αρχείο Timer0.c. Η χρονομέτρηση γίνεται με τον Timer0, ο οποίος είναι 8bit timer. Ο Timer0 είναι προγραμματισμένος σε λειτουργία CTC (http://porlidas.gr/AVR/TimPWM2560Gr.htm) και είναι ενεργοποιημένες και οι δύο μονάδες σύγκρισης (OCR0A και OCR0B) να εκτελούν διακοπή (interrupt) στο σημείο σύγκρισης.

Η ρουτίνα void Timer0_init(void) περιέχει τις απαραίτητες εντολές για το initialization του Timer. Ο prescaler είναι ρυθμισμένος για διαίρεση της συχνότητας ρολογιού συστήματος δια 1024 ώστε η συχνότητα μέτρησης να είναι 16MHz/1024=15625Hz. Οι δύο μονάδες σύγκρισης έχουν την ίδια τιμή (124) ώστε εκτελείται διακοπή και από τις δύο μονάδες[6], οι οποίες εκτελούνται με συχνότητα 15625Hz/125=125Hz. Η μεταβλητή output[] είναι ένας μονοδιάστατος πίνακας 8 στοιχείων όπου τοποθετούνται οι χαρακτήρες ASCII για την εμφάνιση του χρόνου με τις θέσεις 2 και 5 περιέχουν τον χαρακτήρα ":" για την απεικόνιση του χρόνου.

Η ρουτίνα ISR(TIMER0_COMPA_vect) περιέχει τις εντολές που εκτελούνται κατά τη διακοπή της μονάδας σύγκρισης OCR0A και αφορούν τη μέτρηση του χρόνου. Η μεταβλητή countsec είναι 16bit και περιέχει τον υπόλοιπο χρόνο της μέτρησης. Εφόσον είναι μεγαλύτερη από 0 και το operation mode είναι 21 (countdown on), με την πρώτη if αυξάνει τη μεταβλητή tim κατά ένα. Όταν η tim φτάσει την τιμή 124 εκτελείται η if που ακολουθεί, εκτελώντας ουσιαστικά ακόμα μια διαίρεση, της συχνότητας που εκτελούνται οι διακοπές με το 125, δηλαδή 125Hz/125=1Hz. Μέσα στην if μηδενίζεται η tim και ελαττώνεται ο υπόλοιπος χρόνος (countsec) κατά ένα. Όταν ο υπόλοιπος χρόνος γίνει μηδέν σταματάει η αντίστροφη μέτρηση και η ψηφιακή έξοδος και η έξοδος του LED γίνονται 0.

Η ρουτίνα ISR(TIMER0_COMPB_vect) περιέχει τις εντολές που εκτελούνται κατά τη διακοπή της μονάδας σύγκρισης OCR0B και αφορούν τον timer σφάλματος της ροής του προγράμματος errtim. Ο errtim αυξάνει κατά ένα κάθε φορά που εκτελείται η διακοπή, δηλαδή με συχνότητα 125Hz. Επίσης, μηδενίζεται σε κάθε κύκλο του βρόχου while(1) του main.c κατά τη ροή του προγράμματος. Αν από αστοχία μείνει πατημένο το ROT, το πρόγραμμα θα κολλήσει μέσα στην while (!(bit_is_set(PINB,0))); που βρίσκεται λίγο πριν τη switch στο main.c, κάτι που μπορεί να προκαλέσει ανασφαλή κατάσταση[7]. Σε αυτή την περίπτωση δεν θα μηδενιστεί ο errtim από την ροή του προγράμματος με αποτέλεσμα, μετά από 2 δευτερόλεπτα να ξεπεράσει την τιμή 249 και να εκτελεστεί η if κατά την οποία η ψηφιακή έξοδος και η έξοδος του LED γίνονται 0 και γίνεται μετάβαση στο operation state 8.

Η ρουτίνα void CalcTim(uint16_t countsec1) υπολογίζει τον υπόλοιπο χρόνο. Καλείται στο μέσα από το main.c να μετατρέψει την τιμή του countsec σε ώρες/λεπτά/δευτερόλεπτα (ωω:λλ:δδ) και να αποθηκεύσει τα δεδομένα στη μεταβλητή output[]. Αρχικά μηδενίζονται οι όροι της output[] που περιέχουν τιμές χρόνου. Στη συνέχεια γίνεται χρήση βρόχων while με επαναλαμβανόμενες αφαιρέσεις[8]. Πρώτα υπολογίζονται οι δεκάδες των ωρών και μεταφέρονται στον αντίστοιχο όρο της output[]. Ακολουθούν οι μονάδες των ωρών μετά οι δεκάδες λεπτών κλπ. Στο τέλος, η μεταβλητή output[] περιέχει τον υπόλοιπο χρόνο σε αριθμητικές αξίες.

Η ρουτίνα void WrTim(void) αναλαμβάνει την εμφάνιση του υπόλοιπου χρόνου στην οθόνη. Μετατρέπει τις αριθμητικές αξίες της μεταβλητής output[] σε χαρακτήρες ASCII και ταυτόχρονα τους στέλνει στην οθόνη. Η μετατροπή γίνεται μέσα στον βρόχο της for όπου καλείται η ρουτίνα αποστολής χαρακτήρα WrDa για κάθε ένα στοιχείο της output το οποίο γίνεται OR με την τιμή 0011 0000 (http://porlidas.gr/AVR/TipsGr.htm).

 

Ο έλεγχος του ADC βρίσκεται στο αρχείο ADC.c. Η ρουτίνα void InitADC(void) περιέχει τις απαραίτητες εντολές για το initialization του ADC. Ενεργοποιεί τον ADC για απλή μετατροπή, συχνότητα του κυκλώματος προσέγγισης τάσης 62kHz, τάση αναφοράς εσωτερική 2.56V, αριστερή στοίχιση, επιλογή του ADC0 (PF0), απενεργοποίηση της ψηφιακής εισόδου της PF0 και αρχικοποίηση των σταθερών όρων της μεταβλητής voutput[] για την εμφάνιση της τάσης στην οθόνη.

Η ρουτίνα void GetADC(void) αναλαμβάνει τη μέτρηση της αναλογικής τάσης. Εκτελούνται 8 μετρήσεις με διαφορά 5ms μεταξύ τους λαμβάνοντας τα 8 MSB με ανάγνωση του ADCH. Οι μετρήσεις αθροίζονται κατά την ανάγνωση στη 16bit μεταβλητή AdcOut. Στη συνέχεια γίνεται δεξιά ολίσθηση 3 bit στην AdcOut η οποία ισοδυναμεί με διαίρεση με το 8. Με αυτόν τον τρόπο στην AdcOut μένει τελικά η μέση τιμή των 8 μετρήσεων που έγιναν σε συνολικό διάστημα περίπου 37ms (7 φορές ο ενδιάμεσος χρόνος των 5ms μεταξύ των μετρήσεων από την πρώτη έως την όγδοη μέτρηση συν 8 φορές 0.25ms χρόνος για την ολοκλήρωση της κάθε μέτρησης). Επιλέχθηκε 5ms χρονική καθυστέρηση μεταξύ των μετρήσεων ώστε, σε περίπτωση που υπάρχει θόρυβος από την κυμάτωση 50 ή 60Hz της εναλλασσόμενης τάσης αστικού δικτύου ρεύματος, να μην συμπίπτει η περιοδικότητα της κυμάτωσης με τις μετρήσεις και να αποδίδεται σωστά η μέση τιμή[9].

Με την if που ακολουθεί γίνεται έλεγχος υπέρβασης της τάσης κατωφλιού. Σε περίπτωση που υπάρξει υπέρβαση, επαναλαμβάνεται η διαδικασία της μέτρησης και αν συνεχίζει η μετρούμενη τάση να είναι μεγαλύτερη από την τάση κατωφλιού, εκτελείται η ρουτίνα υπέρβασης τάσης όπου η ψηφιακή έξοδος και η έξοδος του LED γίνονται 0, το operation state γίνεται 4 και καλείται η ρουτίνα εμφάνισής του. Για την εκτέλεση της ρουτίνας υπάρχει όρος, το πρόγραμμα να μην είναι ήδη στα operation states 4 ή 6 (υπέρβασης τάσης ή ρύθμισης τάσης κατωφλιού), ώστε να μπορεί να γίνει ρύθμιση της τάσης κατωφλιού όταν υπάρχει υπέρβαση.

Η ρουτίνα void CalcOutputADC(unsigned char AdcOut1) μετατρέπει τη δυαδική τιμή της μεταβλητής της τάσης σε χαρακτήρες ASCII. Η μέγιστη τιμή του καταχωρητή ADCH, συνεπώς και της AdcOut μετά την ολίσθηση, είναι 255 (FF) και αντιστοιχεί στην τάση Vref η οποία είναι 2.56V. Μπορούμε έτσι να θεωρήσουμε ότι η τιμή της AdcOut ισοδυναμεί με την τιμή της τάσης σε εκατοστά του Volt. Η προσέγγιση αυτή προσδίδει πολύ μικρό σφάλμα στο αποτέλεσμα (0.4%), συγκρίσιμο με το σφάλμα μέτρησης (±2 LSB=0.2%). Η απεικόνιση της τάσης στην οθόνη γίνεται στη συνέχεια με ένα ακέραιο και δύο δεκαδικά ψηφία. Κατά την κλήση της ρουτίνας η τιμή της AdcOut μεταφέρεται στην εσωτερική μεταβλητή AdcOut1 της ρουτίνας[10]. Αρχικά μηδενίζονται οι όροι της μεταβλητής output[] που αφορούν τα αριθμητικά στοιχεία. Στη συνέχεια γίνεται χρήση βρόχων while με επαναλαμβανόμενες αφαιρέσεις[11]. Πρώτα υπολογίζονται οι εκατοντάδες, οι οποίες θα αναπαριστούν το ακέραιο ψηφίο της τάσης και μεταφέρονται στον αντίστοιχο όρο της voutput[]. Ακολουθούν οι δεκάδες, οι οποίες θα αναπαριστούν το πρώτο δεκαδικό ψηφίο της τάσης και μεταφέρονται στον αντίστοιχο όρο της voutput[], ενώ το υπόλοιπο που θα μείνει στην AdcOut1 είναι οι μονάδες, οι οποίες θα αναπαριστούν το δεύτερο δεκαδικό ψηφίο της τάσης. Η μετατροπή των αριθμητικών αξιών των όρων της voutput[] σε χαρακτήρες ASCII γίνεται με τις OR που ακολουθούν[12]. Η ρουτίνα void WrADC(void) αναλαμβάνει την εμφάνιση της τάσης στην οθόνη.

 

Ο έλεγχος του ROT βρίσκεται στο αρχείο ROT.c. Το ROT είναι του τύπου detents=pulses (http://porlidas.gr/AVR/ROT2560Gr.htm) και προκαλεί τις διακοπές INT4 και INT5. Η ρουτίνα ISR(INT4_vect) περιέχει τις εντολές που εκτελούνται με την ενεργοποίηση της διακοπής INT4 από το κατερχόμενο μέτωπο του παλμού τη στιγμή που το ROT ξεκινάει CW περιστροφή και κλείνει η επαφή Α. Αρχικά γίνεται 1ms καθυστέρηση για την αναπήδηση της επαφής και στην συνέχεια μηδενισμός του μετρητή (μεταβλητή Ri) για την έξοδο από την while που ακολουθεί σε περίπτωση αστοχίας του ROT. Το πρόγραμμα παραμένει στη while όσο η επαφή είναι κλειστή, εκτός αν παρέλθει ο χρόνος του 1.25s (Ri>250), οπότε εκτελείται η ρουτίνα σφάλματος διακόπτη, το πρόγραμμα μεταβαίνει σε operation state 8, όπου η ψηφιακή έξοδος και η έξοδος του LED γίνονται 0, ενεργοποιείται το buzzer παράγοντας ήχο διάρκειας 300ms και καλείται η ρουτίνα εμφάνισης του operation state. Σε φυσιολογική λειτουργία, καθώς περιστρέφεται το ROT κλείνει και η επαφή Β και στη συνέχεια ανοίγει η Α. Το πρόγραμμα βγαίνει από τη while όταν ανοίξει η επαφή Α, ακολουθεί 1ms καθυστέρηση για την αναπήδηση της επαφής και στη συνέχεια εξετάζεται η θέση της επαφής Β. Αν είναι κλειστή και το πρόγραμμα βρίσκεται σε operation state μικρότερο του 21, εκτελείται αύξηση της κατάλληλης μεταβλητής ανάλογα με το operation state (operation state, ώρες, λεπτά, Vset). Το πρόγραμμα παραμένει στη συνέχεια στη while που ακολουθεί για όσο διάστημα είναι κλειστή η επαφή Β (και σε αυτή τη while υπάρχει ρουτίνα σφάλματος του διακόπτη). Μετά το άνοιγμα της επαφής και την έξοδο από τη while ακολουθεί 1ms καθυστέρηση για την αναπήδηση του διακόπτη και ολοκληρώνεται η ρουτίνα.

Η ρουτίνα ISR(INT5_vect) περιέχει τις εντολές που εκτελούνται με την ενεργοποίηση της διακοπής INT5 από το κατερχόμενο μέτωπο του παλμού τη στιγμή που το ROT ξεκινάει CCW περιστροφή και κλείνει η επαφή B. Οι εντολές της ρουτίνας είναι ίδιες με τις εντολές της ISR(INT4_vect) με τη διαφορά ότι οι επαφές είναι αντίστροφα στη σειρά και οι μεταβλητές που ελέγχονται ελαττώνουν.

Η ρουτίνα ISR(INT0_vect) περιέχει τις εντολές που εκτελούνται με την ενεργοποίηση της διακοπής INT0 από το ανερχόμενο μέτωπο του παλμού της ψηφιακής εισόδου. Αρχικά γίνεται έλεγχος της εισόδου, στη συνέχεια καθυστέρηση 10ms και αν συνεχίζει η είσοδος να είναι 1, το πρόγραμμα μεταβαίνει σε operation state 5, όπου η ψηφιακή έξοδος και η έξοδος του LED γίνονται 0, ενεργοποιείται το buzzer παράγοντας ήχο διάρκειας 300ms και καλείται η ρουτίνα εμφάνισης του operation state.

Η ρουτίνα void InitROT(void) περιέχει τις απαραίτητες εντολές για το initialization του ROT. Ενεργοποιούνται οι διακοπές, καθορίζεται το μέτωπο του παλμού, ορίζονται τα ports.

 

Ο έλεγχος της οθόνης βρίσκεται στο αρχείο LCD.c.Οι απαραίτητες πληροφορίες για τη λειτουργία εμφανίζονται σε οθόνη 2x16, όμως το λογισμικό έχει γραφεί με τέτοιο τρόπο ώστε να είναι δυνατή η χρήση οθόνης 4x20 χωρίς τροποποίηση[13]. Οι μονοδιάστατοι πίνακες περιέχουν τα απαραίτητα logo, τα οποία εμφανίζονται στη δεύτερη σειρά όταν καλείται από το πρόγραμμα η ρουτίνα void TypeOper(void), ανάλογα με το operation state που ικανοποιεί τη switch. Η ρουτίνα void TypeMainLogo(void) έχει νόημα μόνο όταν χρησιμοποιείται οθόνη 4x20. Οι υπόλοιπες ρουτίνες είναι για το initialization της οθόνης και για εγγραφή χαρακτήρων ή εντολές στην οθόνη (http://porlidas.gr/AVR/LCD2x16Gr.htm).

 

Ακολουθεί το Header file: LCD_keyb_ROT.h

#include <avr/io.h>

#include <util/delay.h>

#include <avr/interrupt.h>               // For "sei()", "cli()" functions

 

uint8_t out;                              // LCD.c

uint16_t AdcOut;                         // ADC.c

uint8_t tim;                             // Timer0.c

uint8_t output [8];                      // Timer0.c

uint8_t voutput [5];                     // ADC.c

uint8_t oper;                            // ROT.c, main.c

uint16_t countsec;                       // Timer0.c, ROT.c

uint8_t Vset;                            // ROT.c, ADC.c

uint8_t Vsi;                             // main.c

uint8_t errtim;                          // main.c, Timer0.c

uint8_t Ri;                              // ROT.c

 

#define lcdport PORTL                    // Define LCD PORT

#define lcdddr DDRL                      // Define LCD DDR

#define lcdreg PINL                      // Define LCD PIN

#define CurL 0b00010000                  // Cursor Left (instruction)

#define CurR 0b00010100                  // Cursor Right (instruction)

#define DisL 0b00011000                  // Display Left (instruction)

#define DisR 0b00011100                  // Display Right (instruction)

#define CDRC 0b00000001                  // Clear Display and Reset Cursor (instruction, use 2ms delay after)

#define RDRC 0b00000010                  // Reset Display and Reset Cursor (instruction, use 2ms delay after)

 

#define Da 0b00000100                    // RS Register Select, data signal (0 for instruction)

#define Rd 0b00000010                    // R/W Read/Write from/to LCD signal (0 for write)

#define En 0b00001000                    // Enable signal

 

void WrIn (uint8_t tmp);                 // Write Instruction routine

void WrDa (uint8_t tmp);                 // Write Data routine

void SeCu (uint8_t addr);                // Send cursor to a specific address (0-39, 64-103) or a specific assignment (L1R1-L4R20)

void SeCG (uint8_t addr);                // Set CGRAM address

void InitLCD (void);                     // Start up LCD, initialization routine

void TypeMainLogo (void);                // Main Logo routine

void InitROT (void);                     // ROT routine

void Timer0_init();                      // Timer0.c

void CalcTim (uint16_t countsec1);       // Timer0.c

void WrTim (void);                       // Timer0.c

void InitADC (void);                     // ADC.c

void GetADC (void);                      // ADC.c

void CalcOutputADC (unsigned char AdcOut1);     // ADC.c

void WrADC (void);                       // ADC.c

void TypeOper (void);                    // LCD.c

 

#define pm 0x10                   // plus minus

#define ae 0x1A                   // almost equal

#define ti 0x1D                   // middle ~

#define t2 0x1E                   // ^2

#define t3 0x1F                   // ^3

#define po 0xA5                   // £

#define ye 0xA6                   // yen

#define pt 0xA7                   // Pt

#define fm 0xA8                   // f(math)

#define zm 0xAD                   // 0(math)

#define it 0xB5                   // i2

#define dv 0xB8                   // divide

#define se 0xB9                   // less or equal

#define be 0xBA                   // great or equal

#define dl 0xBB                   // <<

#define dg 0xBC                   // >>

#define ne 0xBD                   // not equal

#define ro 0xBE                   // root

#define in 0xC2                   // infinity

#define re 0xC4                   // return

#define au 0xC5                   // up

#define ad 0xC6                   // down

#define ar 0xC7                   // right

#define al 0xC8                   // left

#define tm 0xD0                   // trade mark

#define pg 0xD2                   // paragraph

#define gG 0xD4                   // gama cap

#define gD 0xD5                   // delta cap

#define gU 0xD6                   // theta cap

#define gL 0xD7                   // lamda cap

#define gJ 0xD8                   // ksi cap

#define gP 0xD9                   // pi cap

#define gS 0xDA                   // sigma cap

#define gT 0xDB                   // taf cap

#define gF 0xDC                   // fi cap

#define gC 0xDD                   // psi cap

#define gV 0xDE                   // omega cap

#define ga 0xDF                   // alpa

#define gb 0xE0                   // beta

#define gg 0xE1                   // gama

#define gd 0xE2                   // delta

#define ge 0xE3                   // epsilon

#define gz 0xE4                   // zita

#define gh 0xE5                   // ita

#define gu 0xE6                   // theta

#define gi 0xE7                   // iota

#define gk 0xE8                   // kapa

#define gl 0xE9                   // lamda

#define gm 0xEA                   // mi

#define gn 0xEB                   // ni

#define gj 0xEC                   // ksi

#define gp 0xED                   // pi

#define gr 0xEE                   // ro

#define gs 0xEF                   // sigma

#define gt 0xF0                    // taf

#define gy 0xF1                   // ypsilon

#define gf 0xF2                   // fi

#define gv 0xF3                   // psi

#define gw 0xF4                   // omega

#define dn 0xF5                   // down

#define ri 0xF6                   // right

#define le 0xF7                   // left

 

#define L1R1  0x00   // LCD 2x16 (1602) & 4x20 (2004) 1st line 1st rank

#define L1R2  0x01

#define L1R3  0x02

#define L1R4  0x03

#define L1R5  0x04

#define L1R6  0x05

#define L1R7  0x06

#define L1R8  0x07

#define L1R9  0x08

#define L1R10 0x09

#define L1R11 0x0A

#define L1R12 0x0B

#define L1R13 0x0C

#define L1R14 0x0D

#define L1R15 0x0E

#define L1R16 0x0F

#define L1R17 0x10

#define L1R18 0x11

#define L1R19 0x12

#define L1R20 0x13

 

#define L2R1  0x40   // LCD 2x16 (1602) & 4x20 (2004) 2nd line 1st rank

#define L2R2  0x41

#define L2R3  0x42

#define L2R4  0x43

#define L2R5  0x44

#define L2R6  0x45

#define L2R7  0x46

#define L2R8  0x47

#define L2R9  0x48

#define L2R10 0x49

#define L2R11 0x4A

#define L2R12 0x4B

#define L2R13 0x4C

#define L2R14 0x4D

#define L2R15 0x4E

#define L2R16 0x4F

#define L2R17 0x50

#define L2R18 0x51

#define L2R19 0x52

#define L2R20 0x53

 

#define L3R1  0x14   // LCD 2x16 (1602) 1st line 21st rank & 4x20 (2004) 3rd line 1st rank

#define L3R2  0x15

#define L3R3  0x16

#define L3R4  0x17

#define L3R5  0x18

#define L3R6  0x19

#define L3R7  0x1A

#define L3R8  0x1B

#define L3R9  0x1C

#define L3R10 0x1D

#define L3R11 0x1E

#define L3R12 0x1F

#define L3R13 0x20

#define L3R14 0x21

#define L3R15 0x22

#define L3R16 0x23

#define L3R17 0x24

#define L3R18 0x25

#define L3R19 0x26

#define L3R20 0x27

 

#define L4R1  0x54   // LCD 2x16 (1602) 2nd line 21st rank & 4x20 (2004) 4th line 1st rank

#define L4R2  0x55

#define L4R3  0x56

#define L4R4  0x57

#define L4R5  0x58

#define L4R6  0x59

#define L4R7  0x5A

#define L4R8  0x5B

#define L4R9  0x5C

#define L4R10 0x5D

#define L4R11 0x5E

#define L4R12 0x5F

#define L4R13 0x60

#define L4R14 0x61

#define L4R15 0x62

#define L4R16 0x63

#define L4R17 0x64

#define L4R18 0x65

#define L4R19 0x66

#define L4R20 0x67

 


[1] Από κατασκευής υπάρχουν στην πλακέτα Arduino MEGA2560 το LED με το buffer του.

[2] Στον κώδικα δεν γίνεται χρήση εντολών ανάγνωσης από την οθόνη, θα πρέπει όμως σε αυτή την περίπτωση, στην πλακέτα της οθόνης να συνδεθεί το R/W στη γείωση.

[3] Το cut off γίνεται με διακοπή που προκαλείται από το ανερχόμενο μέτωπο του παλμού και όχι από υψηλή λογική στάθμη. Αν επιτρεπόταν στο πρόγραμμα να εκτελέσει της λειτουργίες του κουμπιού, θα μπορούσε να γίνει επαναφορά ενώ υπάρχει ενεργό cut off ή να μπει στο μενού ρύθμισης τάσης και στη συνέχεια έξοδο από αυτό και εκκίνηση της χρονομέτρησης και έτσι παράκαμψη της επαναφοράς από cut off.

[4] Είναι περιττή η εντολή, όμως βελτιώνει την ασφάλεια του συστήματος από άστοχη ενεργοποίηση της εξόδου.

[5] Ο λόγος που στη συνθήκη γίνεται χρήση του operation state διάφορο του 8, είναι για να μην μπορεί το πρόγραμμα να εισέλθει στο μενού ρύθμισης τάσης ενώ δεν έχει γίνει επαναφορά η διακοπή από ERR(OR). Αν επιτρεπόταν, θα ήταν δυνατόν να γίνει παράκαμψη της επαναφοράς μέσα από την είσοδο στο μενού ρύθμισης τάσης και μετά έξοδο από αυτό.

[6] Σε περίπτωση που οι μονάδες σύγκρισης είχαν διαφορετική τιμή, θα εκτελούνταν μόνο η διακοπή τις μονάδας με τη μικρότερη τιμή, γιατί σε CTC mode ο μετρητής μηδενίζεται στο σημείο σύγκρισης, συνεπώς ποτέ δεν θα έφτανε στην τιμή της μεγαλύτερης μονάδας για προκληθεί η διακοπή της.

[7] Δεν θα εμφανίζεται ο υπόλοιπος χρόνος, δεν θα γίνεται μέτρηση τάσης.

[8] Σύμφωνα με τη λογική που παρουσιάζεται στη http://porlidas.gr/AVR/TipsGr.htm. Η μέθοδος αυτή χρησιμοποιείται αντί διαιρέσεων με υπόλοιπο, οι οποίες δεσμεύουν πολύ μνήμη.

[9] Πραγματοποιείται μια μέτρηση κάθε 0.53π ή 0.63π για κυμάτωση 50Hz ή 60Hz με αποτέλεσμα να πραγματοποιούνται οι 8 μετρήσεις σε 2.1 ή 2.5 περιόδους αντίστοιχα.

[10] Η AdcOut είναι 16bit, όμως όπως αναφέρθηκε, η μέγιστη τιμή που μπορεί να πάρει είναι 255 (FF), συνεπώς δεν αποτελεί πρόβλημα η ασυμφωνία εύρους.

[11] Σύμφωνα με τη λογική που παρουσιάζεται στη http://porlidas.gr/AVR/TipsGr.htm. Η μέθοδος αυτή χρησιμοποιείται αντί διαιρέσεων με υπόλοιπο, οι οποίες δεσμεύουν πολύ μνήμη.

[13]  Οι θέσεις 17 έως 20 γεμίζουν με κενά στις δύο πρώτες σειρές, ενώ στις δύο τελευταίες εμφανίζεται ένα logo.

 

 

 

 

 

main.c

int main(void)

{

_delay_ms(100);         // Stabilize system

InitLCD();              // Initialize LCD 16x2 or 20x4

InitROT();              // Initialize ROT (type of rot: detents = pulses)

Timer0_init();          // Initialize Timer

InitADC();              // Initialize ADC

sei();                  // Enable interrupts

DDRB |= (7 << 5);       // Outputs: PB7 (Board LED), PB6 (Digital out), PB5 (Buzzer)

PORTB &= ~(7 << 5);     // LED and Buzzer off, Digital out->0, by init

DDRB &= ~(1<<0);        // PB0 Input (ROT Button)

PORTB |= (1<<0);        // PB0 Enable pull up resistor

countsec = 43200;       // Timer Init state (12:00:00)

Vset = 140;             // Over Voltage 1.40V Init state

if (bit_is_set(PIND,0)) // Check for "cut off"

oper = 5;

else

oper = 0;               // Init Operation state 0: "Ready for countdown"

 

while (1)

{

TypeMainLogo();         // Display main logo (no need for LCD 2x16)

CalcTim(countsec);      // Calculate remaining time routine (Timer.c)

WrTim();                // Display remaining time routine (LCD.c)

GetADC();               // Measure Voltage routine (ADC.c)

if (oper == 6)          // Operation state 6 "Over Voltage set up" condition

CalcOutputADC(Vset);    // Calculate Voltage routine -cut off V- (ADC.c)

else                    // All other states

CalcOutputADC(AdcOut);  // Calculate Voltage routine -ADC- (ADC.c)

WrADC();                // Display Voltage routine (ADC.c)

TypeOper();             // Display operation state

 

if (!(bit_is_set(PINB,0)))     // ROT Button pressed condition

_delay_ms(5);                  // Button debouncing

if (!(bit_is_set(PINB,0)))     // ROT Button still pressed

{

if (!(bit_is_set(PIND,0)))   // No cut off condition   

{

PORTB |= (1 << 5);           // Buzzer short

_delay_ms(10);

PORTB &= ~(1 << 5);

Vsi = 0;                          // Reset "Over Voltage set up" counter

while (!(bit_is_set(PINB,0)))     // Over Voltage set up loop

{

Vsi++;                          // Increase "Over Voltage set up" counter

_delay_ms(5);

if ((Vsi > 200) && (oper != 8)) // Long press enter/leave "Over Voltage set up"

{

PORTB |= (1 << 5);             // Buzzer short

_delay_ms(10);

PORTB &= ~(1 << 5);

_delay_ms(100);

PORTB |= (1 << 5);             // Buzzer short

_delay_ms(10);

PORTB &= ~(1 << 5);

_delay_ms(100);

PORTB |= (1 << 5);             // Buzzer short

_delay_ms(10);

PORTB &= ~(1 << 5);

if (oper != 6)                 // Enter "Over Voltage set up" condition

{

PORTB &= ~(3 << 6);          // LED off, digital out->0

oper = 6;                    // Set operation state 6 "cut off V set up"

TypeOper();                  // Display operation state

}

else                          // Leave "Over Voltage set up" condition

{

oper = 0;                    // Set operation state 0 "ready for countdown"

TypeOper();                  // Display operation state

oper = 7;                    // Set intermediate operation state 7

}                 

while (!(bit_is_set(PINB,0)));

}                        

}

switch(oper)

{

case 0:                          // Start countdown condition

if (countsec > 0)

oper = 21;                       // Set operation state 21 "countdown on"

_delay_ms(100);                  // Delay for buzzer

PORTB |= (1 << 5);               // Buzzer short

_delay_ms(10);

PORTB &= ~(1 << 5);

break;

 

case 1:                   // Operation state 1 "reset timer" condition

countsec = 43200;         // Set timer at init state (12:00:00)

break;

                          

case 2:                   // Operation state 2 "set hours" condition

oper++;                   // Increase operation state to state 3: "set hours"

break;

                          

case 3:                   // Operation state 3 "set minutes" condition

oper = 0;                 // Set operation state 0 "ready for countdown"

break;

                          

case 4:                   // Operation state 4 "over voltage" condition

oper = 0;                 // Set operation state 0 "ready for countdown"

break;

                          

case 5:                   // Operation state 5 "cut off" condition

oper = 0;                 // Set operation state 0 "ready for countdown"

break;

                          

case 7:                   // Intermediate operation state 7 condition

oper = 0;                 // Set Operation state to 0 "ready for countdown"

break;

                          

case 8:                   // Operation state 8 "error" condition

oper = 0;                 // Set Operation state to 0 "ready for countdown"

break;

                          

case 21:                  // Operation state 21 "countdown on" condition

oper = 0;                 // Set operation state 0 "ready for countdown"

break;     

}

}

else                         // Combined with: "if (!(bit_is_set(PIND,0)))"

{

PORTB &= ~(3 << 6);        // LED off, digital out->0

oper = 5;                  // Enter "cut off" condition

TypeOper();                // Display operation state

PORTB |= (1 << 5);         // Buzzer long+ cut off condition

_delay_ms(500);

PORTB &= ~(1 << 5);

}                                                       

}

             

if (oper == 21)                // Operation state 21 "countdown on" condition

PORTB |= (3 << 6);             // LED on, digital out->1

else                           // All other states condition

PORTB &= ~(3 << 6);            // LED off, digital out->0

             

errtim = 0;                    // Reset error timer

}

}

 

Timer0.c

#include "LCD_keyb_ROT.h"

 

ISR(TIMER0_COMPA_vect)

{

if ((countsec > 0) && (oper == 21))    // Countdown condition

tim += 1;                              // Increase division factor

if (tim > 124)// Division factor for counter frequency f=((clk/1024)/125)/125=1Hz

{

tim = 0;                              // Reset division factor

countsec -=1;                         // Decrease seconds

if (countsec == 0)                    // "time out" condition

{ 

PORTB &= ~(3 << 6);                 // LED off, digital out->0

oper = 0;                      // Set operation state 0 "ready for countdown"

PORTB |= (1 << 5);                  // Buzzer long "stop"

_delay_ms(300);

PORTB &= ~(1 << 5);

}

}

}

 

ISR(TIMER0_COMPB_vect)

{

errtim += 1;                                  // Increase error timer

if (errtim > 249)       // Error timer for frequency f=((clk/1024)/125)/250=0.5Hz

{

errtim = 0;                           // Reset error timer

PORTB &= ~(3 << 6);                   // LED off, digital out->0

oper = 8;                             // Set operation state 8 "error"

PORTB |= (1 << 5);                    // Buzzer long "stop"

_delay_ms(300);

PORTB &= ~(1 << 5);

TypeOper();                           // Display operation state (LCD.c)

}

}

 

void Timer0_init(void)                   // Initialize TIMER0

{

TCCR0A |= (1 << WGM01);           // CTC mode

TCCR0B |= (5 << CS00);            // clk/1024

TIMSK0 |= (1 << OCIE0A);          // Enable CTC Int Output Compare Unit A (time)

TIMSK0 |= (1 << OCIE0B);          // Enable CTC Int Output Compare Unit B (error)

OCR0A = 124;  // Reset Timer at 124 for Interrupt frequency f=(clk/1024)/125=125Hz

OCR0B = 124;  // Reset Timer at 124 for Interrupt frequency f=(clk/1024)/125=125Hz

output[2] = ':';                  // Default for Timer appearance

output[5] = ':';                  // Default for Timer appearance

}

 

void WrTim (void)                   // Display remaining time

{

SeCu (L1R1);                      // Send cursor to a specific address 0-80 (LCD)

_delay_ms (5);

for (uint8_t i1 = 0; i1 < 8; i1++)//Send [Output] value to LCD while converting

                                  // numbers to ASCII

WrDa ((output[i1] | 0b00110000));

for (uint8_t i1 = 0; i1 < 3; i1++)

WrDa(' ');                                   

}

 

void CalcTim (uint16_t countsec1)   // Convert seconds counter to hour (hh:mm:ss)

{

output[0] = 0;                    // Reset registers

output[1] = 0;

output[3] = 0;

output[4] = 0;

output[6] = 0;

output[7] = 0;

while (countsec1 > 35999)         // Calculate hours decades

{

countsec1 -= 36000;

output[0]++;

}

while (countsec1 > 3599)          // Calculate hours monads

{

countsec1 -= 3600;

output[1]++;

}

while (countsec1 > 599)           // Calculate minutes decades

{

countsec1 -= 600;

output[3]++;

}

while (countsec1 > 59)            // Calculate minutes monads

{

countsec1 -= 60;

output[4]++;

}

while (countsec1 > 9)             // Calculate seconds decades

{

countsec1 -= 10;

output[6]++;

}

output[7] = countsec1;            // Rest is seconds monads

}

 

ADC.c

#include "LCD_keyb_ROT.h"

 

void InitADC (void)

{

ADCSRA |= ((1 << ADEN) | (7 << ADPS0)); // ADC Enable, single conversion, 62.5kHz

ADMUX |= ((3 << REFS0) | (1 << ADLAR)); //IntRef 2.56V, left adjust result, PF0

                 // (REFS0:1 = 0->AREF, 1->AVCC,2->IntRef 1.1V, 3->IntRef 2.56V)

DIDR0 |= (1 << 0);                     // Disable Digital Input PF0

voutput[1] = '.';                      // Default for Voltage appearance

voutput[4] = 'V';                      // Default for Voltage appearance

}

 

void GetADC (void)

{

AdcOut=0;                              // Reset variable

for (uint8_t i2=0; i2<8; i2++)         // 8 conversions

{

ADCSRA |= (1 << ADSC);                // Start conversion

while (!(ADCSRA & (1 << ADIF)));      // Wait for conversion to complete

AdcOut += ADCH;                       // Add counts

_delay_ms(5);                         // delay 5ms between conversions

}

AdcOut = (AdcOut >> 3);                // Division by 8 to calculate average

if (AdcOut > Vset)                      // "over voltage" condition V>1.4V

{

_delay_ms(6);                         // Delay 6ms and repeat measurement

for (uint8_t i2=0; i2<8; i2++)        // 8 conversions

{

ADCSRA |= (1 << ADSC);              // Start conversion

while (!(ADCSRA & (1 << ADIF)));    // Wait for conversion to complete

AdcOut += ADCH;                      // Add counts

_delay_ms(5);                       // delay 5ms between conversions

}

AdcOut = (AdcOut >> 3);               // Division by 8 to calculate average

}   

if (AdcOut > Vset)                      // "over voltage" condition V>1.4V      

if ((oper != 4) && (oper != 6))        // All states except 4 and 6 condition

{

PORTB &= ~(3 << 6);                   // LED off, digital out->0

oper = 4;                             // Set operation state 4 "over voltage"

PORTB |= (1 << 5);                    // Buzzer long "stop"

_delay_ms(300);

PORTB &= ~(1 << 5);

TypeOper();                           // Display operation state

}                 

}

 

void CalcOutputADC (unsigned char AdcOut1)

{

voutput[0] = 0;                        // Reset variables

voutput[2] = 0;

voutput[3] = 0;

      

while (AdcOut1 > 99)                   // Calculate hundreds

{

AdcOut1 -= 100;

voutput[0]++;

}

while (AdcOut1 > 9)                    // Calculate decades

{

AdcOut1 -= 10;

voutput[2]++;

}

 

voutput[0] |= 0b00110000;              // Convert hundreds to ASCII

voutput[2] |= 0b00110000;              // Convert decades to ASCII

voutput[3] = (AdcOut1 | 0b00110000);   // Rest is monads while convert to ASCII

}

 

void WrADC (void)

{

SeCu (L1R12);                        // Send cursor to a specific address 0-80 (LCD)

_delay_ms (5);

for (uint8_t i1 = 0; i1 < 5; i1++)   // Send [Output] value to LCD

WrDa (voutput[i1]);

for (uint8_t i1 = 0; i1 < 4; i1++)   // Send [Output] value to LCD

WrDa (' ');

}

 


ROT.c

#include "LCD_keyb_ROT.h"         // For ROT type Pulses=detents

 

ISR(INT4_vect) // Interrupt INT4, PE4 RotA, CW Increase, start moving rotor (A->0, B=1)

{

_delay_ms(1);                    // ROT contact debouncing

Ri = 0;                          // Reset counter -Escape from "while" loop-

while (!(bit_is_set(PINE,4)))    // Wait while A=0

{                                // Escape from "while" loop with ERROR

Ri++;                                                                                

_delay_ms(5);

if (Ri > 250)

{

PORTB &= ~(3 << 6);          // LED off, digital out->0

if (oper != 8)

{

oper = 8;                  // Set operation state 8 "error"

PORTB |= (1 << 5);         // Buzzer long "stop"

_delay_ms(300);

PORTB &= ~(1 << 5);                                          

TypeOper();                // Display operation state (LCD.c)

}                                                                           

break;

}

}

_delay_ms(1);                    // ROT contact debouncing

if ((bit_is_set(PINE,4)) && (!(bit_is_set(PINE,5))) && (oper < 21)) // Rotor moving (A=1, B=0)

{

if (oper < 2)                                // Operation state condition

oper++;                                      // Increase operation state

else if ((oper == 3) && (countsec < 43141))  // "Set minutes" condition

countsec = (countsec + 60);                  // Increase minutes

else if ((oper == 2) && (countsec < 39601))  // "Set hours" condition

countsec = (countsec + 3600);                // Increase hours

else if (oper == 6)                          // "Cut off V set up" condition

Vset += 1;                                   // Increase V

}                                                                                     

Ri = 0;                          // Reset counter -Escape from "while" loop-

while (!(bit_is_set(PINE,5)))    // Wait while B=0

{                                // Escape from "while" loop with ERROR

Ri++;

_delay_ms(5);

if (Ri > 250)

{

PORTB &= ~(3 << 6);          // LED off, digital out->0

if (oper != 8)

{

oper = 8;                  // Set operation state 8 "error"

PORTB |= (1 << 5);         // Buzzer long "stop"

_delay_ms(300);

PORTB &= ~(1 << 5);                                          

TypeOper();                // Display operation state (LCD.c)

}

break;

}

}

_delay_ms(1);                    // ROT contact debouncing

}

 

ISR(INT5_vect)                    // Interrupt INT5, PE5 RotB, CCW Decrease, start moving rotor (B->0, A=1)

{

_delay_ms(1);                    // ROT contact debouncing

Ri = 0;                          // Reset counter -Escape from "while"-

while (!(bit_is_set(PINE,5)))    // Wait while B=0

{                                // Escape from "while" loop with ERROR

Ri++;

_delay_ms(5);

if (Ri > 250)

{

PORTB &= ~(3 << 6);          // LED off, digital out->0

if (oper != 8)

{

oper = 8;                   // Set operation state 8 "error"

PORTB |= (1 << 5);          // Buzzer long "stop"

_delay_ms(300);

PORTB &= ~(1 << 5);                                          

TypeOper();                // Display operation state (LCD.c)

}

break;

}

}

_delay_ms(1);

if ((bit_is_set(PINE,5)) && (!(bit_is_set(PINE,4))) && (oper < 21)) //Rotor moving (B=1, A=0)

{

if ((oper > 0) && (oper < 2))                // Operation state condition

oper--;                                      // Decrease operation state

else if ((oper == 3) && (countsec > 59))     // "Set minutes" condition

countsec = (countsec - 60);                  // Decrease minutes

else if ((oper == 2) && (countsec > 3599))   // "Set hours" condition

countsec = (countsec - 3600);                // Decrease hours

else if (oper == 6)                          // "Cut off V set up" condition

Vset--;                         // Decrease V

}                                                                               

Ri = 0;                          // Reset counter -Escape from "while"-

while (!(bit_is_set(PINE,4)))    // Wait while A=0

{                                // Escape from "while" loop with ERROR

Ri++;

_delay_ms(5);

if (Ri > 250)

{

PORTB &= ~(3 << 6);          // LED off, digital out->0

if (oper != 8)

{

oper = 8;                  // Set operation state 8 "error"

PORTB |= (1 << 5);         // Buzzer long "stop"

_delay_ms(300);

PORTB &= ~(1 << 5);                                          

TypeOper();                // Display operation state (LCD.c)

}

break;

}

}

_delay_ms(1);

}

 

ISR(INT0_vect)

{

if (bit_is_set(PIND,0))          // Digital in->1 condition

_delay_ms(10);                   // Button debouncing

if (bit_is_set(PIND,0))          // Digital in->1

if (oper != 5)                   // "Cut off" condition

{

PORTB &= ~(3 << 6);            // LED off, digital out->0

oper = 5;                      // Set operation state 5 "cut off"

PORTB |= (1 << 5);             // Buzzer long "stop"

_delay_ms(300);

PORTB &= ~(1 << 5);

TypeOper();                    // Display operation state

}

}

 

void InitROT (void)               // Start up, initialization routine

{

EICRB |= (1<<ISC41);// Interrupt by the falling edge INT4 (For ROT type Pulses=detents)

EICRB |= (1<<ISC51);// Interrupt by the falling edge INT5 (For ROT type Pulses=detents)

EICRA |= (3<<ISC00);             // Interrupt by the rising edge INT6 (For digital in)

EIMSK |= (1<<INT4);              // Enable INT4 interrupt

EIMSK |= (1<<INT5);              // Enable INT5 interrupt

EIMSK |= (1<<INT0);              // Enable INT6 interrupt

DDRE &= ~((1<<4)|(1<<5));        // Port E4, E5 inputs

PORTE |= (3<<PORTE4);            // Port E4, E5 Enable pull up

DDRD &= ~(1<<0);                 // Port D0 input

PORTD |= (1<<0);                 // Port D0 Enable pull up

}

 

LCD.c.

#include "LCD_keyb_ROT.h"

 

uint8_t logo[] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ','w','w','w','.','p','o','r','l','i','d','a','s','.','g','r',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ','D','i','m','i','t','r','i','o','s',' ','P','o','r','l','i','d','a','s',' ',' '};

 

uint8_t start [] = {'S','T','A','R','T'};

uint8_t stop [] = {' ','S','T','O','P'};

uint8_t reset [] = {'R','E','S','E','T'};

uint8_t set [] = {'S','E','T'};

uint8_t rot [] = {'R','O','T'};

uint8_t press [] = {'P','R','E','S','S'};

      

 

void WrIn (uint8_t tmp)                  // Write Instruction routine

{

lcdport &= (1<<0);                     // Reset LCD port

out = tmp & 0b11110000;                // Upper

lcdport = (lcdport | out);                                                      

_delay_us (100);

lcdport = (lcdport | En);

_delay_us (100);

lcdport = (lcdport & ~En);

_delay_us (100);

lcdport &= (1<<0);

out = tmp << 4;                        // Lower

lcdport = (lcdport | out);

_delay_us (100);

lcdport = (lcdport | En);

_delay_us (100);

lcdport = (lcdport & ~En);

_delay_us (50);

}

 

void WrDa (uint8_t tmp)                  // Write Data routine

{

lcdport &= (1<<0);                     // Reset LCD port

out = tmp & 0b11110000;                // Upper

lcdport = (lcdport | out | Da);

_delay_us (100);

lcdport = (lcdport | En);

_delay_us (100);

lcdport = (lcdport & ~En);

_delay_us (100);

lcdport &= (1<<0);

out = tmp << 4;                        // Lower

lcdport = (lcdport | out | Da);

_delay_us (100);

lcdport = (lcdport | En);

_delay_us (100);

lcdport = (lcdport & ~En);

_delay_us (50);

}

 

void SeCu (uint8_t addr)                 // Send cursor to a specific address (0-80)

{

WrIn (0b10000000 | addr);

}

 

void SeCG (uint8_t addr)                 // Set CGRAM address

{

WrIn (0b01000000 | addr);

}

 

void TypeMainLogo (void)                 // Main Logo routine

{

SeCu(L3R1);

_delay_ms(2);

for (uint8_t i1 = 20; i1 < 40; i1++)   // Send logo

WrDa (logo[i1]);

SeCu(L4R1);

_delay_ms(2);

for (uint8_t i1 = 60; i1 < 80; i1++)   // Send logo

WrDa (logo[i1]);

}

 

void TypeOper (void)                     // Display routine

{

SeCu(L2R1);                            // Set cursor at line 2, 1st character left

_delay_ms(5);     

      

switch (oper)

{   

case 0:                        // "ready for countdown", Display: PRESS START/ROT>

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(press[i1]);

WrDa(' ');

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(start[i1]);

WrDa('/');

for (uint8_t i1 = 0; i1 < 3; i1++)

WrDa(rot[i1]);

WrDa('>');

for (uint8_t i1 = 0; i1 < 4; i1++)

WrDa(' ');

break;

      

case 21:                       // "countdown on", Display: PRESS STOP

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(press[i1]);

WrDa(' ');

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(stop[i1]);

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(' ');

break;

      

case 1:                        // "reset timer", Display: PRESS RESET/ROT>

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(press[i1]);

WrDa(' ');

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(reset[i1]);

WrDa('/');

for (uint8_t i1 = 0; i1 < 3; i1++)

WrDa(rot[i1]);

WrDa('>');

break;

      

case 2:                        // "set minutes" Display: ROT SET m/PRESS>

for (uint8_t i1 = 0; i1 < 3; i1++)

WrDa(rot[i1]);

WrDa(' ');

for (uint8_t i1 = 0; i1 < 3; i1++)

WrDa(set[i1]);

WrDa(' ');

WrDa('h');

WrDa('/');

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(press[i1]);

WrDa('>');

break;

      

case 3:                        // "set hours" Display: ROT SET h/PRESS>

for (uint8_t i1 = 0; i1 < 3; i1++)

WrDa(rot[i1]);

WrDa(' ');

for (uint8_t i1 = 0; i1 < 3; i1++)

WrDa(set[i1]);

WrDa(' ');

WrDa('m');

WrDa('/');

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(press[i1]);

WrDa('>');

break;

      

case 4:                        // "over voltage" Display: H! V/PRESS RESET

WrDa('H');

WrDa('!');

WrDa(' ');

WrDa('V');

WrDa('/');

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(press[i1]);

WrDa(' ');

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(reset[i1]);

break;

      

case 5:                        // "cut off" Display: C.O./PRESS RESET

WrDa('C');

WrDa('.');

WrDa('O');

WrDa('.');

WrDa('/');

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(press[i1]);

WrDa(' ');

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(reset[i1]);

for (uint8_t i1 = 0; i1 < 4; i1++)

WrDa(' ');

break;

      

case 6:                        // "Over Voltage set up" Display: ROT SET V/PRESS>

for (uint8_t i1 = 0; i1 < 3; i1++)

WrDa(rot[i1]);

WrDa(' ');

for (uint8_t i1 = 0; i1 < 3; i1++)

WrDa(set[i1]);

WrDa(' ');

WrDa('V');

WrDa('/');

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(press[i1]);

WrDa('>');

break;

      

case 8:                        // "error" Display: ERR /PRESS RESET

WrDa('E');

WrDa('R');

WrDa('R');

WrDa(' ');

WrDa('/');

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(press[i1]);

WrDa(' ');

for (uint8_t i1 = 0; i1 < 5; i1++)

WrDa(reset[i1]);

for (uint8_t i1 = 0; i1 < 4; i1++)

WrDa(' ');

break;

}

}

 

void InitLCD (void)                      // Start up, initialization routine

{

lcdddr |= ~(1<<0);                     // PORTx1:7 outputs (LCD)

lcdport &= (1<<0);                     // Reset LCD port

_delay_ms (20);                        // Power on delay 20ms

out = 0b00110000;                      // Initialization signal

lcdport = (lcdport | out);             // Init (1, 2, 3)

_delay_us (100);

lcdport = (lcdport | En);              // 1

_delay_us (100);

lcdport = (lcdport & ~En);

_delay_ms (5);

      

lcdport = (lcdport | En);              // 2

_delay_us (100);

lcdport = (lcdport & ~En);

_delay_us (200);

 

lcdport = (lcdport | En);              // 3

_delay_us (100);

lcdport = (lcdport & ~En);

_delay_ms (5);

 

lcdport &= (1<<0);

out = 0b00100000;                      // 4 bit interface

lcdport = (lcdport | out);

_delay_us (100);

lcdport = (lcdport | En);

_delay_us (100);

lcdport = (lcdport & ~En);

_delay_us (50);

 

WrIn (0b00101100);      // 001, 4 bit, 2 lines 2x16 (4 lines 4x20), 5x11 dots (LCD),xx

WrIn (0b00001111);      // 00001xxx, Display on, cursor off, blinking off (LCD)

WrIn (0b00000110);      // 000001xx, Cursor increase, display not shift (LCD)

WrIn (CDRC);            // Clear display, reset cursor (LCD)

_delay_ms (2);

}

 

Σας ευχαριστώ για την υποστήριξή σας ώστε να γίνει η ιστοσελίδα μου καλύτερη.

© 2021 Πορλιδάς Δημήτριος