tinyBrd – przerwania

Nettigo tinyBrd
Nettigo tinyBrd

tinyBrd to fajne narzędzie. Niecałe 5 µA w trybie uśpienia… to długie tygodnie pracy na zwykłej baterii CR2032. Nasz testowy tinyBrd w tej chwili działa już 9 tygodni, wysyłają co 30 sekund temperaturę z DS18B20. A co jeżeli nie potrzebujesz wysyłać danych regularnie?

Mając taki uniwersalny, programowalny i zdalny czujnik jak tinyBrd można mieć pomysły na reakcję gdy tylko coś się zdarzy. Mowa o nieregularnych zdarzeniach, w przeciwieństwie do cyklicznej pracy jak np wysyłania pomiaru co 30 sekund. Pomyślmy, np wysłanie informacji o jakiś nieregularnych zdarzeniach, odczytanych np z czujnika zbliżeniowego (kontaktronu).

Dotychczas wymagało to regularnego wybudzania i sprawdzania czy nastąpiło dane zdarzenie. Gorzej, jeżeli zdarzenie może trwać krótko i jest duża szansa, że nasz tinyBrd będzie spał gdy nastąpi zdarzenie.

Dziś został upubliczniony nowe NettigoTinyBrdCore, który zawiera nowy komponent – PinChangeInterrupt. By uaktualnić zainstalowane Nettigo tinyBrd Core – postępuj zgodnie z instrukcją instalacji bieżącej wersji.

Jak skorzystać z przerwań na tinyBrd?

Przerwanie jest to zewnętrzne zdarzenie, na które może kontroler zareagować w sposób asynchroniczny. Czyli, z punktu widzenia programisty – definiujesz funkcję obsługującą przerwanie (czyli kod który ma się wykonać gdy zdarzenie nastąpi), rejestrujesz ją, i od tego momentu, gdy nastąpi to zdarzenie funkcja jest wywoływana automagicznie. Bez żadnego sprawdzania stanu wejść przez program.

Skoro mowa o Pin Change to, tak, dokładnie – tym zdarzeniem jest zmiana stanu pinu cyfrowego. Przerwanie może zostać wywołane w momencie RISING, FALLING lub CHANGE. Odpowiednio gdy stan pinu zmieni z niskiego na wysoki (RISING), z wysokiego na niski (FALLING) lub w obu przypadkach (CHANGE).

Kod omawiany w tym wpisie jest do pobrania tutaj: Obsługa przerwań – tinyBrd.

Pierwszy krok – włączając plik nagłówkowy informujesz że chcesz korzystać z tych funkcji:

#include "PinChangeInterrupt.h"

Jako najprostszą ilustrację – zrobimy licznik zliczający pojawienie się sygnału wysokiego na wejściu cyfrowym. Potrzebujemy zmiennej na przechowywanie licznika:

volatile unsigned int cnt = 0;
volatile bool irq_trigger = false;

Zwróć uwagę na słowo kluczowe volatile, jego użycie zapewni nam, że gdy użyjemy w kodzie wartości cnt to będzie to wartość aktualna. W naszym przypadku nie jest to duży problem, ale zapamiętaj to jako regułę – jeżeli zmienna ma służyć do przekazywania wartość z funkcji obsługi przerwania do głównego programu – używaj volatile.

Czas na samą funkcję obsługi przerwania. Musi ona być krótka i ograniczona do minimum

void tick() {
  cnt++;
  irq_trigger = true;
};

Nie zwraca żadnej wartości ani nie pobiera argumentów. Jedyne co robi to zwiększa licznik oraz ustawia flagę irq_triggered, by przekazać fakt, że zostało wywołane przerwanie. W setup ustawiamy dwie sprawy związane z przerwaniem:

  pinMode(1, INPUT_PULLUP);
  attachPcInterrupt(1, tick, RISING);

Pin powinien być w trybie INPUT, ponieważ tutaj używam przycisku, lub kabelka na płytce stykowej, więc używam INPUT_PULLUP by mieć pewność, że nie będzie przypadkowych wyzwoleń przerwania. W głównej pętli loop najpierw sprawdzamy czy przerwanie nie zostało wywołane, jeśli tak to wysyłamy na serial bieżącą wartość licznika:

  if (irq_trigger) {
    irq_trigger = false;
    debug.println(F("IRQ was triggered"));
    debug.println(cnt);
  }

Zmieniła się nieco funkcja sleep (w porównaniu z poprzednimi wersjami Nettigo tinyBrd Core). Otóż teraz zwraca ona wartość typu bool. Prawdę gdy tinyBrd wyszło ze uśpienia po zadanym czasie, a fałsz gdy wybudzenie nastąpiło na skutek przerwania. Używanie przerwań może trochę skomplikować życie, jeżeli chciałbyś by tinyBrd wysyłało regularnie dane przez NRF. W trakcie sleep nie działa zegar, więc nie da się odmierzyć upływu czasu. Bez przerwań, wywołując sleep jesteś w stanie kontrolować co ile zostanie wysłany pakiet. Na przykład taki pseudokod pętli loop:

Dokonaj pomiaru
Wyślij wyniki przez NRF
Przejdź w tryb uśpienia na 30 sekund

