Időzítők

  • Időzítők felépítése és működése
  • Időzítési idő beállítása, előosztó
  • Példa programok egyszeri és többszöri időzítésre
  • Példa program bármely kivezetés PWM működésének beállítására
  • Belső regiszterek részletesebb leírása

Időzítőkről eddig nem sok szó esett. Leginkább azért, mert nem volt rá igazán szükségem. Most azonban több olyan program írásába is belekezdtem, amiket szinte lehetetlen az időzítők által generált megszakítások nélkül megoldani. Fogjunk hát hozzá, és ismerjük meg működésüket részletesen.

A leírásban sokszor fogok a vezérlő belső regisztereire hivatkozni. Ezek használatát könnyebb megérteni, ha elolvasod a „Digitális ki és bemenetek kezelése alacsony szinten” című leírásomnak legalább az elejét. Ott példákat láthatsz arra, hogy lehet az Arduino programban a vezérlő belső regisztereit írni és olvasni. Mivel itt is belső regiszterekkel dolgozunk, ez szükséges tudás a megértéshez!

Szeretném előre jelezni, hogy a leírásom közel sem teljes, nem tértem ki minden részletre, ami az ATMega328 adatlapjában megtalálható. Egyes nehezebben megérthető, illetve számomra kevésbé fontos tulajdonságoknak nem jártam utána, nem próbáltam ki, és így le sem írtam. Aki minden tudni akar, az először töltse le valahonnan az orosz „Mindent tudni akarok!” című ismeretterjesztő sorozatot, nézze végig, aztán vegyen egy nagy levegőt, és olvassa el az adatlapot!

Kezdjük a megszakításokkal. Minden vezérlő, és így az általunk legjobban ismert ATMega328 is többféle megszakítással rendelkezik. A külső kivezetéseken megjelenő digitális jelek által generált megszakításokról találsz egy leírást itt, ezt esetleg érdemes bemelegítésként elolvasni. Összefoglalva a témát, csak annyit érdemes egyenlőre tudni, hogy ha valamilyen külső vagy akár belső feltétel teljesül a vezérlőben, akkor a loop()-ban megírt programunk futása megszakad, a vezérlő elmenti a folytatáshoz szükséges összes fontos adatot a memóriába, és elindít egy általunk előre megírt külön programot, ami csinál valamit. Amikor befejezte azt a valamit, a loop() programja folytatódik tovább mintha mi sem történt volna. természetesen a loop() folytatása előtt minden szükséges adatot visszatölt a vezérlő. Ezzel a technikával olyan érzetünk lehet, mintha több program is futna a vezérlőben egyszerre. Az Arduino környezetben már eleve beépítettek néhány programunkkal párhuzamosan futó szolgáltatást. Pl. Az egyik időzítőt beállították úgy, hogy ezred másodpercenként generáljon egy megszakítást. A megszakításkor lefutó programrész semmi mást nem csinál, mint megnöveli egy belső változó értékét 1-el. Ez a változó long típusú (4 byte hosszú), és ezzel 4 294 967 296-ig tud elszámolni ekkor átfordul azaz nullázódik. Ismerjük és használjuk is ezt a változót, mert a millis() függvény ennek tartalmát kérdezi le. A program elindulása óta eltelt időt lehet mérni vele, és nagyon kényelmessé teszi az Arduino környezetben a programozást.

Ha szeretnél magadnak egy másodperc alapon működő órát írni, akkor a beépített millis() függvény ezredmásodperces felbontása túl nagy. Ha azonban ezt a leírást figyelmesen elolvasod, ígérem, hogy akár írhatsz magadnak egy masodperc() függvényt!

Most, hogy kedvet kaptunk az időzítők felhasználására, ismerkedjünk meg az ATMega328 belsejében rejtőzködő eszközökkel. Három időzítőt építettek be! Működési elvük teljesen azonos, bár kisebb nagyobb különbség van köztük. Alapvető dolgokban azonosak, és a működési módjaik beállítása is azonos módon történik. Sok szó esik majd ebben a leírásban az időzítők működésével kapcsolatos belső regiszterek működéséről. A regiszterek nevei azonosak és egy számmal különböztetjük meg, hogy melyik időzítőhöz van közük. Pl. a TCNT1, TCNT2 és a TCNT3 egy-egy számláló, ami a nevéhez írt „index” szerinti időzítőhöz tartozik. Ha általánosságban beszélünk egy-egy regiszterről, akkor szoktuk az index értéket „x”-el jelölni. Pl. ebben az esetben TCNTx az általános név.

