Megszakítások

Bajban vagyok a megszakításokkal, mert szeretek mindig valamilyen értelmes gyakorlati megvalósítást bemutatni. Azonban a megszakításokat ez idáig szinte semmire nem használtam a gyakorlatban. Elméletileg tudom mire jók, példa programot is építettem, ami alább olvasható, de nem találtam semmi olyan szituációt, ahol hasznát vehetném. A távoli jövőben, és egy közeli galaxisban azonban már körvonalazódik egy feladat, amiben szerepe lehet. Kitaláltam, hogy készítek egy adatrögzítő áramkört. Az lenne a feladata, hogy beállítható időnként (mondjuk percenként, vagy tíz percenként, óránként stb.) beolvasson egy adatot és tárolja SD kártyán. A gyakorlati példa a hűtőszekrény hőmérsékletének a megfigyelése lenne. Ehhez egy RTC órát szeretnék használni, amiben leprogramozom a következő mérési időpontot, az óra a SQW kimenetén adni fog egy riasztást, aminek hatására a program elvégzi a mérést. Azért szeretnék RTC órát használni, mert közben az Arduino két mérés közt aludni fog, hogy ne fogyasszon sokat, hiszen egy ilyen áramkörnél az akkumulátoros táplálás a célszerű. Nem vezetékezhetem be a hűtőszekrényt!!

Annak, aki igazán kezdő, és még nem is hallott a megszakításokról, kezdjük egy kis elmélettel. A C-ben fő programunk egy folyamatos ciklusban fut. Ha tehát egy esemény bekövetkezését fel akarjuk deríteni, akkor a fő ciklusba kell beépíteni egy lekérdezést. Előfordul azonban, hogy a főprogramunk olyan funkciót lát el, ami csak hosszú időközönként hajtódik végre. Pl. tegyük fel, hogy óra programot írtunk, aminek a feladata, hogy percenként egyszer frissítse fel a kijelző tartalmát. Természetesen adja magát, hogy a loop() függvényünk végére tegyünk egy delay(60000); utasítást, és a következő futás előtt várakozzunk egy percet. Ekkor azonban egy teljes percig várakozunk, a program nem csinál semmit, vagyis egy ilyen fő ciklusban csak percenként egyszer tudnánk megnézni, hogy történt-e valamilyen esemény. Ha a figyelt eseményre gyorsan kell beavatkozni, akkor a percenként egyszeri figyelés nagyon ritka. Egy ilyen szituációban jön jól a megszakításkezelés használata.

A program is sokat egyszerűsödhet ennek a technikának a használatával. Fut ugyanis a fő ciklus, és ha érkezik a chip megfelelő kivezetésére egy jel, megszakad a fő ciklus végrehajtása, és egy függvény fut le. Ha végrehajtódott a függvény, a főprogramunk fut tovább. Az ATmega vezérlőkben a megszakítások arra is használhatók, hogy az alvó chip-et felébresszék. Erről bővebben az elemes működésnél olvashatsz.

Az ATmega328 vezérlőnek két kivezetése van, ami megszakításokat kezel. Ezek a D2 és D3 kivezetések. A D2 kivezetésre az interrup0, a D3 kivezetésre pedig az interrup1 megszakítás paraméterezhető. Ez annyit jelent, hogy a programban meg kell adni azoknak a függvényeknek a nevét, amiket az adott lábon történő esemény megtörténtekor meg fog hívni a vezérlő. Mindkét kivezetésre külön külön kapcsolhatjuk be a megszakítás kezelését. Erre szolgál az attachInterrup()  nevű függvény aminek három paramétere van:

  • megszakítás sorszáma (0 vagy 1), ez dönti el, hogy melyik bemeneti lábat figyeli a chip
  • meghívásra kerülő függvény neve a programon belül
  • a bemeneti esemény figyelésének módja

Egy digitális bemenet alacsony és magas értékeket tud megkülönböztetni, tehát nem akármit lehet figyelni. Megszakítást okozhat, ha alacsony bemeneti állapotról magas állapotra vált a bemenet, vagy fordítva. Összesen négy figyelési módot tud megkülönböztetni a megszakításkezelés:

  • LOW: a bemenet alacsony állapotban van
  • CHANGE: ha a bemenet értéke változik, magasról alacsonyra vagy alacsonyról magasra
  • RISING: ha a bemenet alacsonyról magasra változik (felfutó él)
  • FALLING: ha a bemenet magasról alacsonyra vált (lefutó él)

Ha egy nyomógomb lenyomását akarjuk érzékelni, akkor nem mindegy, hogy ezek közül melyiket alkalmazzuk. Tegyük fel, hogy a nyomógombot úgy kötjük be, hogy egy felhúzó ellenállás a bemenetet folyamatosan magas szinten tartja, és a gombnyomás a bemenetet a földre húzza le. Ha azt a pillanatot akarjuk „elkapni” amikor megnyomjuk a nyomógombot, akkor használhatjuk a FALLING paraméter szerinti működést. Ha azonban a nyomógomb elengedésének a pillanatát szeretnénk felhasználni valamilyen folyamat vezérlésére, akkor a RISING-et kell használnunk, mert akkor fog a megszakításra megírt függvényünk elindulni, amikor elengedjük a nyomógombot, és a bemenet alacsony szintje magas szintre vált. Ha a nyomógomb megnyomásakor és elengedésekor is végre akarjuk hajtani a függvényt, akkor használjuk a CHANGE paramétert. Biztosan feltűnt, hogy nem említettem a LOW paramétert. Bevallom, nem értem ennek működését, mert a bemenet alacsony értéke egy statikus állapot, nincs benne semmi változás. Arra gyanakszom, hogy itt is azt a pillanatot lehet az interrupt indítására használni, amikor magasról alacsony jelszintre vált a bemeneti jel. Ez azonban a FALLING móddal egyezik meg. Egyszer majd biztosan kiderül a különbség, jelenleg nem érdekel.
A nyomógombos példám sántít, mert a valóságos nyomógombok prellesek. Erről a bemenetek kezelésénél olvashatsz. Egy valós nyomógomb esetében egynél többször változik a jelszint megnyomáskor és elengedéskor is, ezért ez így nem fog jól működni. Ha azonban egy érzékelőt kötsz a bemenetre, ami ténylegesen egy jelszint változást produkál valamilyen eseményre, akkor már használhatók ezek a megszakítás paraméterek, és mindegyik hatására mást fog csinálni a programod. Pl. ha egy mozgásérzékelőt használsz, ténylegesen megszámolhatod a helyiséget elhagyó személyek számát a FALLING használatával. Ne felejtsük el, hogy egy mozgásérzékelő akkor ad magas jelet a kimenetén, ha mozgást érzékel, és akkor esik le nullára a kimenete, ha egy beállított ideig nem történik körülötte semmi.

Lássuk a példa programot:

void setup() {
  Serial.begin(9600);
  Serial.println("elindul");
  pinMode(2, INPUT); //megszakítás 0 bemenet előkészítése
  pinMode(3, INPUT); //megszakítás 1 bemenet előkészítése

  pinMode(13, OUTPUT); //a 13-as kivezetésen van a LED
  digitalWrite(13, LOW); //led nem világít

  attachInterrupt(0, int_0, LOW); //a kettes bemeneten 0 jelenik meg akkor meghívódik a int_0 függvény
  attachInterrupt(1, int_1, LOW); //a hármas bemeneten 0 jelenik meg akkor meghívódik a int_1 függvény

}

void loop() {
  Serial.println("Dolgozik...");
  delay(500);
}

void int_0() {
  // a 2-es bemeneten érkezett egy 0 jelszint, bakapcsoljuk a led-et
  digitalWrite(13, HIGH);
}

void int_1() {
  // a 3-es bemeneten érkezett egy 0 jelszint, kikapcsoljuk a led-et
  digitalWrite(13, LOW);
}

A program nagyon egyszerű dolgot csinál. Bekapcsolja a soros portot, és másodpercenként egyszer kiírja a soros portra, hogy „Dolgozik…”! Futtatáskor természetesen el kell indítani a soros monitort, ha ezt látni akarjuk. Ez jelképezi azt az utasítás sorozatot, amit másodpercenként egyszer elindítok, és  csinál valamit. Mivel delay() függvényt használok a programban, az időzítés ideje alatt (egy másodpercig) az Arduino semmivel nem foglalkozik. Ha ide tennék be egy bemenet lekérdezést, akkor rossz esetben egy másodpercig nyomni kellene a nyomógombot, hogy a program észrevegye. Azonban a program attachInterrup() sorában bekapcsoltuk a megszakítás kezelést a 0-s és az 1-es megszakításra is. A 2-es bemenetre kötött nyomógomb megnyomása megszakítja a főprogram futását, HIGH-ra állítja a 13-as kimenetet, amin épp egy beépített led található. Ekkor a led világítani kezd. Ha az 1-es megszakítás által figyelt 3-as bemeneten nyomjuk meg a nyomógombot, akkor pedig kikapcsoljuk a led-et. Tehát a led azonnal felgyullad vagy elalszik, ha nyomogatjuk a gombokat, nem kell egy másodpercet várni megnyomott gombbal. Ha lakásvilágítást vezérelsz, ez nem hátrány.

Van egy picike csalás a programban. A nyomógomb ugyanis prelles, azaz egy megnyomásra többször is meghívja a neki megfelelő megszakítás függvényt. Vagyis a led-et egy megnyomással többször is be illetve kikapcsoljuk. Ez persze nem látszik, hisz már az első kontaktus is bekapcsolja a led-et, a többi már nem változtat semmit a led állapotán. Ha a megszakítás függvénybe is beteszünk egy soros portra történő írást, akkor viszont látni fogjuk, hogy a gomb megnyomásakor a soros portra küldött szöveg többször is megjelenik. Az én nyomógombommal két háromszor, de néha még négyszer is. Ez jelen esetben nem baj. Viszont következmény, hogy ebben a szituációban és programban tök mindegy, hogy melyik megszakítási módot használod. Kipróbáltam a LOW, CHANGE, RISING, FALLING mindegyikét. A leg rögtön reagált, vagy felgyulladt vagy elaludt attól függően, melyik gombot nyomtam meg.

Van néhány tulajdonsága a megszakításnak. A delay() nem használható egy megszakítás függvény végrehajtásakor. A millis() függvényben található érték sem növekszik tovább, amig a megszakítással meghívott függvény hajtódik végre. Ez teljesen logikus. A millis és a delay is megszakításokkal működik, és a megszakítás megszakítása olyan konfliktusokat okoz, amit ebben az egyszerű vezérlőben nem kezeltek. Ha pl. a főprogramban épp időzít egy dalay, és a megszakítás függvényben is szeretnél időzíteni, akkor két delay-ra lenne szükséged, melyek egymástól függetlenül tudják, hogy hol jártak az időzítésben. Igen ám, de ha az egyik delay időzítése megszakad egy megszakítás során, honnan fogja tudni, hogy mennyi időt töltött a vezérlő a másik függvényben, és ezért mennyivel kevesebbet kellene időzítenie, hogy az eredeti időt kapjuk az időzítés során.

A fenti konliktus sok-sok évvel ez előtt megtapasztaltam. Építettem, egy nagyon „profi” órát, amivel 4 kimenetet lehetett különböző időpontokban ki és bekapcsolni. TMS1122 volt az óra IC neve, ha jól emlékszem. Azt tapasztaltam, ha sokat szórakozok az órával, és programozgatom, nyomkodom a gombokat, akkor az óra késni kezd. Akkor nem értettem, hogy ennek mi az oka. Ma már tudom. A nyomógombokat belül megszakításokkal kezelte és az óra programban a millis()-nek megfelelő funkciók megszakadtak, és egy pici idő mindig elveszett! Jó ezt tudni, amikor a megszakítást olyan programban használjuk, amiben számít a végrehajtási idő!

Még egy elméleti tudnivaló van hátra, a megszakítás megszakításának megakadályozása. Ez akkor fordulhat elő, ha a megszakításkor meghívott függvény olyan hosszú ideig fut, hogy akár újabb megszakítás is érkezhet a bemenetre. Sajnos nem tudtam kipróbálni értelmes példával, de ennek a szitunak a kezelésére van három függvény:

  • detachInterrupt () – a megadott számú megszakításkezelést kikapcsolja
  • interrupts() – bekapcsolja a megszakítás kezelést, ha ki lettek kapcsolva
  • noInterrupts () – kikapcsolja a megszakításkezelést

Az utóbbi két függvény értelmezésem szerint mindenféle megszakítás kezelését kikapcsolja, tehát a millis() sem működik tovább. Ez akkor lehet hasznos, ha annyira időkritikus programkódot futtatunk, hogy már a belső megszakítások (a külső meg pláne) megzavarnák a működését.  

Mennyire volt hasznos amit olvastál?

Kattints egy csillagra az értékeléshez!

Sajnálom, hogy amit olvastál nem volt hasznos számodra!

Szeretném ha elégedett lennél!

Írd le kérlek, hogy mi nem tetszett!?