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).