Folytassuk is a gondoltmenetet a TCNTx regiszterrel. Ez a regiszter egy számláló, ami az időzítő lelke. A számláló bemenetére ismert és pontos frekvenciájú négyszögjelet kapcsolunk, minek hatására számol felfelé (vagy akár lefelé). Amikor elérte a maximális értéket, akkor átfordul, azaz nullázódik a tartalma. Az átfordulás pillanata rögvest képes egy megszakítás generálására, ha az a vezérlőben engedélyezve van. A számláló mellé még beépítettek két egymástól független összehasonlító egységet is (komparátort), amik figyelik a számláló értékét, és ha ez elér egy előre beállított értéket, akkor szintén megszakítást generálnak. Természetesen az összehasonlító áramkör által figyelt érték általunk beállítható a vezérlő egy-egy regiszterében. Ezeknek a regisztereknek a neve OCRxA és OCRxB (az „x” az időzítő sorszámát jelöli).

Az időzítő belső számlálója természetesen – nem közvetlenül – de a kvarc kristállyal működő pontos órajelre van rákötve. Általában ez 16Mhz, ezért én végig ezzel fogok számolni. Azt azonban fontos tudni, hogy ez a külső kvarc kristály lehet más frekvenciájú (pl. 8Mhz), ezt figyelmebe kell venni ha az időzítőkkel dolgozunk.

Mint említettem három időzítőt építettek be az ATMega328 vezérlőbe. Könnyen kitalálható a nevük: TIMER0, TIMER1 és TIMER2. Az időzítők számlálója korlátozott hosszúságú. A TIMER0 és a TIMER2 csupán 8 bites. A TIMER1 azonban már 16 bites. Első lépésben nézzük meg, mekkora időket tudunk időzíteni ezekkel a mennyiségekkel. A 16Mhz órajel periódus ideje (1/16000000)=62,5ns (nanosekundum). Ha ezt osztjuk egy 8 bites számmal, akkor maximum 62,5ns*256=16us (mikrosekundum) időt kapunk. Osztást emlegettem, de szoroztam! Ennek oka, hogy az osztás csökkenti a frekvenciát, vagyis növeli a periódus időt! A 16 us egyébként pont egy 62,6Khz-s négyszögjel periódus ideje. A TIMER1-nél valamivel jobb a helyzet, itt a számláló ugyanis 65535-el (16 bit) órajel után fordul át, vagyis a kapott időzítés 4,096ms (millisekundum), ami 244Hz frekvenciának felel meg. Ha nagyobb időket szeretnénk előállítani illetve mérni, akkor ezek túl gyorsak. A vezérlő tervezői azonban segítettek rajtunk, mert a számlálók elé betettek egy programozható előosztót (prescaller a becsületes neve). Találtam egész jó ábrát a működésről:

Ez az előosztó néhány előre beállítható értékkel rendelkezik pl. 1,8,64,256,1024. Ha a 16Mhz frekvenciát leosztjuk pl. 1024-el, akkor a TIMER0 és TIMER2 időzítése 16us-ről nagyjából 16ms-re növekszik. A TIMER1 pedig kb 4,1 másodpercre. Ezzel már lehet kezdeni valamit. Természetesen sokkal nagyobb időzítéseket is elő fogunk tudni állítani, mert a megszakításkor hívott programot mi írjuk, és ott gondoskodhatunk arról, hogy pl . csak minden ezredik meghíváskor történjen valami!

Az jól látható, hogy a három beépített időzítő működési elve teljesen azonos. Nézzük hát meg, hogy milyen regiszterekbe kell információt írni ahhoz, hogy történjen valami. Most a TIMER0 időzítőről fogok beszélni, de amit leírok igaz a többire is, csak a 0 helyett 1-et illetve 2-őt kell írni a regiszter nevekben. Kell először egy számláló, ami számolja az előosztóból érkező impulzusokat. Ennek neve TCNT0. Ezt a regisztert írhatjuk és olvashatjuk is. Ha kiolvassuk a tartalmát, akkor megtudhatjuk, hol jár éppen a számlálásban. Ha pedig írjuk, akkor beállíthatunk egy kezdő értéket, amiről elkezd fölfelé számolni, ha kap a bemenetén órajelet. Amikor elérte a maximumot, (TIMER0 és TMIER2 esetén ez 255, TIMER0-nál 65535), akkor átfordul a számláló, generál egy megszakítást, és 0-ról folytatja a számlálást. Természetesen ha engedélyezzük az átfordulás (túlcsordulás) eseményének megszakítását, akkor az általunk megírt megszakítás program újra beállíthatja a számláló kezdő értékét. Így jól „finnomhangolhatjuk” az időt, de erről később! (Zárójelben jegyzem meg, hogy az időzítő arra is ad lehetőséget, hogy egy adott számláló értéknél nullázódjon a számláló, de erről is később!)

