AVR-GCC. Παίζοντας με Interrupts.

Τεκμηρίωση του ATmega8.

Τα Interrupts, ή ρουτίνες Interrupts είναι ειδικά κομμάτια κώδικα που θα μπορούσαμε να πούμε πως εκτελούνται παράλληλα με το κυρίως πρόγραμμά μας. Στο παρακάτω διάγραμμα βλέπουμε την “παράλληλη” εκτέλεση.

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

“Απαιτήσεις” ρουτίνας ενός interrupt.
Για να αποφύγουμε ανεπιθύμητες καταστάσεις ακολουθούμε κάποιους κανόνες για τις ρουτίνες των interrupt. Οι ρουτίνες πρέπει να είναι μικρές και εύκολα επεξεργάσιμες.

  • Δεν πρέπει να γίνονται εκτεταμένοι υπολογισμοί μέσα στην ρουτίνα. *
  • Να μην υπάρχουν μεγάλοι βρόχοι.
  • Παρόλο που είναι δυνατή η χρήση άλλων ή του ίδιου interrupt στην ρουτίνα δεν συνίσταται αν δεν ξέρουμε πολύ καλά τον τρόπο λειτουργίας της διαδικασίας.

Μεγαλύτερες διαδικασίες μπορούν συνήθως να χωριστούν σε “τμήμα του interrupt” στον ISR (Interrupt Service Routine **) και σε “τμήμα εργασίας” στο κυρίως πρόγραμμα. Για παράδειγμα η αποθήκευση της κατάστασης των εισόδων στην EEPROM σε συγκεκριμένα χρονικά διαστήματα:
“Τμήμα interrupt”, σύγκριση χρόνου (Timer, RTC) που πέρασε με τον χρόνο που πρέπει να γίνει η εγγραφή. Αν ταιριάζουν ενεργοποιούμε μία καθολική σημαία (flag, που πρέπει να είναι δηλωμένη σαν volatile). Στο “τμήμα εργασίας” στο κυρίως πρόγραμμα ελέγχουμε την σημαία και αναλόγως αποθηκεύουμε η όχι τα δεδομένα.

* Υπάρχει η σπάνια περίπτωση που πρέπει να επεξεργαστούμε απευθείας τιμές από τον ADC. Ιδιαίτερα όταν οι τιμές έρχονται γρήγορα η μία μετά την άλλη. Τότε δεν υπάρχει άλλη επιλογή και η επεξεργασία θα γίνει στον ISR. Με την χρήση του κατάλληλου clock και του κατάλληλου μικροελεγκτή μπορεί να αποφευχθεί η κατάσταση αυτή.

** Άρθρο της WikiPedia για το ISR: Interrupt handler

Πηγές interrupt.
Οι παρακάτω καταστάσεις μπορούν να προκαλέσουν ένα interrupt στον ATmega8 (σελίδα 46 της τεκμηρίωσης).

  • Reset
  • Εξωτερικό interrupt 0
  • Εξωτερικό interrupt 1
  • Timer/Counter2, Σύλληψης Γεγονότος (Capture Event)
  • Timer/Counter2, Υπερχείλιση (Overflow)
  • Timer/Counter1, Σύλληψης Γεγονότος (Capture Event)
  • Timer/Counter1, Ταύτισης Σύγκρισης (Compare Match)
  • Timer/Counter1, Υπερχείλιση (Overflow)
  • Timer/Counter0, Υπερχείλιση (Overflow)
  • SPI, Ολοκλήρωση σειριακής μεταφοράς
  • UART, λήψη χαρακτήρα
  • UART, καταχωρητής δεδομένων άδειος
  • UART, αποστολή χαρακτήρα
  • ADC, ολοκλήρωση μετατροπής
  • EEPROM, EEPROM έτοιμη
  • Αναλογικός Συγκριτής
  • TWI, Two Wire Interface
  • Store Program Memory Ready

Ο αριθμός των διαθέσιμων interrupt διαφέρει για κάθε μικροελεγκτή.

Γενικές πληροφορίες για την εκτέλεση των Interrupt.
Κάθε φορά που ενεργοποιείτε ένα interrupt αυτό απενεργοποιεί το Global Interrupt Enable bit στον καταχωριστή κατάστασης (Status Register) SREG για να αποτρέψει άλλα interrupt. Παρόλο που σε αυτό το σημείο μπορείτε να ενεργοποιήσετε το bit αυτό σε καμία περίπτωση δεν προτείνεται. Το bit ενεργοποιείτε αυτόματα μόλις ολοκληρωθεί η ρουτίνα του interrupt. Εάν κατά την διάρκεια αυτή ενεργοποιηθούν και κάποια άλλα interrupt το bit ενεργοποιείτε και οι ρουτίνες των νέων interrupt εκτελούνται με σειρά προτεραιότητας μετά την τρέχουσα ρουτίνα. Αυτό μπορεί να δημιουργήσει προβλήματα όταν φτάνουν με μεγάλο ρυθμό κάποια σημαντικά interrupts. Μπορεί να οδηγήσει σε μπλοκάρισμα άλλων interrupt μικρότερης βαρύτητας. Ένας από τους λόγους που πρέπει να διατηρούμε τις ρουτίνες των interrupt μικρές.

Interrupt με τον μεταγλωττιστή AVR-GCC.
Οι συναρτήσεις για τον χειρισμό των interrupt περιέχονται στο αρχείο κεφαλίδων interrupt.h (signal.h για παλιότερες εκδόσεις) της avr-libc.

// Για τις sei(), cli() και ISR():
#include <avr/interrupt.h>

Η εντολή sei() ενεργοποιεί τα interrupt. Αυτό που κάνει στην ουσία είναι να ενεργοποιεί (λογικό 1) το Global Interrupt Enable bit στον καταχωρητή κατάστασης SREG.

 sei();

Η εντολή cli() απενεργοποιεί τα interrupt. Αυτό που κάνει στην ουσία είναι να απενεργοποιεί (λογικό 0) το Global Interrupt Enable bit στον καταχωρητή κατάστασης SREG.

cli();

Συχνά θέλουμε ένα τμήμα του κώδικα μα μην διακοπεί από κάποιο interrupt. Είναι λογικό λοιπόν να ξεκινάμε το τμήμα αυτό με την εντολή cli() και να το τελειώνουμε με την sei(). Θα ήταν μειονέκτημα να έχουμε απενεργοποιήσει τα interrupt πριν το τμήμα που θέλουμε και να παραμείνουν ανενεργά μετά. Η sei() θα ενεργοποιούσε τα interrupt άμεσα, (ανεξάρτητα του τι συμβαίνει) γεγονός που ενδεχομένως να δημιουργούσε προβλήματα. Είναι προτιμότερο να δουλέψουμε με τον τρόπο του παρακάτω παραδείγματος.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>

//...

void DoNotInterrupt(void)
{
   uint8_t tmp_sreg;  // Προσωρινή μεταβλητή για τον Status Register

   tmp_sreg = SREG;   // Αποθήκευση του Status register (και το bit I)
   cli();             // Απενεργοποίηση των Interrupt

   /* Από εδώ και κάτω κώδικας χωρίς interrupt */

   /* Τέλος κώδικα χωρίς interrupt */

   SREG = tmp_sreg;   // Επαναφορά του Status Register

}

// Ο παρακάτω τρόπος πρέπει να αποφεύγετε
void NotSoGood(void)
{
   cli();

   /* Από εδώ και κάτω κώδικας χωρίς interrupt */

   sei(); // Ενεργοποίηση των Interrupt

}

int main(void)
{
   //...

   cli();
   // Απενεργοποίηση των Interrupt

   DoNotInterrupt();
   // Και μετά την κλήση είναι απενεργοπιημένα

   sei();
   // Ενεργοποίηση των Interrupt

   DoNotInterrupt();
   // Ακόμη ενεργά
   //...

   /* Ανεπιθύμητη κατάσταση με τις cli/sei: */
   cli();
   // Απενεργοποίηση των Interrupt

   NotSoGood();
   // Μετά την κλήση τα interrupt είναι ενεργοποιημένα
   // κάτι που μπορεί να μην είναι θεμιτό.
   //...
}

