16. Προγραμματίζοντας τις διακοπές στη γλώσσα C

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

Στη C δεν υπάρχει εντολή που να διαχειρίζεται τις διακοπές. Στο Atmel Studio, τα επόμενα έχουν προστεθεί για τη διαχείριση των διακοπών.
1) Interrupt include file: θα πρέπει να συμπεριλάβουμε το interrupt header file εάν θέλουμε να χρησιμοποιήσουμε τις διακοπές στο πρόγραμμα μας με την ακόλουθη εντολή:
#include <avr\interrupt.h>
2) cli() και sei(): στη γλώσσα Assembly οι εντολές CLI και SEI μηδενίζουν και θέτουν το bit I του καταχωρητή SREG αντίστοιχα. Στο Atmel Studio οι εντολές cli() και sei() κάνουν το ίδιο αντίστοιχα.
3) Ορίζοντας την ISR: για να γράψουμε μια ISR (interrupt service routine) για μια διακοπή, θα πρέπει να χρησιμοποιήσουμε την ακόλουθη δομή:

ISR(interrupt vector name)
{
     //our program
}

Για το interrupt vector name θα πρέπει να χρησιμοποιήσουμε το ISR όνομα του ακόλουθου πίνακα. Για παράδειγμα, η επόμενη ISR εξυπηρετεί την Timer0

ISR(TIMER0_COMP_vect)
{
}

Ακολουθεί ο πίνακας ονομάτων διακοπών στο vector table:

O C compiler αυτόματα προσθέτει εντολές στην αρχή των ISRs οι οποίες αποθηκεύουν όλους τους καταχωρητές γενικού σκοπού GPRs και τον καταχωρητή κατάστασης SREG στο σωρό. Μερικές εντολές προσθέτονται στο τέλος των ISRs για να ξαναφορτωθούν οι καταχωρητές.

Παράδειγμα: Χρησιμοποιήστε τον Timer0 για να παράγετε μια τετραγωνική κυματομορφή στο πιν PORTB.5 ενώ την ίδια στιγμή μεταφέρονται δεδομένα από την PORTC στη PORTD.

Απάντηση:
#include <avr/io.h>
#include <avr/interrupt.h>

ISR(TIMER0_OVF_vect)         //ISR for Timer0 overflow
{
        TCNT0 = -32;
        PORTB ^= (1<<5);    //toggle PORTB.5
}
int main()
{
       DDRB |= (1<<5);      //DDRB.5 = output
       TCNT0 = -32;         //timer value for 2μs
       TCCR0A = 0x00;
       TCCR0B = 0x01;       //Normal mode, int clk, no prescaler
       TIMSK0 = (1<<TOIE0); //enable Timer0 overflow interrupt
       sei();
       DDRC = 0x00;         //make PORTC input
       DDRD = 0xFF;         //make PORTD output
       while(1)             //wait here
              PORTD = PINC;
}

Παράδειγμα: Χρησιμοποιώντας τις διακοπές των Timer0 και Timer1, παράγετε τετραγωνικές κυματομορφές στα πινς ΡΒ1 και ΡΒ3 αντίστοιχα, ενώ ταυτόχρονα μεταφέρονται δεδομένα από την PORTC στη PORTD.

Απάντηση
#include <avr/io.h>
#include <avr/interrupt.h>
int main()
{
    DDRB |= (1<<1)|(1<<3);   //κάνε τα DDRB.1 και DDRB.3 έξοδο
    DDRC = 0x00;             //κάνε την PORTC είσοδο   
    DDRD = 0xFF;             //κάνε την PORTD έξοδο
    TCNT0 = -160;
    TCCR0A = 0x00;
    TCCR0B = 0x01;           //Normal mode, int clk, no prescaler
    TCNT1H = (-1600)>>8;     //το υψηλότερο byte
    TCNT1L = (-1600)         //το χαμηλότερο byte
    TCCR1A = 0x00;
    TCCR1B = 0x01;
    TIMSK0 = (1<<TOIE0);    //ενεργοποίησε τη διακοπή του Timer0
    TIMSK1 = (1<<TOIE1);    //ενεργοποίησε τη διακοπή του Timer1
    sei();                  //καθολική ενεργοποίηση διακοπών
    while(1)                //περίμενε εδώ
           PORTD = PINC;
}
ISR(TIMER0_OVF_vect)       //ρουτίνα ISR για την διακοπή Timer0 overflow
{
    TCNT0 = -160;
    PORTB ^= (1<<1);       //εναλλαγή του PORTB.1
}
ISR(TIMER1_OVF_vect)       //ρουτίνα ISR για την διακοπή Timer0 overflow
{
    TCNT1H = (-1600)>>8;
    TCNT1L = (-1600);
    PORTB ^= (1<<3);      //εναλλαγή του PORTB.3
}
Σημείωση: μπορούμε να χρησιμοποιήσουμε την εντολή "TCNT1 = -1600;" στη θέση των ακόλουθων εντολών:
TCNT1H = (-1600)>>8;
TCNT1L = (-1600);