Mint említettem, van két összehasonlító áramkör, ami megszakítást generálhat egyezés esetén. Ezeknek az összehasonlítóknak a komparálási értékét az OCR0A és OCR0B regiszterekben tárolhatjuk. Ezekbe a regiszterekbe kell beírnunk azokat az értékeket, melyeknél szeretnénk ha megszakítást generálna a hozzá tartozó összehasonlító áramkör. Nyilván ezek a regiszterek 8 bitesek a TIMER0 és TIMER2 esetében, míg a TIMER 0-nál 16 bitesek. És akkor itt kell megjegyeznem, hogy nem csak megszakítást generálhatnak ezek a regiszterek, hanem az időzítő számlálóját is nullázhatják. Ez a CTC üzemmód, de erről még a későbbiekben további szót ejtünk.

Ezek a legalapvetőbb regiszterek, de vannak még lényegesek, melyekkel a megszakításokat lehet engedélyezni, és be kell állítani az előosztót. Kezelésük kicsit összetett, mert minden egyes bitnek más jelentése van. Azonban nem kell megijedni, nem bonyolult, és könnyen elvégezhetjük a beállításokat! Van két kontroll regiszter TCCRxA és TCCRxB (TIMER1-nél három van, így itt találunk egy TCCR1C regisztert is). Ezek közül egyenlőre csak a TCCRxB-vel foglalkozunk, mert ennek három bitjével lehet beállítani az előosztót. Nem csak az előosztót lehet beállítani, hanem meg lehet adni az órajel forrását is, ami lehet a belső órajel, és lehet egy külső órajel is (csak a TIMER0 ésTIMER1 esetében), illetve teljesen kikapcsolható az órajel, vagyis leállítható az időzítő. Továbbá van még egy igen fontos regiszter a TIMSKx. Ebben a regiszterben lehet ugyanis egy-egy bittel a túlcsordulás, komparátor A és B megszakításokat engedélyezni vagy tiltani. Ha ezeket a biteket nem billentjük be 1-be, akkor az adott megszakítás nem fog generálódni. Meg kell még említenem a TIFRx regisztert, amely a a túlcsordulás illetve komparálási feltételek teljesülését jelzi egy-egy bittel. Ha pl. a túlcsordulás megtörténik, és a neki megfelelő bit bebillen 1-be. Ebből a regiszterből bármikor lekérdezhető, hogy történt-e megszakítás. Ha ezeket a jelzőbiteket használjuk, akkor gondoskodni kell a törlésükről, hogy legközelebb is jelezhessék, hogy megtörtént  az esemény. Ha engedélyeztük a megszakítást, akkor a megszakítás rutin meghívása automatikusan elvégzi a törlést, nekünk nem kell vele foglalkozni. Ha nincs megszakítás engedélyezve, akkor pedig elegendő kiolvasni a regiszter tartalmát és ettől a bitek máris törlődnek.

Mielőtt belemélyednénk az egyes kontroll regiszterek rejtelmeibe, nézzünk néhány klasszikus időzítő funkciót, amihez az alapvető beállításokat feltétlenül meg kell ismernünk. Példaként most a TIMER1 időzítő regisztereit vettem elő, de mint említettem a többi időzítő ezen tulajdonságait ugyanígy lehet beállítani, csak a névben az index változik:

Előosztó beállítása. A TCCR1B regiszter alsó három bitje (CS12,CS11,CS10), szolgál az órajel forrás illetve előosztó kiválasztására:

A fenti táblázat minden időzítőnél más egy ici-picit, a részletek most még nem érdekesek!

Megszakítások engedélyezése. TIMSK1 megszakítás maszk regiszter alsó három bitje szolgál erre a célra:

OCIE1B – B összehasonlító megszakítás engedélyezés
OCIE1A – A összehasonlító megszakítás engedélyezés
TOIE1 – túlcsordulás megszakítás engedélyezés.

Ha valamelyik megszakítást engedélyezni szeretnénk, az illető bit-et állítsuk be 1-re. Ha 0-ra állítjuk, nem fog megszakítás keletkezni.

