2. Τύποι μεταβλητών – αριθμητικοί και λογικοί τελεστές

Μεταβλητές και τύποι δεδομένων

Στον προγραμματισμό των μικροελεγκτών AVR χρησιμοποιούμε μεταβλητές για το χειρισμό των δεδομένων του προβλήματος που προσπαθούμε να επιλύσουμε. Στον προγραμματισμό με τη γλώσσα C, oι μεταβλητές πρέπει να δηλώνονται πριν τις χρησιμοποιήσουμε. Μια δήλωση μεταβλητής πρέπει να περιλαμβάνει το όνομα και το τύπο μεταβλητής και αν θέλουμε μπορούμε να τις αρχικοποιούμε ταυτόχρονα με τη δήλωση της. Το όνομα μιας μεταβλητής πρέπει να ξεκινά με γράμμα (Α έως Ζ ή a έως z) ακολουθούμενο από κανένα ή περισσότερα γράμματα, αριθμητικά ψηφία ή χαρακτήρες υπογράμμισης.

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

Μια μεταβλητή του τύπου void αντιπροσωπεύει το τίποτα. Ο τύπος void χρησιμοποιείται κυρίως με τις συναρτήσεις για να δείξει ότι μια συνάρτηση δεν επιστρέφει τίποτα ή δεν έχει παραμέτρους εισόδου.

Παράδειγμα

Γράψε ένα πρόγραμμα για τον AVR στη γλώσσα C το οποίο να στέλνει τις τιμές 00-FF στη θύρα Β.

#include <avr/io.h>
int main(void)
{ 
       unsigned char z;
       DDRB = 0xFF;                    // Η θύρα Β σαν έξοδος
       for(z=0; z<=255; z++)
           PORTB=z;
       return 0;
}
/* Σημείωση: το πρόγραμμα ποτέ δεν τερματίζεται στον βρόγχο for διότι εάν αυξήσεις τον τύπο μεταβλητής unsigned char όταν είναι 0xFF θα πάρει την τιμή μηδέν*/

Ο τύπος δεδομένων unsigned  char παριστάνει 8bit δεδομένα χωρίς πρόσημο, δηλαδή τιμές 0 – 255 (0 – 0xFF). Θα πρέπει να δίνουμε προσοχή στο μέγεθος των δεδομένων που χρησιμοποιούμε και θα πρέπει αν είναι δυνατόν να χρησιμοποιούμε τον τύπο unsigned char αντί int. Ο τύπος char παριστάνει προσημασμένα 8bit δεδομένα με το MSB να είναι το πρόσημο και τα υπόλοιπα 7bits παριστάνουν το μέγεθος, έτσι για αυτό τον τύπο έχουμε το εύρος τιμών από -128 έως +128

Ο τύπος unsigned int παριστάνει 16bit θετικούς ακεραίους στην περιοχή 0 έως 65535. Ο τύπος δεδομένων int παριστάνει 16bit προσημασμένες τιμές στο εύρος -32768 έως +32767 με το MSB να παριστάνει το πρόσημο με αποτέλεσμα να έχουμε 15bit διαθέσιμα για το μέγεθος των τιμών.

Παράδειγμα

Γράψε ένα πρόγραμμα για τον AVR στη γλώσσα C το οποίο να εναλλάσει τα bits της πόρτας Β 100.000 φορές

#include <avr/io.h>
int main(void)
{
      unsigned long z;
      DDRB = 0xFF;              // Η θύρα Β σαν έξοδος
      for(z=0; z<100000; z++)
      {
            PORTB = 0x55;
            PORTB = 0xAA;
      }
      while(1);                 // μείνε εδώ για πάντα
      return 0;
}

Επίσης έχουμε τον 32bit μη προσημασμένο τύπο unsigned long ακεραίων τιμών και τον τύπο long για προσημασμένους αριθμούς με συνολικό μέγεθος 32bit μαζί με το πρόσημο που ορίζεται από το MSB.

Τέλος υπάρχει και ο τύπος δεδομένων float (ή double) με μέγεθος 32bit που παριστάνει αριθμούς κινητικής υποδιαστολής.

Σταθερές

Υπάρχουν τέσσερις τύποι σταθερών: characters, integers, floating – point and strings. Η σταθερά χαρακτήρα είναι ένας ακέραιος γραμμένος σαν ένας χαρακτήρας μέσα σε μονούς αποστρόφους, όπως τον ‘a’. Η σταθερά χαρακτήρα παριστάνεται από τον αντίστοιχο κωδικό ASCII του χαρακτήρα. Ένα string είναι μια ακολουθία από μηδέν ή περισσότερους χαρακτήρες μέσα σε διπλούς αποστρόφους όπως: “ATmega328 is a microcontroller made by Atmel” Κάθε ξεχωριστός χαρακτήρας σε ένα string παριστάνεται με την ASCII τιμή του.