W rezultacie tinyBrd będzie wysyłać dane co ok 30 sekund. Niestety, jeżeli są włączone przerwania, to obsługa takiego zdarzenia wymaga by procesor został wybudzony. Po skończeniu obsługi przerwania sleep nie wie na ile powinien iść jeszcze spać, więc w ogóle przerywa działanie. Dlatego liczenie czasu może być bardziej skomplikowane.
Co można zrobić? Interesującym tropem wydaje się podział czasu na drobne kawałki. Czyli, zamiast jednego sleep na 30 sekund np zrobić 30 razy sleep po 1 sekundzie. Jeżeli przyjdzie przerwanie, to „urwie” nam maksymalnie jedną sekundę. Będzie się to wiązało z niewielkim wzrostem zużywanej mocy, ale jak się to przełoży na Twój projekt to sam musisz ocenić.

Dobrze, wróćmy do naszego kodu. Skoro sleep pozwala nam teraz się zorientować, czy wybudzenie nastąpiło na skutek upływu zadanego czasu przy wywołaniu czy na skutek przerwania, to skorzystajmy z tej opcji:

  debug.println(F("Sleep"));
  if (sleep(5000)) {
    debug.println(F("Sleep did finish"));
  } else {
    debug.println(F("IRQ triggered wakeup"));
  }

Wgrajmy ten przykład (do ściągnięcia całość: irq1) na tinyBrd, (wcześniej oczywiście musisz uaktualnić Nettigo tinyBrd Core – postępuj zgodnie z instrukcją instalacji bieżącej wersji). Następnie do pinu D0 podepnij konwerter USB/Serial (możesz użyć Arduino UNO ze zwartym pinem RESET do GND).

Do pinu D1 podłącz przycisk zwierający go do masy. Możesz też użyć przewodu do zwarcia go do masy w celu wyzwolenia przerwania. Uruchom jakiś monitor portu szeregowego (może być ten z Arduino IDE), skonfiguruj go na prędkość 38400 i wskaż port, do którego dopięty jest konwerter USB/Serial. Przykładowy wynik działania (dodano numery linii by ułatwić orientację):

        1 START-------->
        2 Sleep
        3 Sleep did finish
        4 Sleep
        5 IRQ triggered wakeup
        6 Sleep
        7 IRQ triggered wakeup
        8 IRQ was triggered
        9 1
       10 Sleep
       11 IRQ triggered wakeup
       12 Sleep
       13 IRQ triggered wakeup
       14 IRQ was triggered
       15 2

Na co zwrócić uwagę? Np linia 3 – funkcja sleep skończyła pracę z powodu upłynięcia czasu. Linia 5 – sleep wyszedł z powodu przerwania, ale nie został zwiększony licznik. Dopiero w linii 7 wyszedł ze sleep a w liniach 8 i 9 jest wyświetlona wartość licznika.

Dlaczego tak się dzieje? Tutaj można omawiać szczegółowo ustawienia trybu sleep ATtiny ale w skrócie:

  • ustawiliśmy wejście D1 w tryb INPUT_PULLUP
  • domyślnie po przejściu w sleep napięcie (bo jest włączony pullup) na D1 pozostanie bez zmian
  • zwarcie do masy D1 w trakcie snu powoduje zmianę stanu, jednak by określić czy należy wywołać przerwanie, procesor musi się obudzić i sprawdzić czy wywołać procedurę przerwania. Ponieważ zmiana jest z 1 na 0, więc procedura tick nie jest wywoływana, ale sleep kończy pracę
  • po wyjściu ze sleep w funkcji loop jest tylko jeden if a potem znowu wchodzi tinyBrd w sleep, więc odpięcie D1 od masy powoduje wybudzenie, jednak zmiana z 0 na 1 tym razem pasuje do zdefiniowanego kierunku (RISING) i jest wywołane tick – licznik zostaje zwiększony

Czyli bądź świadom, że sleep może zgłosić zakończenie pracy z powodu przerwania, ale sama procedura przerwania nie została wywołana. By mieć pewność że procedura zostanie wywołana za każdym razem gdy sleep zwrócił wartość false – użyj maski przerwania CHANGE a nie RISING czy FALLING.

Jak już trochę przetestujesz przerwania i sleep, zakomentuj ifa z sleep i odkomentuj kod niżej z delay. Jak wgrasz na tinyBrd ten kod, to zobaczysz, że jest tutaj inaczej. Przerwanie nie powoduje zakończenia pracy delay, jest to funkcja jak inne, więc procedura obsługi przerwania wykona się w tle, zwiększając licznik. Dopiero po upływie czasu podanego w delay zostanie wypisana wartość licznika. Oczywiście – jeżeli w czasie delay przerwanie wywołano kilka razy – licznik zliczy wszystkie.

Teraz, na koniec – ile czasu zajmuje wybudzenie się tinyBrd z sleep? By to przetestować, pokaże wam ten wykres:

Pobudka tinyBrd
Pobudka tinyBrd

Żółty sygnał to jest wejście przerwania, a procedura obsługi przerwania ma za zadanie ustawić wyjście cyfrowe w stan HIGH używając digitalWrite (przebieg niebieski). Jak widać między impulsem przerwania a zmianą stanu wyjścia mija ok 6 ms i to można przyjąć za czas potrzebny do wybudzenia tinyBrd z uśpienia.