Vyberte váš jazyk

Vaša IP adresa: 216.73.217.154
Počet návštev: 1649

Časovače a čítače v mikrokontroléroch AVR

Časovače/čítače (Timer/Counter) patria medzi najdôležitejšie periférne obvody každého mikrokontroléra. Ich hlavnou úlohou je nezávisle od hlavného procesora (CPU) odmeriavať presné časové intervaly, počítať vonkajšie udalosti (impulzy) alebo generovať signály s pulzne-šírkovou moduláciou (PWM).

Základom každého časovača je hardvérový čítač (dátový register), ktorý sa s každým prichádzajúcim hodinovým impulzom inkrementuje (zvyšuje svoju hodnotu o 1). Taktovací signál môže byť odvodený priamo zo systémového oscilátora mikrokontroléra (v režime časovača) alebo privádzaný z externého pinu (v režime čítača). Na zníženie vstupnej frekvencie hodín slúži takzvaná preddelička (prescaler), ktorá umožňuje predeliť systémovú frekvenciu definovaným pomerom (napr. 8, 64, 256, 1024), vďaka čomu vieme odmeriavať aj podstatne dlhšie časové úseky bez pretečenia registra.

Mikrokontrolér ATmega328P disponuje tromi nezávislými časovačmi:

  • Timer0 – 8-bitový časovač/čítač s podporou PWM a porovnávacích registrov,
  • Timer1 – 16-bitový časovač/čítač umožňujúci vysoké rozlíšenie a záchyt vstupnej hrany (Input Capture),
  • Timer2 – 8-bitový časovač/čítač s možnosťou asynchrónneho taktu z externého hodinového kryštálu (vhodný pre hodiny reálneho času – RTC).

V tejto lekcii sa zameriame na 8-bitový časovač Timer0. Využitie hardvérových časovačov namiesto softvérových slučiek oneskorenia (napr. funkcie typu _delay_ms()) je pre vstavané systémy kľúčové: časovač beží autonómne na pozadí, neblokuje výpočtovú kapacitu procesora a v kombinácii s prerušeniami umožňuje dosiahnuť presné, deterministické časovanie aplikácie.

Dôležité registre

V tejto časti sú opísané niektoré registre a ich bity súvisiace s nastavením časovača Timer0, ktoré sú využité v príkladoch nižšie.


TCCR0A - Register slúži hlavne na výber módu 8-bitového časovača Timer0.

reg_TCCR0A.png
  • Na výbere módu časovača sa uplatňujú bity WGM02, WGM01 a WGM00. Pozor bit WGM02 je obsiahnutý v registri TCCR0B! Nastavenie módu sa vykonáva podľa tabuľky nižšie. Bližšie opíšem iba mód 2, pretože tento mód sa často využíva a je použitý aj v príklade č. 4. Tento mód sa skrátene nazýva CTC (Clear Timer on Compare Match), čo znamená, že časovač "počíta" do hodnoty, ktorá je uložená v registri OCR0A. Po dosiahnutí tejto hodnoty dochádza k automatickému nulovaniu hodnoty časovača (register TCNT0) a pokračovaní v inkrementácii časovača.
reg_TCCR0A_table.png

TCCR0B - Register slúži hlavne na výber frekvencie 8-bitového časovača Timer0.

reg_TCCR0B.png
  • Bit WGM02 sa uplatňuje pri výbere módu časovača Timer0. Nastavenie módu je uvedené pri popise registra TCCR0A.
  • Na nastavenie frekvencie (periódy časovača) slúžia bity CS02, CS01 a CS00. Nastavenie frekvencie časovača sa odvíja od frekvencie MCU. V nasledujúcej tabuľke je uvedené nastavenie frekvencie časovača Timer0. V prípade, že niektorý z bitov CS02, CS01 alebo CS00 nie je nastavený na logickú nulu (okrem prvého riadka v tabuľke), dochádza k automatickému spusteniu časovača.
reg_TCCR0B_table.png