Μια ακέραια σταθερά είναι μια κυριολεκτική τιμή:  5678 είναι μια σταθερά int. Μια σταθερά long γράφεται με τρόπο ώστε στο τέλος να καταλήγει με το γράμμα l ή L π.χ.  87651234L.

Όπως στη γλώσσα Assembly, ένας αριθμός στη C μπορεί να οριστεί σε διαφορετική βάση. Η μέθοδος που προσδιορίζει τη βάση του αριθμού γίνεται με το κατάλληλο πρόθεμα στον αριθμό. Τα προθέματα για διαφορετικές βάσεις φαίνονται στον ακόλουθο πίνακα:

Δήλωση μεταβλητών

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

int i, j, k;
char x, y;

Μπορούμε να δίνουμε μια αρχική τιμή κατά τη δήλωση της μεταβλητής, όπως στο ακόλουθο παράδειγμα:

int i = 0;
char  echo = ‘y’;

Αριθμητικές πράξεις

Στη γλώσσα C μπορούμε να εκτελέσουμε αριθμητικές πράξεις μεταξύ μεταβλητών και σταθερών. Οι αριθμητικές πράξεις στη C φαίνονται στον ακόλουθο πίνακα:

Η έκφραση a%b δίνει το υπόλοιπο όταν ο αριθμός a διαιρείται με τον αριθμό b. Ο τελεστής % εφαρμόζεται μόνο σε ακέραιους αριθμούς. Ο τελεστής / προκαλεί διαίρεση που όταν οι τελεσταίοι είναι ακέραιοι αριθμοί, δίνει την ακέραιη τιμή του υπολοίπου.

Ο τελεστής ++ προσθέτει την τιμή 1 στην μεταβλητή, ενώ ο – – αφαιρει την τιμή 1 από την μεταβλητή. Ανάλογα εάν ο τελεστής ++ (ή ο –) βρίσκεται μπροστά ή πίσω στη μεταβλητή, ή αύξηση (μείωση) εκτελείται πριν ή μετά την χρήση της μεταβλητής στην έκφραση. Για παράδειγμα, στην επόμενη έκφραση:

x = ++y

η μεταβλητή y αυξάνει πριν εκχωρηθεί στη μεταβλητή x, ενώ στην επόμενη έκφραση

a = 3 + y++

Το άθροισμα της τιμής 3 και της μεταβλητής y εκχωρούνται στη μεταβλητή a και μετά η μεταβλητή y αυξάνει κατά 1.

Σχεσιακοί και Λογικοί τελεστές

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

Οι σχεσιακοί τελεστές δημιουργούν απλές παραστάσεις που κάθε μια τους μπορεί να πάρει τιμή true ή false. Αν μια παράσταση είναι true αποτιμάται με την τιμή 1, ενώ αν είναι false αποτιμάται με την τιμή 0.

Παράδειγμα

Αν για τις ακέραιες τιμές x=3, y=5 και z=8 τότε ισχύουν:
x==y      false
y!=z      true
x>x       false
z<=(x+y)  true

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

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

Η λειτουργία ενός λογικού τελεστή περιγράφεται συνήθως με τον αντίστοιχο πίνακα αληθείας που δίνει την τιμή της σύνθετης παράστασης για κάθε συνδυασμό των επιμέρους απλών παραστάσεων. Αν p και q είναι δυο απλές παραστάσεις τότε οι πίνακες αληθείας των λογικών τελεστών AND, OR και NOT δίνονται στον προηγούμενο πίνακα.

Παράδειγμα

Αν για τις ακέραιες μεταβλητές a=2, b=3 και c=5 τότε ισχύουν:
(a==b)||(b!=c)          true
!((c-b)==a)             false
((a+b)!=c)&&((a*b)>=c)  false
(a>0)&&((b!=3)||(a<=c))  true

Τελεστές ανά bit στη γλώσσα C

