Przerwania to mechanizm pozwalający mikrokontrolerom na reagowanie na zdarzenia zewnętrzne w czasie rzeczywistym, bez konieczności ciągłego sprawdzania stanu pinów w pętli głównej programu.
Gdy określone zdarzenie wystąpi na wejściu cyfrowym Arduino, procesor natychmiast przerywa wykonywanie bieżącego kodu i uruchamia zaplanowaną procedurę obsługi przerwania (ISR).
W praktycznych zastosowaniach robotyki i elektroniki przerwania są niezwykle przydatne, szczególnie gdy system musi reagować na szybkie zmiany stanu — na przykład przy obsłudze przycisków, czujników ruchu czy liczników impulsów.
Ograniczenia techniczne w Arduino UNO/Nano
Procesory ATmega328 na płytkach Arduino UNO i Nano obsługują przerwania zewnętrzne wyłącznie na pinach D2 i D3. To ograniczenie wynika z architektury układu i nie można go obejść zwykłym podłączeniem czujnika do innego pinu.
Aby kod był przenośny między różnymi płytkami i uniknąć błędów, stosuj funkcję digitalPinToInterrupt(). Przykładowe wywołanie:
attachInterrupt(digitalPinToInterrupt(2), funkcjaObslugi, RISING);
Funkcja attachInterrupt()
Podstawowa funkcja do przypisywania przerwań to attachInterrupt(), która przyjmuje trzy parametry:
- numer przerwania – określony za pomocą
digitalPinToInterrupt(numer_pinu); - nazwa funkcji obsługi – funkcja wywoływana przy zajściu przerwania;
- tryb wyzwalania – określa, kiedy przerwanie ma się aktywować.
Tryby wyzwalania przerwań
Sposób aktywacji przerwania zależy od charakteru sygnału. W Arduino UNO/Nano dostępne są cztery główne tryby:
| Tryb | Opis |
|---|---|
| LOW | przerwanie wywoływane, gdy pin utrzymuje stan niski |
| RISING | przerwanie wywoływane przy zmianie stanu z niskiego na wysoki (zbocze narastające) |
| FALLING | przerwanie wywoływane przy zmianie stanu z wysokiego na niski (zbocze opadające) |
| CHANGE | przerwanie wywoływane przy każdej zmianie stanu pinu |
Wybór trybu zależy od czujnika i logiki projektu. Na przykład przycisk lub kontaktron zwykle wyzwala przerwanie na zboczu opadającym (FALLING), gdy dochodzi do zwarcia do masy.
Obsługa kontaktronu (magnetyczny przełącznik reed)
Kontaktron to miniaturowy przełącznik, który przewodzi prąd w obecności pola magnetycznego. Stosuje się go m.in. w licznikach obrotów, detektorach otwarcia drzwi i przepływomierzach.
Praktyczny przykład – licznik impulsów z kontaktronu
Poniżej znajduje się kompletny kod Arduino, który zlicza impulsy z kontaktronu podłączonego do pinu D2 i bezpiecznie odczytuje licznik w pętli głównej:
volatile unsigned int steps = 0;
void setup() {
Serial.begin(9600);
pinMode(2, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(2), onStep, FALLING);
}
void loop() {
// Bezpieczny odczyt zmiennej modyfikowanej w ISR (sekcja krytyczna)
noInterrupts();
unsigned int stepsCopy = steps;
interrupts();
Serial.println(stepsCopy);
delay(5000);
}
void onStep() {
static unsigned long lastTime = 0;
unsigned long timeNow = millis();
// Programowa eliminacja drgań styków ~50 ms
if (timeNow - lastTime < 50) return;
steps++;
lastTime = timeNow;
}
Kluczowe elementy kodu
Warto zwrócić uwagę na trzy kwestie:
- volatile – zmienne modyfikowane w ISR (tu:
steps) deklaruj jakovolatile, aby kompilator nie optymalizował ich w sposób zagrażający poprawności; - eliminacja drgań styków – mechaniczne przełączniki (także kontaktrony) powodują wielokrotne, krótkie zwarcia podczas zamykania. Opóźnienie ~50 ms filtruje fałszywe impulsy i zapobiega błędnemu zliczaniu;
- atomowy odczyt licznika – na AVR odczyt 16-bitowej zmiennej nie jest atomowy. Na czas kopiowania wartości wyłącz przerwania (
noInterrupts()/interrupts()), a w reszcie programu pracuj na kopii.
Obsługa czujnika PIR (pasywny czujnik podczerwieni)
Czujnik PIR wykrywa zmiany promieniowania podczerwonego, co pozwala na detekcję ruchu ciepłych obiektów (osób, zwierząt). W odróżnieniu od kontaktronu, wyjście PIR zwykle pozostaje w stanie wysokim przez cały czas trwania wykrytego ruchu.
Przykład obsługi czujnika PIR
Minimalna procedura przerwania ustawia jedynie flagę, a logika reakcji odbywa się w pętli głównej:
volatile boolean motionDetected = false;
void setup() {
Serial.begin(9600);
pinMode(2, INPUT);
attachInterrupt(digitalPinToInterrupt(2), onMotion, RISING);
}
void loop() {
if (motionDetected) {
Serial.println("Ruch wykryty!");
motionDetected = false;
}
}
void onMotion() {
motionDetected = true;
}
ISR powinna być możliwie najkrótsza i nie blokować programu długimi operacjami (np. komunikacją, opóźnieniami, drukowaniem przez Serial).
Globalne zarządzanie przerwaniami
Arduino umożliwia tymczasowe wyłączenie wszystkich przerwań za pomocą noInterrupts() i ponowne ich włączenie poprzez interrupts():
noInterrupts(); // sekcja krytyczna – kod wymagający działania bez przerw
// ...
interrupts();
Ważne – wyłączenie przerwań dezaktywuje również wewnętrzne przerwania Arduino, na których opierają się funkcje millis() i delay(). Zdarzenia, które wystąpią w tym czasie, nie zostaną zarejestrowane.
Gdy używać, a gdy unikać przerwań
Kiedy przerwania są wskazane
Przerwania sprawdzają się idealnie, gdy:
- system musi reagować natychmiast na zdarzenie zewnętrzne,
- program główny wykonuje czasochłonne operacje (np. obsługa LCD, komunikacja sieciowa),
- liczba potencjalnych źródeł przerwań jest niewielka (2–3),
- chcesz uniknąć stałego sprawdzania stanu pinów w pętli głównej.
Kiedy przerwań unikać
Praktyczne doświadczenie podpowiada:
- jeśli pętla główna (
loop()) wykonuje się szybko, a opóźnienie rzędu setek milisekund–1 sekundy jest akceptowalne – rozważ polling zamiast przerwań, - programy z przerwaniami są trudniejsze do debugowania i diagnozowania błędów,
- jeśli potrafisz znaleźć prostsze rozwiązanie bez przerwań, wybierz je.
Ograniczenia i uwagi praktyczne
Zmienne współdzielone
Każdą zmienną modyfikowaną w ISR i w pętli głównej deklaruj jako volatile, a jej wielobajtowy odczyt/zapis zabezpieczaj sekcją krytyczną (noInterrupts()/interrupts()).
Ograniczona liczba przerwań
UNO/Nano oferują tylko dwa przerwania zewnętrzne. W bardziej złożonych projektach rozważ Arduino Mega (więcej przerwań) lub wykorzystanie zewnętrznych ekspanderów/multiplekserów sygnałów.
Interakcja z funkcjami czasowymi
Wyłączenie przerwań rozregulowuje wewnętrzny licznik czasu używany przez millis(), delay() i delayMicroseconds(). W aplikacjach wymagających precyzyjnego pomiaru czasu zachowaj szczególną ostrożność.