Οι AVR όπως όλοι οι μικροελεγκτές καταλαβαίνουν δεδομένα που αποτελούνται από σύνολα από ψηφία 0ς και 1ς. Έτσι με αυτό τον τρόπο, οι εντολές που θέλουμε να εκτελούν οι μικροελεγκτές AVR, πρέπει να βρίσκονται κωδικοποιημένες στη μνήμη προγράμματος σαν ένα σύνολο δυαδικών αριθμών. Για την εύκολη δημιουργία προγραμμάτων, έχουν αναπτυχθεί πολλά «εργαλεία» που μας βοηθούν να γράφουμε κώδικα σαν μια ακολουθία εντολών για τους AVR. Αυτά τα «εργαλεία» είναι οι γλώσσες προγραμματισμού που μετατρέπουν το πρόγραμμα εντολών σε κώδικα υπό μορφή δυαδικών αριθμών που ο μικροελεγκτής καταλαβαίνει.
Η πρώτη γλώσσα που αναπτύχθηκε ήταν η γλώσσα Assembly, η οποία είναι μια low – level γλώσσα προγραμματισμού, η οποία αναπαριστά τη γλώσσα μηχανής. Τα συμβολικά ονόματα που χρησιμοποιούνται στη γλώσσα Assembly αντιστοιχούν στον κώδικα από 0ς και 1ς των εντολών που αντιπροσωπεύουν τη γλώσσα μηχανής.
Με την πάροδο του χρόνου όπου οι εφαρμογές των μικροελεγκτών έγιναν πιο σύνθετες, αναπτύχθηκαν high level γλώσσες προγραμματισμού όπως η γλώσσα C. Για την παραγωγή του κώδικα μηχανής, χρησιμοποιούνται high level γλωσσών προγραμματισμού σαν βάση εφαρμογών ηλεκτρονικού υπολογιστή, που ονομάζονται compilers και που μετατρέπουν τα προγράμματα μας σε γλώσσα μηχανής.
Μια high – level γλώσσα προγραμματισμού είναι η C, που θα παρουσιάσουμε σε επόμενες ενότητες, που μέσω μιας εφαρμογής ηλεκτρονικού υπολογιστή (compiler) μας επιτρέπει να μετατρέπουμε το πρόγραμμα μας γραμμένο με τη γλώσσα C σε γλώσσα Assembly, με την οποία ο μικροελεγκτής καταλαβαίνει. Η ανάπτυξη κώδικά με την γλώσσα C προσφέρει πολλά πλεονεκτήματα, αλλά όμως παρουσιάζει ένα μειονέκτημα. Το hex αρχείο κώδικα μηχανής που παράγει και που πρέπει να φορτώσουμε στη μνήμη προγράμματος (μνήμη Flash), είναι σχετικά μεγαλύτερο από αυτό που θα δημιουργούνταν αν γράφαμε την εφαρμογή μας άμεσα με την γλώσσα Assembly. Από την άλλη μεριά τα πλεονεκτήματα που έχουμε όταν γράφουμε σε γλώσσα C (high – level γλώσσα) είναι σημαντικά αφού μας επιτρέπει να παράγουμε κώδικα AVR πολύ εύκολα, αφού μας παρέχει την ευκολία να παρακολουθούμε το πρόγραμμα μας γραμμένο σε C, να ενσωματώσουμε κώδικα που είναι γραμμένος σε βιβλιοθήκες, να επαναχρησιμοποιούμε κώδικα, ενώ μπορούμε να μεταφέρουμε προγράμματα γραμμένα σε γλώσσα C σε άλλους μικροελεγκτές με λίγες ή καθόλου αλλαγές.
Εισαγωγή στη γλώσσα C
Για τους λόγους που αναφέραμε στην προηγούμενη παράγραφο, η γλώσσα C εκτόπισε τη γλώσσα Assembly στις εφαρμογές που αναπτύσσονται με τους AVR. Ένα πρόγραμμα σε γλώσσα C ανεξάρτητα από το μέγεθος του, αποτελείται από συναρτήσεις και μεταβλητές. Μια συνάρτηση αποτελείται από εκφράσεις που ορίζουν τι πρέπει να εκτελεστεί. Μια έκφραση μέσα σε μια συνάρτηση μπορεί να είναι μια δήλωση, μια εκχώρηση με αριθμητικές πράξεις, μια κλήση συνάρτησης ή από εκφράσεις ελέγχου της ροής εκτέλεσης του προγράμματος.
Μια μεταβλητή είναι μια θέση μνήμης που αποθηκεύει μια τιμή που χρησιμοποιείται κατά την εκτέλεση του προγράμματος. Κάθε πρόγραμμα σε γλώσσα C απαιτεί την συνάρτηση main η οποία θα δώσει την εκτέλεση του προγράμματος στις εντολές που περιέχει. Ακολουθεί ένα απλό παράδειγμα, προγράμματος σε γλώσσα C.
(1) #include <avr\io.h>
(2) void main(void)
(3) {
(4) char a, b, c; /*Δήλωση τριών μεταβλητών */
(5) DDRA = 0xFF; // Διαμόρφωση της θύρας Α για έξοδο δεδομένων
(6) a = 6; //Εκχώρηση της τιμής 6 στη μεταβλητή a
(7) b = 9; //Εκχώρηση της τιμής 9 στη μεταβλητή b
(8) c = a + b //Εκχώρηση του αθροίσματος a+b στη μεταβλητή c
(9) PORTA = c; //Έξοδος της μεταβλητής στη πόρτα Α
(10) while(1); // Μένει σε ένα ατέρμων βρόγχο
(11) }
Η πρώτη γραμμή #include <avr\io.h> του παραπάνω προγράμματος προκαλεί την ενσωμάτωση του αναφερόμενου αρχείου στον κώδικα που γράφουμε για τον AVR. Αυτή η γραμμή εμφανίζεται στην αρχή του κάθε προγράμματος στη γλώσσα C του μικροελεγκτή AVR που αναπτύσσουμε. Όταν χρησιμοποιούμε το AVR Studio IDE για να γράψουμε ένα πρόγραμμα, ο χρήστης θα πρέπει να δηλώσει τον τύπο AVR που θα χρησιμοποιήσει. Αυτόματα για τον τύπο AVR που θα δηλώσουμε, εισάγεται το header αρχείο που περιέχει τους κατάλληλους ορισμούς για όλους τους Ι/Ο καταχωρητές που θα ενσωματωθούν στον κώδικα έτσι ώστε ο χρήστης να μπορεί να χρησιμοποιήσει συμβολικά ονόματα για τους Ι/Ο καταχωρητές.
Στη δεύτερη γραμμή δηλώνεται η συνάρτηση main() από την οποία ξεκινά η εκτέλεση του προγράμματος. Η αγκύλη στη γραμμή σημειώνει την έναρξη εκτέλεσης του προγράμματος μέσα στη συνάρτηση main(). Κάθε πρόγραμμα σε C θα πρέπει να περιέχει μια μόνο συνάρτηση main(). Η εκτέλεση του προγράμματος τελειώνει με το τέλος της συνάρτησης main(). Στη τέταρτη γραμμή δηλώνονται τρεις μεταβλητές (a, b και c) με τύπο χαρακτήρα. Στη γλώσσα C μια μεταβλητή θα πρέπει να δηλώνεται πριν χρησιμοποιηθεί.
Η πέμπτη γραμμή εκχωρεί την τιμή 0xFF στον Ι/Ο καταχωρητή DDRA που διαμορφώνει την θύρα PORTA σαν έξοδο. Η έκτη και η έβδομη γραμμή εκχωρεί μια τιμή σε μια μεταβλητή. Στην όγδοη γραμμή προστίθενται οι μεταβλητές a και b και το άθροισμα εκχωρείται στη μεταβλητή c. Η ένατη γραμμή εκχωρεί την τιμή της μεταβλητής c στη θήρα PORTA η οποία μπορεί να οδηγεί LEDs ή ενδείκτες επτά τμημάτων. Η δέκατη γραμμή είναι ένας ατέρμων βρόγχος που προκαλεί τη CPU να μείνει σε αυτή την εντολή για πάντα. Η αγκύλη στη τελευταία γραμμή δείχνει το τέλος της συνάρτησης main().
Μπορούμε να εισάγουμε σχόλια σε ένα πρόγραμμα γραμμένο σε C που να επεξηγεί τη δομή του κώδικα έτσι ώστε να είναι εύκολη η συντήρηση του. Μπορούμε να έχουμε σχόλια μιας γραμμής ή πολλαπλών γραμμών. Οι διπλοί χαρακτήρες // ορίζουν σχόλια μιας γραμμής στην οποία η CPU αγνοεί το κείμενο μετά τους χαρακτήρες // έως το τέλος της γραμμής. Οι χαρακτήρες /* ορίζουν την αρχή των σχολίων πολλαπλών γραμμών που τελειώνουν με τους χαρακτήρες */ και η CPU αγνοεί το κείμενο που είναι μέσα σε αυτούς τους χαρακτήρες ορισμού σχολίων πολλαπλών γραμμών.
Χρονικές καθυστερήσεις
Υπάρχουν τρεις τρόποι με τους οποίους μπορούμε να κάνουμε χρονικές καθυστερήσεις στην γλώσσα C για τους μικροελεγκτές AVR. (1) Χρησιμοποιώντας ένα απλό βρόγχο for (2) Χρησιμοποιώντας συναρτήσεις μέσα από την βιβλιοθήκη της γλώσσας C και (3) Χρησιμοποιώντας τους χρονιστές του μικροελεγκτή AVR.
Για την δημιουργία χρονικής καθυστέρησης με τον βρόγχο for θα πρέπει να έχουμε υπόψη δυο παράγοντες που επηρεάζουν την ακρίβεια της χρονικής καθυστέρησης:
1] Η τιμή της συχνότητας του κρυστάλλου ο οποίος είναι συνδεμένος στους ακροδέκτες XTAL1 και XTAL2, είναι ο πιο σημαντικός παράγοντας για τον υπολογισμό και την ακρίβεια της χρονικής καθυστέρησης. Η περίοδος του χρόνου εκτέλεσης μιας εντολής είναι συνάρτηση της συχνότητας του κρυστάλλου.
2] Ο δεύτερος παράγοντας που επηρεάζει τη χρονική καθυστέρηση είναι ο compiler που χρησιμοποιούμε για την μεταγλώττιση του προγράμματος C. Σαν αποτέλεσμα διαφορετικοί compilers παράγουν διαφορετικό κώδικα γλώσσας μηχανής κάτι που επηρεάζει την χρονική καθυστέρηση που θέλουμε να πετύχουμε.
Για τους παραπάνω λόγους όταν χρησιμοποιούμε ένα βρόγχο for για να δημιουργούμε χρονικές καθυστερήσεις στη γλώσσα C, θα πρέπει να χρησιμοποιήσουμε ένα παλμογράφο για να μετράμε τη χρονική καθυστέρηση με ακρίβεια.
Στο ακόλουθο πρόγραμμα χρησιμοποιούμε ένα βρόγχο for μέσα στον ορισμό της συνάρτησης delay100ms() για τη δημιουργία χρονικής καθυστέρησης.
Παράδειγμα
Γράψε ένα πρόγραμμα στη γλώσσα C για ένα AVR που να εναλλάσσει όλα τα bits της θύρας Β συνεχόμενα με καθυστέρηση 100ms. Θεώρησε ότι χρησιμοποιείς τον ATmega328 με XTAL=16MHz
#include <avr/io.h> //standard AVR header
void delay100ms(void)
{
volatile unsigned long i;
for(i=0; i < 47060; i++); //try different number on your compiler
//and examine the result
}
int main(void)
{
DDRB = 0xAA;
while(1)
{
PORTB = 0xAA;
delay100ms();
PORTB = 0x55;
delay100ms();
}
return 0;
}
Στη συνάρτηση delay100ms() η λέξη κλειδί volatile λέει στον compiler ότι η μεταβλητή i χρησιμοποιείται από άλλα μέρη του προγράμματος και προκαλεί τον compiler να μην βελτιστοποιήσει τα κομμάτια του κώδικα που χρησιμοποιούν αυτή την μεταβλητή.
Ένας άλλος τρόπος για την δημιουργία χρονικών καθυστερήσεων είναι με τις υπάρχουσες συναρτήσεις της βιβλιοθήκης delay.h του Atmel Studio, όπως _delay_ms() και _delay_us()
Παράδειγμα
Γράψε ένα πρόγραμμα στη γλώσσα C για ένα AVR που να εναλλάσσει όλους τους ακροδέκτες της θύρας C συνεχόμενα με καθυστέρηση 10ms. Χρησιμοποίησε την συνάρτηση καθυστέρησης της βιβλιοθήκης Atmel Studio
#define F_CPU 16000000UL //In Atmel Studio define F_CPU before using delay.h
// to inform the compiler the crystal frequency (16MHz)
#include <util/delay.h>
#include <avr/io.h>
int main(void)
{
DDRB = 0xFF; //PORTA is output
while(1) {
PORTB = 0xFF;
delay_ms(10);
PORTB = 0x55;
delay_ms(10);
}
return 0;
}
Παράδειγμα
Γράψε ένα πρόγραμμα στη γλώσσα C για τον AVR το οποίο θα παίρνει τα δεδομένα ανά 50ms από τη θύρα Β και να τα μεταφέρει στη θύρα C
#include <avr/io.h>
#define F_CPU 16000000UL //In Atmel Studio define F_CPU before using delay.h
// to inform the compiler the crystal frequency (16MHz)
#include <util/delay.h>
int main(void)
{
unsigned char temp;
DDRB = 0x00;
DDRC = 0xFF;
while(1)
{
temp = PINB;
PORTC = temp;
_delay_ms(50);
}
return 0;
}