8. Προγραμματισμός Ι/Ο θυρών και χειρισμός bit των I/O καταχωρητών

Στην οικογένεια των μικροελεγκτών AVR υπάρχει ένας αριθμός θυρών για Ι/Ο λειτουργίες που είναι διαφορετικός για κάθε μέλος της οικογένειας AVR. Στον 28-pin μικροελεγκτή ATmega 328 υπάρχουν συνολικά 23 πινς για τις τρεις θύρες PORTB, PORTC και PORTD. Τα υπόλοιπα πινς χρησιμοποιούνται σαν VCC, GND, RESET, AREF, AGND και AVCC.

Ο αριθμός των θυρών που μπορεί να έχει ένας μικροελεγκτής AVR εξαρτάται από τον αριθμό των ακροδεκτών (πινς) της συσκευασίας του. Για παράδειγμα ο 8-pin  AVR έχει τη θύρα Β μόνο, ενώ η 64-pin έκδοση έχει τις θύρες Α έως G. O 28-pin AVR έχει τρεις θύρες και συγκεκριμένα τις PORTB, PORTC και PORTD. Για να χρησιμοποιήσουμε αυτές τις θύρες σαν είσοδο ή έξοδο θα πρέπει να τις προγραμματίσουμε κατάλληλα όπως θα δούμε στην συνέχεια.

Επιπλέον τα πιν μιας θύρας μπορεί να έχουν άλλες λειτουργίες όπως ADC, timers, interrupts και σειριακή επικοινωνία. Θα μελετήσουμε αυτές τις εναλλακτικές λειτουργίες σε επόμενες ενότητες. Σε αυτό το κεφάλαιο θα μελετήσουμε τις απλές Ι/Ο λειτουργίες των μικροελεγκτών AVR. Όχι όλες οι θύρες έχουν 8 πινς. Για παράδειγμα στον ATmega328 η θύρα C έχει 7 πινς.

Σε κάθε θύρα ενσωματώνονται τρεις Ι/Ο καταχωρητές που ονομάζονται PORTx, DDRx και PINx. Για παράδειγμα για την θύρα Β έχουμε PORTB, DDRB και PINB. Σημείωση με τον όρο DDR εννοούμε Data Direction Register ενώ με το ΡΙΝ εννοούμε Port Input pins. Επίσης σημειώστε ότι καθένας από τους Ι/Ο καταχωρητές έχει 8 bit πλάτος και κάθε θύρα έχει το μέγιστο των 8 pins, επομένως κάθε bit ενός Ι/Ο καταχωρητή αναφέρεται σε ένα από τα pins της θύρας. Στη συνέχεια θα περιγράψουμε πως θα έχουμε πρόσβαση στους Ι/Ο καταχωρητές που είναι ενσωματωμένοι στις θύρες.

Διαμόρφωση του καταχωρητή DDR σαν έξοδο δεδομένων

Κάθε μια από τις θύρες Β, C, D του ATmega328 μπορεί να χρησιμοποιηθεί σαν είσοδος ή έξοδος δεδομένων. Ορίζοντας κατάλληλα τον καταχωρητή DDRx μπορούμε να κάνουμε τη δεδομένη θύρα σαν είσοδο ή έξοδο. Για παράδειγμα, για να κάνουμε μια θύρα έξοδο δεδομένων, γράφουμε 1 σε όλα τα bits του καταχωρητή DDRx. Με άλλα λόγια για να στείλουμε δεδομένα σε όλα τα πινς της θύρας Β, πρώτα καταχωρούμε την τιμή 0b11111111 στον καταχωρητή DDRB και έτσι με αυτό τον τρόπο κάνουμε όλα τα πιν σε κατάσταση εξόδου. Στη συνέχεια βάζουμε τα δεδομένα που θέλουμε να έχει η θύρα στον καταχωρητή PORTB.

Στο ακόλουθο απόσπασμα κώδικα, εναλλάσσουμε όλα τα 8bit της θύρας Β ατέρμονα με κάποια καθυστέρηση μεταξύ “on” και “off”.

.ORG 0x300
       LDI R20, 0xFF
       OUT DDRB, R20 ; Διαμόρφωση των bits της θύρας Β σαν έξοδοι
       LDI R21, 0x00
       OUT PORTB, R21
AGAIN: COM R21       ; Εναλλαγή των περιεχομένων του R21
       OUT PORTB, R21
       CALL DELAY    ;  Κλήση ρουτίνας καθυστέρησης
       RJMP AGAIN

