6. Megszakítás kezelés - Arduino és programozása

Keresés a weboldalon

Programozási alapismeretek
Tartalomhoz ugrás

Főmenü:

6. Megszakítás kezelés

Haladás
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
}
void loop() {
digitalWrite (32,HIGH);
delay (5000);
digitalWrite (32,LOW);
delay (5000);
swichstat = !digitalRead (45); // kapcsoló állapot negált értékének változóba való beolvasása
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*/
}
void loop() {
digitalWrite (32,HIGH); delay (5000); digitalWrite (32,LOW); delay (5000); //32-s LED ki/be kapcsolása 5 sec késleltetéssel
}
void interr () {
swichstat = !digitalRead (18); // kapcsoló állapot negált értékének változóba való beolvasása
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
Az utasítás szintaktikája
attachInterrupt (digitalPinToInterrupt(pin), ISR, mode)
  • 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
 Kiírás időszelenként, kiíró rutin használatával, helyiértékek egyenkénti be/kikapcsolásával*/
byte zero [] = {1, 1, 1, 1, 1, 1, 0}; //0 szegmensei
byte one [] = {0, 1, 1, 0, 0, 0, 0};  //1 szegmensei
byte two [] = {1, 1, 0, 1, 1, 0, 1};  //2 szegmensei
byte three [] = {1, 1, 1, 1, 0, 0, 1};//3 szegmensei
byte four [] = {0, 1, 1, 0, 0, 1, 1}; //4 szegmensei
byte five [] = {1, 0, 1, 1, 0, 1, 1}; //5 szegmensei
byte six [] = {1, 0, 1, 1, 1, 1, 1};  //6 szegmensei
byte seven [] = {1, 1, 1, 0, 0, 0, 0};//7 szegmensei
byte eight [] = {1, 1, 1, 1, 1, 1, 1};//7 szegmensei
byte nine [] = {1, 1, 1, 1, 0, 1, 1}; //9 szegmensei
byte szegment [6] = {};//átmeneti szegmensadat tároló
byte segma [7] = {};//1-s helyiérték tároló
byte segmb [7] = {};//10-s helyiérték tároló
byte segmc [7] = {};//100-s helyiérték tároló
byte segmd [7] = {};//1000-s helyiérték tároló
byte a = 0; //aktuális "a" szegmens tárolója
byte b = 0; //aktuális "b" szegmens tárolója
byte c = 0; //aktuális "c" szegmens tárolója
byte d = 0; //aktuális "d" szegmens tárolója
byte e = 0; //aktuális "e" szegmens tárolója
byte f = 0; //aktuális "f" szegmens tárolója
byte g = 0; //aktuális "g" szegmens tárolója
byte dp = 0; //aktuális "dp" szegmens tárolója
int t= 5; // 1 helyiérték felvillantási ideje
int egy = 0;
int tiz = 0;
int szaz = 0;
int ezer = 0;
volatile int num =1000; //ide kerül a kiírandó szám induló értéke
volatile int swichstat = LOW; // kapcsoló állapot inkrementálásnál, kezdeti érték 0
volatile int lastswichstat = LOW; // utolsó kapcsoló állapot inkrementálásnál
volatile int swichstatl = LOW; // kapcsoló állapot, dekrementálásnál kezdeti érték 0
volatile int lastswichstatl = LOW; // utolsó kapcsoló állapot dekrementálásnál

void setup() {
//30-37 szegmens LED-k kimenetként való beállítása

for ( byte i = 30; i < 38; i++){
pinMode (i, OUTPUT);
 }
//9-12 helyiérték bekapcsolók kimenetként való beállítása  
for ( byte i = 9; i < 13; i++){
pinMode (i, OUTPUT);
 }
 num=1000;// induló érték
 pinMode (18, INPUT_PULLUP);
 pinMode (19, INPUT_PULLUP);
 
 attachInterrupt(5, beolvas, FALLING); //18-s portra kötött kapcsoló megszakítása és beolvas() rutin indítása
 attachInterrupt(4, leszam, FALLING); //19-s portra kötött kapcsoló megszakítása és leszam() rutin indítása
}
void loop() {
//adatok feltöltése helyiértékenként a tömbbel megadott számértékre
//a feltöltésre kerülő szám tömbjének nevét mi adjuk meg!  

if (num >= 10000) { //ha eléri a 9999-s értéket 0-ra vált
  num = 0;}
if (num < 0) { //ha kisebb lesz 0-nál 9999ra vált
  num = 9999;}   

  bont();
  
  if (ezer == 0) {              // ha az "ezer" változó 0,
for(int m = 0; m < 7; m++) { // akkor a zero[] tömb adatait olvassa át a ciklus
segmd[m] = zero[m];}}        //  a helyiértékhez tartozó "segmd" változóba.
 if (ezer == 1) {           // innen minden az előzőek szerint történik, csak
 for(int m = 0; m < 7; m++) { // az 1-s alakiérték miatt itt a one[] került  
 segmd[m] = one[m];}}         // feltöltésre, és így tovább 9-ig
   if (ezer == 2) {
     for(int m = 0; m < 7; m++) {
     segmd[m] = two[m];}}
      if (ezer == 3) {
       for(int m = 0; m < 7; m++) {
       segmd[m] = three[m];}}
        if (ezer == 4) {
         for(int m = 0; m < 7; m++) {
         segmd[m] = four[m];}}
          if (ezer == 5) {
           for(int m = 0; m < 7; m++) {
           segmd[m] = five[m];}}
            if (ezer == 6) {
             for(int m = 0; m < 7; m++) {
             segmd[m] = six[m];}}
              if (ezer == 7) {
               for(int m = 0; m < 7; m++) {
               segmd[m] = seven[m];}}
                if (ezer == 8) {
                 for(int m = 0; m < 7; m++) {
                 segmd[m] = eight [m];}}
                  if (ezer == 9) {
                   for(int m = 0; m < 7; m++) {
                   segmd[m] = nine[m];}}

// értékadás helyiértékenként  100-s helyiértéken                     
// a "szaz" valtozó értéke szerinti tömb kerül a segmc˙[] tömbbe  
if (szaz == 0) {
for(int m = 0; m < 7; m++) {
segmc[m] = zero[m];}}
 if (szaz == 1) {
 for(int m = 0; m < 7; m++) {
 segmc[m] = one[m];}}
   if (szaz == 2) {
     for(int m = 0; m < 7; m++) {
     segmc[m] = two[m];}}
      if (szaz == 3) {
       for(int m = 0; m < 7; m++) {
       segmc[m] = three[m];}}
        if (szaz == 4) {
         for(int m = 0; m < 7; m++) {
         segmc[m] = four[m];}}
          if (szaz == 5) {
           for(int m = 0; m < 7; m++) {
           segmc[m] = five[m];}}
            if (szaz == 6) {
             for(int m = 0; m < 7; m++) {
             segmc[m] = six[m];}}
              if (szaz == 7) {
               for(int m = 0; m < 7; m++) {
               segmc[m] = seven[m];}}
                if (szaz == 8) {
                 for(int m = 0; m < 7; m++) {
                 segmc[m] = eight [m];}}
                  if (szaz == 9) {
                   for(int m = 0; m < 7; m++) {
                   segmc[m] = nine[m];}}
                   
// értékadás helyiértékenként  10-s helyiértéken  
// a "tiz" valtozó értéke szerinti tömb kerül a segmb˙[] tömbbe                    
if (tiz == 0) {
for(int m = 0; m < 7; m++) {
segmb[m] = zero[m];}}
 if (tiz == 1) {
 for(int m = 0; m < 7; m++) {
 segmb[m] = one[m];}}
   if (tiz == 2) {
     for(int m = 0; m < 7; m++) {
     segmb[m] = two[m];}}
      if (tiz == 3) {
       for(int m = 0; m < 7; m++) {
       segmb[m] = three[m];}}
        if (tiz == 4) {
         for(int m = 0; m < 7; m++) {
         segmb[m] = four[m];}}
          if (tiz == 5) {
           for(int m = 0; m < 7; m++) {
           segmb[m] = five[m];}}
            if (tiz == 6) {
             for(int m = 0; m < 7; m++) {
             segmb[m] = six[m];}}
              if (tiz == 7) {
               for(int m = 0; m < 7; m++) {
               segmb[m] = seven[m];}}
                if (tiz == 8) {
                 for(int m = 0; m < 7; m++) {
                 segmb[m] = eight [m];}}
                  if (tiz == 9) {
                   for(int m = 0; m < 7; m++) {
                   segmb[m] = nine[m];}}

// értékadás helyiértékenként  1-s helyiértéken
// az "egy" valtozó értéke szerinti tömb kerül a segma˙[] tömbbe  
if (egy == 0) {
for(int m = 0; m < 7; m++) {
segma[m] = zero[m];}}
 if (egy == 1) {
 for(int m = 0; m < 7; m++) {
 segma[m] = one[m];}}
   if (egy == 2) {
     for(int m = 0; m < 7; m++) {
     segma[m] = two[m];}}
      if (egy == 3) {
       for(int m = 0; m < 7; m++) {
       segma[m] = three[m];}}
        if (egy == 4) {
         for(int m = 0; m < 7; m++) {
         segma[m] = four[m];}}
          if (egy == 5) {
           for(int m = 0; m < 7; m++) {
           segma[m] = five[m];}}
            if (egy == 6) {
             for(int m = 0; m < 7; m++) {
             segma[m] = six[m];}}
              if (egy == 7) {
               for(int m = 0; m < 7; m++) {
               segma[m] = seven[m];}}
                if (egy == 8) {
                 for(int m = 0; m < 7; m++) {
                 segma[m] = eight [m];}}
                  if (egy == 9) {
                   for(int m = 0; m < 7; m++) {
                   segma[m] = nine[m];}}

// helyiértékek kiíratása
for (byte k=0; k < 7; k++) {  
szegment [k] = segma [k];
}//1-s helyiérték adat átadása a kiiró tömbbe
digitalWrite(9, HIGH);//1-s helyiérték bekapcsolása
cikl();// kiiró funkcióhívás
 delay (t);//időszeletnyi késleltetés
 digitalWrite(9, LOW);//1-s helyiérték kikapcsolása
 clr ();// aktuális adatok törlése
//10-s helyiérték kiiratása  
for (byte j=0; j < 7; j++) {  
szegment [j] = segmb [j];
}
 digitalWrite(10, HIGH);
cikl ();
 delay (t);
 digitalWrite(10, LOW);
 clr ();
//100-s helyiérték kiiratása
for (byte l=0; l < 7; l++) {  
szegment [l] = segmc [l];
}
digitalWrite(11, HIGH);
digitalWrite (30, dp);
cikl();
 delay (t);
 digitalWrite(11, LOW);
 digitalWrite(30, LOW);   
 clr ();
//1000-s helyiérték kiiratása
for (byte o=0; o < 7; o++) {  
szegment [o] = segmd [o];
}
digitalWrite(12, HIGH);
cikl();
 delay (t);
 digitalWrite(12, LOW);
 clr ();
}  
 
  
void bont ()   {
ezer = num / 1000;// 1000-s helyiérték leválasztása
szaz = (num - ezer * 1000) / 100;// 100-s helyiérték leválasztása
tiz = (num - ezer * 1000 - szaz * 100) / 10;// 10-s helyiérték leválasztása
egy = num - ezer * 1000 - szaz * 100 - tiz * 10; }

//kiíró funkció, szegmensre bontás, kiírás
void cikl ()
{
      a = szegment [0];
      b = szegment [1];
      c = szegment [2];
      d = szegment [3];
      e = szegment [4];
      f = szegment [5];
      g = szegment [6];
   
      digitalWrite(37, a);
      digitalWrite(36, b);
      digitalWrite(35, c);
      digitalWrite(34, d);
      digitalWrite(33, e);
      digitalWrite(32, f);
      digitalWrite(31, g);  
}

//átmeneti tároló törlés funkció
void clr () {
a=0; b=0; c=0; d=0; e=0; f=0; g=0;//aktuális szegmensérték 0-s
for (int i=0; i < 7; i++) {
szegment [i] = 0; }
}

void beolvas () {
// A nyomógomb megnyomására a num értéke növekszik

swichstat = !digitalRead (18); // kapcsoló állapot negált értékének változóba való beolvasása
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
 {
  num=num + 1;
}
lastswichstat = swichstat;//utolsó állapot beállítása az aktuális kapcsoló állapotra
}

void leszam () {
// A nyomógomb megnyomására a num értéke csökken
swichstatl = !digitalRead (47); // kapcsoló állapot negált értékének változóba való beolvasása
if (swichstatl == HIGH && lastswichstatl == HIGH) {
  num=num - 1;
}
lastswichstatl = swichstatl;//utolsó állapot beállítása az aktuális kapcsoló állapotra
}
Vissza a tartalomhoz | Vissza a főmenühöz