TCNT0 - Register (8-bit), ktorý uchováva aktuálnu hodnotu 8-bitového časovača Timer0. Register umožňuje čítanie aj zápis. Zápis do registra TCNT0 blokuje (odstráni) Compare Match pri nasledujúcom impulze časovača. Ak budeme modifikovať hodnotu TCNT0 pri spustenom časovači, tak sa môže stať, že nedôjde ku Compare Match medzi TCNT0 a OCR0A.

reg_TCNT0.png

OCR0A - Register (8-bit), ktorý obsahuje 8-bitovú hodnotu, ktorá je neustále porovnávaná s hodnotou časovača TCNT0. Zhoda registrov TCNT0 a OCR0A môže vyvolať prerušenie alebo generovať signál na vývode OC0A v závislosti od nastavenia módu časovača.

reg_OCR0A.png

TIMSK0 - Register (8-bit), ktorý slúži na vypnutie/zapnutie špecifického typu prerušenia v súvislosti s časovačom. Pre zapnutie/vypnutie špecifických prerušení slúžia registre OCIE0B, OCIE0A a TOIE0.

reg_TIMSK0.png
  • Ak je na bit OCIE0B zapísaná hodnota logickej jednotky a zároveň sú globálne povolené prerušenia (sei()), tak je zapnuté prerušenie Timer/Counter Compare Match B. Prerušenie je vyvolané nastavením bitu OCF0B v registri TIFR0.
  • Ak je na bit OCIE0A zapísaná hodnota logickej jednotky a zároveň sú globálne povolené prerušenia (sei()), tak je zapnuté prerušenie Timer/Counter Compare Match A. Prerušenie je vyvolané nastavením bitu OCF0A v registri TIFR0.
  • Ak je na bit TOIE0 zapísaná hodnota logickej jednotky a zároveň sú globálne povolené prerušenia (sei()), tak je zapnuté prerušenie pri pretečení časovača (timer overflow). Prerušenie je vyvolané nastavením bitu TOV0 v registri TIFR0.

TIFR0 - Register (8-bit), ktorý indikuje, či došlo k špecifickému typu prerušenia. Prerušenie je indikované pomocou bitov OCF0B, OCF0A a TOV0.

reg_TIFR0.png
  • Ak bit OCF0B obsahuje hodnotu logickej jednotky, tak došlo k zhode (Compare Match) medzi hodnotou časovača a hodnotou uloženou v registri OCR0B. Nulovanie bitu OCF0B je zabezpečené automaticky vykonaním zodpovedajúceho vektora prerušenia. Alternatívne môžeme bit OCF0B vynulovať manuálne zápisom logickej jednotky na tento bit. Ak je nastavený bit OCF0B a zároveň je nastavený bit OCIE0B tak dôjde k vykonaniu prerušenia (Timer/Counter Compare Match Interrupt).
  • Ak bit OCF0A obsahuje hodnotu logickej jednotky, tak došlo k zhode (Compare Match) medzi hodnotou časovača a hodnotou uloženou v registri OCR0A. Nulovanie bitu OCF0A je zabezpečené automaticky vykonaním zodpovedajúceho vektora prerušenia. Alternatívne môžeme bit OCF0A vynulovať manuálne zápisom logickej jednotky na tento bit. Ak je nastavený bit OCF0A a zároveň je nastavený bit OCIE0A tak dôjde k vykonaniu prerušenia (Timer/Counter Compare Match Interrupt).
  • Bit TOV0 sa zmení na jednotku keď dôjde k pretečeniu časovača. Nulovanie bitu TOV0 je zabezpečené automaticky vykonaním zodpovedajúceho vektora prerušenia. Alternatívne môžeme bit TOV0 vynulovať manuálne zápisom logickej jednotky na tento bit. Ak je nastavený bit TOV0 a zároveň je nastavený bit TOIE0 tak dôjde k vykonaniu prerušenia (Timer/Counter0 Overflow interrupt).

V nasledujúcich príkladoch sú uvedené rôzne spôsoby blikania LED s využitím časovača. LED má bliknúť 5x za sekundu, pričom čas rozsvietenej diódy je rovnaký ako čas zhasnutej diódy. Uvedenej situácii zodpovedá nasledujúci časový diagram.