ISR.
Η ISR αντικατέστησε την signal() που χρησιμοποιούνταν σε παλιότερες εκδόσεις της avr-libc. Η ISR είναι η συνάρτηση που αναλαμβάνει την επεξεργασία ενός interrupt όταν αυτό προκαλείτε. Το όρισμα που δέχεται πρέπει να είναι ένα interrupt vector. Τα interrupt vector είναι καθορισμένα στο αντίστοιχο αρχείο καφαλίδων ioxxx.h του κάθε μικροελεγκτή. Τα ονόματα είναι αυτά που υπάρχουν και στην τεκμηρίωση του μικροελεγκτή με την διαφορά πως τα κενά έχουν αντικατασταθεί από κάτω παύλες ( πχ. το “Α Vectoer” γίνεται “A_Vector”).

Για παράδειγμα ακολουθεί τμήμα από το αρχείο iom8.h που αφορά τον atmega8.

//...
/* $Id: iom8.h 2115 2010-04-05 23:19:53Z arcanum $ */

/* avr/iom8.h - definitions for ATmega8 */
//...

/* Interrupt vectors */

/* External Interrupt Request 0 */
#define INT0_vect                       _VECTOR(1)
#define SIG_INTERRUPT0                  _VECTOR(1)

/* External Interrupt Request 1 */
#define INT1_vect                       _VECTOR(2)
#define SIG_INTERRUPT1                  _VECTOR(2)

/* Timer/Counter2 Compare Match */
#define TIMER2_COMP_vect                _VECTOR(3)
#define SIG_OUTPUT_COMPARE2             _VECTOR(3)

/* Timer/Counter2 Overflow */
#define TIMER2_OVF_vect                 _VECTOR(4)
#define SIG_OVERFLOW2                   _VECTOR(4)

/* Timer/Counter1 Capture Event */
#define TIMER1_CAPT_vect                _VECTOR(5)
#define SIG_INPUT_CAPTURE1              _VECTOR(5)

/* Timer/Counter1 Compare Match A */
#define TIMER1_COMPA_vect               _VECTOR(6)
#define SIG_OUTPUT_COMPARE1A            _VECTOR(6)

/* Timer/Counter1 Compare Match B */
#define TIMER1_COMPB_vect               _VECTOR(7)
#define SIG_OUTPUT_COMPARE1B            _VECTOR(7)

//...

Παραδείγματα χρήσης της ISR με κάποιο interrupt vector.

#include <avr/interrupt.h>

ISR(INT0_vect)
{
    /* Κώδικας Interrupt */
}

ISR(TIMER0_OVF_vect) /* Ξεπερασμένη μορφή: SIGNAL(SIG_OVERFLOW0) */
{
    /* Κώδικας  Interrupt */
}

ISR(USART_RXC_vect) /* Ξεπερασμένη μορφή: SIGNAL(SIG_UART_RECV) */
{
    /* Κώδικας Interrupt */
}

// Κτλ για τα υπόλοιπα interrupt vector.

Κατά την εκτέλεση της συνάρτησης όλα τα interrupt απενεργοποιούνται αυτόματα. Κατά την έξοδο από την συνάρτηση τα interrupt ενεργοποιούνται πάλι.
Αν κατά την εκτέλεση της συνάρτηση προκύψει κάποιο νέο interrupt (ίδιο ή διαφορετικό) ενεργοποιείτε το αντίστοιχο bit (interrupt flag register) και η κατάλληλη ρουτίνα interrupt καλείτε όταν το πρόγραμμα εξέλθει από την τρέχουσα συνάρτηση.

Ένα πρόβλημα που προκύπτει κατά την εκτέλεση της ρουτίνας του interrupt είναι να προκύψουν και άλλα παρόμοια interrupts. Καλείται η αντίστοιχη ρουτίνα αλλά δεν είμαστε σε θέση να γνωρίζουμε αν το συγκεκριμένο interrupt έχει προκύψει μία δύο ή περισσότερες φορές. Για τον λόγο αυτό πρέπει πάλι να τονίσουμε πόσο σημαντικό είναι η πολύ γρήγορη (στα πλαίσια του ανθρωπίνως δυνατού) έξοδος από μία ρουτίνα ενός interrupt.

Advertisements
This entry was posted in AVR, Electronics and tagged , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s