Εισαγωγή στον προγραμματισμό των AVR με τη γλώσσα C

Οι AVR όπως όλοι οι μικροελεγκτές καταλαβαίνουν αριθμούς που αποτελούνται από ένα σύνολο από 0ς και 1ς. Με αυτό τον τρόπο, οι εντολές που θέλουμε να εκτελέσουν οι μικροελεγκτές AVR, βρίσκονται κωδικοποιημένες στη μνήμη προγράμματος σαν ένα σύνολο δυαδικών αριθμών. Έχουν αναπτυχθεί πολλά «εργαλεία» που μας βοηθούν στην συγγραφή προγραμμάτων τα οποία αποτελούνται από μια ακολουθία εντολών για τους AVR. Αυτά τα «εργαλεία» είναι οι γλώσσες προγραμματισμού που παράγουν κώδικα υπό μορφή δυαδικών αριθμών.

Η πρώτη γλώσσα που αναπτύχθηκε είναι η γλώσσα Assembly, η οποία είναι μια low – level γλώσσα προγραμματισμού που αναπαριστά τη γλώσσα μηχανής. Τα συμβολικά ονόματα που απαρτίζουν ένα πρόγραμμα που γράφουμε με τη γλώσσα Assembly αντιστοιχούν στον κώδικα από 0ς και 1ς των εντολών που αντιπροσωπεύουν τη γλώσσα μηχανής.

Με την πάροδο του χρόνου όπου οι εφαρμογές των μικροελεγκτών έγιναν πιο σύνθετες, αναπτύχθηκαν high level γλώσσες προγραμματισμού όπως η γλώσσα  C. Όμως για να παραχθεί ο κώδικας μηχανής με αυτές τις γλώσσες προγραμματισμού και για τις οποίες ο μικροελεγκτής καταλαβαίνει χρησιμοποιούμε κατάλληλες εφαρμογές ηλεκτρονικού υπολογιστή, που ονομάζονται compilers.

Η δημιουργία κώδικα για τους μικροελεγκτές, με την χρήση compiler, γίνεται με χρήση high – level γλώσσες προγραμματισμού, όπως η γλώσσα C, που θα παρουσιάσουμε σε επόμενες ενότητες. Η ανάπτυξη κώδικά με την γλώσσα C προσφέρει πολλά πλεονεκτήματα, αλλά όμως παρουσιάζει ένα μειονέκτημα. Το hex αρχείο κώδικα μηχανής που παράγει και που πρέπει να φορτώσουμε στη μνήμη προγράμματος (μνήμη Flash) είναι σχετικά μεγαλύτερο από αυτό που θα δημιουργούνταν αν γράφαμε την εφαρμογή μας με την γλώσσα Assembly. Τα πλεονεκτήματα που έχουμε αν γράφουμε σε γλώσσα C (high – level γλώσσα) είναι σημαντικά αφού μας επιτρέπει να παράγουμε κώδικα AVR πολύ εύκολα παρά με τη γλώσσα Assembly αφού μας επιτρέπει την ευκολία να παρακολουθούμε το πρόγραμμα μας γραμμένο σε 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 = cx;        //Έξοδος της μεταβλητής στη πόρτα Α
(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;
}