Lássunk hát néhány egyszerű programozási példát mindezek használatára. Előzetesen még annyit szeretnék általánosságban megjegyezni, hogy amikor az időzítők regisztereit piszkáljuk, célszerű letiltani minden megszakítást, mert ennek hiányában könnyen hibás működést kaphatunk. Több regisztert is be kell állítani, ha közben érkezik egy megszakítás, ami hosszabb ideig lefoglalja a vezérlőt, esetleg nem teljesülnek az elvárt időzítési idők. Az Arduino programban a cli() függvény a vezérlő összes megszakítását letiltja. A sei() függvény pedig mindet engedélyezi!

1/A Egyszeri időzítési feladat, nem kell megismételni. Példa feladatunkban a beépített led kezdjen el világítani amikor megnyomunk egy nyomógombot, amit a 2-es bemenetre kötöttünk rá. Majd a led 1 sec elteltével magától kapcsoljon ki. A másodperc időzítéshez sejthető, hogy legalább 1024-es előosztót kell bekapcsolni. Ekkor egy tick (a négyszögjel felfutó éle), ami növeli a TCNT1 értékét 1024×62,5ns=64us (16Mhz órajel esetén az órajel periódus ideje 62,5 nanosekundum, így ennek 1024 szerese, 64 mikrosekundum lesz a számláló bemenő órajelének periódusideje). Ebből következik, hogy 1 másodperc időzítéséhez 1/0.000064=15625 ciklust kell várnunk (64us=0,000064sec). Tehát ha a TCNT1 regiszterbe 65536-15625=49911-et írunk, akkor a számláló pont 1 sec múlva fog túlcsordulni. Példa programunkban a setup() részben engedélyezzük a túlcsordulás megszakítást, de a B kontrol regiszterben nem választunk ki órajelforrást az időzítő számlálójának. Ekkor számláló nem kap órajelet és áll. Azonban a loop()-ban a nyomógomb megnyomásakor beállítjuk a számláló értékét a kiszámított értékre, és beállítjuk az előosztót 1024-re. Ebben a pillanatban megérkezik az órajel, és a számláló elkezd felfelé számolni, amíg túl nem csordul. A túlcsorduláskor meghívott megszakítás függvény aztán lekapcsolja az órajelet a számlálóról, és kikapcsolja a led-et is. Íme a program:

ISR(TIMER1_OVF_vect) // ez a megszakításkor lefutó programrész 
{ 
    TCCR1B=B00000000;  //Az időzítő leáll, mert nincs órajel forrás és előosztó kiválasztva
    digitalWrite(13, LOW); //beépített LED kikapcsolása
}

void setup()
{
    pinMode(2,INPUT);digitalWrite(2,HIGH);  //2-es kivezetés bemenet, felhúzó ellenállás bekapcsolva
    pinMode(13,OUTPUT);   //beépített led kivezetése kimenet
    digitalWrite(13,LOW);  //led nem világít
    cli();     //az időzítő beállításának idejére tiltjuk a megszakításokat
    TCCR1A=0;           //A kontroll regiszter beállítás (ez egyszerűbb időzítéseknél általában 0)
    TCCR1B=0;              //ezzel most nincs órajel engedélyezve a TCNT1 bemenetén, tehát az időzítő áll
    TIMSK1=B00000001;   //engedélyezzük a túlcsordulás megszakítást
     TCCR1B=B00000000;  //Az időzítő leáll, mert nincs órajel forrás és előosztó kiválasztva
     //minden beállítás kész, a számláló beállított kezdőértékkel vár hogy elindulhasson,
    sei();         //vége a beállításnak, engedélyezzük a megszakításokat
}

void loop() {
    if(digitalRead(2)==1) {  //megnyomták a nyomógombot
        digitalWrite(13,HIGH);   //beépített led bekapcsolása
        TCNT1=49911;        //időzítő számláló kezdőértékének beállítása
        TCCR1B=B00000101;   //1024 előosztó beállítása, ezzel indul a számláló
         //a program fut tovább, és egy másodperc múlva a led világítani kezd
   }
}

1/B Folyamatosan indítgatja az időzítő a megszakításhoz rendelt programot, és minden indításkor ki illetve be kapcsoljuk a beépített led-et (ez a klasszikus blink program máshogy). Legyen a változások között eltelt idő 1 másodperc. Ez a feladat abban különbözik az előzőtől, hogy engedélyezzük a megszakítást (pl. jelen esetben a túlcsordulás megszakítását), elindítjuk az időzítőt a kiválasztott előosztó bekapcsolásával, és nem is állítjuk le. Többféle lehetőségünk van arra, hogy az egyes megszakítások között eltelt idő azonos legyen. Egyik megoldás szerint (ezt fogjuk most megírni), beállítunk a TCNT1 regisszterbe egy előre kiszámított értéket, és a megszakítás programrészben rögvest újra beírjuk ugyanazt az értéket. Így a számlálás minden esetben ugyanarról az értékről indul. Lássuk a feladat megoldását! A program kicsivel egyszerűbb int az előző, hiszen alig kell valamit csinálni.

