Włącznik zasilania do Raspberry Pi – pilot IR

Brak wyłącznika w Raspberry Pi dla wielu osób jest poważnym mankamentem tej platformy. W trakcie pisania jakiegoś kodu, czy ogólnie podczas prac prowadzonych na Raspberry zwykle nie jest to duży problem. I tak logujesz się na na Raspberry i kończąc pracę możesz wydać komendę sudo poweroff.

Jednak gdy projekt jest już skończony zazwyczaj okazuje się, że aby wyłączyć Raspberry sam musisz włączyć swój komputer, by móc się na nim zalogować i wydać właściwą komendę?

Czemu nie odłączyć RPi po prostu od prądu?

W domyślnych konfiguracjach system operacyjny działający na RPi może w każdej chwili dokonywać jakiś zapisów w systemie plików (logi, działanie składowych systemu). Gwałtowne odcięcie zasilania w takim momencie prędzej czy później prowadzi do uszkodzenia danych na karcie microSD i Raspberry przestanie startować.

Idealnym rozwiązaniem byłby dodatek do Raspberry, pozwalający na bezpieczne odłączenie zasilania, oraz na ponowne włączenie zasilania. Może to być przycisk, ale równie dobrze może to być pilot do telewizora. Nie ukrywam, w wypadku media center zwłaszcza ponowne włączanie przez odpięcie i przypięcie kabla zasilającego jest irytujące.

Sami zbudujemy urządzenie

W przypadku media center, jest szansa, że niedaleko leży jakiś pilot, który ma dużo nieużywanych przycisków. Zwykle producent telewizora oferuje dodatkowe funkcje sterowania innymi urządzeniami tego samego producenta. Jeżeli nie masz ich, to wciśnięcie takiego przycisku nie powoduje żadnej akcji.

Wystarczy zbudować układ, który może nauczyć się kodu IR i  w reakcji na wybrany kod wyłączać zasilanie, odcinając je sterując tranzystorem. Ponieważ Raspberry nie może być wyłączone tak od ręki, więc zanim nasze urządzenie odetnie zasilanie, musi poinformować Raspberry o planowanej przerwie w zasilaniu. Wystarczy wysterować jedno z wejść GPIO, a prosty skrypt działający na Raspberry wyda komendę sudo poweroff. Nasze urządzenie musi odczekać pewien czas i może odłączyć zasilanie. Włączenie Raspberry (po kolejnym odbiorze kodu z pilota) będzie polegać na prostym przywróceniu zasilania.

Czego potrzebujemy

Po pierwsze – mikrokontroler. My w naszym prototypie użyliśmy tinyBrd, którego serce to ATtiny84. Mniejsze procesory raczej nie będą się nadawać, bo potrzeba przynajmniej 512 bajtów pamięci RAM i EEPROM.

Do odbierania sygnału z pilota potrzebny jest odbiornik TSOP4838, sterować zasilaniem będziemy z wykorzystaniem tranzystora MOSFET STP16NF06L.

Oprócz tego potrzebować będziemy kilku rezystorów (220 Ω, 10 kΩ), diody do sygnalizacji stanów, przyciski, przewody, płytkę prototypową. Słowem wszystko co powinien mieć na wyposażeniu fan DIY.

Z nietypowych rzeczy, to potrzebujemy czegoś do podłączenia zasilania do Raspberry Pi. Ponieważ ma ono wtyk micro USB, to możemy odciąć kable USB i przylutować się do właściwych kabelków. Drugim rozwiązaniem jest użycie adapteru wtyku micro USB. Podobnie z zasilaniem naszego układu. Albo skorzystamy z drugiej części obciętego kabla micro USB albo użyjemy adapteru gniazda micro USB, lub szybkozłączki do wtyku zasilającego 5.5/2.1 (zależy to głównie od rodzaju zasilacza).

Nasz testowy układ w całej krasie
Nasz testowy układ w całej krasie

Przejdźmy do konkretów

Sygnał IR – można znaleźć wiele projektów w sieci pokazujących jak dekodować/wysyłać sygnały IR. Nas interesuje tylko to pierwsze i skorzystaliśmy z kodu na https://learn.adafruit.com/ir-sensor/using-an-ir-sensor jako startera.

Jak działa odbiornik IR? Jest to sygnał cyfrowy, czyli składa się z 0 i 1. Tyle że, 0 i 1 nie są reprezentowane przez stałe wartości sygnału. W Arduino jesteś przyzwyczajony że 0 to 0V a 1 to ok 5V. Nadając sygnał IR dioda nadawcza może świecić albo nie. To mogło by być nasza 1 i 0. Ale.. po pierwsze diody IR podczas nadawania pobierają zwykle dużo prądu (w porównaniu do diod działających w świetle widzialnym). Jak pobierają dużo prądu to i grzeją się bardziej. Gdybyśmy nią świecili przez dłuższy czas, przegrzeje się i ulegnie zniszczeniu. Drugi powód jest taki, że światło IR występuje powszechnie w otaczającym nas  świecie. Gdyby odbiornik reagował na sam poziom światła, to np słońce mogłoby włączyć twój TV, albo otworzyć bramę. Nie spodobałoby Ci się to :)

