Przerwania w tinyBrd

tinyBrd jest niewielkim, prostym i wygodnym w użyciu modułem zintegrowanym z nadajnikiem radiowym NRF24L01. W jednej płytce dostajesz gotowy do użycia moduł, który programujesz z użyciem Arduino IDE. Począwszy od kwietnia 2016 oprogramowanie tinyBrd obsługuje przerwania - zmiana stanu dowolnego pinu cyfrowego może wybudzić tinyBrd z funkcji sleep.

Dzięki temu tinyBrd nie przegapi żadnego ważnego zdarzenia - naciśnięcia przycisku, zmiany stanu kontaktronu itp. Jednocześnie może przez większość czasu pracować w trybie uśpienia - pobiera wtedy mniej niż 5 µA!

Jak używać przerwań na tinyBrd

Wystarczy mieć zainstalowany Nettigo tinyBrd Core w wersji 1.2 lub późniejszej (zgodnie z instrukcją) by można skorzystać z biblioteki PinChangeInterrupt. W tym celu w szkicu dodajemy na początku:

#include "PinChangeInterrupt.h"

Teraz mamy dostęp do funkcji attachPcInterrupt, która przyjmuje argumenty:

Jeśli tinyBrd jest w trybie uśpienia, zostanie wybudzone z tego trybu po każdej zmianie stanu pinu do którego przypisałeś funkcję obsługi. Czyli jeżeli masz przerwanie przypisane na FALLING, to funkcja zostanie wywołana tylko jeżeli zmieni się stan pinu z HIGH na LOW. Jednak każda zmiana będzie wybudzać tinyBrd ze sleep (czyli z LOW na HIGH też przerwie sleep).

Do każdego wolnego pinu możesz przypisać funkcję przerwania. Maksymalnie można obsłużyć przerwania na trzech pinach jednocześnie. Użycie tej biblioteki spowoduje zajęcie 20 bajtów pamięci RAM.

Jak pisać procedurę obsługi przerwań?

Zasady są takie same jak dla Arduino. Funkcja musi być krótka (chodzi o czas wykonywania a nie ilość linijek kodu). Zmienne, które funkcja przerwań używa, a mają być odczytywane przez główny program trzeba przy definicji oznaczyć słowem kluczowym volatile.

Przerwania świetnie nadają się do obsługi zdarzeń asynchronicznych - takich, które zachodzą w niedających się przewidzieć momentach. Np - naciśnięcie przycisku przez użytkownika. Załóżmy że tinyBrd będzie w uśpieniu przez cały czas, a po naciśnięciu przycisku zmierzy temperaturę i wyśle do Raspberry Pi.

Nadawanie danych

Tym razem użyjemy prostego analogowego czujnika temperatury MCP9700 (zwykle dotąd używaliśmy DS18B20). Dla uproszczenia, tinyBrd będzie nadawać tylko dwie wartości - swój ID i temperaturę, dlatego tak definiujemy pakiet wysyłany przez tinyBrd:

struct SensorData
{
  byte id;
  float payload;
} data;

Do pinu D1 podłączymy przycisk, tak by po jego naciśnięciu tinyBrd wysyłał temperaturę. W pozostałym czasie, tinyBrd ma być uśpione i co 10 sekund migać diodą podłączoną do pinu D10.

Skąd wziąć temperaturę? Już dawno temu opisaliśmy szczegółowo użycie MCP9700 na Arduino. Na nasze szczęście, tinyBrd jest kompatybilne z Arduino IDE, więc kod tam podawany (minus kod obsługi LCD) możemy bezpośrednio użyć na tinyBrd:

#define SENSOR_PIN A1

float temperatureRead() {
  float temp = analogRead(SENSOR_PIN) * 1.1 / 1024.0 - 0.5;
  temp = temp / 0.01;
  return temp;
}

Jak widać MCP podłączamy do pinu A1 (D9). W formule przeliczającej napięcie na temperaturę używamy wartości 1.1, bo napięcie 1.1V to wewnętrzne źródło odniesienia przetwornika ADC (opisane szczegółowo w linkowanym wyżej artykule o MCP9700 i Arduino). Opis ten dotyczy procesora ATmega328 użytego w Arduino, ale ATiny84 użyty w tinyBrd jest w tym zakresie zgodny z ATMega328).

By odczyt był poprawny w funkcji setup pojawia się polecenie zmieniające tryb pracy przetwornika ADC, tak by korzystać z tego napięcia odniesienia:

  analogReference(INTERNAL);