ISR(TIMER1_OVF_vect) // ez a megszakításkor lefutó programrész 
{ 
      TCNT1=49911;  //Az időzítő számlálójának mindig ugyanazt a kezdő értéket adjuk (túlcsordulásig
                             //15625 tick telik el, ami 1024-es előosztóval pont 1 sec
    digitalWrite(13, !digitalRead(13)); //beépített LED felkapcsolása, ha előtte nem világított, és 
                                                          //kikapcsolás ha világított
}

void setup()
{
pinMode(13,OUTPUT);   //beépített led kivezetése kimenet
digitalWrite(13,LOW);  //led nem világít
    cli();     //az időzítő beállításának idejére tiltjuk a megszakításokat
    TCCR1A=0;           //A kontroll regiszter beállítás (ez egyszerűbb időzítéseknél általában 0)
    TCCR1B=B00000101;         //beállítjuk az 1024-es előosztót, időzítő ettől kezdve számol
    TCNT1=49911;        //időzítő számláló kezdőértékének beállítása
    TIMSK1=B00000001;   //engedélyezzük a túlcsordulás megszakítást
    sei();         //vége a beállításnak, engedélyezzük a megszakításokat
}

void loop() {
}

Ennek a megoldásnak nagy előnye, hogy a loop()-ban nincs semmi! Szabadon írhatunk egy tetszőleges programot, ami bármit csinál a led rendületlenül villog!

1/C Most következzen ugyanannak a feladattnak egy másfajta megoldása. Az A összehasonlítót fogjuk felhasználni. Értékéhez mindig hozzáadunk 15625-öt, így amíg a TCNT1 eléri a megnövekedett értéket, pont egy másodperc fog eltelni. Viszont a számlálónk nem végtelen méretű, mi történik, amikor az aktuális értékhez hozzáadunk 15625-öt és az eredmény nagyobb lesz, mint az ábrázolható maximális 65536? Nagyon egyszerű: amennyivel nagyobb az eredmény, annyi lesz a beállított kezdőérték, vagyis csak a túlcsordult érték kerül át a számlálóba. Így jól viselkedik a program minden helyzetben, mindig 1 másodpercet fog időzíteni. A program kicsit változik, hiszen nem a túlcsordulás megszakítását kell engedélyeznünk, hanem az A komparátor megszakítását is. Lássuk a forrást:

ISR(TIMER1_COMPA_vect) // ez az A komparátor egyezésekor keletkezett megszakításkor lefutó programrész 
{ 
      OCR1A+=15625;  //Az összehasonlító regiszter tartalmát megnöveljük 15625-el, 1024 előosztóval 
                                         //ez pont  1 sec
    digitalWrite(13, !digitalRead(13)); //beépített LED felkapcsolása, ha előtte nem világított, és 
                                                          //kikapcsolás ha világított
}

void setup()
{
pinMode(13,OUTPUT);   //beépített led kivezetése kimenet
digitalWrite(13,LOW);  //led nem világít
    cli();     //az időzítő beállításának idejére tiltjuk a megszakításokat
    TCCR1A=0;           //A kontroll regiszter beállítás (ez egyszerűbb időzítéseknél általában 0)
    TCCR1B=B00000101;         //beállítjuk az 1024-es előosztót, időzítő ettől kezdve számol
    TCNT1=0;        //időzítő számláló kezdőértéke
    TIMSK1=B00000010;   //engedélyezzük az A komparátor egyezésének megszakítását
    OCR1A=15625;   //Az első megszakítás így 15625 tick múlva következik be
    sei();         //vége a beállításnak, engedélyezzük a megszakításokat
}

void loop() {
}

Érdemes még azt is észre venni, hogy a TIMER1 16 bites, és a TCNT1, OCR1A és OCR1B regiszterek is 16 bitesek. Valójában ezek a regiszterek 2 nyolc bites regiszterből állnak, de az Arduino környezetben megoldották, hogy ezzel nem kell foglalkoznunk. A vezérlőben azonban ezek a regiszterek két részből állnak. Pl a TCNT1 az TCNT1H és TCNT1L regiszterekkel is kezelhető.

