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 jako volatile, 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ść.