Ένα σπουδαίο χαρακτηριστικό της γλώσσας C είναι η δυνατότητα της να εκτελεί πράξεις σε επίπεδο bit. Ενώ κάθε προγραμματιστής της γλώσσας C είναι εξοικειωμένος με τις λογικές πράξεις AND(&&), OR(||) και NOT(!), πολλοί από αυτούς είναι λιγότερο εξοικειωμένοι με τους τελεστές σε επίπεδο bit όπως AND(&), OR(|), EX-OR(^) και την αναστροφή (~), τη δεξιά μετατόπιση (>>) και την αριστερή μετατόπιση (<<). Αυτοί οι τελεστές χρησιμοποιούνται σε συστήματα μιας πλακέτας με μικροελεγκτή, επομένως η κατανόηση τους είναι η βάση στη σχεδίαση εφαρμογών με μικροελεγκτές.

Ακολουθούν μερικά παραδείγματα με τους τελεστές σε επίπεδο  bit

0x35  &  0x0F  =  0x05    ή     0b00110101  &  0b00001111  =  0b00000101
0x04  |  0x68  =  0x6C    ή     0b00000100  |  0b01101000  =  0b01101100
0x54  ^  0x78  = 0x2C     ή     0b01010100  ^  0b01111000  =  0b00101100
~0x55 = 0xAA              ή    ~(0b01010101)  = 0b10101010

Παράδειγμα

Γράψε ένα πρόγραμμα σε γλώσα C για τον AVR που να εναλλάσσει το bit 4 της θύρας Β

#include <avr/io.h>
int main(void)
{
       DDRB = 0xFF;                         //Διαμόρφωσε τη θύρα Β σαν έξοδος
       while(1)
       {
                PORTB =  PORTB | 0b00010000;  //θέσε σε ένα το bit 4 της θύρας Β
                PORTB  = PORTB & 0b11101111;  //μηδένισε το bit 4 της θύρας Β
       }
       return 0;
}

Παράδειγμα

Γράψε ένα πρόγραμμα σε γλώσσα C για τον AVR το οποίο να διαβάζει το bit 5 της θύρας C. Εάν είναι HIGH να στέλνει την τιμή 0x55 στην θύρα Β, διαφορετικά να στέλνει την τιμή 0xAA στη θύρα Β

#include <avr/io.h>
int main(void)
{
    DDRB  =  0xFF;                       //Η θύρα Β σαν έξοδος
     DDRC = 0x00;                        //Η θύρα C σαν είσοδος
    while(1)
     {
        if((PINC & 0b00100000) != 0) //Έλεγξε το bit 5 του Ι/Ο καταχωρητή  PINC
            PORTB  =  0x55;
        else
            PORTB = 0xAA;
      }
      return 0;
}

Παράδειγμα

Γράψε ένα πρόγραμμα σε γλώσσα C για τον AVR το οποίο να διαβάζει την κατάσταση του bit 5 της θύρας Β και να τη στέλνει στο bit 7 της θύρας C συνεχόμενα.

#include <avr/io.h>
int main(void)
{
      DDRB = DDRB &  0b11011111 ; //bit 5 της θύρας Β διαμορφώνεται σαν είσοδος
      DDRC = DDRC  |  0b10000000; //bit 7 της θύρας C διαμορφώνεται σαν έξοδος
      while(1)
      {
          if(PINB  &  0b00100000)
              PORTC = PORTC | 0b10000000;  //θέσε σε 1 το bit 7 της θύρας C
           else
              PORTC = PORTC & 0b01111111;  //μηδένισε το bit 7 της θύρας C
     }
     return 0;
}

Παράδειγμα

Γράψε ένα πρόγραμμα στη γλώσσα C για τον AVR το οποίο να εναλλάσσει όλα τα bits της θύρας Β συνεχόμενα: (α) χρησιμοποιώντας τον τελεστή αντιστροφής (β) κάνοντας την πράξη EX-OR

(α)
#include <avr/io.h>
int main(void)
{
     DDRB  =  0xFF;             // Η θύρα Β σαν έξοδος
     PORTB = 0xAA;
     while(1)
           PORTB = ~PORTB;
    return 0;
}

(β)
#include <avr/io.h>
int main(void)
{
     DDRB  =  0xFF;             // Η θύρα Β σαν έξοδος  
     PORTB = 0xAA;
     while(1)
                  PORTB  =  PORTB  ^  0xFF;
     return 0;
}

Σύνθετοι τελεστές εκχώρησης σε επίπεδο bit

Για να μειώσουμε τον κώδικα μιας εφαρμογής στη γλώσσα C για τον AVR μπορούμε να χρησιμοποιήσουμε τους σύνθετους τελεστές εκχώρησης, όπως φαίνονται στον ακόλουθο πίνακα:

Παράδειγμα