Παράδειγμα: Χρησιμοποιώντας τις διακοπές των Timer0 και Timer1 γράψε ένα πρόγραμμα το οποίο: (α) η PORTC να απαριθμεί κάθε φορά που ο Timer1 υπερχειλίζει. Θα πρέπει να υπερχειλίζει μια φορά στο δευτερόλεπτο. (β) Παλμοί τροφοδοτούν τον Timer0 ο ποίος χρησιμοποιείται σαν μετρητής. Όταν τα περιεχόμενα του μετρητή φτάσουν στα 200, να εναλλάσσει το πιν PORTB.5 (γ) να εναλλάσσει το PORTB.1 συνεχόμενα.

Απάντηση
#include <avr/io.h>
#include <avr/interrupt.h>
int main()
{
     DDRC = 0xFF;                    //κάνε την PORTC έξοδο
     DDRB |= (1<<5);                 //PORTB.5 έξοδος
     DDRB |= (1<<1);                 //PORTB.1 έξοδος  
     PORTD |= (1<<4);                //ενεργοποίησε τις pull-up αντιστάσεις  
     TCNT0 = -200;                   //φόρτωσε τον Timer0 με -200
     TCCR0A = 0x00;
     TCCR0B = 0x06;                  //Normal mode, falling edge, no prescaler
     TIMSK0 = (1<<TOIE0);            //ενεργοποίησε την διακοπή του Timer0
     TCNT1H = (-62500)>>8;           //το υψηλότερο byte
     TCNT1L = (-62500)&0xFF;         //υπερχείληση μετά από 31250 παλμούς ρολογιού
     TCCR1A = 0x00;                  //Normal mode
     TCCR1B = 0x04;                  //internal clock, prescaler 1:256
     TIMSK1 = (1<<TOIE1);            //ενεργοποίησε την διακοπή του Timer 1
     sei();                          //καθολική ενεργοποίηση διακοπών
     while(1)
        PORTB ^= (1<<1);             //εναλλαγή του ΡΒ1
}
ISR(TIMER0_OVF_vect)                 //ρουτίνα ISR για την υπερχείλιση του Timer0
{
     TCNT0 = -200;
     PORTB ^= (1<<5);                //εναλλαγή του PORTB.5
}
ISR(TIMER1_OVF_vect)                 //ρουτίνα ISR για την υπερχείλιση του Timer1
{
      TCNT1H = (-62500)>>8;          // το υψηλότερο byte
      TCNT1L = (-62500)&0xFF;        //υπερχείλιση μετά από 62500 παλμούς ρολογιού
      PORTC++;                       //αύξηση του PORTC
}

Παράδειγμα: χρησιμοποιώντας τον Timer0 και γράψτε ένα πρόγραμμα το οποίο να εναλλάσσει το πιν PORTB.5 κάθε 15μs, ενώ ταυτόχρονα δεδομένα μεταφέρονται από την PORTC στην PORTD. Θεώρησε XTAL = 16MHz

Απάντηση
#include <avr/io.h>
#include <avr/interrupt.h>
int main(){
     DDRB |= 0x20;           //κάνε το ΡΒ5 έξοδο
     OCR0A = 239;
     TCCR0A = (1<<WGM01);    //CTC mode, internal clk, no prescaler
     TCCR0B = 0x01;
     TIMSK0 = (1<<OCIE0A);   //ενεργοποίησε τη διακοπή Timer0 compare match
     sei();                  //καθολική ενεργοποίηση διακοπών
     DDRC = 0x00;            //κάνε την PORTC είσοδο
     DDRD = 0xFF;            //κάνε την PORTD έξοδο
     while(1)
          PORTD = PINC;
}
ISR(TIMER0_COMPA_vect)     //ρουτίνα ISR για Timer0 compare match
{
     PORTB ^= 0x20;        //εναλλαγή του PORTB.5
}