Numer sensora ustawiamy na sztywno na 1 (data.id = 1), pin D1 ustawiamy w tryb pracy wejścia (pinMode(1, INPUT_PULLUP)), pin D10 w tryb wyjścia (pinMode(10,OUTPUT)). Pozostaje jeszcze aktywacja modułu Radio i włączenie przerwania. By je włączyć korzystamy z funkcji attachPcInterrupt:

  attachPcInterrupt(1, irq, FALLING);

Pierwszy argument (1) to numer pinu do którego przyczepiamy przerwanie, drugi to nazwa funkcji (irq). Jest to sama nazwa, bez nawiasów jak przy wywołaniu. Ostatni argument to kierunek zmiany sygnału - FALLING z 1 na 0, CHANGE - jakakolwiek zmiana i RISING zmiana z 0 na 1.

Jak wygląda funkcja irq?

volatile boolean sendData = false;

void irq() {
  sendData = true;
}

Jedynym zadaniem to ustawienie flagi sendData na true. Dzięki temu, kod wewnątrz będzie wiedział że czas wysłać dane:

  if (sendData) {
    sendData = false;
    data.payload = temperatureRead();
    radioWrite(data);
    Radio.off();
    delay(30);
  }

Jak widzicie, zmienna sendData została zdefiniowana z użyciem flagi volatile, jak wspomniane było wcześniej.

Program działa tak. Co 10 sekund mignij diodą. Za każdym przebiegiem sprawdź czy sendData jest true, jeżeli tak, to odczytaj temperaturę i ją wyślij. Odczekaj krótką chwilę (delay(30)) by uniknąć kolejnej próby wysłania, jeżeli przycisk będzie nierówno łapał (tutaj możemy tego użyć jako debouncingu).

Wielokrotnie na Akademii pokazywaliśmy wysyłanie danych do Raspberry, więc szczegółowo funkcji radioWrite nie będę opisywał, jeśli chcesz wiedzieć, przeczytaj sekcję Nadawanie Danych w linkowanym artykule.

Odbieranie danych na Raspberry

Zaczniemy od drugiej strony, czyli od prostego programu odbierającego dane na Raspberry Pi. Podłączamy do RPi NRF24L01+ korzystając z NRF-hat. Zakładam, że na RPi już zainstalowaliście bibliotekę w pythonie, tak jak podaliśmy w instrukcji instalacji softu.

W tym wideo omawiamy szczegółowo jak odbierać dane na Raspberry korzystając z uniwersalnego skryptu NRFListen:

Instrukcja odbioru danych na Raspberry

W skrócie, komenda do odebrania danych na Raspberry to:

 python3 receive.py --format =Bf 99 0 3

Skrypt receive.py jest w katalogu ~/tinyBrdScripts/NRFListen jeżeli instalowaliście zgodnie z opisem w instrukcji.

Całość kodu

Przykład omawiany tutaj znajduje się na GitHubie

Wnioski

Podłącz przycisk od D1 (drugą jego stronę do GND). Możesz użyć kabelka i zwierać D1 do GND zamiast przycisku.

Dane do Raspberry zostaną wysłane tylko po przytknięciu przewodu do GND (lub wciśnięciu przycisku). Jeżeli przytrzymasz kabelek/przycisk to zauważysz, że po jego puszczeniu dioda na D10 miga krótko, ale tinyBrd nie wysyła danych do Raspberry. Dlaczego?

Po każdym wyjściu z funkcji sleep tinyBrd miga diodą. Kiedy wychodzi z funkcji sleep? Wtedy gdy minie czas podany w argumencie funkcji, dlatego miga co 10 sekund gdy nie naciskasz przycisku. Przerwanie gdy zostanie wyzwolone, wybudza procesor z funkcji sleep. Dzieje się tak przy każdej zmianie stanu wejścia do którego jest dopięte przerwanie.

Inaczej mówiąc procesor sprzętowo obsługuje tylko przerwanie typu CHANGE. Rozpoznanie kierunku (FALLING/RISING) jest obsługiwane w kodzie. Dlatego każda zmiana stanu pinu wybudza tinyBrd ze sleep (następuje mignięcie diodą które jest z szkicu zaraz po sleep), ale tylko ta zmiana pasująca do podanego kierunku (tutaj to było FALLING) powoduje wywołanie funkcji irq i skutkuje potem wysłaniem danych do Raspberry.