© Harry Broeders.
Deze pagina is bestemd voor studenten van de Haagse Hogeschool - Academie voor Technology, Innovation & Society Delft groep EQ1.2
In de vorige opdracht werd de tijd tussen het verschuiven van het patroontje bepaald door de volgende wachtlus.
void wait(void) {
volatile int i;
for (i = 0; i < 30000; ++i)
/*empty*/;
}
Het is moeilijk om op deze manier een exacte tijdvertraging te realiseren. Als je een bepaalde tijd wilt wachten kun je beter een van de timer/counters die in de ATmega32 zijn ingebouwd gebruiken. Een vertraging van 0,25 seconde kan eenvoudig met behulp van timer/counter 1 (de 16 bits timer/counter) worden gerealiseerd.
CS12
, CS11
en
CS10
bits in het TCCR1B
register (Klokfrequentie
ATmega32 op practicum = 3.6864 MHz). In dit geval kan het beste een prescale
waarde van 1024 worden gebruikt.
TCNT1
register laadt met een waarde zodat na 0,25 seconde een
overflow optreedt dan kun je vervolgens wachten totdat die overflow optreedt.
TOV1
bit in het
TIFR
register geset.
TOV1
bit kan gereset worden door
er een 1
naar toe te schrijven (raar maar waar)!
Zie: Bitn... voor gevorderen: Het strijken van de vlag.
Pas het programma uit opgave 1 zodanig aan dat het verschuiven van het patroontje
op de LED's wordt aangestuurd met behulp van een Timer/Counter 1. Het patroontje
moet telkens na 250 ms opschuiven. Pas de functie wait
aan zodat
gebruik gemaakt wordt van polling: TCNT1
initialiseren,
TOV1
resetten en wachten tot TOV1
geset wordt.
Vergeet niet om in het begin van het hoofdprogramma timer/counter 1 aan te
zetten in "normal" mode met een prescale waarde van 1024.
In opdracht 2a heb je gebruik gemaakt van polling, dit wordt "busy waiting" genoemd. Je kunt op deze manier als je staat te wachten niets anders doen. Pas het programma zodanig aan dat het verschuiven van het patroontje op de LED's wordt aangestuurd met behulp van een Timer/Counter 1 Overflow interrupt. Het patroontje moet weer telkens na 250 ms opschuiven. Maak daarbij gebruik van een variant van het in de les behandelde voorbeeldprogramma.
#include <avr/io.h>
#include <stdint.h>
#include <avr/interrupt.h>
ISR(TIMER1_OVF_vect) {
TCNT1 = ...;
/* bepaal volgende patroon */
PORTB = ...;
}
int main(void) {
TCCR1A = ...; /* normal mode */
TCCR1B = ...; /* prescaler 1024 */
TCNT1 = ...;
DDRB = 0xFF;
TIMSK |= ...;
sei();
while (1) {
/* hier kun je iets anders doen */
}
return 0;
}
Het verschuiven van het patroontje en het aansturen van de LEDs moet allemaal in de ISR gebeuren zodat het hoofdprogramma uit een lege oneindige lus bestaat.
Informatie over de benodigde I/O registers van Timer/Counter1 kun je vinden in de datasheet van de ATmega32. De belangrijkste registers en tabellen zijn hieronder weergegeven:
Als de ATmega32 een interrupt krijgt dan wordt alleen gereageerd als de
I
flag in het SREG
geset is. Als de I
flag geset is en er komt een interrupt dan wordt eerst de huidige instructie
afgemaakt en daarna wordt de I
flag in het SREG
gereset en de PC
op de stack geplaatst. Vervolgens wordt naar
de ISR (Interrupt Service Routine) gesprongen. Aan het einde van de
ISR moet een RETI
(RETurn from Interrupt) instructie
worden gebruikt zodat de PC
weer van de stack wordt gehaald
en de I
flag in het SREG
weer wordt geset.
De ATmega32 "weet" van tevoren niet wanneer de interrupt zal optreden. Op de een of andere manier moet de programmeur opgeven welke ISR uitgevoerd moet worden bij het optreden van een bepaalde interrupt. Omdat de programmeur ook niet van tevoren weet op welke plaats het programma onderbroken zal worden door een ISR kan de ISR niet worden aangeroepen met een "normale" functieaanroep.
In een zogenaamde interrupt vector tabel zet de programmeur voor elke
gebruikte interrupt een JMP
instructie naar het adres van de
ISR (de plaats waar de JMP
naar het adres van de ISR moet worden
ingevuld wordt de interrupt vector genoemd). Bij de ATmega32 bevindt
deze interrupt vector tabel zich in het geheugen op de adressen
$000
t/m $029
. In de documentatie van de ATmega32
kun je vinden welk adres voor een bepaalde interrupt wordt gebruikt.
Een ISR kan in C als volgt gedeclareerd worden:
void
isr(void
) __attribute__((signal));void
isr(void
) { /* code van de ISR */ }
In de include file avr/interrupt.h is een macro ISR
gedefinieerd
waarmee dit eenvoudiger kan:
ISR(isr) { /* code van de ISR */ }
Een ISR is dus een "soort" functie die aangeroepen wordt als de betreffende
interrupt optreed. Omdat je niet weet wanneer de interrupt zal optreden kun
je geen argumenten meegeven en ook geen return type gebruiken. De
__attribute__((signal))
is nodig
om de compiler te vertellen dat de functie moet eindigen met een
RETI
(ReTurn from Interrupt) instructie in plaats van met de
RET
(RETurn from subroutine) instructie waarmee een gewone functie
eindigt. De compiler zorgt ervoor dat de waarde van bepaalde registers (waaronder
het SREG
) aan het begin van de ISR gesaved en aan het einde
van de ISR weer teruggezet worden.
Als de ATmega32 een interrupt gaat afhandelen worden alle interrupt automatisch
gemaskeerd (door het I
bit in het SREG
nul te maken).
Tijdens de RETI
instructie worden de interrupts weer vrijgegeven
(door het I
bit in het SREG
één te
maken).
Als main
wordt aangeroepen na een reset staan de interrupts
gemaskeerd. Je moet dus zelf het I
bit in het SREG
register 1 maken om de interrupts vrij te geven. Dit kan eenvoudig worden
gedaan door de in de include file avr/interrupt.h gedeclareerde functie
sei
aan te roepen:
sei();
In de interrupt vector tabel moet een JMP
naar het startadres
van de ISR worden ingevuld. De gcc compiler (die door Atmel Studio wordt gebruikt)
doet dit automatisch als je de ISR een bepaalde naam geeft. De Timer/Counter1
Overflow interrupt service routine moet de naam TIMER1_OVF_vect
hebben.