.ORG 0x500
DELAY: LDI R16, 0xFF  ; Ορισμός της ρουτίνας καθυστέρησης
LOOP:  NOP
       NOP
       DEC R16   ; Μείωση κατά ένα του καταχωρητή R16
       BRNE LOOP ; Επανάληψη όσο ο R16 δεν είναι μηδέν
       RET

Διαμόρφωση του καταχωρητή DDR σαν είσοδο δεδομένων

Για να κάνουμε μια θύρα σαν είσοδο δεδομένων, θα πρέπει να θέσουμε με 0 τα bit του καταχωρητή DDRx για αυτή τη θύρα και στη συνέχεια να διαβάσουμε τα δεδομένα που υπάρχουν στα πινς. Αυτό το κάνουμε με το διάβασμα των περιεχομένων του καταχωρητή ΡΙΝ.

Σημείωση: για να πάρει η CPU δεδομένα από τα πινς πρέπει η CPU να διαβάσει τα περιεχόμενα του καταχωρητή PINx. Αντίθετα για να στείλει η CPU δεδομένα στα πινς μιας θύρας θα πρέπει να χρησιμοποιήσουμε τον καταχωρητή PORTx αφού πρώτα θέσουμε τα bit του καταχωρητή DDR σε 1.

Υπάρχει μια αντίσταση pull-up για κάθε πιν του μικροελεγκτή AVR. Εάν έχουμε θέσει τα πινς μιας θύρας σαν είσοδοι θέτοντας τα bits του DDRx ως 0, για να ενεργοποιήσουμε την pull-up αντίσταση δίνουμε την τιμή 1 στα αντίστοιχα πινς του καταχωρητή PORTx. Για να απενεργοποιήσουμε την εσωτερική αντίσταση απλά δίνουμε το λογικό 0 στα αντίστοιχα πινς του καταχωρητή PORTx.

Στις περιπτώσεις στις οποίες τίποτα δεν είναι συνδεμένο στα πινς ή οι διασυνδεμένες συσκευές έχουν υψηλή αντίσταση, η εσωτερική αντίσταση προκαλεί pull up στον ακροδέκτη πιν, για αυτόν τον ακροδέκτη στον οποίο η εσωτερική αντίσταση έχει ενεργοποιηθεί.

Διαμόρφωση μεμονωμένων πινς μιας θύρας σαν είσοδο ή έξοδο

Δεν είναι ανάγκη να διαμορφώσουμε όλα τα πινς μιας θύρας ταυτόχρονα σαν εισόδους ή εξόδους. Θέτοντας τα κατάλληλα bit του καταχωρητή DDR σαν 0 ή 1 διαμορφώνουμε τα αντίστοιχα πινς σαν εισόδους ή εξόδους.

Με άλλα λόγια, για να χρησιμοποιήσουμε τα πινς μιας θύρας σαν εισόδους ή εξόδους, θα πρέπει κάθε bit του καταχωρητή DDRx να τεθεί στην κατάλληλη τιμή 0 ή 1 (0 για είσοδο, 1 για έξοδο).

Παράδειγμα

Γράψε ένα πρόγραμμα το οποίο να εναλλάσει συνεχώς όλα τα 8 bit της θύρας Β, με κάποια καθυστέρηση μεταξύ των καταστάσεων “on” και “off”

       LDI R16, 0xFF           ; R16 = 0b11111111
       OUT DDRB, R16           ; κάνε την θύρα Β σαν έξοδο δεδομένων
L1:    LDI R16, 0x55           ; R16 = 0b01010101 = 0x55
       OUT PORTB, R16          ; βάλε την τιμή 0x55 στα πινς της θύρας Β
       CALL DELAY
       LDI R16, 0xAA           ; R16 = 0b10101010 = 0xAA
       OUT PORTB, R16          ; βάλε την τιμή 0xAA στα πινς της θύρας Β
       CALL DELAY
       RJMP L1

Παράδειγμα

Γράψτε ένα πρόγραμμα το οποίο να διαβάζει τα δεδομένα από την θύρα C, αφού πρώτα ενεργοποιηθούν οι εσωτερικές αντιστάσεις και να τα αποθηκεύει σε μια θέση της RAM

.EQU MYTEMP 0x120    ; θέση αποθήκευσης
LDI R20, Ox00        ; R20 = 0b000000
OUT DDRC, R20        ; κάνε την πόρτα C σαν είσοδο δεδομένων
LDI R20, 0xFF        ; R20 = 0b11111111
OUT PORTC, R20       ; ενεργοποίηση όλων των εσωτερικών αντιστάσεων της θύρας C
NOP
IN R21, PINC         ; διάβασε από την θύρα C
STS MYTEMP, R21      ; αποθήκευσε τα δεδομένα στην μνήμη RAM