casovac_led.png

Príklad č. 1

Tento príklad demonštruje najjednoduchší spôsob generovania časového oneskorenia pomocou časovača Timer0 v predvolenom normálnom režime (Normal mode) s využitím metódy cyklického dopytovania (polling) na príznak pretečenia.

Časovač je nakonfigurovaný zápisom do registra TCCR0B s preddeličkou 8, čo pri systémovej frekvencii 8 MHz znamená, že register TCNT0 sa inkrementuje s frekvenciou 1 MHz (každú 1 µs). K pretečeniu 8-bitového registra (z 255 na 0) dochádza každých 256 µs, kedy sa v registri TIFR0 nastaví príznak pretečenia TOV0. V hlavnej slučke program tento príznak testuje, softvérovo ho nuluje (zápisom logickej 1) a inkrementuje počítadlo pretečení. Po dosiahnutí 391 pretečení (391 × 256 µs ≈ 100,1 ms) dôjde k negovaniu stavu pinu PORTB0, čím sa dosiahne pravidelné blikanie LED s periódou 200 ms (frekvencia 5 Hz).

#include <avr/io.h>

int main(void)
{
DDRB=255; //PORTB ako vystupny (vsetky piny)
PORTB=0; //vsetky vystupy na log.0 -> vsetky LED zhasnute
TCCR0B|=(1<<CS01); // zapne casovac, predelicka 8 -> frekvencia casovaca 1 MHz
unsigned int pocet_preteceni=0; //pocitadlo preteceni
while (1)
{
if ((TIFR0&(1<<TOV0))==(1<<TOV0)) //ci nastalo pretecenie
{
pocet_preteceni++; //inkrementovanie poctu preteceni
TIFR0|=(1<<TOV0); //nulovanie priznaku pretecenia zapisom log.1
}
if (pocet_preteceni==391) //391 preteceni = 100.096 ms
{
pocet_preteceni=0; //aby sa odznova pocitali pretecenia
PORTB^=(1<<PORTB0); //zmena stavu LED na pine PORTB0
}
}
}

Príklad č. 2

V druhom príklade je časové oneskorenie realizované priamym čítaním a manuálnym nulovaním hodnoty v dátovom registri časovača TCNT0.

Časovač Timer0 je opäť spustený s preddeličkou 8, čo pri systémovom takte 8 MHz znamená krok čítača každú 1 µs. V hlavnom cykle program priamo monitoruje hodnotu v registri TCNT0. Ak hodnota dosiahne alebo prekročí 200 (uplynutie 200 µs), register TCNT0 sa softvérovo vynuluje a hodnota pomocného počítadla sa zvýši o 1. Po dosiahnutí 500 opakovaní (500 × 200 µs = 100 ms) sa stav diódy na vývode PORTB0 neguje, čím LED zmení svoj stav (zasvieti sa alebo zhasne). Hoci je tento prístup intuitívny, manuálne prepisovanie registra TCNT0 v softvéri môže spôsobiť mierne časové nepresnosti spôsobené oneskorením vykonávania inštrukcií.

#include <avr/io.h>

int main(void)
{
DDRB=255; //PORTB ako vystupny (vsetky piny)
PORTB=0; //vsetky vystupy na log.0 -> vsetky LED zhasnute
TCCR0B|=(1<<CS01); // zapne casovac, predelicka 8 -> frekvencia casovaca 1 MHz
unsigned int pocitadlo=0;
while (1)
{
if (TCNT0>=200) //ci ubehlo 200us
{
pocitadlo++; //inkrementacia pocitadla (kazdych 200us)
TCNT0=0; //nulovanie casovaca, aby znova pocital od nuly
}
if (pocitadlo==500) //ci ubehlo 100ms=500 x 200us
{
pocitadlo=0;
PORTB^=(1<<PORTB0); //bitova negacia
}
}
}

Príklad č. 3

V treťom príklade je generovanie časového oneskorenia založené na hardvérovom porovnávaní hodnôt (Output Compare Match) v predvolenom normálnom režime časovača Timer0.

