Μέχρι στιγμής όλα τα προγράμματα που αφορούν τις διακοπές, γράφτηκαν σε γλώσσα 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
}