Προγραμματίζοντας τους Ι/Ο καταχωρητές σε επίπεδο bit

Στη συνέχεια θα μελετήσουμε περεταίρω τις Ι/Ο εντολές. Θα μελετήσουμε τον χειρισμό Ι/Ο bit διότι είναι ένα δυνατό σημείο στον προγραμματισμό και χρησιμοποιείται ευρέως στην οικογένεια AVR.

Οι Ι/Ο θύρες και χειρισμός σε επίπεδο bit

Μερικές φορές θέλουμε να έχουμε πρόσβαση σε μόνο 1 ή 2 bit μιας θύρας σε αντίθεση με το σύνολο των 8bits. Ένα δυνατό χαρακτηριστικό των Ι/Ο είναι η δυνατότητα να έχουμε πρόσβαση σε μεμονωμένα bits μιας θύρας, χωρίς να επηρεάσουμε τα υπόλοιπα bits σε αυτή τη θύρα. Στον ακόλουθο πίνακα αναφέρονται οι εντολές πάνω σε ένα bit για τους AVRs. Οι εντολές αυτού του πίνακα μπορούν να χρησιμοποιηθούν για οποιοδήποτε από τους χαμηλότερους 32 Ι/Ο καταχωρητές. Ο πίνακας δείχνει τους χαμηλότερους Ι/Ο καταχωρητές.

SBI (set bit in I/O register)

Για να θέσουμε σε λογικό 1 ένα bit ενός δοθέν Ι/Ο καταχωρητή, χρησιμοποιούμε την ακόλουθη σύνταξη:

SBI  ioReg, bit_num

Όπου ioReg μπορεί να είναι ένας από τους χαμηλότερους 32 Ι/Ο καταχωρητές (διευθύνσεις 0 έως 31) και bit_num είναι το επιθυμητό bit από 0 εως 7. Στον πίνακα μπορείς να δεις την λίστα των χαμηλότερων 32 Ι/Ο καταχωρητών. Για παράδειγμα η ακόλουθη εντολή χρησιμοποιείται για να θέσει σε λογικό 1 το bit5 της θύρας Β. SBI  PORTB, 5 ;  PB5=1

CBI (clear Bit in I/O register)

Για να μηδενήσουμε ένα απλό bit ενός δοθέντος Ι/Ο καταχωρητή, χρησιμοποιούμε την  ακόλουθη σύνταξη:

CBI ioReg,  bit_number

Για παράδειγμα το ακόλουθο απόσπασμα κώδικα εναλλάσσει το πιν ΡΒ2 συνεχώς:

        SBI  DDRB, 2   ; bit=1 κάνε το ΡΒ2 σαν πιν εξόδου
AGAIN:  SBI  PORTB, 2  ; θέσε το πιν PB2 σε λογικό 1
        CALL DELAY
        CBI PORTB, 2   ; θέσε το πιν ΡΒ2 σε λογικό 0
        CALL DELAY
        RJUMP AGAIN

Για τις Ι/Ο θύρες θα πρέπει να θέτουμε το κατάλληλο bit στον καταχωρητή DDRx κατάλληλα ώστε το πιν να γίνεται έξοδος (ή είσοδος). Σημειώστε ότι το ΡΒ2 είναι το τρίτο bit της θύρας Β (το πρώτο bit είναι το PB0 το δεύτερο bit είναι το ΡΒ1 κ.τ.λ.)

Παράδειγμα

Γράψε ένα πρόγραμμα το οποίο να παράγει μια τετραγωνική κυματομορφή στο πιν 2 της θύρας Β, με 66% duty cycle

; 66% duty cycle σημαίνει ότι η διάρκεια της "on" κατάστασης
; είναι διπλάσια από εκείνης της "off" κατάστασης
           SBI  DDRΒ, 2   ; θεσε το πιν 2 της θύρας Β σαν έξοδο
HERE:      SBI  PORTB, 2  ; θέσε το πιν ΡΒ2 σε κατάσταση HIGH
           CALL DELAY     ; κάλεσε την υπορουτίνα DELAY
           CALL DELAY
           CBI PORTB, 2   ; θέσε το πιν ΡΒ2 σε κατάσταση LOW
           CALL DELAY
           RJMP HERE      ; ατέρμων βρόγχος

Ελέγχοντας ένα πιν εισόδου

Για να αποφασίσει η CPU λαμβάνοντας υπό όψη ένα δοθέν bit ενός Ι/Ο καταχωρητή χρησιμοποιούμε τις εντολές SBIC (Skip if Bit in I/O register Cleared) και  SBIS (Skip if Bit in I/O register Set). Αυτές οι εντολές επιτρέπουν στη CPU να εξετάζει ένα απλό πιν και να παίρνει αποφάσεις που εξαρτώνται  εάν είναι 0 ή 1. Θα πρέπει να ξαναπούμε ότι οι εντολές SBIC και SBIS μπορούν να χρησιμοποιηθούν για οποιοδήποτε bits των χαμηλότερων 32 Ι/Ο καταχωρητών, συμπεριλαμβανομένου των Ι/Ο θυρών Β, C, D κ.τ.λ.

SBIS (Skip if Bit in I/O register Set)

Για να εξετάσει η CPU την κατάσταση ενός απλού bit ενός Ι/Ο καταχωρητή  χρησιμοποιούμε την εντολή SBIS. Με αυτή η εντολή η CPU ελέγχει αυτό το bit και παρακάμπτει την ακριβώς επόμενη εντολή εάν αυτό το bit είναι σε λογικό 1.

SBIC (Skip if Bit in I/O register Cleared)

Επίσης, για να εξετάσει η CPU την κατάσταση ενός απλού bit μπορούμε να χρησιμοποιούμε και την εντολή SBIC. Με αυτή την εντολή η CPU εξετάζει την κατάσταση του απλού bit και εάν αυτό είναι στο λογικό 0, η CPU παρακάμπτει την ακριβώς επόμενη εντολή.

Παράδειγμα

Φτιάξε ένα μικρό σύστημα συναγερμού στο οποίο ο ακροδέκτης PC1 είναι συνδεμένος με ένα διακόπτης ο οποίος είναι στερεωμένος με μια πόρτα. Εαν το PC1 πάει σε LOW σημαίνει ότι η πόρτα είναι ανοικτή. Κάνε το σύστημα τέτοιο έτσι ώστε όταν ανοίξει η πόρτα να ηχήσει ένας βομβητής συνδεμένος στο πιν ΡΒ3

           CBI DDRC, 1    ; θέσε το πιν PC1 σαν είσοδο
           SBI DDRD, 3    ; θέσε το πιν ΡD3 σαν έξοδο
HERE:      SBIC PINC, 1   ; ατέρμων βρόγχος, βγαίνει όταν PC1=0
           RJUMP HERE
           SBI PORTD, 3   ; ενεργοποίησε τον βομβητή
STOP:      RJUMP STOP     ; σταμάτα εδώ

Παράδειγμα

Ένας διακόπτης είναι συνδεμένος με τον ακροδέκτη PC5. Γράψε ένα πρόγραμμα το οποίο να ελέγχει την κατάσταση του διακόπτη και να κάνει τα εξής α) Εάν ο διακόπτης είναι σε κατάσταση LOW να στέλνει το γράμμα ‘Α’ στην θύρα Β και β) Εάν ο διακόπτης είναι σε κατάσταση HIGH να στέλνει το γράμμα ‘Β’ στη θύρα Β.

            CBI DDRC, 5        ; κάνε το πιν PC5 σε κατάσταση εισόδου
            LDI R20, 0xFF
            OUT DDRB, R20      ; κάνε τη θύρα Β σε κατάσταση εξόδου
AGAIN:      SBIS PINC, 5       ; παράκαμψε την επόμενη εντολή εάν PC5=1
            RJMP OVER          ; ο διακόπτης είναι σε κατάσταση LOW
            LDI R21, 'B'       ; R21 = 'B'  ASCII letter B
            OUT PORTB, R21     ; έξοδος στην πόρτα Β, PORTB = 'B'
            RJUMP AGAIN
OVER:       LDI R21, 'A'       ; R21 = 'A'  ASCII letter A
            OUT PORTB, R21     ; PORTB = 'B'
            RJUMP AGAIN

Παράδειγμα

Ένας διακόπτης είναι συνδεμένος στο πιν ΡC1. Γράψε ένα πρόγραμμα το οποίο θα διαβάζει την κατάσταση του διακόπτη και να την αποθηκεύει στη θέση 0x250 της μνήμης RAM

       .EQU MYTEMP = 0x250     ; το όνομα MYTEMP έχει τιμή 0x250
        CBI DDRD, 4            ; κάνε το πιν PD4 σαν είσοδο
AGAIN:  SBIC PIND, 4           ; παράκαμψε την επόμενη εντολή εάν PD4=0
        RJUMP OVER
        LDI R20, 0             ; R20 = 0
        STS MYTEMP, R20        ; γράψε την τιμή 0 στη θέση 0x250 της RAM 
        RJUMP AGAIN
OVER:   LDI R20, 0x01          ; R20 = 0x01
        STS MYTEMP, R20        ; γράψε την τιμή 0x01 στη θέση 0x250 της RAM
        RJUMP AGAIN