LOW-LEVEL PROGRAMIRANJE ARDUINA: TIMER INTERRUPTI

UVOD

U trećem nastavku serije low-level programiranja AVR mikrokontrolera upoznati ćemo se s nekim novim registrima. Koristiti ćemo timer interrupte kako bi kreirali vlastitu delay funkciju te u konačnosti zablinkali ledicu na pinu13. Ponovno ćemo koristiti C programiranje, a već u idućem tutorijalu prebacujemo se na Assembler.

Do sada smo odradilo ovo:
Lekcija 1. Blinkanje LEDice
Lekcija 2. Bitwise operatori

ŠTO JE TIMER?

Timer (točnije Timer/Counter) je komad hardwarea ugrađen u mikrokontroler (i drugi kontroleri i procesori ga imaju). Timer programiramo preko posebnih registara. Npr. konfiguriramo prescaler, njegov mode, waveform generator no o svemu detaljnije uskoro.

Kako radimo s Dasduinom, odnosno Atmel AVR ATmega328 mikrokontrolerom i ovdje ćemo se koncentrirati na njega. ATmega328 ima tri timera: timer0, timer1 i timer2. Timer0 i timer2 su 8bitni timer, timer1 je 16bitni. Glavna razilika je rezolucija, 8bitni je 256 vrijednosti a 16bitno 65536 za veću rezoluciju.
Svi timeri ovise o satu sustava, on je na Dasduinu 16Mhz. Svi timeri na Arduino firmwareu (bootloaderu) su konfigurirani na 1kHz frekvenciju i svi interrupti su omogućeni.

Zamislite da imate brojač koji kada pritisnete gumb se povećava za jedan. Timer/counter radi na istom principu: broji otkucaje sata. Spomenili smo već da je brzina brojanja 16 miliuna otkucaja u sekundi, otprilike 63ns po otkucaju ili operaciji. 8bitni timer će tako brojati od 0-255, a 16 bitni od 0-65535. Vrijeme potrebno da se counter resetira je 256/16.000.000 = 16us, odnosno 65536/16.000.000 = 4ms za 16bitni registar. Ne baš praktičan način za zablinkati LEDicu svakih 1 sekundu. Umjesto kontroliranja brzine timer/countera koristiti ćemo nešto što se zove prescaler. U nastavku bavimo se registrima zaduženim za timere, a u jednom od njih definirati ćemo i prescaler.

Prescaler

Prescaler definira brzinu određenog timera (timer0, timer1 ili timer2) prema formuli:

(brzina timera [Hz]) = (brzina Arduino clocka (16MHz) [Hz]) / prescaler

Prema datasheetu prescalaru su definirane vrijednosti od 1, 8, 64, 256 i 1024. Vidljivo je kako će prescaler 1 inkrementirati clock od 16Mhz, prescaler 8 2Mhz, prescaler 64 250kHz i td. Jednostavno rečeno, prescaler postavljen na 1024 će svakih 1024 otkucaja 16Mhz timer/countera na svoj counter (timer0, timer1, timer2) dodati 1. Najjednostavnije rečeno dok Mujo pojede 1024 čevapa, Haso će 1.

Vratimo se timerima. Prvi novi registar koji ćemo spomenuti u ovom tutorialu je OCR – output compare register u koji ćemo spremiti compare match vrijednost. Čini se previše odjednom? Nije, već smo sve ovo spomenuli samo smo im dali službena imena. Ubacimo u formulu gore kako bismo lakše vizualizirali:

formula

Objašnjenje:
16Mhz – brzina timer/countera clocka Arduina, uvrštavamo kao 16.000.000 Hz
brzina.timera – s obzirom kako želimo blinkati LEDicu svakih sekundu, uvrštavamo frekvenciju 1Hz
-1 – counter broj od 0 pa uvrštavamo -1 u formulu

compare match registar

Iz tablice vidimo kako je najmanji prescalar koji možemo koristiti 256 i to za 16bitni timer1. Razlog? Vrijednost 62499 možemo spremiti u 16bitni registar timera1.
Za mjerenje countera u frekvenciji 1Hz(1sekunda) možemo koristiti i prescaler 1024, međutim točnije je koristiti manji prescaler. U nijednom od ovih slučajeva ne možemo koristiti 8bitne timere timer0 i timer2.

Sada kada smo odabrali timer i prescaler vrijeme je da upoznamo registre koje trebamo postaviti.

Registri

Za više pogledajte datasheet

TCCR1A Timer count control register – 0b00000000 (normal)(normal)
TCCR1B Timer count control register – 0b00000100 (no icr)(256-prescaler)
TCCR1C Timer count control register – 0b00000000 (no force)

OCR1AH output compare register – high byte 0xF4
OCR1AL output compare register – low byte 0x23

Na ovim registrima postavljamo izračunatu compare match vrijednost, 62499. S obzirom kako postavljamo posebno highi i low byte pretvoramo 62499 iz dekadskog u heksadekadski a to je 0xF423. Pa je tako high byte = 0xF4, a low byte 0x23.

TIMSK1 timer interrupt mask 0b00000010 (output compare u enable – OCIE1A)

DDRB
PORTB

DDRB PORTB registre smo već spomenuli u prvoj lekciji.

C KOD

void setup() {
  DDRB = B00100000; // portB5 (pin13) postavljamo kao output
  cli();   //onemogucujemo sve interrupte, kako timer intuerrupt ne bi bio prekinut drugim interruptom s većim prioritetom
  TCCR1A = 0b00000000;
  TCCR1B = 0b00000100;
  TCCR1C = 0b00000000;
  OCR1AH = 0xF4;
  OCR1AL = 0x23;
  TIMSK1 = 0b00000010;
  sei();   // ponovno omogucujemo interrupte, ukljucujuci i timer interrupte
}
ISR(TIMER1_COMPA_vect) { // timer compare interrupt service routine
  PORTB ^= 0b00100000;   // XOR operator, ako je upaljena LEDica gasimo je i obratno
}
void loop() {
}

TIMER BIBLIOTEKA

Nekoliko je timer biblioteka koje koriste različite funkcije. Odraditi ćemo samo one koje bismo i koristili u primjeru blinkanja LEDice, više pronađite na Arduino referencama.

Timer0
Koristi se za funkcije kao delay(), millis() and micros().

delay() – zaustavlja mikrokontroler za zadani broj milisekundi, postoji i funkcija delayMicroseconds() i sl.
millis() – funkcija u koju je spremljeno vrijeme od pokretanja mikrokontrolera, ili resetrianja, u milisekundama. Do overflowa može brojati 15ak dana.
micros() – funkcija u koju je spremljeno vrijeme od pokretanja mikrokontrolera, ili resetrianja, u mikrosekundama. Do overflowa može brojati 70ak minuta.

Primjer blinkanja LEDice bez delay() fje:

unsigned long proslo_vrijeme = 0;        // generalno koristimo unsigned long za varijable koje spremaju vrijeme
void setup() {
  pinMode(13, OUTPUT);
}
void loop() {
  unsigned long trenutno_vrijeme = millis();  // spremamo trenutno vrijeme od pokretanja mikrokontrolera
  if (trenutno_vrijeme - proslo_vrijeme >= 1000) {
    proslo_vrijeme = trenutno_vrijeme; // spremi kada je poslijednji put blinknula LEDica
    digitalWrite(13, digitalRead(13) ^ 1); // mijenjaj stanje LEDice
  }
}

ZAKLJUČAK

Osim što nam, funkcije koje smo upravo odradili, omogućuju multitasking daju nam i puno bolji uvid u vrijeme odvijanja operacija. Usporedimo li blinkanje LEDice programirane na ovaj način i one Arduino digitalWrite() i delay() funkcijama primjetiti ćemo kako nakon određenog vremena dolazi do odstupanja u trenutcima blinkanja.

Za što točnije praćenje rada mikrokontrolera i interrupta nužno je supustiti se na niži programski jezik. Nakon ovih kratkih uvodnih lekcija, od sljedeće, krećemo s Assembly programiranjem.