Do porovnávacieho registra OCR0A je zapísaná hodnota 200. Pri preddeličke nastavenej na 8 a frekvencii MCU 8 MHz sa čítač TCNT0 inkrementuje každú 1 µs, takže k zhode s hodnotou v OCR0A dochádza každých 200 µs. Táto udalosť nastaví príznak porovnania OCF0A v registri TIFR0. Hlavný programový cyklus tento príznak testuje, softvérovo ho vynuluje zápisom logickej 1 a navýši hodnotu pomocného počítadla. Po načítaní 500 zhôd (500 × 200 µs = 100 ms) program neguje stav pinu PORTB0 a LED zmení svoj svit. Tento prístup je presnejší než priame čítanie registra TCNT0, keďže samotná detekcia zhody prebieha na úrovni interného hardvéru časovača.

#include <avr/io.h>

int main(void)
{
DDRB=255; //PORTB ako vystupny (vsetky piny)
PORTB=0; //vsetky vystupy na log.0 -> vsetky LED zhasnute
TCCR0B|=(1<<CS01); // zapne casovac, predelicka 8 -> frekvencia casovaca 1 MHz
OCR0A=200; //nastavenie registra zhody (200 = 200us)
unsigned int pocitadlo=0;
while (1)
{
//ci bit OCF0A v reg. TIFR0 je nastaveny na jednotku ->
//doslo k zhode TCNT0 a OCR0A
if ((TIFR0&(1<<OCF0A))==(1<<OCF0A))
{
pocitadlo++;
TIFR0|=(1<<OCF0A); //zmazanie priznaku zhody zapisom log.1
}
if (pocitadlo==500) //ubehlo 100ms=500x200us
{
pocitadlo=0;
PORTB^=(1<<PORTB0); //zmena stavu LED na pine PORTB0
}
}
}

Príklad č. 4

Štvrtý príklad predstavuje najefektívnejší a v praxi najpoužívanejší spôsob práce s časovačom: využitie hardvérového režimu CTC (Clear Timer on Compare Match) v kombinácii s obsluhou prerušenia (ISR).

Nastavením bitu WGM01 v registri TCCR0A sa aktivuje režim CTC, v ktorom sa hodnota čítača TCNT0 automaticky hardvérovo vynuluje v okamihu dosiahnutia hodnoty uloženej v registri OCR0A (hodnota 200 = 200 µs). Zápisom do registra TIMSK0 (OCIE0A = 1) a volaním makra sei() je povolené prerušenie od porovnania. Každých 200 µs MCU automaticky pozastaví hlavný kód a vykoná obsluhu prerušenia TIMER0_COMPA_vect, kde sa inkrementuje počítadlo. Po 500 vykonaniach (100 ms) sa zneguje stav pinu PORTB0. Výhodou tohto prístupu je nulová záťaž hlavného programu — nekonečný cyklus while(1) v main() zostáva úplne prázdny a procesor v ňom môže vykonávať iné výpočty alebo byť uspatý do úsporného režimu.

#include <avr/io.h>
#include <avr/interrupt.h> //kniznica preruseni

unsigned int pocitadlo=0;

ISR(TIMER0_COMPA_vect) //obsluha prerusenia casovaca0
{
//sem hlavny program (main) "odskoci" kazdych 200us
pocitadlo++;
if (pocitadlo==500) //ubehlo 100ms = 500 x 200us
{
pocitadlo=0;
PORTB^=(1<<PORTB0); //zmena stavu LED na pine PORTB0
}
}

int main(void)
{
DDRB=255; //PORTB ako vystupny (vsetky piny)
PORTB=0; //vsetky vystupy na log.0 -> vsetky LED zhasnute
TCCR0B|=(1<<CS01); // zapne casovac, predelicka 8 -> frekvencia casovaca 1 MHz
OCR0A=200; //nastavenie registra zhody (200 = 200us)

//automaticke nulovanie TCNT0 pri dosiahnuti hodnoty v OCR0A
TCCR0A|=(1<<WGM01);
TIMSK0=(1<<OCIE0A); //zapnutie prerusenia casovaca0
sei(); //globalne povolenie preruseni
while (1)
{
}
}