Παράδειγμα: Χρησιμοποιώντας τον Timer1, γράψτε ένα πρόγραμμα το οποίο να εναλλάσσει το πιν PORTB.5 κάθε δευτερόλεπτο, ενώ ταυτόχρονα δεδομένα μεταφέρονται από την PORTC στη PORTD. Θεώρησε XTAL = 16MHz.

Απάντηση
#include <avr/io.h>
#include <avr/interrupt.h>
int main() {
     DDRB |= (1<<5);          //κάνε το ΡΒ5 έξοδο
     OCR1A = 15624;
     TCCR1A = 0x00;           //CTC mode, internal clk, prescaler = 1024
     TCCR1B = 0xOD;
     TIMSK1 = (1<<OCIE1A);    //ενεργοποίησε τη διακοπή Timer1 compare match A
     sei();                   //καθολική ενεργοποίηση διακοπών
     DDRC = 0x00;             //κάνε την PORTC είσοδο
     DDRD = 0xFF;             //κάνε την PORTD έξοδο
     while(1)
          PORTD = PINC;
}
ISR(TIMER1_COMPA_vect)        //ρουτίνα ISR για την διακοπή Timer1 compare match
{   
    PORTB ^= (1<<5);          //εναλλαγή του PORTB.5
}

Παράδειγμα: Ας υποθέσουμε ότι το πιν ΙΝΤ0 είναι συνδεμένο με ένα διακόπτη που κανονικά είναι σε high. Γράψε ένα πρόγραμμα που να εναλλάσσει το PORTB.5 κάθε φορά που το πιν INT0 πηγαίνει σε low. Χρησιμοποίησε την εξωτερική διακοπή σε κατάσταση level-triggered.

Απάντηση
#include <avr/io.h>
#include <avr/interrupt.h>
int main()
{
      DDRB = 1<<5;        //PB5  σαν έξοδο
      PORTD = 1<<2;       //ενεργοποίηση της pull-up αντίστασης
      EIMSK = (1<<INT0);  //ενεργοποίησε την εξωτερική διακοπή 0
      sei();              //καθολική ενεργοποίηση διακοπών
      while(1);
}
ISR(INT0_vect)            //ρουτίνα ISR για την εξωτερική διακοπή 0
{
      PORTB ^= (1<<5);    //εναλλαγή του PORTB.5
}

Παράδειγμα: Γράψε ένα πρόγραμμα το οποίο όταν το ΙΝΤ0 παίρνει low, να εναλλάσσει το πιν PORTB.5 μόνο μια φορά.

Απάντηση
#include <avr/io.h>
#include <avr/interrupt.h>
int main()
{
        DDRB = 1<<5;          //PB5 σαν έξοδο
        PORTD = 1<<2;          //ενεργοποίηση της pull-up αντίστασης
        EICRA = 0x2;          //make INT0 falling edge triggered
        EIMSK = (1<<INT0);    //ενεργοιήση της εξωτερικής διακοπής 0
        sei();                //καθολική ενεργοποίηση διακοπών
        while(1);
}
ISR(INT0_vect)                //ρουτίνα ISR για την εξωτερική διακοπή 0
{
        PORTB ^= (1<<5);      //εναλλαγή του PORTB.5
}

Παράδειγμα: Τα πινς ΡΒ0, ΡΒ2 και ΡΒ3 είναι συνδεμένα με διακόπτες. Γράψε ένα πρόγραμμα το οποίο να κάνει το πιν PORTB.5 high όταν οποιοδήποτε από τους διακόπτες αλλάξει κατάσταση.

Απάντηση
#include <avr/io.h>
#include <avr/interrupt.h>
int main()
{
         DDRB = 1<<5;                    //κάνε το ΡΒ5 σαν έξοδο
         PCMSK0 = (1<<0)|(1<<2)|(1<<3);
         PORTB = (1<<0)|(1<<2)|(1<<3);   //ενεργοποίηση pull-up αντιστάσεων
         PCICR = (1<<PCIE0);      //ενεργοποίηση διακοπών αλλαγής κατάστασης PORTB   
         sei();                   //καθολική ενεργοποίηση διακοπών
         while(1);
}
ISR(INT0_vect)                    //ρουτίνα ISR για την εξυπηρέτηση διακοπής
{
         PORTB |= (1<<5);         //PORTB.5 = HIGH
}