MICPRG opdracht 2.

© Harry Broeders.

Deze pagina is bestemd voor studenten van de Haagse Hogeschool - Academie voor Technology, Innovation & Society Delft groep EQ1.2

Timing.

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.

Opdracht 2a.

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.

Opdracht 2b.

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:

Extra uitleg.

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.