Arduino interrupt – megszakítások

A megszakítások olyan események, amelyek a program futása közben történnek, az Arduino ilyenkor megszakítja a főprogram futását, hogy azonnal reagáljon a külső eseményekre.

Az Arduino számos megszakítást használ. Ilyen például az első prioritású megszakítás, a Reset. Az USART, SPI, TWI/I2C kommunikációs portok. Időzítók/számlálók, a Watchdog, az Arduino eszköz egyes lábain bekövetkező jelszint változások stb. Az alábbi lista összefoglalja az ATmega328 alapú eszközök megszakításait prioritási sorrendben.

1. Reset
2. External Interrupt Request 0 (pin D2) (INT0_vect)
3. External Interrupt Request 1 (pin D3) (INT1_vect)
4. Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
5. Pin Change Interrupt Request 1 (pins A0 to A5) (PCINT1_vect)
6. Pin Change Interrupt Request 2 (pins D0 to D7) (PCINT2_vect)
7. Watchdog Time-out Interrupt (WDT_vect)
8. Timer/Counter2 Compare Match A (TIMER2_COMPA_vect)
9. Timer/Counter2 Compare Match B (TIMER2_COMPB_vect)
10. Timer/Counter2 Overflow (TIMER2_OVF_vect)
11. Timer/Counter1 Capture Event (TIMER1_CAPT_vect)
12. Timer/Counter1 Compare Match A (TIMER1_COMPA_vect)
13. Timer/Counter1 Compare Match B (TIMER1_COMPB_vect)
14. Timer/Counter1 Overflow (TIMER1_OVF_vect)
15. Timer/Counter0 Compare Match A (TIMER0_COMPA_vect)
16. Timer/Counter0 Compare Match B (TIMER0_COMPB_vect)
17. Timer/Counter0 Overflow (TIMER0_OVF_vect)
18. SPI Serial Transfer Complete (SPI_STC_vect)
19. USART Rx Complete (USART_RX_vect)
20. USART, Data Register Empty (USART_UDRE_vect)
21. USART, Tx Complete (USART_TX_vect)
22. ADC Conversion Complete (ADC_vect)
23. EEPROM Ready (EE_READY_vect)
24. Analog Comparator (ANALOG_COMP_vect)
25. 2-wire Serial Interface (I2C) (TWI_vect)
26. Store Program Memory Ready (SPM_READY_vect)

Előfordulhat, hogy az Arduino programunkban több megszakítási esemény következik be. Ilyenkor egyszerre csak egy megszakítás futhat le, a többi megszakítás prioritásuktól függően az aktuálisan futó megszakítás után hajtódik végre.

A megszakítások alapértelmezés szerint engedélyezve vannak, de ideiglenesen kikapcsolhatók a noInterrupts() függvény hívásával. Ez a funkció hasznos lehet, amikor az Arduino kódjának kritikus szakaszában vagyunk, és nem akarjuk, hogy bármilyen külső esemény befolyásolja a kód futását. Kivételt képez ezalól a reset megszakítás, amely nem tíltható le.

Csak indokolt esetben használjuk, mert ha kikapcsoljuk a megszakításokat, az egyes funkciók hibás működését okozhatja. Ha befejeződött a kritikus kódrészlet futtatása, az interrupts() függvény hívásával engedélyezzük a megszakításokat.

void loop()
{
  noInterrupts();
  // kritikus kódrészlet ide
  interrupts();

  // további kód...
}

A külső megszakítások hasznosak lehetnek pl. gombnyomás kezelésére, enkoderek impulzusainak figyelésére, miközben a főprogramban egyéb más feladatot végezhetünk.

Az attachInterrupt() függvény lehetővé teszi, hogy az Arduino dedikált lábain bekövetkezett változás megszakítási eseményt váltson ki. A következő táblázatban láthatjuk egyes arduino táblák megszakításokhoz használható lábait.

Arduino táblaMegszakításhoz használható lábak
Uno, Nano, Mini és egyéb 328 alapú táblák2, 3
Uno WiFi Rev.2, Nano Everyminden digitális tű
Mega, Mega2560, MegaADK2, 3, 18, 19, 20, 21 ( a 20-as és 21-es érintkezők nem használhatók megszakításokhoz, miközben I2C kommunikációra használják)
Micro, Leonardo, egyéb 32u4 alapú táblák0, 1, 2, 3, 7
Zerominden digitális pin, kivéve a 4
MKR Család táblái0, 1, 4, 5, 6, 7, 8, 9, A1, A2
Nano 33 IoT2, 3, 9, 10, 11, 13, A1, A5, A7
Nano 33 BLE, Nano 33 BLE Senseminden pin
Dueminden digitális pin
101minden digitális érintkező (Csak a 2., 5., 7., 8., 10., 11., 12., 13. érintkező működik a CHANGE funkcióval )

