tinyBrd: deepSleep, czyli poniżej 1 µA

deepSleep-1TinyBrd od samego początku miał mieć zastosowania w energooszczędnych scenariuszach. Właśnie pojawiła się nowa wersja Nettigo tinyBrd Core, która w bibliotece Battery.h ma oprócz znanej już funkcji sleep, nową funkcję – deepSleep.

Jak nazwa sugeruje usypia ona tinyBrd w taki sposób, że pobór prądu jeszcze bardziej spada. deepSleep w odróżnieniu od „zwykłego” sleep wyłącza również układ watchdoga w procesorze ATtiny84. Konsekwencją tego jest to, że ze takiego trybu pracy może wybudzić tylko przerwanie (pinChangeInterrupt wprowadzone w poprzedniej wersji tinyBrd Core). Nie można podać czasu po jakim tinyBrd sam się wybudzi. Jest to pewna niedogodność, która pozwala tylko w niektórych przypadkach skorzystać z tego trybu. Więc po co się nim w ogóle przejmować? Otóż, odpowiedzią jest pobór prądu. O ile w trybie „zwykłego” sleep tinyBrd pobiera między 4 a 5 µA, to w razie użycia deepSleep pobór prądu spada poniżej 1µA !

Gdzie można użyć tego trybu? Otóż wszędzie tam, gdzie tinyBrd ma czekać przez długi czas na jakieś zdarzenie i wysłać o nim informację. By przetestować ten tryb pracy wymyśliłem sobie projekt czujnika zamknięcia bramy garażowej.

Chcę by Raspberry Pi dostawało informację o każdym otwarciu i zamknięciu drzwi garażowych. Potem pomyślimy o tym by te informacje wysyłać do np Domoticza by ten mógł zareagować gdy drzwi są otwarte zbyt długo. Po co taki alarm? Otóż często w garażu znajdują się różne przyłącza wodno-kanalizacyjne. O ile otwarcie drzwi garażu przez długi czas latem nie stanowi potencjalnego zagrożenia to zostawienie na noc otwartych (lub niedomkniętych) drzwi zimą może być nieprzyjemne  w skutkach. Zamarznięta instalacja to jest duży problem, bo zwykle po jej odmrożeniu trzeba się liczyć z kosztowaną naprawą.

OK, więc plan jest taki – zakładamy na drzwi czujnik, mikroprzełącznik typu „krańcówka”, taką jak ta:

krancowkaPod blaszką umieszczony jest zwykły przełącznik. Gdy drzwi docisną blaszkę przełącznik się zewrze. Jeśli dobrze umieścimy przełącznik, okaże się że zamykając/otwierając bramę przełączymy go. Podpinając wyjście przycisku do wejścia tinyBrd na którym będzie zawieszone przerwanie będziemy mogli wybudzić z deepSleep tinyBrd za każdą zmianą stanu przycisku. Krańcówka ma trzy wyjścia. Jedno wspólne (wejście) – oznaczone C  i dwa wyjścia. Wyjście NO jest podłączone do wejścia wspólnego gdy blaszka nie jest dociśnięta a wyjście NC jest podłączone do C gdy blaszka jest dociśnięta.

Na wejściu cyfrowym tinyBrd włączymy tryb INPUT_PULLUP, by nie było przypadkowych zmian stanu. Jednak ma to swoje konsekwencje. Włączając ten tryb po prostu włączamy rezystor między wejście cyfrowe a szynę zasilającą. Dzięki temu gdy wejście jest pozostawione „niepodłączone” (czyli przełącznik jest otwarty i do wejścia nic nie jest przez niego podłączone) jest tam zawsze napięcie Vcc, czyli logiczny stan HIGH. Jednak gdy przycisk w krańcówce zostanie zamknięty, to wówczas (ponieważ tak musimy go podłączyć, by procesor mógł wykryć zmianę stanu) wejście zostanie zwarte z masą (GND). Ponieważ ten rezystor jest cały czas podłączony, przez niego popłynie prąd. Można znaleźć informację że rezystory pullup w AVR mają wartości od 40 do 60 kiloomów. Przy 40k i zasilaniu 3V prąd płynący tam to będzie 3/40k=75 µA. Całkiem sporo jeśli tinyBrd w uśpieniu bierze 1 µA.

Najprostszym rozwiązaniem wydaje się zestawienie tak układu, by wejście było zwarte do masy gdy drzwi są otwarte. Zwykle są one otwarte tylko przez kilkanaście minut dziennie. Dłużej jeżeli latem dzieci wyjmują rowery i nie zamkną drzwi, co im się często zdarza. Ale i tak zaryzykuję i nie będę komplikował układu – po prostu założę że bateria wytrzyma te krótkie okresy większego poboru, bo przez 90-95% czasu będą drzwi zamknięte i pobór będzie poniżej 1 µA.