Γράψε ένα πρόγραμμα στη γλώσσα C για τον AVR το οποίο να διαβάζει το bit 5 της θύρας Β. Εάν είναι 1 κάνε το bit 4 της θύρας Β είσοδο, διαφορετικά άλλαξε το pin 4 της θύρας Β σαν έξοδο. Χρησιμοποίησε σύνθετους τελεστές εκχώρησης:

#include <avr/io.h>
int main(void)
{
    DDRB &= 0b11011111;  // το bit 5 της θύρας Β διαμορφώνεται σαν είσοδο
    while(1)
    {
        if(PINB & 0b00100000)
            DDRB &= 0b11101111;  //το bit 4 της θύρας Β διαμορφώνεται σαν είσοδο
        else
            DDRB |= 0b00010000;  // το bit 4 της θύρας Β διαμορφώνεται σαν έξοδο
    }
    return 0;
}

Τελεστές μετατόπισης σε επίπεδο bit στη γλώσσα C

Υπάρχουν δυο τελεστές μετατόπισης που εφαρμόζονται πάνω σε ακεραίους στη γλώσσα C. Αυτοί οι τελεστές μετατοπίζουν τα bits δεξιά ή αριστερά του ακεραίου γραμμένου στην δυαδική μορφή. Δες τον ακόλουθο πίνακα:

Παρακάτω φαίνονται παραδείγματα των τελεστών μετατόπισης στη γλώσσα C:

0b00010000 >> 3 = 0b00000010  /* μετατόπιση των bits προς τα δεξιά 3 φορές */
0b00010000 <<3 = 0b10000000  /*  μετατόπιση των bits προς τα αριστερά 3 φορές */
1 << 3 = 0b00001000  /* μετατόπιση προς τα αριστερά κατά 3 φορές */

Πολλές φορές όταν γράφουμε κώδικα χρησιμοποιούμε ακεραίους που στη δυαδική τους αναπαράσταση αποτελούνται από μια θέση από μονάδα και οι υπόλοιπες θέσεις από μηδενικά. Σε μια τέτοια περίπτωση μπορούμε να αφήσουμε τον compiler για την δημιουργία των μονάδων και των μηδενικών με την χρήση των τελεστών μετατόπισης. Για παράδειγμα αντί να γράψουμε 0b00100000 μπορούμε να γράψουμε 0b00000001 << 5 ή να γράψουμε απλά 1 << 5.

Μερικές φορές χρειαζόμαστε αριθμούς όπως ο 0b11101111. Για να παράγουμε τέτοιους αριθμούς κάνουμε αρχικά μετατόπιση και στη συνέχεια αντιστροφή. Για παράδειγμα για να δημιουργήσουμε τον αριθμό 0b11101111 μπορούμε να γράψουμε  ~(1<<5)

Παράδειγμα

Γράψε ένα πρόγραμμα στη γλώσσα C για τον AVR ο οποίος να διαβάζει το bit 7 της θύρας Β. Εάν είναι 1, κάνε το bit 4 της θύρας Β σαν είσοδο αλλιώς άλλαξε το pin 4 της θύρας Β σαν έξοδο.

#include <avr/io.h>
int main(void)
{
    DDRB = DDRB & ~(1<<7);     //το bit 7 της θύρας Β διαμορφώνεται σαν είσοδο
    while(1)
    {  
        if(PINB & (1<<7))
           DDRB = DDRB & ~(1<<4);//το bit 4 της θύρας Β διαμορφώνεται σαν είσοδο
        else
           DDRB = DDRB | (1<<4);  // το bit 4 της θύρας Β διαμορφώνεται σαν έξοδο
    }
    return 0;
}
/* Σημείωση: χρησιμοποιώντας σύνθετους τελεστές εκχώρησης μπορούμε να γράψουμε "DDRB &= ~(1<<4);" Και "DDRB |= (1<<4);".  */

Παράδειγμα

Γράψε ένα πρόγραμμα στη γλώσσα C για τον AVR που να διαβάζει την κατάσταση του bit 5 της θύρας Β και να το στέλνει στο bit 7 της θύρας C συνεχόμενα.

#include <avr/io.h>
int main(void)
{
      DDRB &= ~(1<<5);  // το bit 5 της θύρας Β διαμορφώνεται σαν είσοδο
      DDRC |= (1<<7);      // το bit 7 της θύρας C διαμορφώνεται σαν έξοδο
      while(1)
      {
          if(PINB &  (1<<5))
                PORTC  |=  (1<<7); // θέσε σε ένα το bit 7 της θύρας C
          else
                PORTC &= ~(1<<7); // θέσε σε μηδέν το bit 7 της θύρας C
      }
      return 0;
}