1/D Ugyanannak a feladatnak a megoldása CTC üzemmód használatával. Az időzítő azt is meg tudja csinálni, hogy amikor a komparátor egyezés megszakítást generál, egyben a TCNT1 számlálót is nullázza. Ezt hívják CTC (Clear Timer on Compare) üzemmódnak. Így aztán az előző példa programja még egyszerűbb lehet. Ahhoz, hogy ezt az üzemmódot beállítsuk, a TCCR1B kontrol regiszter 3. ÉS 4. bitjébe mást kell írni. Később majd a részletekben látni fogjuk, hogy ebben a két kontroll regiszterben (A ás B) elég sokféle üzemmód és működési mód állítható be, de most még ne foglalkozzunk a részletekkel. Az alábbi forrásban láthatjuk a regiszterekbe beírt értéket, és ez legyen egyelőre elég:

ISR(TIMER1_COMPA_vect) // ez az A komparátor egyezésekor keletkezettmegszakításkor lefutó programrész 
{ 
//itt most nem kell semmit beállítani, mert a megszakítással egyidőben a TCNT1 is törlődik 
      digitalWrite(13, !digitalRead(13)); //beépített LED felkapcsolása, ha előtte nem világított, és 
                                                          //kikapcsolás ha világított
}

void setup()
{
pinMode(13,OUTPUT);   //beépített led kivezetése kimenet
digitalWrite(13,LOW);  //led nem világít
    cli();     //az időzítő beállításának idejére tiltjuk a megszakításokat
    TCCR1A=0;           //Erre a regiszterre most nincs szükség a CTC módhoz
    TCCR1B=B00001101;         //beállítjuk az 1024-es előosztót (bit0=1,bit1=0,bit2=1), időzítő ettől kezdve számol, valamint a CTC módot (bit3=1, bit4=0)
    TCNT1=0;        //időzítő számláló kezdőértéke
    TIMSK1=B00000010;   //engedélyezzük az A komparátor egyezésének megszakítását
   OCR1A=15625;   //Az első megszakítás így 15625 tick múlva következik be
    sei();         //vége a beállításnak, engedélyezzük a megszakításokat
}

void loop() {
}

Legyen ez most pont a beépített led a 13-as Arduino kivezetésen. Ez pont nem PWM képes. Igaz ugyan, ha elhasználjuk valamelyik TIMER-t a PWM jel előállítására, akkor valamelyik PWM-nek jelölt kivezetés már nem lesz az. Használjuk most a TIMER2 időzítőt, mert az Arduino PWM kimenetek egyébként is „csak” 8 bitesek. A feladat roppant egyszerű. Be kell állítanunk valamilyen periódus időt, ami alatt a számláló elszámol 0-ról 255-re. Ez egyébként úgy emlékszem 4-500Hz körüli érték, Vagyis ha az időzítő számlálójának túlcsordulása adná az ütemet, és az 400Hz, akkor a számláló bemenetén 256×400=100000Hz-nek kellene lennie a frekvenciának. Ez viszont azt jelenti, hogy a 16Mhz-t pont 128-al kell osztanunk. Ekkor a bemeneten 125Khz frekvencia lesz. Ezt csökkenti még a TIMER2 8 bites számlálója 488hz-ra, és ezzel meg is volnánk. Tehát 128-as előosztó kell, és a túlcsordulás kapcsolja fel a led-et. Az egyik összehasonlító egyezése meg kikapcsolja a led-et. Nem kell mást csinálni, mint a kitöltési tényezőt beírni az összehasonlító regiszterébe, és kész a kívánt kitöltésű PWM jel. Legyen a kívánalom most éppen 10%-os kitöltés. Ekkor az összehasonlítóba (255/100)*10=~26-ot kell írni. Lássuk a programot:

ISR(TIMER2_OVF_vect) // ez a megszakításkor lefutó programrész 
{ 
      digitalWrite(13, HIGH); //beépített LED felkapcsolása
}

ISR(TIMER2_COMPA_vect) // ez az A komparátor egyezésekor keletkezettmegszakításkor lefutó programrész 
{ 
    digitalWrite(13, LOW); //beépített LED lekapcsolása
}

