|
|
|
De AVR beschikt over een interrupt-systeem, dit is een zeer handige, maar soms verraderlijke functie. Een interrupt is een onderbreking van het hoofdprogramma om even (op dat moment) belangrijkere dingen uit te voeren. Nadien keert het programma terug naar de plaats in het hoofdprogramma op het punt waar het onderbroken werd.
Je kan het interrupt-systeem van de AVR vergelijken het het volgende voorbeeld: Je start je PC op (init) en even daarna ben je vlijtig bezig op de PC (hoofdprogramma) en op eens roept de mama (interrupt) dat het eten klaar is. Dan ga je natuurlijk eten en stopt even met de PC (Je handelt het interrupt af). Wanneer je klaar bent met eten, ga je terug aan de slag met de PC (Return naar het hoofdprogramma).
Het gebruik van interrupts kan ook problemen met zich mee brengen. Een interrupt kan afgaan op verschillende events (gebeurtenissen): Extern Interrupt, Timer overflow, Data ontvangen via UART, ... Bij gebruik van veel interrupts kan het zijn dat je AVR bijna heel de tijd bezig met interrupts uit te voeren en niet toe komt aan het uitvoeren van het hoofdprogramma. Daarom: houd interrupts kort, eenvoudig, ga geen functies aanroepen, en plaats zeker geen loops in een interrupt.
Als de AVR in een interrupt (I1) zit, kan dit niet nog eens worden onderbroken door een ander interrupt (I2). Het 2de interrupt I2 zal ook niet verloren gaan, als interrupt I1 uitgevoerd is, begint de AVR aan interrupt I2. Als je interrupts nu te snel achterelkaar komen, raakt je AVR hopeloos achter en dat soort scenario's wil je echt wel vermijden. Als je een globale variabele hebt gedeclareerd, die je ook binnen interrupts wilt kunnen aanpassen moet je die volatile declaren. Zo vertel je de compiler dat deze variabelen overal (ook binnen interrupts) beschikbaar moeten zijn.
// For use outside interrupts (normal use) u08 globalVar; // This varibale might be used inside an interrupt routine volatile u08 globalVar2;
De AVR heeft heel wat interrupt sources, (Externe, timers, SPI, ...) deze kun je allemaal individueel enable'en. De AVR heeft ook een globale interrupt enable. Deze moet ook enabled zijn voordat eender welk interrupt kan optreden. (Denk hieraan als je met interrupts bezig bent, maar het lijkt niet te lukken: misschien zijn interrupts nog niet global enabled)
// Global interrupt enable sei(); // Global interrupt disable cli();
De manier waarop je interrupts afhandelt kan per compiler verschillen omdat dit normaal iets is wat in c niet is ingebakken. Dus iedere compiler-ontwerper heeft er zijn eigen draai aan kunnen geven. Ik bespreek alleen de manier waarop het werkt in AVR-GCC (in combinatie met AVR Studio). In AVR-GCC is dat opgelost mbv een vector table, deze linkt een interrupt aan een voorgedefinieerd naam. Door eigen functies de juiste naam te geven worden deze opgeroepen bij een bepaalt interrupt. De Namen zijn door de AVR-GCC library gedefinieerd. Je kan zelf daar mee zitten knoeien, maar dat doe je best niet als je alles portable wilt houden.
// To use interrupts, the following include is required #include <avr/interrupt.h> ... // The following routine will be called when an interrupt // is generated by INT0. ISR( INT0_vect ){ // User code here } // Like INT0_vect there are familiar interrupts vectors: // - INT1_vect // - INT2_vect
Hieronder een dom voorbeeld van het gebruik van Extern interrupt. Deze interrupt veroorzakende pinntjes zijn zeer beperkt, de Mega32 heeft er 3. Terwijl sommige kleinere AVR slechts 1 extern interrupt pin hebben. Deze speciale pinnen worden aangeduid als: INT0, INT1, INT2, ... INTx. Deze worden ook wel interrupt pinnen genoemd. Deze pinnen kunnen een interrupt veroorzaken als:
- Laag niveau op de pin*
- Falling edge (van hoog naar laag gaande signalen)
- Rising edge (van laag naar hoog gaande signalen)
*Zo lang de pin laag is, blijft deze interrupts geven!
In het voorbeeld knippert PC7 continu. PC0, PC1 en PC2 kun je toggelen door op respectievelijk drukknoppen van INT0, INT1 of INT2 te drukken.
Bij gebruik van drukknopjes ontstaat er altijd wel contactdender, deze kun je hardware- of softwarematig wegwerken. In het voorbeeld doe ik dat door minstens 40ms te wachten nadat het interrupt werd geactiveerd. Als de contactdender dan nog niet over is, nog eens 40ms wachten.
Hardwarematig los je dat op door een capaciteit over de drukknop te plaatsen, doe dit als je hardware nog moet ontwerpen indien je drukknoppen mbv van interrupts wilt afhandelen. Als je de status van de drukknoppen nakijkt in het hoofdprogramma is het niet nodig om te ontdenderen.
#define F_CPU 8000000 #include "s/g.h" #include "util/delay.h" //! deze include is nodig vanaf het moment er interrupts worden gebruikt #include "avr/interrupt.h" //! main functie, start programma, moet altijd aanwezich zijn int main(void){ outb(PORTB, 0x04); ///< pullup PB2 outb(DDRB, 0x0); ///< PORTB als ingang outb(PORTC, 0xFF); ///< Alle uitgangen hoog outb(DDRC, 0xFF); ///< PORTC volledig als uitgang outb(PORTD, 0x0C); ///< PD2..3 hoog (interne pullup) outb(DDRD, 0x0); ///< PORTD volledig als uitgang // configureer interrupt voor INT0, INT1 en INT2 /** * ISC01 ISC00 * 0 0 low level * 0 1 toggle * 1 0 falling edge * 1 1 rising edge **/ sbi(MCUCR, ISC01); ///< INT0 falling edge sbi(GICR, INT0); ///< enable INT0 sbi(MCUCR, ISC11); ///< INT1 falling edge sbi(GICR, INT1); ///< enable INT1 /** * ISC2 * 0 falling edge * 1 rising edge **/ cbi(MCUCSR, ISC2); ///< INT2 falling edge sbi(GICR, INT2); ///< enable INT1 sei(); ///< global interrupt enable while(1){ tbi(PORTC, 7); ///< toggle PC7 _delay_ms(750); } return 0; } //! INT0 routine ISR(INT0_vect){ tbi(PORTC, 0); ///< toggle PC0 do{ ///< ontdendering _delay_ms(40); }while(!rbi(PORTD, 2)); } //! INT1 routine ISR(INT1_vect){ tbi(PORTC, 1); ///< toggle PC1 do{ _delay_ms(40); }while(!rbi(PORTD, 3)); } //! INT2 routine ISR(INT2_vect){ tbi(PORTC, 2); ///< toggle PC2 do{ _delay_ms(40); }while(!rbi(PORTB, 2)); }
LET OP: dit is niet echt een goede implementatie van interrupts! maar een voorbeeld. Om te beginnen is softwarematig ontdenderen van interrupt-based drukknopjes niet echt simpel om proper op te lossen. In het voorbeeld doe ik dit kortweg door een 40ms te wachten, dit is iets wat je in een echt programma nooit! mag doen (slecht voorbeeld, had ik al gezegd). Nooit delays of loops (lussen: while, for) in een interrupt plaatsen. (Ooooh, en ik deed dat, dus dit is een slecht voorbeeld, wist je dat al?) Interrupt houd je best kort, zodat het hoofdprogramma niet te lang word onderbroken.
|
|