Przerwanie na każdym pinie Arduino Uno
Początkujący użytkownicy Arduino mogą nie wiedzieć nawet o istnieniu czegoś takiego jak przerwania, jeżeli jednak budujemy bardziej złożony system na naszym mikrokontrolerze warto się nimi zainteresować, albowiem bardzo ułatwiają one komunikację z różnego rodzaju podzespołami. Niestety na płytkach wyposażonych w Atmegę328 (Uno, Leonardo) mamy do dyspozycji tylko 2 przerwania zewnętrzne. W porównaniu do całkowitej ilości pinów cyfrowych to bardzo mało. Jeżeli jednak nieco bardziej zagłębimy się w temat przerwań na tym procesorze to okaże się, że każdy pin cyfrowy obsługuje PCINT, czyli Pin Change Interrupt. W dzisiejszym artykule dowiemy się jak je wykorzystać.
Przerwanie zewnętrzne vs. PCINT
Od strony niskopoziomowej przerwanie zewnętrzne ma wiele korzyści względem PCINT.
- jest znacznie szybsze
- pozwala na wybranie wyzwalacza (FALLING, RISING, LOW, HIGH, CHANGE)
- każde przerwanie ma swój własny wektor czyli najprościej mówiąc funkcję obsługującą
Stosując PCINT możemy je co prawda zamontować na każdym pinie cyfrowym z tą różnicą, że na wszystkie piny producent przeznaczył zaledwie 3 wektory, oraz wyzwala je każda zmiana stanu pinu. To na barkach programisty spoczywa rozróżnienie który pin wyzwolił przerwanie (jeżeli korzystamy z kilku pinów na jednym wektorze) oraz jaki to był typ wyzwolenia (FALLING, RISING). To oczywiście kosztuje pewną ilość cykli procesora, więc jeszcze bardziej spowalnia kod. Myślę jednak, że w amatorskich oraz niektórych pół-profesjonalnych zastosowaniach takie spowolnienie nie będzie miało znacznego wpływu na ogólną sprawnosć projektu (czym jest kilka cykli w porównaniu do 16 milionów na sekundę, które wykonuje atmega).
Jak używać PCINT?
Oczywiście można to zrobić niskopoziomowo, ustawić odpowiednie rejestry, potem bawić się w rozpoznawanie co przyszło itp., ale przecież nie po to kupilismy sobie Arduino, żeby bawić się w low-level. Z tak dużą społecznością jaką skupia w okół siebie Arduino można liczyć, że czego nie wymyślimy to będzie do tego łatwa w obsłudze biblioteka. Tak też jest i tym razem :) Poprzez menedżer bibliotek ściągnijmy więc taką o nazwie PinChangeInterrupt.
Ustawiamy przerwanie
W funkcji setup należy wywołać taką oto metodę:
attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(INTERRUPT_PIN), interruptRoutine, FALLING);
gdzie INTERRUPT_PIN to numer pinu cyfrowego Arduino dla którego włączamy przerwanie, interruptRoutine to nazwa funkcji bez parametrów, zwracająca typ void, która zostanie wywołana w przypadku nadejścia przerwania. Ostatni parametr to wyzwalacz, czyli co wzbudzi nasze przerwanie (zbocze opadające – FALLING, rosnące – RISING czy zmiana stanu pinu – CHANGE).
Ale zaraz, co tu się dzieje? Przecież dopiero mówiłem że w przypadku PCINT trzeba samemu rozpoznawać jakie zdarzenie wyzwoliło przerwanie i który pin. Odpowiedź jest bardzo prosta: magia wysokopoziomowych bibliotek ;)
Zastosowanie w tym przykładzie funkcji digitalPinToPinChangeInterrupt ułatwia nam robotę. Gdyby nie to musielibyśmy w tym miejscu podać numer PCINT dla danego pinu (obrazek poniżej)
Czhodzi o numer na szarym tle, podczas gdy numer pinu na płytce Arduino oznacozny jest kolorem różowym (albo fioletowym?).
Przerwanie możemy też w dowolnym momencie wyłączyć wywołuąc
detachPinChangeInterrupt(digitalPinToPinChangeInterrupt(INTERRUPT_PIN));
ewentualnie zapauzować oraz wznowić jego wykrywanie:
// pauza disablePinChangeInterrupt(digitalPinToPinChangeInterrupt(INTERRUPT_PIN)); ... // wznowienie enablePinChangeInterrupt(digitalPinToPinChangeInterrupt(INTERRUPT_PIN));
Jeżeli jako wyzwalacz wybierzemy CHANGE to w momencie obsługiwania przerwania możemy sprawdzić, czy ta zmiana była ze stanu wysokiego na niski, czy z niskiego na wysoki:
uint8_t trigger = getPinChangeInterruptTrigger(digitalPinToPinChangeInterrupt(INTERRUPT_PIN));
Teraz zmienną trigger możemy porównać ze stałymi RISING lub FALLING, np. tak:
if(trigger == RISING) {
// Zmiana ze stanu LOW na HIGH
} else {
// Zmiana ze stanu HIGH na LOW
}
Demonstracja działania
W momencie nadejścia przerwania procesor przestaje robić to, co robił i bezwarunkowo przechodzi do obsługi owego przerwania. Bardzo łatwo sprawdzimy, czy wszystko działa blokując w funkcji loop() procesor na 10 sekund.
#include "PinChangeInterrupt.h"
#define INTERRUPT_PIN 8
void setup() {
Serial.begin(9600);
pinMode(INTERRUPT_PIN, INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT);
attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(INTERRUPT_PIN), interruptRoutine, CHANGE);
}
void interruptRoutine() {
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
void loop() {
delay(10000);
Serial.println("Minelo 10 sekund");
}
Po wgraniu powyższego kodu procesor powinien blokować się na 10 sekund, a następnie wypisywać na port szeregowy napis „Minelo 10 sekund” i tak w kółko. Możemy zauważyć, że pomimo blokowania procesora funkcją delay() po wciśnięciu przycisku podłączonego do pinu 8 stan wbudowanej w Arduino diody LED zmienia się. Oznacza to że przerwanie działa.
Jak pisać funkcje przerwań?
Zasada jest jedna: muszą one być szybkie. Unikamy więc tam wypisywania czegokolwiek na port szeregowy oraz wykonywania czasochłonnych rzeczy, a już przede wszystkim nie powinniśmy w takiej funkcji używać opóźniaczy (delay, delayMicroseconds).