O tym fakcie (upływie prądu przez rezystor pullup po zwarciu wejścia do masy) trzeba zawsze pamiętać rozważając rozwiązanie waszego projektu. Oczywiście jeśli mówimy o wersjach układów low-power, bo jeśli zasilasz swój układ z sieci – to nie masz się czym przejmować.

Na zdjęciu na początku artykułu widać wyraźnie jeszcze fotorezystor. Po co on jest? Otóż po zamknięciu drzwi tinyBrd ma monitorować czy czasem nie zostało zapalone światło w garażu. Ale o tym jak to robi, będę pisać oddzielnie, teraz tylko wersja „czujnika drzwi”.

Zasilanie tinyBrd

Zasilanie zapewni nam bateria CR2032. By zminimalizować potrzebę użycia kabelków, użyję gniazdo na taką baterię. Jest ono nieco za duże na pole prototypowe na tinyBrd, ale dzięki odcięciu jednej nóżki i użyciu krótkiego mostka mamy podłączone zasilanie a jednocześnie układ pozostaje kompaktowy i nic „nie lata” na przewodach. Żałuję że mam mało zdjęć, ale te powinny wystarczyć by objaśnić pomysł:

Gniazdo ma wyprowadzenia: dwa z „plusem” i jedno z masą. Wycinając jedno z plusem (i zaklejając taśmą na wszelki wypadek) możemy wsadzić gniazdo w pole protytpowe, tak by pin masy był w pinach z masą na polu prototypowym, a krótka zworka doprowadzi plus baterii do Vcc na tinyBrd.

Krańcówka i jej mocowanie

Jak wygodnie podłączyć krańcówkę? No musi już być na przewodzie. Ale nie trzeba lutować do tinyBrd, szczerze polecam złącze śrubowe. Na Nettigo mamy takie złącze w rastrze 2.54mm. Co prawda chińczyk robi je trochę dziwnie, tak że nie da się zestawić ich w długie szeregi, ale 3-5 pinów pokrywają doskonale. Takie złącze jest na tinyBrd powyżej, na zdjęciu (to zielone).

Pozostaje montaż krańcówki na ramie bramy garażowej. Musisz wybrać takie miejsce by krańcówka łapała gdy drzwi są dokładnie zamknięte. Przyłóż ją w kilku miejscach i popróbuj „na sucho” na pewno bez trudu znajdziesz właściwą pozycję. Przyklej/przymocuj. Ja na początek po prostu ją przykleiłem. Jeżeli odpadnie z czasem – poszukam sposobu na trwalsze mocowanie.

Mocowanie krańcówki
Mocowanie krańcówki

Teraz słowo wytłumaczenia – nie jest to zamocowane na klej uniwersalny (aka hot glue). On tylko przez pierwsze 24h ma trzymać, pod spodem jest klej o wysokiej wytrzymałości (to białe) ale on właśnie potrzebuje 24h by związać w pełni. SZCZERZE ODRADZAM KORZYSTANIE Z BŁYSKAWICZNYCH KLEJÓW TYPU KROPELKA. Są one naprawdę niebezpieczne i w mojej rodzinie były przypadki narobienia sobie kłopotów – pomimo, że robiła to osoba dorosła, wystarczy z nimi chwila nieuwagi by napytać sobie biedy. Nie miejcie nawet w domu tego typu klejów.

Nieco wyżej – w miejscu nie narażonym na utrącenie przepadkiem przykleiłem tinyBrd (oczywiście z taśmą izolacyjną na spodzie, by nie było przypadkowego zwarcia).

tinBrd zamontowany na bramie
tinBrd zamontownay na bramie

Arduino IDE w rękę i programujemy

W pobliżu działa Raspberry Pi zbierający dane z czujników temperatury, więc nie będę kombinował i użyję dokładnie takiej struktury jak w poprzednich przykładach.

struct SensorData
{
  byte id;
  long battery;
  byte status;
  float payload;
  byte seq;
  byte retry;
} data;

Struktura w tym przypadku będzie niosła następujące informacje:

  • id nr sensora
  • battery stan napięcia zasilania
  • status – stan krańcówki – (0/1)
  • payload – wartość odczytana z fotorezystora (w tej wersji szkicu tylko ją wysyłamy, w przyszłości kod rozbudujemy o analizę wartości)
  • seq i retry to kolejne numery pakietów by RPi mogło śledzić jakość połączenia

