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ábla | Megszakításhoz használható lábak |
---|---|
Uno, Nano, Mini és egyéb 328 alapú táblák | 2, 3 |
Uno WiFi Rev.2, Nano Every | minden digitális tű |
Mega, Mega2560, MegaADK | 2, 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ák | 0, 1, 2, 3, 7 |
Zero | minden digitális pin, kivéve a 4 |
MKR Család táblái | 0, 1, 4, 5, 6, 7, 8, 9, A1, A2 |
Nano 33 IoT | 2, 3, 9, 10, 11, 13, A1, A5, A7 |
Nano 33 BLE, Nano 33 BLE Sense | minden pin |
Due | minden digitális pin |
101 | minden 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.
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.
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.
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.
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.
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;
}
}
Funduino UNO XXL Tanulókészlet
hirdetés
A következő részben körbejárjuk a tömböket.