void setup()
{
pinMode(13,OUTPUT);   //beépített led kivezetése kimenet
digitalWrite(13,LOW);  //led nem világít
    cli();     //az időzítő beállításának idejére tiltjuk a megszakításokat
    TCCR2A=0;           
    TCCR2B=B00000101;         //beállítjuk az 128-as előosztót, időzítő ettől kezdve számol + CTC mód
    TCNT1=0;        //időzítő számláló kezdőértéke
    TIMSK2=B00000011;   //engedélyezzük az A komparátor egyezésének megszakítását
                                                //és a túlcsordulás megszakítását is
    OCR2A=26;   //beállítjuk a 10%-os kitöltési tényezőhöz tartozó értéket. 
    sei();         //vége a beállításnak, engedélyezzük a megszakításokat
}

void loop() {
}

Sajna a beépített led pont a power led mellett található. Utóbbi teljes fényerővel világít és teljesen elvakít ha ránézel. Így nem igazán látható a jelentős fényerő különbség a 10% és a 100%-ék között. Próbáld valamivel letakarni a power led-et, és máris látványosabb lesz a különbség.

  • Az időzítő számláló a TIMER0 és TIMER2 esetében 8 bites (255-ig számol és nullázódik), míg a TIMER1 16 bites (65535-ig számol és nullázódik).
  • Lehetőség van külső órajel fogadására, ami a számlálót léptetni fogja. Ebben az esetben nem időzítőről, inkább számlálóról beszélünk. Az ugyanis nem követelmény, hogy a külső bemenetre kapcsol órajel szabályos kitöltésű és frekvenciájú négyszögjel legyen. Lehetnek ezek időnként megjelenő impulzusok, és időzítőnk megszakítást generálhat pl. minden 10. impulzusnál. Vagy akár könnyedén csinálhatunk frekvencia mérőt, ha pl 100 impulzus beérkezési idejét mérjük meg a keletkező megszakítással stb.
  • Van mindhárom időzítőnek kifejezetten PWM üzemmódja. Ekkor az időzítő komparátorához hozzárendelt kimenet kitöltési tényezőjéről gondoskodik a TIMER.
  • A PWM üzemmódnak van „fázishelyes” működési módja. Ekkor az időzítő először felfele számol, és a komparátor egyezésekor felkapcsolja a kimenetet, aztán a maximális számláló érték elérésekor elkezd visszafelé számlálni, és számláló egyezéskor lekapcsolja a kimenetet. Ezzel a PWM jelünk valóban szimmetrikus időben, ha valamilyen módon tudjuk, hol van a jel közepe. Állítólag ez szervómotorok esetén fontos tulajdonság.
  • Van gyors PWM üzemmód. Ez a klasszikus PWM előállítási algoritmus, mert csak felfele számol a számláló, és a maximális érték elérésekor nullázódik vagy éppen bebillen, nem jártam utána, hogy mi történik, de ez a lényegen nem változtat. Belátható, hogy igy a PWM jel nem szimmetrikus, viszont pont dupla a frekvenciája.
  • Említettem már a CTC módot, amikor is a komparátor nem csak megszakítást generálhat, hanem a számlálót is törli.

időzítő/számláló A és B vezérlő regiszter

Minden időzítőhöz található egy-egy vezérlő regiszter. Ezek a TCCRxA és TCCRxB regiszterek. A két regisztert érdemes együtt tárgyalni, mert erősen összefüggenek egymással. A következő biteket láthatjuk a TIMER0 és TIMER2 8 bites időzítők regisztereiben:

Ha valaki jól figyelt az írásom első részében található beállítási példákban, az észre vehette, hogy a TCCRxA regiszterekben általában nem állítottam be semmit. Ennek oka, hogy a példákban csak normál időzítő módot használtunk, és ennek a regiszternek a tartalma a normál módban csupa nulla. PWM módban a komparátorok kimenetét közvetlenül összekötjük valamelyik (gyárilag előre bevesalt, nem változtatható) vezérlő kivezetéssel, és ezekben az üzemmódokban lesz szerepe az itt található biteknek. Az üzemmód beállításnak három bit fele meg, a WGMx2,WGMx1,WGMx0. Sajna „nem fértek el” a bitek egy regiszterben, ezért a WGMx2 bitet a TCCRxB-ben találjuk. A fenti táblázatban ezek a sárga színű bitek. Ide másolom az adatlapból az üzemmód táblázatot a TIMER0-ból:

Némileg különbözik ez a két regiszter a TIMER1 esetében. Ne felejtsük el, hogy ez az időzítő 16 bites, és lényegesen több tulajdonsággal ruházták fel, mint két „rövidebb” testvérét:

