A "Feladatok" megoldási problémája 7 szegmenses kijelő vezérlésénél
A "Feladatok" menüpont 10. példájánál jött elő egy vezérlési probléma. A feladatban 7 szegmenses kijelzőn kellett egy tetszóleges 4 jegyű számot megjeleníteni, ami a korábban tanultak alapján nem okoz gondot. Viszont szerepelt a kiírásban, hogy az induló számot lehessen léptetni előre egy gomb megnyomásával. A megoldás ugyan egyszerű, mégis adódik egy probléma, ha megnyomjuk a gombot, az nem egyesével számol, hanem "pörög". Ennek az a magyarázata, hogy a kijelzőnek viszonylag szapora frissítés kell, így csak néhény milisecundum időre lehetnye csak lenyomva tartani a léptető gombot, különben minden gomblekérdezésnél a megnyomott állapot léptet egyet. Az első gondolatunk, hogy késleltetjük a gombkezelő rutin futását. Ez ugyan lehet megoldás, de ezzel lassítjuk az LCD-re kiírás szaporaságát is, ami -kis késleltetés esetén is- villódzást okoz, nagyobb késleltetésnél szakaszos kiírást. Felmerül a kérdés, hogy lehetne a gomb kiolvasását késleltetni anélkül, hogy ne váljon szakaszossá a 7 szegmenses kijelző frissítése? A válasz, a gombkezelést egyy megszakítási ágra helyezzük!
Mi az a megszakítás (interrupt) és mire való?
Az eddig tanult programok egy haladási szálon működtek, nevezhetjük lineárisnak a folyamatot. A megszakítás azt jelenti, van a programunkban olyan rutin, mely csak bizonyos feltétel, például egy kapcsoló megnyomására indul el. Ilyen feltételes elágazásos rész megtalálható egy normál programban is, mi a különbség? Igen, egy feltétel vizsgálat lehet egy lineáris programban is, de a processzor minden ciklusban azt vizsgálja, és ha van változás lekezeli. Megszakítás esetén viszont nem vizsgálja a processzor az eszközt, az eszköz jelzi a processzornak, ha változás van ( megszakítás kérés! ) , amire a processzor felfüggeszti az addig végrehajtott folyamatot, elmenti a folyamatvégrehajtás minden jellemzőjét, és azonnal kiszolgálja a megszakítást kérő rutint, majd annak feldolgozása után, visszatér a fő folyamathoz, és ott folytatja ahol abbahagyta. Mire jó ez? Nézzünk egy rövid példát. Példánk a korábbi LED kapcsolgatós példa, kiegszítve egy hosszú időzítésű LED villogtatással. Kipróbálva a programot, reménytelenné válik a 31-s LED kapcsolgatása, mert oly csekéllyé vált a működési időhöz képest, hogy sikerüljön eltalálni a kapcsoló beolvasásának időpontját.
Ez az eredeti példa, kiegészítve egy 10 sec-s folyamattal
// a példában a késleltetés miatt egy hosszabb ideig tartó folyamatot vizsgálunk
// A nyomógomb megnyomására a LED ellentétes állapotot vesz fel.
int swichstat = LOW; // kapcsoló állapot, kezdeti érték 0
int lastswichstat = LOW; // utolsó kapcsoló állapot
void setup() {
pinMode(31, OUTPUT); // 31-s LED beállítása
pinMode(32, OUTPUT);// 32-s LED beállítása
pinMode(45, INPUT_PULLUP); // 45-s porton lévő kapcsoló olvasásra állítása belső felhúzó ellenállással
if (swichstat == HIGH && lastswichstat == HIGH) /*Ha a kapcsoló aktuális értéke 1 és az előző állapot is 1 volt, akkor váltsa át a LED állapotát*/
{
digitalWrite (31, !digitalRead(31));// LED állapot beolvasása és negálása
}
Ez a módosított, megszakítással vezérelt előző példa
/* A főprogram most a 32-s porton lévő LED 5 sec-s ki/be kapcsolgatása. A 31-s porton található LED csak a kapcsoló működtetésének láthatóvá tétele miatt került beépítésre*/
volatile int swichstat = LOW; // kapcsoló állapot, kezdeti érték 0
volatile int lastswichstat = LOW; // utolsó kapcsoló állapot
void setup() {
pinMode(31, OUTPUT); // nyomógomb megnyomását érzékelő LED
pinMode(32, OUTPUT); // villogó LED
pinMode(18, INPUT_PULLUP); // 18-s porton lévő kapcsoló olvasásra állítása belső felhúzó ellenállással
attachInterrupt(5, interr, CHANGE); /*megszakítás kérés, megszakítási szint 5 (ez a 18-s port), "interr" megszakításkor meghívott rutin neve, CHANGE megszakítási üzemmód, váltásra aktiválódjon*/
if (swichstat == HIGH && lastswichstat == HIGH) /*Ha a kapcsoló aktuális értéke 1 és az előző állapot is 1 volt, akkor váltsa át a LED állapotát*/
{
digitalWrite (31,!digitalRead(31));// LED állapot beolvasása és a negált érték kiírása
}
lastswichstat = swichstat;//utolsó állapot beállítása az aktuális kapcsoló állapotra
delay(10); //10 ms időhagyás
}
Példa magyarázata A programok futási próbája után érzékelhető a különbség. Míg az eredeti változatban nem igazán sikerül bekapcsolni a LED-t, addig a megszakításos program esetén, a 31-s LED bekapcsolása még annak időzített vezérlése alatt -delay állapotban- is sikerül! A megszakítási funkció viszont a fejlesztő panelon korlátozott. Látható a mintaprogramban is, hogy ki kellett zárni a 45-s porton lévő kapcsolót, mivel azon porton nem lehetséges megszakítás kezelés. Az Arduino Mega viszonylag jól fel van szerelve a megszakításokhoz használható portokkal, 6 db külső megszakítást képes kezelni a 2-3 és 18-21 portokon. Ezeken a portokon viszont a fejlesztő panelon nincs kapcsoló! Ezért a megszakítás kezeléshez a "dugdosós" panelt, és külön kapcsolót kell igénybe venni. Ott elérhetők a 18 és 19-s portok. Előbbi megszakítási szintje 5, utóbbinak 4.. Példánkban a 18-s portot használtuk.
A megszakítás programozása és az Arduino Mega megszakítási tulajdonságai
digitalPinToInterrupt(pin): azon digitális port láb megszakítási szintje, melyhez kapcsolódik a megszakítást kérő eszköz. A Megánál 6 ilyen digitális port található (D2=INT0, D3=INT1, D21=INT2, D20=INT3, D19=INT4, D18=INT5).Ide tehát nem a port száma, hanem a megszakítási kód kerül "INT" nélkül! Pl. 18 port használata esetén 5!
ISR: Ide annak a rutinnak a neve kerül, mely kezeli a megszakítást! Pl. a példában az "interr" név.
mode: a megszakítás finomhangolása, hogy milyen jel váltson ki megszakítást - LOW alacsony jelszintre induljon - CHANGE változásra induljon (ezt használtuk a példaprogramban) - RISING felfutó élre induljon - FALLING lefutó élre induljon
További tudnivalók a megszakítás kezeléshez
Az ISR rutin nem fogad paramétereket, és nem is küld vissza ilyet!
Egy ISR rutinban, ha kezel valamilyen változót, azt a főprogram elején kell deklarálni, és a "volatile" szócskával kiegészíteni a deklarált változót!
Több ISR rutin használata esetén, egyszerre mindig csak egy rutin futhat, és nem lehet megszakítás rutinból újabb megszakítást hívni!
Megszakításon belül általában nem működnek az időzítő utasítások, így a delay () sem! Kivétel a delayMicroseconds()!
A megszakítást kérő utasítás a void setup () területre kerüljön!
Végül egy jó tanács! A megszakítást kezelő rutin a lehető legrövidebb legyen! Ne feledjük, a megszakítás felfüggeszti a főprogram működését, így az időzítéseket is! A megszakítás-kezelés annyival növeli a főprogramban futó időzítéseket, amennyi időre felfüggeszti a főprogram futását!
Az eredeti felvetés a 7 szegmenses kijelzés le/fel léptetése megszakítással
A működtetést kezdjük a "dugdosós" panelre felszerelt 2 nyomógombbal, a kapcsolt ág föld pontra kötésével , és a nyomógombok 18 és 19-s portra kötésével.
/* A decimális számok szegmensmintái byte formátumú tömbbe kerültek.
Helyiértékenként deklarálásra került a bejövő adatok tárolását végző tömb (segma[]..segmd[]
A szegment [] átmeneti kiíró tömbbe kerül a kiirandó adat