Megszakítások

Tartalom:

  • Mire jó a megszakítás? Gyakorlati példa.
  • Megszakításra alkalmas kivezetések az ATmega328 chip-en, jelszint változásokkal kapcsolatos beállítások
  • megszakítással kapcsolatos programozási ismeretek
  • Példaprogram

——————————————————————

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? É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!