Dobrze, to zacznijmy od pomiaru natężenia światła. Jak powiedziałem – w tej wersji kodu jest to tylko informacja, nie ma żadnych działań podejmowanych przez tinyBrd. W przyszłości, jeżeli stwierdzi zamknięcie drzwi i zapalone światło, odczeka dłuższą chwilę i jeżeli nadal będzie zapalone informację wyśle do RPi. Ale to jeszcze nie teraz. Głównym celem (oprócz monitorowania stanu bramy) jest przetestowanie czasu pracy na CR2032 z deepSleep, dlatego od razu jest kod włączający fotorezystor, by pobór prądu był cały czas zbliżony.

float readLight() {

  pinMode(PHOTOCELL_PWR, OUTPUT);
  digitalWrite(PHOTOCELL_PWR, HIGH);
  delay(2);

  data.payload = analogRead(PHOTOCELL);
  digitalWrite(PHOTOCELL_PWR, LOW);
  pinMode(PHOTOCELL_PWR, INPUT);

}

Fotorezystor jest podłączony w szereg z drugim rezystorem, tworząc dzielnik napięcia. Podobnie jak z pullup, tak długo jak jest zasilany, tak długo płynie przez niego prąd. Ponieważ nie ma potrzeby marnowania prądu na fotorezystor gdy procesor jest uśpiony, to zasilamy go z wyjścia cyfrowego (PHOTOCELL_PWR). Tak długo jak ten pin jest w stanie niskim, nie płynie żaden prąd przez fotorezystor.

Przed pomiarem ustawiamy go w stan HIGH, odczekujemy chwilę. Prawie 1 ms zajmuje temu fotorezystorowi start po powrocie zasilania, więc dlatego delay(2) by zostawić margines błędu. Wartość odczytana jest zwracana jako float – tylko dlatego, że struktura danych odbieranych przez RPi takiej wymaga (wspólny kod  z miernikiem temperatury).

Na koniec wyjście PHOTOCELL_PWR jest ustawiane w stan niski, by prąd nie płynął więcej przez fotorezystor. Ustawienie pinu w tryb INPUT (wg wielu źródeł) powinno prowadzić do oszczędności poboru prądu w trakcie trybu uśpienia. Przyjmuje to na wiarę, szczegółowe testy jak to jest planuję kiedyś  :) zrobić.

Co w setup?

void setup() {
  byte address[5] = {3, 4, 5};
  Radio.begin(address, 100);
  data.id = 20;
  Radio.off();
  pinMode(BUTTON, INPUT_PULLUP);
  attachPcInterrupt(BUTTON, tick, CHANGE);
}

Tradycyjnie uruchamiamy NRFa, przypisujemy id i usypiamy radio. Krańcówka podpięta jest do pinu BUTTON i musi być w trybie INPUT_PULLUP cały czas. Inaczej (jeśli będzie to zwykły INPUT) mogą być przypadkowe wyzwolenia przerwania, które w następnej linii przypisujemy do pinu BUTTON. Funkcja tick jest „symboliczna”. Po pojawieniu się przerwania nie musimy  nic robić, chodzi o to aby  przerwanie spowodowane zmianą stanu przełącznika wybudziło tinyBrd ze snu, dlatego funkcja tick nic nie robi :)

void tick() {
};

Główna pętla loop

Pozostaje omówić funkcję loop:

void loop() {

  readLight();
  data.status = digitalRead(BUTTON);
  data.battery = batteryRead();

  sendData();

  Radio.off();
  deepSleep();
  delay(100);  //debounce


}

Najpierw odczytujemy ilość światła (readLight), potem zapisujemy w polu status stan drzwi (0/1) i wartość napięcia zasilającego. Funkcja sendData wysyła dane do RPi. Korzysta ona z funkcji radioWrite, którą znamy z poprzednich przykładów z DS18B20. sendData powinno upewnić się, że pakiet dotarł. Jeżeli nie otrzyma z RPi potwierdzenia, że dane zostały odebrane, wówczas nie wchodzi w tryb deepSleep, tylko używając funkcji sleep usypia na coraz dłuższe okresy czasu, próbując wysłać dane do RPi.

Po co to? Chodzi o to, by np restart RPi nie spowodował utraty pakietu z informacją o np otwartych drzwiach. Szczerze mówiąc, muszę jeszcze tę funkcję przetestować czy ta cecha jest dobrze zaimplementowana, więc nie będę tym razem opisywał dokładnie jak działa. Zrobię to na wersji finalnej jak już będę pewien, że wszystko gra!

Cały kod

Kod całości można przejrzeć na tym giście albo pobrać w formie pliku .zip: irq-door-sensor

tinyBrd – bardzo low power

Podsumujmy. tinyBrd w takiej konfiguracji działa już dwa tygodnie. Wygląda na to, że wszystkie założenia zostały spełnione. Gdy zbierze się więcej danych pokuszę się o oszacowanie jak długo może ono pracować w takiej konfiguracji na jednej baterii CR2032.

Spis użytych elementów w tym projekcie: