Prekinitve (Interrupt), Krmiljenje zapornice na železniškem prehodu

V tem poglavju bomo spoznali notranje prekinitve, ki jih lahko prožimo s časovniki - Timerji.

Prekinitve so pomembna lastnost mikrokontrolerja (Arduino Uno vsebuje Atmelov mikrokontroler Atmega 328P). Ob prekinitvi mikrokontroler preneha izvajati glavni program (viod loop()) in skoči v prekinitveno rutino, imenovano Interrupt (ISR => Interrupt service routine). Ko se ta zaključi, mikrokontroler nadaljuje izvajanje glavnega programa tam, kjer ga je pred prekinitvijo končal.

Slika 1: Prikaz delovanja prekinitve

Arduino Uno (Atmega 328P) vsebuje tri timerje: Timer0, Timer1 in Timer2. Arduino mega vsebuje še Timer3, Timer4 in Timer5, ki so vsi 16-bitni. V Arduino Uno je Timer0 namenjen zakasnitvam delay( ), millis( ), micros( ), Timer1 uporablja knjižnico za servo motorčke Servo( ), Timer2 pa za generiranje tonov Tone( ). Zato bomo tukaj za prekinitve uporabili s Timer2. V prekinitveni rutini ne moremo uporabiti zakasnitve delay( ), zato se poslužujemo drugačnih trikov, en bo prikazan v primeru krmiljenje zapornice na železniškem prehodu.

Arduino Uno deluje z oscilatorjem 16 MHz, ki neprestano generira signale (strojna ura). Takt s katerim deluje lahko izračunamo:

T = 1/f = 1/16 MHz = 0,0625 µs

Timer2:

Timer2 je 8-bitni števec. Njegovo delovanje nadzirajo registri TCCR2A, TCCR2B, TCNT2, OCR2A, OCR2B, TIMSK2, TIFR2 ASSR in GTCCR. Naj tukaj omenim, da ne bomo takoj spoznali vlogo vseh registrov in njihovih bitov, ampak jih bomo po potrebi spoznavali sproti. Ker je Timer2 8-bitni pomeni, da je lahko njegova največja vrednost 255. Z vsakim taktom strojne ure se vrednost timerja poveča za 1. Ko doseže maksimalno vrednost (255), se postavi na 0, vklopi ustrezno zastavico (Overflow Flag) da vemo, da je prišlo do prekoračitve in začne ponovno povečevati svojo vrednost od 0. Pravimo, da je prišlo do prekoračitve timerjeve vrednosti (overflow). Ob tem timer sproži prekinitev, če smo jo dovolili. V prekinitveni rutini, ki se izvede 1 krat, pobrišemo zastavico, da se bo pri naslednji prekinitvi lahko zopet vklopila.

Slika 2: Povečevanje timerjeve vrednosti

Če nastavimo Timer2 tako, kot je opisano zgoraj in omogočimo prekinitve, se bo le ta sprožila vsakih 15,9375 µs.

tprekinitve = 256 * 0.0625 µs = 16 µs        (fprekinitev = 1/tprekinitve = 62,5 kHz)

Mokrokontroler vsebuje tudi preddelilnik, ki deli prožilne impulze, namenjene timerju Timer2. S preddelilnikom dosežemo, da se Timer2 povečuje po osmih, dvaintridesetih, ... ali po vsakih 1024-tih strojnih ciklih. Preddelilnik je torej koristen, kadar želimo s timerjem Timer2 meriti daljše časovne intervale. Preddelilnik nastavljamo s prvimi tremi biti (CS20, CS21 in CS22) v registru TCCR2B.

Slika 3: Register TCCR2B

Slika 4: Preddelitev je odvisna od nastavitve bitov CS20, CS21 in CS22.

TCNT2 je 8-bitni register, ki se povečuje za 1 ob vsakem strojnem ciklu. Njegovo vrednost lahko nastavimo na želeno vrednost. Če mu določimo vrednost TCNT2 = 130, potem Timer2 ne bo več povečeval vrednosti od 0 do 255 temveč od 130 do 255. Ko bo prekoračil svojo vrednost, bo skočil na 130 in jo od te vrednosti zopet povečeval do 255. V tem primeru bo Timer2 sprožil prekinitev po vsakih 125 strojnih ciklih (255 - 130 = 125).

Slika 5: Povečevanje timerjeve vrednosti po nastavitvi TCNT2

Izračunajmo sedaj ta čas:

tprekinitve = (255 - 130) * 0,0625 µs = 7,8125 µs

Uporabimo še preddelilnik, tako da bita CS20 in CS22 postavimo na 1. S tem dosežemo preddelitev 1:128. To pomeni, da se bo vrednost timerja Timer2 povečala za 1 po vsakih 128-ih strojnih ciklih. S tem smo povečali čas med prekinitvami na 1 ms:

tprekinitve = (255 - 130) * 128 * 0,0625 µs = 1 ms        (fprekinitev = 1/tprekinitve = 1 kHz)

Timer2 bo po opisanih nastavitvah sprožil prekinitev vsako milisekundo.

Opisane nastavitve timerja Timer2 določimo na sledeči način:

    TCCR2B = 0x00;        //Inicializacija, vse bite registra TCCR2B postavimo na 0. Operator 0x pomeni, da je število za njim (00) šestnajstiško.

                                    // (00(16) = 00000000(2)).

    TCNT2 = 130;

    TIFR2 = 0x00;           //Timer2 Interrupt Flag Register => pobrišemo zastavico prekinitve, ki se je postavila na 1, ko se je sprožila prekinitev.

    TIMSK2 = 0x01;        //Omogočimo overflow prekinitve na Timer2. Prvi bit registra TIMSK2, bit 0 postavimo na 1.

    TCCR2A = 0x00;       //Inicializacija, vse bite registra TCCR2A postavimo na 0.

    TCCR2B = 0x05;       //Nastavimo preddelitev 1:128 (05(16) = 00000101(2)) => CS20 in CS22 bita v registru TCCR2B postavimo na 1.

Takšnemu načinu delovanja timerja Timer2 (in tudi ostali timerji) pravimo normalni način oziroma Normal timer mode. Praktično tega načina ne uporabljamo pogosto, ker je neučinkovit, še posebej, če hočemo generirati neke izhodne signale. Če hočemo resetirati timer ponovno na vrednost 0, moramo dodatno pisati del programske kode. Zato je bolje da uporabimo CTC (Clear Timer on Compare) mode, kar bomo spoznali v poglavju Timerji v Arduino Uno (Atmega 328P), ko bomo tudi bolje spoznali njihovo delovanje.

Krmiljenje zapornice na železniškem prehodu:

Krmilje naj deluje po naslednjih zahtevah:

S tipko T1 simuliramo približevanje vlaka železniškemu prehodu, s tipko T2 pa oddaljevanje vlaka od železniškega prehoda. S piskačem simuliramo zvonenje pred spuščanjem zapornice kot zvočno opozorilo udeležencem prometa, da se prehodu približuje vlak. Dve LED diodi prikazujeta utripanje rdečih signalnih luči pred prehodom. Zapornico spušča in dviguje servo motorček (simuliramo enosmerno cesto, ki prečka železniško progo).

Slika 6: Časovni diagram krmiljenja zapornice na železniškem prehodu

Ko senzor (tipka T1) zazna prihod vlaka, piskač 5 krat zapiska v presledkih po 300 ms. Vsak pisk traja 100 ms. Istočasno pričnejo izmenično utripati LED dioda 1 in LED dioda 2. Čas vklopa in čas izklopa posamezne LED diode je enak in traja 500 ms. Ko piskač 5 krat zapiska, se prične zapornica spuščati (servo motorček se iz pozicije 90° zavrti v pozicijo 0°). Za vsako stopijno vrtenja servo motorčka preteče čas 20 ms.Zapornica se iz zgornjega položaja v spodnji položaj premakne v času 1,8 sekunde (90° * 20 ms = 1,8 s).

Zapornica ostane spuščena tako dolgo, dokler senzor (tipka T2) ne zazna odhoda vlaka. V tem času morata LED diodi še kar utripati.

Ko senzor (tipka T2) zazna odhod vlaka, se prične zapornica dvigovati iz spodnjega položaja (servo 0°) v zgornji položaj (servo 90°). Zapornica se dviguje 1,8 sekunde. Ko je zapornica dvignjena, LED diodi prenehata z utripanjem, se izklopita.

Ko se sproži postopek spuščanja zapornice, ponovni pritisk na katerokoli tipko (tipko T1 ali tipko T2) ne sme vplivati na delovanje krmilja. Enako velja za spuščanje zapornice. Ko se zapornica spušča, ponovni pritisk na katerokoli tipko ne sme vplivati na delovanje krmilja.

Program in opis programa:

/*

  Krmiljenje zapornice na železniškem prehodu.

  Avtor: Milan Ivič

  Marec 2014

 */

 #include <Servo.h>               //Vključitev knjižnice za servo motorček.

 int Senzor_Prihod = 3;                 //Senzor prihod (tipka T1) je priključen na pin 3.

 int Stevec_Senzor_Prihod;              //Spremenljivka za shranjevanje števila pritiskov na tipko T1.

 int Stanje_Senzor_Prihod = LOW;    //Inicializacija.

 int Senzor_Odhod = 4;                  //Senzor odhod (tipka T2) je priključen na pin 4.

 int Stevec_Senzor_Odhod;              //Spremenljivka za shranjevanje števila pritiskov na tipko T2.  

 int Stanje_Senzor_Odhod = LOW;   //Inicializacija.

 //Določitev pinov za obe LED diodi, piskač ter Deklariranje za servo motor z imenom Servo1:

 int LED1 = 8;

 int LED2 = 10;

 int Piskac = 7;

 Servo Servo1;

 unsigned int Preklop = 0;              //Uporabimo za preklapljanje LED diod.

 unsigned int Stevec_prekinitev = 0;   //Štejemo število prekinitev, ki jih proži Timer2.

 void setup()

{

  pinMode(LED1, OUTPUT);               //LED1 za utripanje pred zapornico, => izhodni pin.

  pinMode(LED2, OUTPUT);               //LED2 za utripanje pred zapornico, => izhodni pin.

  pinMode(Senzor_Prihod, INPUT);     //Oba senzorja (tipki) => vhodna pina.

  pinMode(Senzor_Odhod, INPUT);

 

  //Inicializacija. Uporabimo zato, da ponovi pritiski na katerokoli tipko ne vplivajo na delovanje krmilja:

  Stevec_Senzor_Prihod = 0;

  Stevec_Senzor_Odhod = 0;

  pinMode(Piskac, OUTPUT);        //Piskač za zvočno opozarjanje => izhodni pin.

  Servo1.attach(9);                     //Servo motorček priključimo na pin 9.

  Servo1.write(90);                     //Za začetno pozicijo servo motorčka => zapornica dvignjena (90°).

 

  noInterrupts();       //V času nastavitve in izvajanja prekinitvene rutine ne sme priti do prekinitve (interrupt).

  //Nastavitve Timer2 za prekinitev vsako 1 ms:

  TCCR2B = 0x00;      //Inicializacija Timer2 registra TCCR2B.

  TCNT2  = 130;         //Preset Timer2, ko prekorači svojo vrednost na vrednost 130.

  TIFR2  = 0x00;        //Timer2 INT Flag Reg: Pobrišemo zastavico prekinitve (Overflow Flag).

  TIMSK2 = 0x01;       //Timer2 INT Reg: Omogočamo prekinitve na Timer2.

  TCCR2A = 0x00;      //Inicializacija Timer2 registra TCCR2A.

  TCCR2B = 0x05;      //Timer2 Control Reg B: Preddelitev 1:128 

}

 void loop()

  //Program neprestano bere stanje tipke T1 in T2 ter vrednosti dodeli ustreznima spremenljivkama:

  Stanje_Senzor_Prihod = digitalRead(Senzor_Prihod);

  Stanje_Senzor_Odhod = digitalRead(Senzor_Odhod);

  //Ali je senzor (tipka T1) zaznal prihod vlaka?

  if(Stanje_Senzor_Prihod == HIGH && Stanje_Senzor_Odhod == LOW && Stevec_Senzor_Prihod < 1)

  {

    //Senzor je zaznal prihod vlaka, izvedi nasljedni blok ukazov. (od { do } stavka if).

    Stevec_Senzor_Prihod = Stevec_Senzor_Prihod + 1;   

 

  /*

   Omogočimo izvajanje prekinitev (interrupts) na Timer2 (overflow prekinitve). Vsako 1 ms bo program skočil v

   prekinitveno rutino, jo izvedel in se vrnil nazaj v glavni program.

 */

    interrupts();

    for(int k = 0; k < 5; k++)        //Piskač bo 5 krat zapiskal (for zanka se bo izvedla 5 krat).

    {

      digitalWrite(Piskac, HIGH);

      delay(100);

      digitalWrite(Piskac, LOW);

      delay(300);     

    }

       

     for(int i = 90; i > 0; i--)       //Zanka for se izvede 90 krat od 90 do 0 po koraku 1.

     {     

      Servo1.write(i);                 //Ob vsakem obhodu zanke for se servo motorček zavrti za 1°.

      delay(20);                       //Za počasnejše spuščanje zapornice => 20 ms za 1°.        

     }    

     Stevec_Senzor_Odhod = 0;    //Inicializacija, da pri prihodu naslednjega vlaka deluje vse po zahtevah.

  }   

 

  //Ali je senzor (tipka T2) zaznal odhod vlaka?

  if(Stanje_Senzor_Prihod == LOW && Stanje_Senzor_Odhod == HIGH && Stevec_Senzor_Odhod < 1)

  {

    interrupts();

    Stevec_Senzor_Odhod = Stevec_Senzor_Odhod + 1;

     for(int j = 0; j < 90; j++)        //Zanka for se izvede 90 krat od 0 do 90 po koraku 1.

     {     

      Servo1.write(j);                  //Ob vsakem obhodu zanke for se servo motorček zavrti za 1°.

      delay(20);                        //Za počasnejše dvigovanje zapornice.

     }

     Stevec_Senzor_Prihod = 0;

     noInterrupts();                    //Onemogočimo prekinitve, v nasprotnem primeru bi LED diodi stalno utripali.

     digitalWrite(LED1, LOW);    //Ko onemogočimo prekinitve, vedno ena LED ostane vklopljena. Katera, je

     digitalWrite(LED2, LOW);    //odvisno od trenutka, ko smo onemogočili prekinitve. Zato obe LED izklopimo.

  } 

}

//Timer2 proži owerflow prekinitve OVF (Overflow Interrupt Vector) vsako 1 ms.

ISR(TIMER2_OVF_vect)              //Prekinitvena rutina.

{  

  Stevec_prekinitev ++;               //Povečevanje števca prekinitev.

  if(Stevec_prekinitev > 499)        //Vsako 500-to prekinitev (vsakih 500 ms) izvedi blok (preklopi LED diodi).

  {

    Preklop =! Preklop;                //Preklopi LED diodi.

    Stevec_prekinitev = 0;            //Inicializacija števca prekinitev.

  }

  digitalWrite(8, Preklop);

  digitalWrite(10, ! Preklop); 

  TCNT2 = 130;                         //Preset Timer2, ko doseže overflow na vrednost 130.

  TIFR2 = 0x00;                         //Timer2 INT Flag Reg: Pobrišemo zastavico prekinitve (Overflow Flag).

}