» Advertenties

Zo 20 Mei 2012, 07:33

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).

File #205

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.
C code -
  1. // For use outside interrupts (normal use)
  2. u08 globalVar;
  3.  
  4. // This varibale might be used inside an interrupt routine
  5. 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)
C code -
  1. // Global interrupt enable
  2. sei();
  3.  
  4. // Global interrupt disable
  5. 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.

C code -
  1. // To use interrupts, the following include is required
  2. #include <avr/interrupt.h>
  3.  
  4. ...
  5.  
  6. // The following routine will be called when an interrupt
  7. // is generated by INT0.
  8. ISR( INT0_vect ){
  9. // User code here
  10. }
  11.  
  12. // Like INT0_vect there are familiar interrupts vectors:
  13. // - INT1_vect
  14. // - 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.
C code -
  1. #define F_CPU 8000000
  2.  
  3. #include "s/g.h"
  4. #include "util/delay.h"
  5.  
  6. //! deze include is nodig vanaf het moment er interrupts worden gebruikt
  7. #include "avr/interrupt.h"
  8.  
  9.  
  10. //! main functie, start programma, moet altijd aanwezich zijn
  11. int main(void){
  12.   outb(PORTB, 0x04);         ///< pullup PB2
  13.   outb(DDRB, 0x0); ///< PORTB als ingang
  14.   outb(PORTC, 0xFF); ///< Alle uitgangen hoog
  15.   outb(DDRC, 0xFF); ///< PORTC volledig als uitgang
  16.   outb(PORTD, 0x0C); ///< PD2..3 hoog (interne pullup)
  17.   outb(DDRD, 0x0); ///< PORTD volledig als uitgang
  18.  
  19.   // configureer interrupt voor INT0, INT1 en INT2
  20.   /**
  21.   * ISC01 ISC00
  22.   * 0 0 low level
  23.   * 0 1 toggle
  24.   * 1 0 falling edge
  25.   * 1 1 rising edge
  26.   **/
  27.   sbi(MCUCR, ISC01); ///< INT0 falling edge
  28.   sbi(GICR, INT0); ///< enable INT0
  29.   sbi(MCUCR, ISC11); ///< INT1 falling edge
  30.   sbi(GICR, INT1); ///< enable INT1
  31.  
  32.   /**
  33.   * ISC2
  34.   * 0 falling edge
  35.   * 1 rising edge
  36.   **/
  37.   cbi(MCUCSR, ISC2); ///< INT2 falling edge
  38.   sbi(GICR, INT2); ///< enable INT1  
  39.   sei(); ///< global interrupt enable
  40.  
  41.   while(1){
  42.     tbi(PORTC, 7);         ///< toggle PC7
  43.     _delay_ms(750);    
  44.   }
  45.  
  46.   return 0;
  47. }
  48.  
  49. //! INT0 routine
  50. ISR(INT0_vect){
  51.   tbi(PORTC, 0); ///< toggle PC0
  52.   do{ ///< ontdendering
  53.     _delay_ms(40);
  54.   }while(!rbi(PORTD, 2));
  55. }
  56.  
  57. //! INT1 routine
  58. ISR(INT1_vect){
  59.   tbi(PORTC, 1); ///< toggle PC1
  60.   do{
  61.     _delay_ms(40);
  62.   }while(!rbi(PORTD, 3));
  63. }
  64.  
  65. //! INT2 routine
  66. ISR(INT2_vect){
  67.   tbi(PORTC, 2); ///< toggle PC2
  68.   do{
  69.     _delay_ms(40);
  70.   }while(!rbi(PORTB, 2));
  71. }


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.
Nog geen reacties.
Naam
a-z A-Z 0-9 _

 
E-mail
Wordt niet getoont.
  (niet verplicht)
 
Mail sturen als
(optie)
Smoerijf reageerd
Eender wie reageaard
Nooit
 
Mail sturen
(optie)
Enkel mailen bij eerste reactie
Bij elke reactie mailen
 
Reactie
Laatste wijziging: Ma 19 Oktober 2009, 10:08