Az Arduinoban megszakítási rutinok, úgynevezett (ISR) Interrupt Service Routines segítségével könnyen, azonnal kezelhetjük ezeket az eseményeket. Viszont a megszakítási rutinok használatakor be kell tartanunk néhány szabályt.

Az ISR-nek nem lehet paramétere, és visszatérési értéke sem. Ha adatot kell átadni a főprogramból az ISR-nek, vagy az ISR-től várunk vissza értéket, használjunk globális változókat. Ezeket a globális változókat deklaráljuk volatile-ként.

A megszakítási rutinnak a lehető leggyorsabban be kell fejezni a futását, csak a legfontosabb dolgokat végezzük el az ISR törzsében, az időigényes feladatokat adjuk át a főprogramnak.

Kerüljük az időzítéseket. A delay() működéséhez megszakításokra van szükség, nem fog működni az ISR-en belül. A millis() függvény is megszakításokat használ a számoláshoz, így soha nem fog növekedni a megszakítási rutin törzsében. A micros() eleinte működik, de pár ms elteltével ez is szabálytalanul kezd viselkedni. Mivel a delayMicroseconds() nem használ számlálót, így az rendesen müködhet.

Az attachInterrupt() függvény segítségével inicializáljuk a külső megszakítást. A függvénynek három paramétere van.

attachInterrupt(interrupt, ISR, mode);

A első paraméter a megszakítás száma. Ezt több féle módon megadhatjuk. Beírhatjuk közvetlenül a megszakítás számát, de ez nem javasolt, mert egyes kártyákon a külső megszakítások más lábhoz lehetnek rendelve, így ez a megoldás rontja a kód hordozhatóságát. Másik lehetőség, hogy megadjuk a láb számát, de ez nem működik minden Arduino lapkán, ezért ez sem javasolt.

A megfelelő megoldás a digitalPinToInterrupt(pin) függvény használata. A digitalPinToInterrupt függvény paramétereként megadjuk a megszakításhoz használt láb számát és visszatér az aktuális arduino kártya interrupt számával.

int pin = 2;
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)

A attachInterrupt() függvény második paramétere az ISR, a megszakítást lekezelő függvény neve.

A harmadik paraméter határozza meg, hogy a megszakításhoz használt láb jelszintváltozásának melyik szakaszában váltson ki megszakítást. Az Arduinoban Négy konstans érték van előre definiálva.

LOW, a megszakítás kiváltása, amikor a láb alacsony jelszinten van.

CHANGE, amikor a láb jelszintje bármilyen módon megváltozik, a megszakítás kiváltódik.

RISING, amikor a láb alacsonyról magas jelszintre változik, aktiválódik a megszakítás.

FALLING, amikor a pin magas jelszintről alacsonyra változik.

Az Arduino Due, Zero és MKR1000 kártyák esetén működik a HIGH, a megszakítás kiváltódik, amikor a láb magas jelszinten van.

Az attachInterrupt() függvényt a setup() szakaszban kell meghívni, valamint szükségünk van egy ISR függvényre, amely kezeli a megszakítást.

const byte buttonPin = 2;
volatile byte buttonState = LOW;

void setup()
{
  pinMode(buttonPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(buttonPin), buttonPressed, FALLING);
}

void loop()
{
  // Főprogram....
}

void buttonPressed()
{
  buttonState = !buttonState;
}

Előfordulhat, hogy az arduino programunkban már nincs szükségünk a megszakítást kiváltó esemény figyelésére, vagy a megszakításhoz használt lábat másként szeretnénk felhasználni. Ilyen esetben kikapcsolhatjuk az adott megszakítást, hogy felszabadítsuk az Arduino erőforrásait.

Erre a célra használjuk az Arduino detachInterrupt() függvényét. Azt hogy melyik megszakítást kell kikapcsolni azt a paraméterként a már ismert digitalPinToInterrupt(pin) függvény által visszaadott érték határozza meg.

detachInterrupt(digitalPinToInterrupt(pin))

Gyakori felhasználási módja a megszakításoknak például forgójeladó jeleinek beolvasása. A megszakítások használatával elkerülhetjük az enkoder jeleinek impulzusvesztést az Arduino programban.

Rotary Encoder Modul, Arduino kompatibilis.

KY-040 Forgójeladó

Rotary Encoder Modul, Arduino kompatibilis.

Csatlakozások:
„GND” = Földcsatlakozás
„+” = 5V táp
„SW” = Nyomógomb Pin
„DT” = Encoder Pin B
„CLK” = Encoder Pin A

hirdetés

Az enkóder, vagy inkrementális jeladó, olyan érzékelő, amely a tengelye elfordulásakor impulzusokat ad ki a kimenetein. Az impulzusok mennyisége arányos a szögelfordulás mértékével, és a forgójeladó felbontásától.