Rögtön látszik, hogy nem is kettő, hanem három regisztere van. A TCCR1C regiszter azonban eléggé kevés jelentőséggel bírt számomra, mert a benne található két bit jelentést már a TIMER és TIMER2 időzítők azonos bitjeinél sem sikerült megértenem, és mivel nem volt szükségem rá, nem is küzdöttem vele. Ennél a regiszter triónál már be is írtam a bitek neveibe az x helyére az 1-et, hiszen itt már csak a TIMER1-ről beszélünk. Első eltérés, hogy a WGM1. kezdetű bitekből 4 db is található. Nyilván azért, mert több üzemmódja van az időzítőnek. Megragadom az alkalmat, és be is másolom az üzemmód táblázatát az adatlapból:

Sajnos van egy rossz hírem minden olvasó számára. A PWM móddal egyáltalán nem foglalkoztam még, mivel nem volt rá szükségem. Kielégítő volt számomra az Arduino környezetben rendelkezésre álló analogWrite() függvény, ami beállítja magának a megfelelő üzemmódot valamint a TCCRxA regsizter 7-4. bitjét és a TCCRxB regsizter 7. és 6. bitjét. Aki a PWM móddal akar koncentráltan foglalkozni, az vegye elő az adatlapot és értelmezze, nem szeretnék senkit az esetleges félreértéseimmel megzavarni. Annyit azért el tudok mondani, hogy a TCCRxA regiszterben található COMxA1 és COMxA0 bitek (zöld háttér) az A komparátor kimenetét kötik össze valamelyik vezérlő kivezetéssel. Ez csak akkor történik meg, ha ezen bitek valamelyik értéke 1. Ha mindkettő 0 (ez az eset volt a program példáimban) akkor a komparátorok nem piszkálják a kimenetet, csak megszakítást eredményezhetnek és bebillentik a megfelelő flag-et a TIFRx regiszterben, illetve CTC üzemmódban nullázzák az időzítő számlálóját. Ennyi eddig nekem elég is volt. Ugyanez a szerepe COMxB1 és COMxB0 biteknek (kék háttér). A TIMER0 és TIMER2 TCCRxB regiszter illetve a TIMER1 TCCR1C regiszter  7. és 6. bitje FOCxA és FOCxB-re hallgat. Értéke mindig 0 kell legyen, ha jól értelmeztem az adatlapot. Valamilyen kényszerített összehasonlítás fogalommal magyarázta az adatlap a szerepét, nekem ez nem világos mit jelent. Későbbi kompatibilitás is szóba került, így elengedtem ezt a témát!

A TIMER1 TCCR1B regiszter 7. és 6. bitje ICNC1 és ICES1 néven zajszűrő és bemeneti él kiválasztásért felelősek. Nem esett még szó arról, hogy a TIMER1 alkalmas úgynevezett rögzítési üzemmódra is. Ekkor értelmezésem szerint bemenetre kapcsolt trigger jelek közti időt képes megmérni, így alkalmas frekvencia vagy periódusidő mérésre. A téma addig még nem érdekelt, így ezzel sem foglalkoztam.

Folytassuk most a számunkra elengedhetetlenül fontos, lényegesen egyszerűbb résszel, a szürke hátterű bitekkel. A CSx2,CSx1,CSx0 bitekkel lehet beállítani az előosztót, illetve az órajel forrást. Ez a három bit az egyes időzítőkben nem teljesen azonos, bár nagyon hasonló. Itt sajna minden időzítőhöz kell egy táblázat, hogy ott éppen mit is jelent. Túl sok értelmezési nehézséget nem okozhatnak a táblázatok, de két dolgot azért megjegyeznék.

  • Ha mindhárom bit 0, akkor az időzítő (mindháromra igaz) nem kap órajelet. Így lehet legegyszerűbben leállítani az időzítést, illetve elindítani.
  • A TIMER0 és TIMER1 esetében megadhatunk külső órajelforrást. Ez jól jöhet pl. akkor, ha össze szeretnénk hangolni több ATMEGA vezérlő időzítőit valamilyen okból, vagy extrém hosszú időzítésre van szükségünk, amihez alacsony frekvenciás külső órajelet kellemes lehet használni.  

Lehetséges előosztó beállítások TIMER0 esetén

Lehetséges előosztó beállítások TIMER1 esetén

Lehetséges előosztó beállítások TIMER2 esetén

Mennyire volt hasznos amit olvastál? Értékelés után szövegesen is leírhatod megjegyzéseidet és véleményedet!

Kattints egy csillagra az értékeléshez!

Szövegesen is leírhatod véleményedet! Ha kérdésed van, ne felejtsd el megadni az email címedet!