Najważniejsze zalety tinyBrd
Płytka tinyBrd powstała jako rezultat naszych prac z bezprzewodowymi sensorami. Potrzebowaliśmy modułu, który pozwoli skoncentrować się na ważniejszych aspektach budowania prototypu. Oszczędność energii, transmisja radiowa musiały być dostępne od ręki.
Oprogramowanie to jest to, co czyni tinyBrd naprawdę użytecznym. Przygotowaliśmy podczas naszych prac szkielet oprogramowania, które jest:
- przejrzyste
- kompatybilne z Arduino IDE
- obsługuje podstawową funkcjonalność:
- tryb oszczędności energii
- transmisję radiową
- monitoring zasilania
W innym artykule pokazaliśmy jak łatwo można zbudować bezprzewodowy czujnik temperatury z tinyBrd. Teraz omówimy dokładniej jak działa oprogramowanie użyte tam oprogramowani i jak je możecie modyfikować na własne potrzeby.
Zakładam, że zainstalowałeś Nettigo tinyBrd Core i przykłady do niego tak jak tam jest to opisane.
Najnowsze wersje
Wszystkie szkice znajdują się na GitHubie: https://github.com/nettigo/tinyBrd
Wersja 1.0 przynosi kilka zmian, które znacznie uprościły cały program. Po pierwsze, niezbędne biblioteki zostały zintegrowane z Nettigo tinyBrd Core. Instalując go, masz od razu biblioteki Radio (komunikacja przez NRF), Battery (tryby uśpienia), biblioteka SPI dla ATtiny84, oraz Storage (do zapisu i odczytu danych z EEPROM).
Nadawnie danych z tinyBrd
Ponieważ do programowania tinyBrd używamy Arduino IDE, główna część programu składa się z dwóch funkcji setup
i loop
. Oprócz nich znajdziemy tam definicję struktury do wysyłania danych.
Definicje
Pierwszym krokiem jest włączenie odpowiednich bibliotek
#include <SPI.h>
#include <Radio.h>
#include <Battery.h>
Od wersji 1.0 Nettigo tinyBrd Core biblioteki te są dostępne po zainstalowaniu obsługi tinyBrd w Arduino IDE.
Następnie definujemy naszą strukturę:
struct SensorData
{
byte id;
long battery;
byte status;
float payload;
byte seq;
byte retry;
} data;
Struktura to sposób na zgrupowanie kilku zmiennych w jednym opakowaniu. Tu wsadziliśmy następujące zmienne:
id
przechowuje unikalny numer modułu nadającego, tak byśmy mogli rozróżnić od którego sensora odebraliśmy dane.battery
służy do zapisania napięcia zasilającego. Jest to liczba całkowita i np. 2986 należy rozumieć jako napięcie 2,986 V.status
może służyć do przekazania informacji o tym, że tinyBrd nie był w stanie poprawnie nawiązać łączności z czujnikiem temperatury. Gdy wysłana zostanie wartość inna niż 0, będziemy wiedzieć, że coś poszło nie tak i odczyt nie jest wiarygodny.payload
tu będziemy przekazywać temperaturę.- kolejne dwa pola zawierają dane, które pozwalają odbiorcy rozeznać się czy dane docierają kompletne - są to pola
seq
(numer kolejny pakietu) iretry
(która próba doręczenia konkretnego pakietu)
Konfiguracja
W setup
kolejno są wywoływane funkcje niezbędne do startu modułu.
void setup()
{
data.id = 6;
data.status = temperature_setup();
byte address[3] = {3,4,5};
Radio.begin(address,100);
}
W pierwszym kroku ustawiamy ID jakiego będzie używał nasz tinyBrd podczas wysyłania danych. Jeżeli masz więcej niż jedno urządzenie nadające w sieci, jest to metoda na ich rozróżnienie.
temperature_setup
odpowiada za skonfigurowanie czujnika temperatury. Jeżeli będziesz chciał podłączyć jakiś własny czujnik, to tutaj musisz wstawić kod niezbędny do uruchomienia czujnika. Wartość zwrócona przez tę funkcję będzie wysyłana do modułu centralnego, jako pole status
. W ten sposób będzie mógł on stwierdzić czy dane wysyłane przez sensor są po jego poprawnej inicjalizacji.
W przykładzie użyty został czujnik DS18B20. Komunikuje się on przez protokół OneWire, jeżeli tinyBrd nie nawiąże komunikacji podczas startu, temperature_setup
zwróci kod błedu i taki będzie wysyłany do odbiornika. W skrócie - jeżeli wartośc pola status
jest różna od 0, wówczas odbiornik wie, że dane wysyłane przez moduł nie są poprawne.
Konfiguracja modułu NRF odbywa się przez polecenie Radio.begin(address,100);
. Używany tutaj adres, to adres pod jakim tinyBrd może odbierać dane. W naszym przykładzie nie robimy tego, więc jego wartość nie ma znaczenia, musi być tylko różna od adresu modułu centralnego (ten podamy podczas wysyłania). Liczba 100 to numer kanału na którym nadaje tinyBrd. Numer ten musi się zgadzać z numerem kanału używanym przez odbiornik.
Główna pętla
W loop
są trzy części. Pierwsza gromadzi dane do wysłania, druga wysyła dane, a trzecia przechodzi w tryb uśpienia i zapewnia powrót z niego.
void loop()
{
//get data
if (data.status) {
data.payload=0;
} else {
data.payload = temperature_read();
}
data.battery = batteryRead();
//send data
radioWrite(data);
//go to sleep
Radio.off();
sleep(15000);
}
Pierwszy if
sprawdza czy czujnik został zainicjowany poprawnie i jeżeli tak (data.status
równe 0), odczytuje temperaturę wywołując funkcję temperature_read
(o niej za chwilę).
Następnie zapisujemy wartość napięcia zasilającego. batteryRead
to funkcja odczytująca napięcie zasilania. Dzięki temu można okreslić czy sensor może jeszcze pracować, czy czas wymienić baterie. Funkcja ta pochodzi z biblioteki Battery, która wchodzi w skład Nettigo tinyBrd Core.
Jakie napięcie oznacza konieczność wymiany baterii? To zależy od użytych elementów. Sam tinyBrd wraz z modułem radiowym będzie poprawnie pracował nawet przy zasilaniu 1,9 V. W naszym przykładzie używamy do pomiaru temperatury DS18B20. Wg noty katalogowej wymaga on napięcia 3 V do pracy i to powinno być napięcie, przy którym wymienimy baterie. Jednak z naszej praktyki wynika, że większość czujników DS pracuje poprawnie zasilane już 2,5 V.
Mając zgromadzone dane, tinyBrd je wyśle, wywołując funkcję radioWrite
. Po wysłaniu danych nie pozostaje nic jak przejść w tryb uśpienia. Najpierw wyłaczamy nadajnik (Radio.off()
) a następnie cały procesor przechodzi w tryb uśpienia. sleep
przyjmuje jako argument liczbę milisekund. Na tyle czasu tinyBrd pozostanie w uśpieniu. Pobór prądu w takim trybie jest na poziomie 9-10 µA - naprawdę niedużo.
Czas uśpienia nie jest precyzyjny! Czas podany funkcji sleep
nie jest dokładną wartością. W naszym przykładzie wpisaliśmy 15 sekund, ale rzeczywisty czas uśpienia będzie się nieznacznie różnić.
Wysyłanie danych
Funkcja radioWrite
realizuje wysyłanie danych zapisanych w strukturze. Sam interfejs biblioteki Radio przyjmie dowolną strukturę (o rozmiarze max 32 bajty) i ją wyśle do odbiornika. My tutaj chcemy kontrolować nieco dokładniej, co się dzieje z transmisją.
Samo wysyłanie danych to po prostu:
Radio.write(addressRemote,data);
addressRemote
to 3 bajtowa tablica zawierająca adres odbiorcy, data
to dowolna struktura do wysłania (nie większa niż 32 bajty).
Po wywołaniu tej funkcji procesor na module NRF zaczyna nadawać dane do odbiorcy, a program wykonuje się dalej. Radio.flush()
pozwala nam sprawdzić jaki status ostatniej transmisji. I tak, jeżeli zwróci nam RADIO_WAITS
, znaczy, że jeszcze nie skończyło nadawać. RADIO_LOST
oznacza, że mimo określonej liczby prób nie zostały dane doręczone. RADIO_SENT
znaczy, że dane zostały wysłane i moduł NRF odebrał potwierdzenie od odbiorcy.
Tutaj mała uwaga - RADIO_LOST
znaczy dokładnie tyle, że nie zostało odebrane potwierdznie doręczenia. Czyli może być tak, że dane nie dotarły do odbiorcy, ale możliwy jest też inny scenariusz. Dane dotarły do odbiorcy, tylko potwierdznie nie zostało odebrane przez nadawcę.
Dla zwiększenia niezawodności, gdy program dostanie odpowiedź RADIO_LOST
, wywoływana jest ponownie funkcja radioWrite
, ze zwiększonym licznikem retry
. Proces wysyłania danych jest powtarzany:
switch(Radio.flush()){
case RADIO_SENT:
data.seq++;
return;
case RADIO_LOST:
radioWrite(data,retry+1);
return;
}
Na początku funkcji mamy test:
if (retry == 5) {
//we have failed transmit..
data.seq ++;
return;
}
Gdy funkcja jest wywoływana 6 raz z rzędu (bo retry
liczymy od 0) już nie próbuje wysłać kolejny raz, tylko wychodzi z funkcji. Kontrola wykonania programu jest przekazywana do miejsca gdzie zostało wywołane radioWrite
,
Przebieg dla nieudanej transmisji
loop
: radioWrite(dane,0)- RADIO_LOST -> wywołanie radioWrite(dane,1)
- RADIO_LOST -> wywołanie radioWrite(dane,2)
- RADIO_LOST -> wywołanie radioWrite(dane,3)
- RADIO_LOST -> wywołanie radioWrite(dane,4)
- RADIO_LOST -> wywołanie radioWrite(dane,5)
- * transmisja uznana za nieudaną, wracamy z pierwszego
if
- * transmisja uznana za nieudaną, wracamy z pierwszego
- powrót
- RADIO_LOST -> wywołanie radioWrite(dane,5)
- powrót
- RADIO_LOST -> wywołanie radioWrite(dane,4)
- powrót
- RADIO_LOST -> wywołanie radioWrite(dane,3)
- powrót
- RADIO_LOST -> wywołanie radioWrite(dane,2)
- powrót do loop
- RADIO_LOST -> wywołanie radioWrite(dane,1)
- przechodzimy w uśpienie
Przebieg dla transmisji wysłanej za trzecim razem
loop
: radioWrite(dane,0)- RADIO_LOST -> wywołanie radioWrite(dane,1)
- RADIO_LOST -> wywołanie radioWrite(dane,2)
- RADIO_SENT -> transmisja zakończona, powrót
- powrót
- RADIO_LOST -> wywołanie radioWrite(dane,2)
- powrót do loop
- RADIO_LOST -> wywołanie radioWrite(dane,1)
- przechodzimy w uśpienie
Gdy wywołujsze funkcję z niej samej (tak robimy z radioWrite
) nazywa się to rekurencją. Przy tej metodzie ważne jest zagwarantowanie sobie, że w końcu przestaniemy wywoływać sami siebie. Dlatego ten warunek na początku funkcji jest konieczny.
Odczyt temperatury
Odczyt temperatury odbywa się w funkcji temperature_read()
, która po prostu wykorzystuje naszą bibliotekę do DS18B20 w wersji dla ATtiny.
Czego się nauczyliśmy
tinyBrd jest elastycznym narzędziem, świetnie nadającym się do budowy własnych czujników bezprzewodowych. Ten artykuł powinien uzmysłowić Wam, że modyfikacja kodu tinyBrd by zaimplementować własne rozwiązania nie jest trudna.
Po przeczytaniu tego artykułu powinniście wiedzieć:
- jak wygląda struktura programu dla tinyBrd
- gdzie modyfikować podstawowe parametry gotowego kodu
- jak w jednej sieci wysyłać dane z kilku sensorów
- jak odczytać napięcie zasilające, które tinyBrd raportuje
- gdzie dodać kod jeżeli chcecie zaimplementować własne rozszerzenia