Dlatego sygnał IR jest nadawany z użyciem modulacji. 0 to faktycznie brak sygnału, ale jedynka to sygnał nadawany z przerwami o określonej częstotliwości. Czyli jeżeli sygnał 1 ma być przez 200 ms, to jest to zestaw impulsów (sygnał/brak sygnału) o określonej częstotliwości przełączania (modulacji).

W ten sposób dioda nadawcza się nie przegrzewa a i w otaczającej nas przyrodzie nie występują źródła takiego sygnału, więc nic przypadkiem nie włączy TV. By nie męczyć się odczytywaniem takiego sygnału przez procesor korzysta się modułów reagujących na taki sygnał a nie zwykłych fototranzystorów działających w paśmie podczerwonym. My użyjemy TSOPXXXX, które ma na wyjściu logiczne 0 gdy nie ma sygnału i 1 gdy jest sygnał modulowany 38kHz.

Główna rola mikrokontrolera to czekanie na sygnał IR i jego zapisanie. Zostaje zapisany w pamięci RAM i w zależności od trybu pracy – albo porównanie ze wzorcem z EEPROM albo zapisanie sygnału jako wzorzec.

Kod całości znajdziesz na : https://gist.github.com/netmaniac/3e0aaac20fb6e8bebf47

Odczyt sygnału robi  funkcja readPulses, logika  jej działania jest następująca:

Funkcja w pętli skanuje stan wyjścia z odbiornika TSOP, jeżeli jest wysoki to zlicza jak długo jest wysoki. Gdy sygnał trwa bardzo długo i w tablicy zapisane są jakieś sygnały, to przechodzi do wyświetlenia rezultatów (printpulses) lub do zapisu w EEPROM (save2EEPROM). Podobnie jest ze stanem niskim. Zapisywany jest czas jego trwania, jeżeli przekracza definiowany czas maksymalny to wywoływana jest printpusles lub save2EEPROM.

Po każdej zmianie sygnału zapisywana jest wartość, jak długi był stan wysoki i jak długo niski i wskaźnik currentpulse (określa miejsce zapisu w tablicy) jest zwiększany.

Funkcja pritnpulses (pamiętajmy to jest przerabiany kod z przykładu Adafruit i nazwa funkcji jest odziedziczona :) ) tak naprawdę ma zakomentowane wyświetlanie całej zgranej tablicy, natomiast wyświetla tylko parę informacji diagnostycznych i sprawdza czy sygnał odczytany przez odbiornik jest taki sam jak zapisany w tablicy wzorcowej w EEPROM.

Funkcja save2EEPROM zapisuje ostatnio odczytaną tablicę w pamięci EEPROM. Korzysta z biblioteki Storage będącej częścią naszego Nettigo tinyBrd Core. W komórce EEPROM o adresie 0 zapisywany jest rozmiar tablicy, tzn ile impulsów zostało odczytanych z naszego odbiornika. W kolejnych zapisywane są odczytane dane. Dzięki pierwszej wartości przy porównywaniu sygnałów wiemy gdzie się zapisany sygnał kończy w pamięci EEPROM, tak by nie odczytać przypadkowych wartości.

Przyjrzyjmy się funkcji setup. Poza standardowymi ustawieniami dotyczącymi wejść/wyjść, odczytujemy jak duża jest tablica (ile impulsów zostało zapisanych) w EEPROM. Zapamiętujemy tę wartość w savedSize do późniejszych porównań (odczyt w funkcji readFromEEPROM). Również w setup dokonujemy sprawdzenia czy wciśnięty został przycisk SETUP_BUTTON, jeżeli tak, układ przechodzi w tryb programowania – będzie cały czas czekał na sygnał, gdy go odbierze zapisze go w EEPROM. Trzeba procesor zresetować (nie trzymając już SETUP_BUTTON) by wystartował i rozpoczął normalną pracę. W trybie normalnym procesor czeka na sygnał pasujący do zapisanego w pamięci EEPROM, po którym następuje zmiana stanu zasilania.

Porównanie sygnału odbywa się w funkcji isEqual zwracające 0 gdy jest różnica a 1 gdy sygnały są identyczne. Kiedy są sygnały różne? Na pewno gdy jest różna liczba impulsów, dlatego najpierw porównujemy ilość odczytanych par impulsów z rozmiarem tablicy w EEPROM. Jeśli są takie same następuje porównanie każdej pary czasów trwania impulsów. Nie porównujemy ich twardo, tylko zostawiamy sobie margines błędu. Ze względu na ‘pływający’ zegar tinyBrd bez zewnętrznego oscylatora, pomiar czasu nie jest bardo precyzyjny. W naszych eksperymentach wybraliśmy aż 20% poziom dozwolonego błędu i nie trafiliśmy na problem false positive, czyli stwierdzenia że został wciśnięty właściwy przycisk choć na pilocie wybrany został inny.

 

Jak dokładnie porównujemy? Dla każdego czasu trwania impulsu wysokiego/niskiego wyliczamy różnice między wartością odczytaną z EEPROM a zapisaną w tablicy w RAM (zmienne d1 i d2) oraz dopuszczalny w wartościach bezwzględnych, stanowiący 20% czasu trwania impulsu odczytanej z EEPROM. Jeżeli któryś z błędów jest mniejszy niż różnica, porównanie sygnałów jest przerywane i zwracana jest odpowiedź: sygnały różne.

Gdy porównaliśmy całe tablice i nie ma różnic (zbyt dużych) zwracamy wartość – sygnały identyczne.

Jak wyłączyć Raspberry?

Pozostaje omówić kwestię wyłączenia. O ile samo wyłączenie elektrycznie to po prostu zmiana napięcia na bramce tranzystora, to zanim to zrobimy musimy powiadomić o tym Raspberry, używając portu GPIO. Domyślnie porty GPIO są w stanie wysokim. Gdy wyślemy na niego sygnał zero, Raspberry zacznie procedurę wyłączenia (sudo poweroff).

Zaczniemy od części elektrycznej. Zanim tinyBrd odetnie napięcie przez zmianę stanu POWER_PIN, najpierw zmieni stan POWER_INFO z wysokiego na niski. Następnie odczeka ustalony czas (POWER_OFF_DELAY) i dopiero wyłączy zasilanie zamieniając stan pinu POWER_PIN. Procedura przywrócenia zasilania jest prostsza – wystarczy zmienić stan POWER_PIN na wysoki. Wszystko to dzieje się w funkcji flipPower.

Nim jednak podłączymy pin tinyBrd POWER_INFO do GPIO (wybraliśmy GPIO4) na Raspberry musimy rozwiązać jedną, bardzo ważną kwestię. Skoro tinyBrd jest zasilany z 5V, również stan wysoki dla tinyBrd oznacza 5V. Podanie takiego napięcia bezpośrednio na pin GPIO Raspberry doprowadzi do jego uszkodzenia. Raspberry działa w logice napięć 3.3V i nie toleruje napięć 5V na wejściach.

Dlatego do między POWER_PIN a masę wstawimy dwa rezystory o większej wartości (użyliśmy 10k) w połączeniu szeregowym a pin GPIO4 podłączamy do punktu między rezystorami. Taki układ nazywa się dzielnikiem napięcia. Ponieważ rezystory są takie same napięcie w punkcie połączenia jest połową napięcia podanego na oba rezystory. W ten sposób napięcie 5V zostaje skonwertowane na 2.5V, co jest wystarczająco blisko napięcia 3.3V by RPi ‘pomyślało’ że to jest stan wysoki.

Kompletny schemat układu

Mając podłączony pin POWER_INFO do Raspberry pozostaje napisanie prostego skryptu, który będzie monitorował stan GPIO4. Aby nie komplikować, użyjemy skryptu w tzw powłoce (ang shell), którego kod wygląda tak:

#!/bin/bash

sudo echo 4 > /sys/class/gpio/export
while ( true ); do
   state=`cat /sys/class/gpio/gpio4/value`;
   printf "\r$state   ";
   if [ $state -eq 0 ]; then
      echo "Power OFF!"
      sudo poweroff
      sleep 30
   fi
   sleep 1;
done

W pierwszym kroku, włączamy dostęp do pinu GPIO4 z poziomu shella, następnie w nieskończonej pętli odczytujemy plik value który zawiera stan pinu. Jeżeli jest to stan 0 wykonujemy komendę poweroff. Jeżeli jest to 1, to czekamy 1 sekundę by nie obciążać procesora RPi i pętla wykonuje się dalej.

Pozostaje uruchamiać automatycznie nasz skrypt. Zapisujemy go w wybranym miejscu na dysku i w pliku /etc/rc.local przed komendą exit 0 dopisujemy:

PEŁNA/ŚCIEŻKA/DO/SKRYPTU &

Znak & na końcu jest bardzo ważny, bez niego proces startu Raspberry zostanie zatrzymany. Np. jeżeli zapisaliśmy skrypt w katalogu domowym użytkownika pi pod nazwą irpower.sh to przed exit 0 dopisujemy:

/home/pi/irpower.sh&

Wszystkie elementy są na miejscu, teraz wyłączamy Raspberry i podłączamy je pod nasz projekt, włączamy i testujemy!

Co dalej?

Rzeczy o które można projekt rozbudować:

  • SETUP_BUTTON może pracować trybie normalnym jako przycisk on/off – jego naciśnięcie może inicjować flipPower
  • ciekawym eksperymentem może być po wyłączeniu zasilania RPi przechodzić w tinyBrd w w uśpienie i wybudzać się tylko co jakiś czas by sprawdzić czy nie ma nadawanego sygnału IR

BOM, czyli lista części

Oto pełna lista części użytych przez nas: