11. Η έννοια της διακοπής στους AVR

Σε αυτή την ενότητα, πρώτα θα εξετάσουμε την διαφορά μεταξύ δειγματοληψιών και διακοπών και στη συνέχεια θα εξετάσουμε τις διακοπές του AVR.

Διακοπές έναντι δειγματοληψιών

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

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

Για παράδειγμα στα προγράμματα που δώσαμε στις προηγούμενες ενότητες, χρησιμοποιήσαμε την εντολή ελέγχου “SBIS TIFR0, TOV0” όπου η CPU περίμενε έως ότου ο timer υπερχειλίσει και καθώς η CPU περίμενε δεν έκανε τίποτε άλλο. Αυτό είναι σπατάλη χρόνου, στην οποία η CPU θα μπορούσε να χρησιμοποιήσει έτσι ώστε να εκτελέσει άλλες χρήσιμες εργασίες.

Στην περίπτωση του timer όταν χρησιμοποιούμε την μέθοδο των διακοπών, η CPU μπορεί να εκτελεί άλλες εργασίες και όταν η σημαία TOV0 γίνει 1, ο timer θα εκτελέσει διακοπή για να εξυπηρετήσει την συσκευή την κατάλληλη στιγμή.

Interrupt service routine

Για κάθε διακοπή θα πρέπει να υπάρχει ένα απόσπασμα κώδικα που να εξυπηρετεί την διακοπή όταν αυτή συμβεί. Αυτό το κομμάτι προγράμματος αναφέρεται ως interrupt service routine ISR (ρουτίνα εξυπηρέτησης διακοπής) ή με το όνομα interrupt hander (διαχείριση διακοπής).

Όταν το σήμα μιας διακοπής ενεργοποιηθεί, δηλαδή να συμβεί μια διακοπή, η CPU αποθηκεύει τον καταχωρητή προγράμματος PC στο σωρό και φορτώνει στον καταχωρητή PC την διεύθυνση της ρουτίνας εξυπηρέτησης διακοπής, με αποτέλεσμα να εκτελεστεί η αντίστοιχη ISR.

Στους περισσότερους μικροελεγκτές, για κάθε διακοπή υπάρχει μια σταθερή θέση στη μνήμη προγράμματος που κρατά την διεύθυνση της ISR που θα εκτελεστεί όταν συμβεί η αντίστοιχη διακοπή. Το σύνολο των θέσεων μνήμης που κρατάνε τις διευθύνσεις των ISR αναφέρονται ως interrupt vector table.

Βήματα για την εξυπηρέτηση μιας διακοπής

Όταν συμβεί μια διακοπή, ο μικροελεγκτής AVR ακολουθεί τα ακόλουθα βήματα:
1] Τελειώνει την εκτέλεση της τρέχον εντολής και αποθηκεύει την διεύθυνση της επόμενης εντολής (program counter) στο σωρό.
2] Πηγαίνει στη σταθερή θέση της μνήμης προγράμματος της αντίστοιχης διακοπής στον πίνακα interrupt vector table, ο οποίος κατευθύνει την CPU στην διεύθυνση της ρουτίνας εξυπηρέτησης διακοπής (ISR)
3] Ο μικροελεγκτής ξεκινά να εκτελεί την ρουτίνα εξυπηρέτησης διακοπής, μέχρι να φθάσει στην τελευταία εντολή η οποία είναι η RETI
4] Με την εκτέλεση της RETI, ο μικροελεγκτής επιστρέφει στο σημείο που διακόπηκε ως εξής: η CPU παίρνει την διεύθυνση του καταχωρητή προγράμματος PC από το σωρό και μετά αρχίζει να εκτελεί τις εντολές μετά από αυτή την διεύθυνση.

Στο ακόλουθο σχήμα φαίνεται το interrupt vector table για τον μικροελεγκτή ATmega328 AVR

Interrupt                                 ROM Location (Hex)
Reset                                       0000
External Interrupt Request 0                0002
External Interrupt Request 1                0004
Pin Change Interrupt Request 0              0006
Pin Change Interrupt Request 1              0008
Pin Change Interrupt Request 2              000A
Watchdog Time-out Interrupt                 000C
Timer/Counter2 Compare Match A              000E
Timer/Counter2 Compare Match B              0010
Time/Counter2 Overflow                      0012
Time/Counter1 Capture Event                 0014
Timer/Counter1 Compare Match A              0016
Timer/Counter1 Compare Match B              0018
Timer/Counter1 Overflow                     001A
Timer/Counter0 Compare Match A              001C
Timer/Counter0 Compare Match B              001E
Time/Counter0 Overflow                      0020
SPI Serial Transfer complete                0022
USART, Receive Complete                     0024
USART, Data Register Empty                  0026
USART, Transmit Complete                    0028
ADC Conversion Complete                     002A
EEPROM Ready                                002C
Analog Comparator                           002E
Two-wire Serial Interface (I2C)             0030
Store Program Memory Ready                  0032            

