Dynamisch beheren van interrupts

Een interrupt is een gebeurtenis waarop een microcontroller reageert door even het normale programmaverloop te onderbreken en een speciaal stukje code uit te voeren om daarna het normale programmaverloop verder te zetten. Zo'n gebeurtenis kan het aflopen van een hardware-timer zijn, het veranderen van de spanning op een bepaalde pin van de PIC, ... Het stukje code heet een Interrupt Service Routine (ISR).

Praktisch gaat het als volgt. Een van de hardwaremodules van de microcontroller brengt het interrupt-signaal van de CPU hoog. De CPU reageert daarop door eerst de program counter (het terugkeeradres) op de stack te plaatsen en dan te springen naar de code op adres 0x008. Op dit adres moet de eerst instructie van de ISR staan. Op het einde van de ISR wordt een RETFIE (return from interrupt) uitgevoerd. Hierdoor wordt het terugkeeradres van de stack gehaald en in de program counter geladen en dus het normale verloop van het programma hervat.

Als er zich verschillende soorten interrupts kunnen voordoen in hetzelfde programma dan moet de ISR eerst controleren welk van de interrupts is opgetreden en afhankelijk hiervan de juiste code uitvoeren. Elke mogelijke bron van interrupts heeft een vlag die hoog staat wanner de interrupt is opgetreden en laag staat in het andere geval. Die vlag is een bit in het PIR1- of het PIR2-register. Door deze vlaggen te controleren kan de ISR zien door welke gebeurtenis de oproep van de ISR veroorzaakt heeft.

Onderstaand voorbeeld toont hoe je een ISR kan schrijven voor de C18-compiler. Lijnen 1 tot 7 zorgen ervoor dat de ISR start op adres 0x08 door op dit adres een sprong te plaatsen naar de eigenlijke ISR die zich op lijnen 9 tot 20 bevindt. Lijn 9 zorgt ervoor dat de functie InterruptServiceRoutine door de compiler als een ISR beschouwd wordt. Dit heeft als belangrijkste gevolg dat op het einde van de functie geen gewone return staat maar een return from interrupt (RETFIE). In de body van de functie zie je dat in eerste instantie gecontroleerd wordt wat de bron van de interrupt is: TIMER1, TIMER3 of beide. Afhankelijk van de oorzaak van de interrupt, wordt de juiste code uitgevoerd.

  1. #pragma code isr_vector=0x08
  2. void isr_vector() {
  3. _asm
  4. goto InterruptServiceRoutine
  5. _endasm
  6. }
  7. #pragma code
  8.  
  9. #pragma interrupt InterruptServiceRoutine
  10. void InterruptServiceRoutine() {
  11. if (PIR1bits.TMR1IF == TRUE) {
  12. // Code for servicing TIMER1-interrupt
  13. }
  14. if (PIR2bits.TMR3IF == TRUE) {
  15. // Code for servicing TIMER3-interrupt
  16. }
  17.  
  18. // ...
  19.  
  20. }

Voor weinig complexe programma's is dit een goede manier van werken, maar voor complexere programma's zorgt dit voor problemen. Voor complexere programma's is het nuttig dat de toepassing wordt opgesplitst in een aantal duidelijk afgebakende functionaliteiten die elk gecodeerd worden in een bibliotheek. Die opsplitsing zorgt ervoor dat de programmeur (of het team van programmeurs), de complexiteit van het programma beter kan beheersen. Daardoor kunnen meerdere mensen makkelijker samen aan hetzelfde project werken en code hergebruiken in meerder projecten.

Doordat interrupts altijd in één enkele interrupt-routine behandeld moeten worden, kunnen ze in principe niet verspreid worden over verschillende bibliotheken. De Dwengo-bibliotheek lost dit probleem op door de interrupts dynamisch te beheren.

De bibliotheek laat toe om tot 5 (default) functies als ISR te registreren met behulp van de functie registerISR. Op een later tijdstip kunnen de functies ook gederegistreerd worden met behulp van de functie deregisterISR. Telkens als een interrupt optreedt, worden alle geregistreerde ISR's uitgevoerd. Voor hetzelfde voorbeeld als hierboven, wordt de code met de dwengoInterrupt-bibliotheek een stuk eenvoudiger:

  1. #include <dwengoInterrupt.h>
  2.  
  3. void timer1ISR() {
  4. if (PIR1bits.TMR1IF == TRUE) {
  5. // Code for servicing TIMER1 interrupt
  6. }
  7. }
  8.  
  9. void timer3ISR() {
  10. if (PIR2bits.TMR3IF == TRUE) {
  11. // Code for servicing TIMER3 interrupt
  12. }
  13. }
  14.  
  15. void main() {
  16. initDwengoInterrupt();
  17. registerISR(timer1ISR);
  18. registerISR(timer3ISR);
  19.  
  20. // ...
  21. // Every time an interrupt occurs the functions timer1ISR
  22. // and timer3ISR are called.
  23. // ...
  24.  
  25. deregisterISR(timer1ISR);
  26.  
  27. // ...
  28. // Every time an interrupt occurs only the function
  29. // timer3ISR is called.
  30. // ...
  31.  
  32. }