ES1 Project 1: Microcontrollers Les 5: Timers/counters & Interrupts Door Hugo Arends, september 2012
Timers/counters Hardware timers/counters worden in microcontrollers gebruikt om onafhankelijk van de CPU te tellen. Hierdoor kunnen andere instructies door de CPU uitgevoerd worden terwijl deze extra hardware telt. Het aflopen van een timer/counter kan op verschillende manier geregistreerd worden, bijvoorbeeld via (software) polling en via interrupts.
Documentatie In de datasheet worden de timers/counters uitvoerig beschreven. Er zijn in de ATmega32A aanwezig: een 8-bit Timer/Counter0 with PWM, een 16-bit Timer/Counter1, en een 8-bit Timer/Counter2 with PWM and Asynchronous Operation. Aangezien de werking van alle timers/counters in principe hetzelfde is, zullen we in deze les uitsluitend de 8-bit Timer/Counter0 bespreken. De werking van een aantal features wordt uitgelegd aan de hand van Figure 14-1. 8-bit Timer/Counter Block Diagram uit de datasheet.
Bron: http://www.atmel.com/dyn/products/product_card.asp?PN=ATmega32A
De module Timer/Counter is de hardware waarin het tellen plaatsvindt. Dit tellen kan zowel naar boven als naar beneden, maar geschiedt altijd in stapjes van één. Dit is een 8-bits register, dus de timer/counter kan alle waarden van 0 t/m 255 aannemen. Het lezen en schrijven naar en van dit register in AVR Studio gaat met de define TCNT0. Control Logic bevat de logica om de timer/counter aan te sturen. Als input heeft dit blok het signaal clkT0. Deze clock bepaald hoe snel de timer/counter met één veranderd. Het signaal is afkomstig van de prescaler of een externe pin. De prescaler is een hardware module met als ingang de systeem clock (F_CPU) en als uitgang een instelbare deling hiervan. Is bijvoorbeeld F_CPU gelijk aan en de prescaler wordt ingesteld op 2, dan is de uitgang . Het resultaat is een puls iedere . De Control Logic module gebruikt deze puls om de timer/counter te laten tellen (met het signaal count). De Control Logic module stelt de richting (Direction) in van het tellen en heeft de mogelijkheid TCNT0
Door Hugo Arends, september 2012
1
op 0 te zetten (Clear). Met bijvoorbeeld een puls iedere om de timer/counter van 0 naar 0 te laten tellen.
duurt het dus
Het instellen van de 8-bit Timer/Counter0 gaat via register TCCR0. Er zijn verschillende instellingen mogelijk, bijvoorbeeld de mode waarin de timer/counter moet werken en wat de clock source is.
Bron: http://www.atmel.com/dyn/products/product_card.asp?PN=ATmega32A
Het register OCR0 staat voor Output Compare Register Zero. Naar dit register kan een 8-bits waarde worden geschreven. Deze waarde wordt vergeleken met de waarde van TCNT0. Zodra deze gelijk zijn kan er een actie op volgen, waaronder het genereren van een Output Compare OC0 interrupt . Er zijn nog twee bijzondere hardware modules, namelijk =0 en =0xFF. Afhankelijk van de modus waarin de timer/counter ingesteld staat kan de waarde van TCNT0 hiermee vergeleken worden. Is TCNT0 gelijk aan één van beiden, dan kan daar afhankelijk van de modus een actie op volgen. Zodra het register TCNT0 de waarde 0xFF heeft bereikt is de maximale waarde bereikt. Wordt er dan nogmaals een puls gegeven dan zal het register TCT0 de waarde 0x00 krijgen. De Control Logic detecteert dit ‘vollopen’ van de timer/counter en set de vlag TOV0 om dit aan te geven.
Interrupts De term interrupt is nu al vaak gevallen, maar wat is dat nu eigenlijk. Stel dat er door een microcontroller twee taken moeten worden uitgevoerd. De eerste taak is het uitvoeren van een ingewikkelde berekening. De tweede taak is het tellen hoe vaak er op een switch wordt gedrukt. Als de microcontroller voor beide taken de helft van de CPU tijd heeft, dan staat de microcontroller dus de helft van de tijd te wachten op input van de gebruiker. Deze tijd kan beter gebruikt worden om de berekening uit te voeren. Interrupts kunnen gebruikt worden om dit te realiseren. Het woord zegt het al, een (lange) taak wordt tijdelijk geïnterrumpeerd door een (korte) taak. De trigger voor het uitvoeren van een interrupt is vaak afkomstig van hardware. Bijvoorbeeld: zodra er een logische 0 wordt gedetecteerd op een input pin (switch wordt ingedrukt) wordt door de microcontroller even de korte taak uitgevoerd. Daarna gaat de lange taak weer verder en krijgt alle processortijd tot er weer een interrupt plaatsvindt. Timers/counters kunnen ook interrupts genereren. Dit is afhankelijk van de mode waarin de timer/counter gebruikt wordt. In bijvoorbeeld de Normal mode wordt interrupt vlag TOV0 geset zodra TCNT0 gelijk is aan 0. In de Clear Timer on Compare Match (CTC) Mode wordt een OC0 interrupt gegenereerd zodra TCNT0 gelijk is aan OCR0 (ga dit na in de datasheet). Aan de hand van een tweetal voorbeelden zullen we bekijken hoe 8-bit Timer/Counter0 ingesteld kan worden om op gezette tijden een interrupt te genereren.
Door Hugo Arends, september 2012
2
Voorbeeld 1 – Normal Mode We ontwikkelen een solution genaamd blinky dat een LED zal laten knipperen op PB0. Hiervoor gebruiken we de Normal Mode. →
Grafisch gezien gebeurt er het volgende. De timer/counter begint in register TCNT0 te tellen. Zodra de stand 255 bereikt is zal bij de volgende clock puls de teller de stand 0 aannemen. Op dit moment moet de timer/counter een Timer Overflow interrupt geven.
TCNT0 value 255
TCNT0
0 t TOV0 interrupt
→
TOV0 interrupt
TOV0 interrupt
Dit wordt gerealiseerd met de code zoals hiernaast weergegeven. Wat opvalt is dat dit programma zijn werk doet geheel op basis van interrupts! Na de initialisatie van PORTB, de timer/counter 0 en de interrupts wordt er in de main routine géén code meer uitgevoerd. NB. Kijkend naar de datasheet, dan zien we dat de inhoud van TCCR0 gelijk wordt aan: TCCR0
0
0
0
0
0
0
0
1
Ga dit voor jezelf na!
De grote vraag is nu natuurlijk hoe lang de tijd is tussen twee TOV0 interrupts. Dit laat zich gemakkelijk uitrekenen. Deze is namelijk afhankelijk van twee variabelen: De systeem clock van de microcontroller: F_CPU De ingestelde prescaler →
Aangezien er geen prescaler ingesteld is, zal de timer/counter dus tellen met de snelheid van F_CPU. Dat betekent een puls iedere (bij 1 MHz). De 8 bits timer/counter telt van 0 t/m 255, dus dat zijn 256 stappen. Daarmee wordt de tijd tussen twee TOV0 interrupts:
Door Hugo Arends, september 2012
3
→
Dat komt neer op een frequentie van:
Deze frequentie is veel te hoog voor ons oog om de LED te zien knipperen! Om de frequentie te verlagen hebben we verschillende opties. De meest voor de hand liggende is de prescaler te gebruiken. →
Stel TCCR0 opnieuw in, maar nu zo dat de prescaler op 1024 staat (het duurt nu dus 1024 pulsen voordat de timer counter een puls krijgt). Zoek in de datasheet op welke bits van TCCR0 logisch 0 en 1 moeten worden. De tijd tussen twee TOV0 interrupts wordt dan:
→
Dat komt neer op een frequentie van:
Dit kunnen we prima waarnemen. De LED zal per seconde ongeveer twee maal aan en twee maal uit zijn!
Voorbeeld 2 – CTC Mode Willen we een exacte timing realiseren, dan kunnen we 8-bit Timer/Counter0 gebruiken in de Clear Timer on Compare Match Mode. In dit voorbeeld wordt de timer/counter ingesteld om iedere 100 ms een interrupt te laten genereren. De solution blinky wordt aangepast, zodat de LED knippert met een frequentie van exact 10 Hz. Dit programma werkt volledig op basis van interrupts. In de main routine worden dus geen instructies uitgevoerd, op initialisatie na. →
Grafisch gezien moet er het volgende gebeuren. De timer/counter begint in register TCNT0 te tellen. Het duurt een bepaalde tijd (100 ms) voordat deze de waarde x bereikt. Op dat moment moet er een Output Compare interrupt gegenereerd worden en TCNT0 gereset, zodat het weer opnieuw kan beginnen. Een dergelijke oplossing kan gerealiseerd worden met de CTC mode. De waarde van x wordt opgeslagen in het register OCR0 en er wordt een OC0 interrupt gegenereerd wanneer OCR0 gelijk is aan TCNT0. Tevens wordt TCNT0 gereset.
TCNT0 value 255
x
OCR0
TCNT0
0
100ms OC0 interrupt
100ms OC0 interrupt
100ms
t
OC0 interrupt
Er zijn in de CTC mode niet twee, maar drie variabelen van invloed op de tijd tussen twee OC0 interrupts: De systeem clock van de microcontroller: F_CPU De ingestelde prescaler De waarde in OCR0 tot waar er geteld wordt We gaan er van uit dat F_CPU gelijk is aan 1MHz.
Door Hugo Arends, september 2012
4
→
De timer/counter telt nu met een frequentie (= aantal pulsen per seconde) van .
Optie 1: géén prescaler.
Dan moet dus de timer/counter tellen tot om een interrupt te genereren iedere 100 ms. Dit levert echter een probleem op, omdat timer/counter0 een 8-bit counter is en er dus ‘slechts’ tot 255 geteld kan worden!! →
De timer/counter telt nu met een frequentie van .
Optie 2: De prescaler wordt ingesteld op 1024.
Dan moet dus de timer/counter tellen tot om een interrupt te genereren iedere 100 ms. Afgerond past dit getal wel in een 8-bit register! →
Dit wordt gerealiseerd met de code zoals hiernaast weergegeven. Na de initialisatie van PORTB wordt de timer/counter ingesteld. het OCR0 register krijgt de zojuist berekende waarde. Vervolgens wordt TCNT0 op nul gezet, zodat zeker is dat deze netjes vanaf nul begint te tellen. NB. Kijkend naar de datasheet, dan zien we dat de inhoud van TCCR0 gelijk wordt aan: TCCR0
0
0
0
0
1
1
0
1
Ga dit voor jezelf na!
De LED knippert nu per seconde vijf keer aan en vijf keer uit! Hoewel timing erg lastig te debuggen is, heeft Atmel Studio een handige manier geïmplementeerd om toch tijd te kunnen meten. →
Zet een breakpunt (F9) in de ISR en start de debugger (F5).
Door Hugo Arends, september 2012
5
→
Maak de Processor view zichtbaar ( ) en reset de Stop Watch (rechtermuis knop).
→
Continue de debugger (F5) en wacht tot het programma wederom stopt bij het breakpoint in de ISR. Bij de Stop Watch lees je nu de tijd af die verstreken is sinds de vorige interrupt. Dit klopt aardig goed met de berekeningen!
Tot slot nog het volgende. De CTC mode kent veel mogelijkheden, maar kent ook beperkingen. Een aantal interessante vragen hierover vind je hieronder. Kun je ze allemaal beantwoorden? De prescaler van de ATmega32A kan ook nog ingesteld worden op 8, 64 en 256. Welke van deze waarden is bruikbaar in bovenstaand voorbeeld? Ga voor jezelf dus na wat de waarde van OCR0 moet worden met deze prescalers en ga na of dit past in de 8 bits ruimte. Met F_CPU gelijk aan 1 MHz, hoe lang bedraagt de maximale tijd tussen twee interrupts die met een 8-bit timer/counter in de CTC mode gerealiseerd kan worden? En met F_CPU is 8 MHz?
Opdracht 1 Start in AVR Studio een nieuw project met de naam blinky. Neem de code over uit het tweede voorbeeld en test het programma. Realiseer vervolgens de volgende taken. Stel de systeem clock van de microcontroller in op 2 MHz. Dit doe je door de fuses aan te passen met de AVR Programming tool. Pas tevens het symbol F_CPU aan (zie les 1 als je niet meer weet hoe dat moet). Pas de instellingen van timer/counter0 dusdanig aan dat de LED met een frequentie van 10 Hz blijft knipperen. Pas de instellingen van timer/counter0 dusdanig aan dat de LED met een frequentie van 20 Hz gaat knipperen. Stel de systeem clock van de microcontroller in op 8 MHz. Pas de instellingen van timer/counter0 dusdanig aan dat de LED met een frequentie van 50 Hz gaat knipperen.
Opdracht 2 Start in AVR Studio een nieuw project met de naam knip. Neem de volgende code over waarin Timer/Counter1 gebruikt wordt. #include
int main(void) { // Initialize Timer/Counter1 TCCR1A = (1<
Door Hugo Arends, september 2012
6
// Reset timer/counter TCNT1 = 0x00; // Initialize OC1A port DDRD |= (1<
Stel de systeem clock van de microcontroller in op 1 MHz (via de fuses) en test het programma met een STK500. Verbindt PORTD met de LED’s. Geef antwoord op de volgende vragen: In welke mode staat timer/counter1 ingesteld (= Waveform Generation Mode)? Hoe staat het gedrag van de output compare pin ingesteld (= Compare Output Mode)? Welke pin van de microcontroller is verbonden met OC1A? Op welke waarde staat de prescaler ingesteld (= Clock Select)? Waarom is het met een 16-bit timer/counter wel mogelijk om iedere seconde een interrupt te laten genereren bij F_CPU = 1 MHz en prescale = 256 in tegenstelling tot een 8-bit timer/counter? Waarom is het niet nodig om een ISR te realiseren? Extra: Voeg aan het programma een ISR toe. Deze ISR zorgt ervoor dat PD0 9 seconde uit is en 1 seconde aan.
Opdracht 3 Start in AVR Studio een nieuw project met de naam stopwatch. Dit programma start met tellen zodra SW0 ingedrukt wordt. De verstreken tijd in seconden wordt binair op de LED´s weergegeven. Zodra SW0 weer ingedrukt wordt stopt het tellen en blijft de verstreken tijd zichtbaar op de LED’s. Wordt SW0 nogmaals ingedrukt, dan gaat het tellen verder. Wordt SW1 ingedrukt, dan wordt de verstreken tijd gereset, maar alleen als de stopwatch niet aan het tellen is. Maak gebruik van een timer/counter om de tijd bij te houden. TIP: Houdt rekening met dender van de switches.
Tijd over Breidt de stopwatch opdracht uit, door de snelheid van tellen instelbaar te maken met SW2. De default snelheid van tellen bedraagt 1Hz. Wordt SW2 ingedrukt dan verdubbeld die snelheid naar 2 Hz. Wordt SW2 nogmaals ingedrukt, dan vertienvoudigd die snelheid naar 10 Hz. Wordt SW2 nogmaals ingedrukt, dan wordt de telfrequentie weer 1 Hz. De snelheid moet aangepast kunnen worden als de stopwatch loopt, maar ook als de stopwatch niet loopt.
In deze onderwijspublicatie is géén auteursrechtelijk beschermd werk opgenomen.
Door Hugo Arends, september 2012
7