A mechanikus inkrementális jeladó tengelyén egy enkodertárcsa van elhelyezve, ez a forgójeladó középső, közös kivezetése, ezt a lábat a gnd-re kapcsoljuk. Az enkodertárcsa egy perforált tárcsa, ezzel az enkodertárcsával érintkeznek az enkoder „A” és „B” csapjai.

enkoder sematikus ábra

Amikor forgatás közben az enkoder „A” és „B” érintkezői hozzáérnek az enkodertárcsához, két négyszögjel keletkezik. Ezek a jelek el vannak tolódva egymáshoz képest, mivel az egyik érintkező a másik előtt érintkezik az enkodertárcsával.

Ha az enkodert az óramutató járásával megegyező irányba forgatjuk, az „A” érintkező a „B” érintkező előtt csatlakozik a tárcsához. Amikor az „A” érintkező alacsony jelszintre változik, a „B” érintkező még magas jelszinten van. Tehát az „A” érintkező szintváltásakor a „B” mindig ellentétes jelszinten van.

enkoder kimeneti négyszögjel óramutató járásával megeggyezó forgásiránynál

Ha a forgójeladót az óramutató járásával ellentétes irányba forgatjuk, a „B” érintkező fog először érintkezni az enkodertárcsával. Ebben az esetben az „A”érintkező szintváltáskor megegyezik a „B” érintkező jelszintjével.

enkoder kimeneti négyszögjel óramutató járásával ellentétes forgásiránynál

Ha megfigyeljük az A és B érintkezők jelszint változásainak sorrendjét, meghatározhatjuk az enkoder forgásirányát.

Csatlakoztassunk egy KY-040 forgó jeladót az Arduino UNO-hoz az alábbi kép alapján. A KY-040 enkoder „A” és „B” („DT” és „CLK”) kimenete az áramköri lapkán 10k ellenállással fel van húzva a tápfeszültségre.

KY-040 enkoder csatlakoztatása az Arduino Uno-hoz

Majd töltsük fel a következő kódot az Arduino UNO-ra. Külön nem írom le a kód működését, a vázlatban bőven kommentáltam.

// az enkoder csatlakozásához használt Arduino Uno lábak
const char clkPin = 2;
const char dtPin = 3;
const char switchPin = 4;

// Az ISR ben is használt változókat volatile-ként kell deklarálni.
// az enkoder elfordulásának számlálásához használt változó.
volatile int counter = 0;

// Az enkoder CLK láb előző állapotát tároló változó
volatile int clkLastState = LOW;

// Az enkoder CLK láb aktuális állapotát tároló változó
volatile int clkCurrentState;


// Az enkoder által kiváltott megszakítások kezelésére használt ISR
void encoderHandler()
{  
  // CLK láb aktuális állapotát eltároljuk a clkCurrentState változóba
  clkCurrentState = digitalRead(clkPin);
  
  // Ha a CLK láb alacsony jelszintje magasba megy át
  if((clkLastState == LOW) && (clkCurrentState == HIGH)) 
  {
    // és a DT láb jelszintje is magas
	if(digitalRead(dtPin) == HIGH)
	{
      // az óramutató járásával ellentétes irányban fordult el az enkoder
	  // csökketjük a számláló értékét
	  counter--;
    }
	// ha viszont a DT láb alacsony 
    else
	{
      // az óramutató járásával megegyező irányban fordult el az enkoder
	  // tehát növeljük a számláló értékét
	  counter++;
    }
	// kiírjuk a soros monitorra a számláló aktuális értékét. 
    Serial.println(counter);
  }
  // CLK láb aktuális állapotát átadjuk a clkLastState változónak
  clkLastState = clkCurrentState;
}

void setup()
{
  Serial.begin(9600);
  
  // Az enkoder CLK és DT lábakat beállítjuk bemenetként. 
  // az enkoder áramköri lapján rendelkezik a felhúzó ellenállásokkal
  pinMode(clkPin, INPUT);
  pinMode(dtPin, INPUT);
  
  // Az enkoder nyomógomb SW lábát is beállítjuk bemenetként.
  // itt az Arduino belső felhúzó ellenállását használjuk
  pinMode(switchPin, INPUT_PULLUP);
  
  // beállítjuk a megszakításokat az enkoder elfordulás figyeléséhez
  attachInterrupt(digitalPinToInterrupt(clkPin), encoderHandler, CHANGE);
  attachInterrupt(digitalPinToInterrupt(dtPin), encoderHandler, CHANGE);
}

void loop()
{
 // megvizsgáljuk a nyomógomb állapotát 
 if(digitalRead(switchPin) == LOW)
  {
    // ha megnyomták, akkor nullázzuk a számlálót
	counter = 0;
  }
}

A következő részben körbejárjuk a tömböket.