Όπως φαίνεται στον προηγούμενο πίνακα, υπάρχει περιορισμένος αριθμός bytes που είναι διαθέσιμος για τις διακοπές. Για παράδειγμα, ένας αριθμός από 2 words (4 bytes) στις θέσεις 0020 και 0021 διατίθενται για την διακοπή υπερχείλισης του Timer0. Κανονικά, η ρουτίνα εξυπηρέτησης διακοπής είναι πολύ μεγάλη για να αποθηκευτεί σε αυτές τις θέσεις. Για αυτό το λόγο μια εντολή JMP τοποθετείται στον interrupt vector table για να δείξει την διεύθυνση της ISR.

Στον πίνακα βλέπουμε ότι υπάρχουν θέσεις 2 words (4 bytes) της μνήμης προγράμματος που αντιστοιχούν στη φάση RESET. Αυτές οι θέσεις έχουν διευθύνσεις 0 – 1. Για αυτό το λόγο, στα προγράμματα μας τοποθετούμε μια εντολή JMP σαν πρώτη εντολή για να κατευθύνει τη CPU μακριά από τον interrupt vector table, όπως φαίνεται στο ακόλουθο σχήμα.

            .ORG  0     ;wake-up ROM reset location
             JMP  MAIN  ;bypass interrupt vector table
;----- the wake-up program
            .ORG  $100
MAIN:       ....        ;enable interrupt flags
            ....

Ενεργοποιώντας και απενεργοποιώντας τις διακοπές

Κατά την διάρκεια του RESET όλες οι διακοπές είναι απενεργοποιημένες. Για να ανταποκριθεί η CPU σε μια διακοπή που θα συμβεί, θα πρέπει να ενεργοποιηθούν με το λογισμικό. Το bit D7 του καταχωρητή κατάστασης SREG είναι υπεύθυνο για την καθολική ενεργοποίηση και απενεργοποίηση των διακοπών.

Με την εντολή “CLI” (Clear Interrupt) θέτουμε Ι=0 που σημαίνει ότι καθολικά δεν θέλουμε καμία διακοπή, ενώ με την εντολή “SEI” (Set Interrupt) θέτουμε I=1, που σημαίνει ότι θέλουμε να ενεργοποιούνται καθολικά οι διακοπές.

Βήματα για την ενεργοποίηση μιας διακοπής

Για να ενεργοποιήσουμε μια οποιαδήποτε διακοπή ακολουθούμε τα ακόλουθα βήματα:
1] Θέτουμε το Bit D7 (I) του καταχωρητή SREG σε HIGH για να μπορούν να λειτουργούν οι διακοπές καθολικά.
2] Όταν Ι=1 κάθε διακοπή μπορεί να ενεργοποιηθεί θέτοντας σε HIGH τη σημαία interrupt enable (IE) για την συγκεκριμένη διακοπή.

Υπάρχουν μερικοί Ι/Ο καταχωρητές που ορίζουν τα interrupt enable bits όπως οι καταχωρητές TIMSKn για τους Timer0, Timer1 και Timer2. Όταν Ι=0 καμία διακοπή δεν μπορεί να εξυπηρετηθεί ακόμα κι αν τα αντίστοιχα interrupt enable bits είναι σε HIGH.

Κατά την ενεργοποίηση μιας διακοπής το Ι bit του καταχωρητή κατάστασης SREG τίθεται σε 0 από τον AVR για να μην έχουμε διακοπή όταν εξυπηρετείται μια άλλη διακοπή. Στο τέλος της ISR η εντολή RETI θέτει Ι=1 για να μπορεί να εξυπηρετήσει η CPU μια άλλη διακοπή.

Παράδειγμα: Γράψε τις εντολές για τις οποίες (α) ενεργοποιεί το Timer0 overflow interrupt and Timer0 compare match A interrupt και (β) απενεργοποιεί το Timer0 overflow interrupt και μετά (γ) δείξε πώς να απενεργοποιήσετε όλες τις διακοπές με μια απλή εντολή.

(α)  LDI R20, (1<<TOIE0)|(1<<OCIE0A)   ; TOIE0=0,  OCIE0A=1
     STS TIMSK0, R20  ; enable Timer0 overflow and compare match A
     SEI              ; allow interrupts to come in

(β) LDS R20, TIMSK0   ; R20 = TIMSK0
    ANDI  R20, 0xFF^(1<<TOIE0)  ; TOIE0 = 0
    STS  TIMSK0, R20           ;  disable Timer0 interrupt

Μπορούμε να γράψουμε τις παραπάνω εντολές ως εξής:
    LDS  R20, TIMSK0      ; R20 = TIMSK
    CBR  R20, 1<<TOIE0    ; TOIE0 = 0
    STS  TIMSK0, R20      ; disable Timer0 interrupt

(γ) CLI
Σημείωση: Στο μέρος (α) μπορούμε να γράψουμε "LDI R20, 0x03" στη θέση της εντολής:
LDI R20, (1<<TOIE0)|(1<<OCIE0A)