Een voorbeeld: Real Time Interrupt
Het interrupt mechanisme zal waarschijnlijk duidelijker worden wanneer we een voorbeeld behandelen waarin interrupts toegepast worden. Hiervoor is het 'loopled' programma
gemaakt. In dit voorbeeld wordt de timing verzorgd door de 'real time interrupt'. Dit is een schakeling (timer) die op gezette tijden een interrupt kan genereren. Door een aantal van deze interrupts af te wachten kan dus ook een vertraging gemaakt worden.
*************************************************
* definiëren van de geheugen map
*************************************************
incl "map512.asm"
*************************************************
* start van het programma
*************************************************
PROGRAM space |kies het programma gebied
reset equ $ |na reset begint de micro op deze plaats
lds #stackend |begin met de stackpointer te laden
ldx #databeg |en zet dan het volledige datagebied op 00
clearram clr 0,x
inx
cpx #dataend
bls clearram
ldx #regsbeg |laat IX op de bank met I/O registers wijzen
*************************************************
* modules
*************************************************
*************************************************
* real-time interrupt
*************************************************
PROGRAM space
rtistart equ $ |het beginadres van deze module
rtirate equ 50 |het aantal interrupts dat geteld wordt
DATA space
rticount rmb 1 |deze teller wordt bij elke interrupt verhoogd
* initialisatie van de real-time interrupt
PROGRAM space
ldx #regsbeg |IX wijst naar de I/O registers
ldab #$7E |vul de entry in de interrupt tabel in
stab rtiint |met een spronginstructie naar de routine
ldd #rtiintentry
std rtiint+1 |die de interrupt afhandelt
bset pactl-regsbeg,x,(rtr1 or rtr0)
|zet de real-time interrupt tijd op 32.768ms
|met 8MHz Kristal (E / 2^16)
bset tmsk2-regsbeg,x,rtii
|laat de interrupts door
bra rtiend |einde van de initialisatie
* subroutine voor de afhandeling van de real-time interrupt
PROGRAM space
rtiintentry equ $
ldab #rtif
stab tflg2 |reset de interrupt-vlag
inc rticount |en tel de interrupt
rti
***** real-time interrupt interface routines
PROGRAM space
* test op time-out. zet de zero-vlag indien er een time-out is.
checktimeout equ $
pshb
sei |houd interrupts tijdelijk tegen
ldab rticount |kijk hoe vaak er een interrupt was
subb #rtirate |indien er minimaal 'rtirate' interrupts
blo checkrti9 |zijn geweest, dan is er een time-out en moet
stab rticount |'rtirate' van de teller worden afgetrokken
clrb |daarnaast moet de zero vlag gezet worden
checkrti9 cli |daarna kunnen de interrupts weer worden
pulb |doorgelaten
rts
rtiend equ $ |einde van de real-time interrupt module
* het aantal bytes dat deze module nodig heeft in het programma gebied
rtisize equ rtiend-rtistart
*************************************************
* LEDs
*************************************************
PROGRAM space
ledstart equ $ |het beginadres van deze module
ledportx equ portbregsbeg
ledport equ portb
led0 equ bit0
led1 equ bit1
led2 equ bit2
DATA space
ledpattern rmb 1 |index naar het huidige patroon voor de leds
* initialisatie van de led's; begin met alle led's uit
PROGRAM space
ldx #regsbeg |IX wijst naar de I/O registers
bclr ledportx,x,(led2 or led1 or led0)
bra ledend |einde van de initialisatie
* de tabel met ledpatronen die stap voor stap wordt afgewerkt
ledtable fcb ( led0)
fcb ( led1 or led0)
fcb (led2 or led1 or led0)
fcb (led2 or led1 or led0)
fcb (led2 or led1 )
fcb (led2 )
fcb 0
fcb 0
steps equ $-ledtable |het aantal stappen in het patroon
* subroutine voor de afhandeling van een stap in het ledpatroon
PROGRAM space
ledstep equ $
pshb
pshx
ldx #ledtable |haal het adres van de patroontabel in IX
ldab ledpattern |en de index in de tabel in B
abx |IX wijst nu op het huidige patroon
ldab ledport |haal het bitpatroon van de output poort
andb #not(led2 or led1 or led0)
orab 0,x |en voeg het nieuwe patroon toe
stab ledport
ldab ledpattern |zet de index daarna op een nieuw patroon
incb |maar blijf binnen de tabel, en zet de
cmpb #steps |index terug op 0 als alle patronen een keer
blo ledstep9 |geweest zijn
clrb
ledstep9 stab ledpattern
pulx
pulb
rts
ledend equ $ |einde van de led module
* het aantal bytes dat deze module nodig heeft in het programma gebied
ledsize equ ledend-ledstart
*************************************************
* het hoofdprogramma
*************************************************
PROGRAM space
cli |laat de interrupts door
main0 bsr checktimeout
bne main0 |wacht op een time-out
bsr ledstep |en doe dan een stap van het looplicht
bra main0 |herhaal dit oneindig
end
Real Time Interrupt generator
Zoals alle I/O schakelingen, is ook de Real Time Interrupt generator in te stellen met behulp van een aantal bits in de I/O registers. Deze registers hebben allemaal hun eigen naam en adres. De namen van de registers zijn natuurlijk in al Uw programma's hetzelfde, dus die zijn weer gedefiniëerd in de file 'map512.asm'.
Niet alleen de I/O registers hebben een eigen naam, ook de verschillende bits in deze registers hebben een naam gekregen. De registers en de bits daarin zullen in de cursus steeds alleen bij naam genoemd worden. In een volgende les krijgt U een overzicht van alle I/O registers en de bits die daarin gedefiniëerd zijn.
De real time interrupt is een interrupt die wordt afgeleid van de interne klok van de
micro-controller. De klokfrequentie wordt gedeeld door een instelbare deelfactor, en met de resulterende frequentie wordt een interrupt gegenereerd.
De deelfactor is instelbaar in het PACTL register, met behulp van de twee bits 'RTR1' en 'RTR0'. De
micro-controller op de SIMPLEX heeft een klokfrequentie van 2 MHz, zodat de tijd tussen twee interrupts als volgt is:
|
RTR1
|
RTR0
|
tijd
|
frequentie
|
|
0
|
0
|
4.096 ms
|
2MHz/2^13
|
|
0
|
1
|
8.192 ms
|
2MHz/2^14
|
|
1
|
0
|
16.384 ms
|
2MHz/2^15
|
|
1
|
1
|
32.768 ms
|
2MHz/2^16
|
Elke keer wanneer de real time interrupt afloopt, wordt een vlag gezet in het TFLG2 register (de RTIF vlag.) Dit bit moet
u zelf terug op '0' zetten, door een '1' te schrijven op de plaats van dit bit in het TFLG2 register.
Of de micro-controller na het aflopen van de real time interrupt daadwerkelijk de interrupt gaat uitvoeren, hangt nog af van het RTII bit in het TMSK2 register. Wanneer dit bit op '0' staat, voert de controller de interrupt niet uit. Wanneer U dit bit op '1' zet, wordt de interrupt normaal afgehandeld.
U kunt dus per interrupt bron besluiten of die betreffende interrupt moet worden afgehandeld of niet. Als de betreffende interrupt wordt doorgelaten, hangt het nog af van het I-bit in het CCR of de interrupt uiteindelijk afgehandeld zal worden. Met het I-bit blokkeert U alle interrupts van de I/O schakelingen tegelijkertijd.
We zullen het programma nu stap- voor stap doorlopen en beschrijven.
*************************************************
* definiëren van de geheugen map
*************************************************
incl "map512.asm"
*************************************************
* start van het programma
*************************************************
PROGRAM space |kies het programma gebied
reset equ $ |na reset begint de micro op deze plaats
lds #stackend |begin met de stackpointer te laden
ldx #databeg |en zet dan het volledige datagebied op 00
clearram clr 0,x
inx
cpx #dataend
bls clearram
ldx #regsbeg |laat IX op de bank met I/O registers wijzen
De file 'map512' wordt ingelezen, zodat de PROGRAM en DATA pointers een waarde hebben, en alle I/O registers en hun bits bij naam bekend zijn aan de assembler.
Het programma start bovenin, en daar wordt eerst de stackpointer geladen. Daarna wordt het RAM op 0 geïnitialiseerd.
Aansluitend volgen de modulen die het programma bevat, in dit geval twee (één voor de real time interrupt, en één voor de
LEDs.)
Real time interrupt module
De module voor de real time interrupt is:
*************************************************
* real-time interrupt
*************************************************
PROGRAM space
rtistart equ $ |het beginadres van deze module
rtirate equ 50 |het aantal interrupts dat geteld wordt
DATA space
rticount rmb 1 |deze teller wordt bij elke interrupt verhoogd
* initialisatie van de real-time interrupt
PROGRAM space
ldx #regsbeg |IX wijst naar de I/O registers
ldab #$7E |vul de entry in de interrupt tabel in
stab rtiint |met een spronginstructie naar de routine
ldd #rtiintentry
std rtiint+1 |die de interrupt afhandelt
bset pactl-regsbeg,x,(rtr1 or rtr0)
|zet de real-time interrupt tijd op 32.768ms
|met 8MHz Kristal (E / 2^16)
bset tmsk2-regsbeg,x,rtii
|laat de interrupts door
bra rtiend |einde van de initialisatie
* subroutine voor de afhandeling van de real-time interrupt
PROGRAM space
rtiintentry equ $
ldab #rtif
stab tflg2 |reset de interrupt-vlag
inc rticount |en tel de interrupt
rti
***** real-time interrupt interface routines
PROGRAM space
* test op time-out. zet de zero-vlag indien er een time-out is.
checktimeout equ $
pshb
sei |houd interrupts tijdelijk tegen
ldab rticount |kijk hoe vaak er een interrupt was
subb #rtirate |indien er minimaal 'rtirate' interrupts
blo checkrti9 |zijn geweest, dan is er een time-out en moet
stab rticount |'rtirate' van de teller worden afgetrokken
clrb |daarnaast moet de zero vlag gezet worden
checkrti9 cli |daarna kunnen de interrupts weer worden
pulb |doorgelaten
rts
rtiend equ $ |einde van de real-time interrupt module
* het aantal bytes dat deze module nodig heeft in het programma gebied
rtisize equ rtiend-rtistart
De real time interrupt module bevat de van modulen bekende onderdelen:
- initialisatie
- interne subroutines
- interface routines
PROGRAM space
rtistart equ $ |het beginadres van deze module
rtirate equ 50 |het aantal interrupts dat geteld wordt
DATA space
rticount rmb 1 |deze teller wordt bij elke interrupt verhoogd
In de kop van de module wordt de PROGRAM pointer gekozen. Het eerste adres van de module krijgt een naam, zodat later (in de listing) te zien is op welk adres in het programmageheugen de module begint. Daarna wordt een constante gedefiniëerd die later in de module gebruikt wordt om de timing te bepalen.
Na selecteren van de DATA pointer wordt een byte gereserveerd dat wordt gebruikt om het aantal interrupts dat de real time interrupt heeft gegenereerd te tellen.
Initialisatie
De initialisatie van de real time interrupt module zorgt voor:
- invullen van de JMP instructie in de interrupt tabel
- instellen van de interrupt tijd
- aanzetten van de real time interrupts
De JMP instructie moet worden ingevuld op het adres in het RAM waar de processor naartoe springt bij een real time interrupt. Dit adres heeft in de file 'map512'
de naam 'rtiint' gekregen. Op dit adres moet de code $7E worden ingevuld (de code voor JMP), en daarna het adres van de routine waar naartoe gesprongen moet worden bij een real time interrupt. De routine die de interrupt afhandelt heet in het programma 'rtiintentry'. Dit adres wordt in register D geladen, en in het RAM geplaatst, juist na de code voor de JMP instructie, dus op adres rtiint+1.
ldab #$7E |vul de entry in de interrupttabel in
stab rtiint |met een spronginstructie naar de routine
ldd #rtiintentry
std rtiint+1 |die de interrupt afhandelt
De interrupt tijd wordt ingesteld door de juiste bits in het PACTL register te setten:
bset pactl-regsbeg,x,(rtr1 or rtr0)
|zet de real-time interrupt tijd op 32.768ms
De interrupts van de real time interrupt worden aangezet door:
bset tmsk2regsbeg,x,rtii |laat de interrupts door
Bedenk, dat het I-bit in het CCR nog niet op '0' gezet is. Ofschoon de real time interrupt 'lokaal' vrijgegeven is, wordt de interrupt nog steeds geblokkeerd door het I-bit.
Na de initialisatie wordt uit de module gesprongen:
bra rtiend |einde van de initialisatie
Interne subroutines
De real time interrupt module heeft één interne subroutine. Deze subroutine wordt door de real time interrupt interrupt geactiveerd. Dus elke 32.768 ms krijgt de processor een interrupt, zoekt uit dat de interrupt van de real time interrupt komt, en springt naar het betreffende adres in het RAM. Daar is tijdens initialisatie een JMP instructie geplaatst, waardoor het programma in de volgende routine terecht komt:
* subroutine voor de afhandeling van de real-time interrupt
PROGRAM space
rtiintentry equ $
ldab #rtif
stab tflg2 |reset de interruptvlag
inc rticount |en tel de interrupt
rti
De routine zet de interrupt-vlag van de real time interrupt weer uit, en verhoogt de interrupt teller. Wanneer de interrupts niet langdurig geblokkeerd zijn, wordt deze teller dus elke 32.768 ms met één verhoogd.
Als de interrupts tijdelijk geblokkeerd zijn (via het I-bit) terwijl de real time interrupt wel 'afloopt', springt de processor niet naar de afhandelingsroutine. Daardoor blijft de interrupt-vlag een '1'. De interrupt blijft dus aktief, maar wordt (nog) niet afgehandeld. Zodra het I-bit de interrupts weer doorlaat, is de interrupt aanvraag er al, en wordt dan meteen afgehandeld. Door dit mechanisme is het veilig de interrupts tijdelijk te blokkeren. De aanvraag voor een interrupt blijft actief totdat de interrupt afgehandeld is. Daardoor zullen er geen interrupts verloren gaan.
Interface routines
De interface routine is de routine die bedoeld is om door het hoofdprogramma te worden aangeroepen. In dit voorbeeld moet het hoofdprogramma uitzoeken of het al tijd is het looplicht een stapje verder te zetten. De interface routine wordt vanuit het hoofdprogramma aangeroepen, en wanneer de routine de Z-vlag in het CCR een '1' maakt, is dit een teken dat het tijd is voor een stapje van het looplicht.
***** real-time interrupt interface routines
PROGRAM space
* test op time-out. Zet de zero-vlag indien er een time-out is.
checktimeout equ $
pshb
sei |houd interrupts tijdelijk tegen
ldab rticount |kijk hoe vaak er een interrupt was
subb #rtirate |indien er minimaal 'rtirate' interrupts
blo checktimeout9 |zijn geweest, dan is er een time-out en moet
stab rticount |'rtirate' van de teller worden afgetrokken
clrb |daarnaast moet de zero-vlag gezet worden
checktimeout9 cli |daarna kunnen de interrupts weer worden
pulb |doorgelaten
rts
De routine bewaart het register dat intern gebruikt wordt (B) op de stack. Daarna wordt de inhoud van de interrupt-teller vergeleken met een constante. Door deze constante een andere waarde te geven kan de loopsnelheid dus veranderd worden.
Als er voldoende interrupts geteld zijn (voldoende tijd verstreken is), wordt de verstreken tijd van de teller afgetrokken, en zet de routine de Z-vlag in het CCR. Als nog niet voldoende interrupts geteld zijn, wordt de Z-vlag een '0'. Aan het eind van de routine wordt register B weer hersteld door het van de stack te halen.
Met de 'sei' instructie bovenaan de routine worden de interrupts geblokkeerd, met de 'cli' instructie onderaan de routine worden de interrupts weer vrijgegeven.
Er is een goede reden om de interrupts tijdens het doorlopen van deze routine te blokkeren. Dit wordt apart behandeld in de paragraaf 'Mutual Exclusion', verderop.
Het hoofdprogramma
Na initialisatie van alle modulen komt het programma in de main loop terecht. Hier wordt eerst het I-bit op '0' gezet, zodat de interrupts worden afgehandeld. Daarna wacht het programma op een time-out van de real time interrupt module, en bij een time-out wordt het looplicht een stapje verder gezet.
*************************************************
* het hoofdprogramma
*************************************************
PROGRAM space
cli |laat de interrupts door
main0 bsr checktimeout
bne main0 |wacht op een time-out
bsr ledstep |en doe dan een stap van het looplicht
bra main0 |herhaal dit oneindig
end
|