GPIO
GPIO - General Purpose Input/Output czyli interfejs do niskopoziomowego komunikowania się z podzespołami i urządzeniami elektronicznymi. Dzięki zastosowaniu tego typu interfejsu RPi potrafi komunikować się z np. prostym sensorem odległości HC-SR04 (omówionym poniżej), sterować przekaźnikami, diodami LED itd. Dzięki odpowiednim bibliotekom i modułom komunikacja taka może odbywać się z poziomu C, Pythona, Scratcha, Basha i wielu innych języków programowania.
Wybór systemu operacyjnego
Żeby rozpocząć pracę z RPi musimy posiadać kartę SD o pojemności co najmniej 4GB. Na oficjalnej stronie RPi znaleźć możemy pięć dystrybucji GNU/Linux oraz RISC OS przygotowanych specjalnie dla RPi. Istnieje również pseudo dystrybucja "NOOBS". Po rozpakowaniu NOOBS na kartę SD i uruchomieniu z niej systemu użytkownik dostaje możliwość łatwego zainstalowania powyższych systemów operacyjnych. Jednakże metoda ta wymaga podłączenia myszki i klawiatury oraz monitora, nie jest więc wygodna do przygotowania systemu w trybie headless.
Instalacja systemu Raspbian
Jak wieloletni użytkownik systemu Ubuntu, do pracy z RPi wybrałem dystrybucję Raspbian. Samo Ubuntu aktualnie nie działa na PRi więc "najbliższą" dystrybucją jest specjalna wersja Debiana Wheezy czyli wspomniany powyżej Raspbian.
Do przygotowania karty SD z najnowszą wersją systemu Raspbian użyjemy dowolnego innego systemu GNU/Linux:
Po pobraniu obrazu instalacyjnego (np. za pomocą sieci Torrent), warto sprawdzić jego sumę kontrolną. Aby to zrobić należy wykonać polecenie sha1sum wraz z nazwą sprawdzanego pliku, np.
sha1sum 2013-09-10-wheezy-raspbian.zip
Uzyskaną w ten sposób sumę kontrolną należy porównać z umieszczoną na oficjalnej stronie.
W następnym kroku należy rozpakować pobrany plik:
unzip 2013-09-10-wheezy-raspbian.zip
Następnie trzeba ustalić w jaki sposób karta SD jest widoczna w systemie. Najprościej jest użyć polecenia
df -h
i poprzez ocenę wielkości poszczególnych partycji i dysków zanotować ten identyfikator dysku który nas interesuje. W moim przypadku na karcie SD była jedna partycja /dev/sdb1 więc odpowiadający jej dysk to /dev/sdb. Przy dokonywaniu identyfikacji warto się upewnić trzy razy, gdyż jeżeli się pomyliliśmy to w następnym kroku usuniemy nieodwracalnie wszystkie partycje z błędnego dysku. Po identyfikacji odmountowujemy wszystkie partycje z docelowego dysku:
umount /dev/sdb1
Najważniejszy krok to faktyczne wgranie obrazu systemu Raspbian na kartę SD. Robimy to za pomocą polecenia dd z prawami administratora. W moim przypadku wyglądało to tak:
sudo dd bs=4M if=2013-09-10-wheezy-raspbian.img of=/dev/sdb
Proces ten trwa kilkanaście minut.
Po zakończeniu powyższego procesu pozostaje tylko przeniesienie cache używanego do kopiowania danych na kartę SD:
sudo sync
Przygotowaną w ten sposób kartę pamięci możemy od razu wykorzystać w RPi.
Użytkownicy systemu Windows
Istnieje również możliwość przygotowania systemu Raspbian za pomocą systemu Windows. Dla początkujących ta metoda może wydawać się nawet nieco prostsza. Należy pobrać dystrybucję Raspbian tak jak w punkcie 1 powyżej. Następnie za pomocą programu Win32 Disk Imager rozpakowany obraz .img możemy wgrać na kartę SD o konkretnej literze (należy sprawdzić w "Moim komputerze"!).
Pierwszy start Raspbiana
Po umieszczeniu karty SD w czytniku, podłączeniu przewodu sieciowego oraz zasilania, R Pi przywita nas radośnie błyskającymi diodkami. Przygotowany przez nas wcześniej system jest skonfigurowany aby pobrać adres IP z serwera DHCP routera do którego jest podłączony. W moim przypadku był to adres 192.168.1.3 co widać na poniższym rysunku: Zamiast jednak używać numeru IP który będzie prawdopodobnie inny, lepiej jest użyć nazwy hosta czyli raspberrypi. Dzięki temu połączenie z RPi będzie wyglądać następująco:
ssh pi@raspberrypi
gdzie pi to domyślny użytkownik, raspberrypi to nazwa komputera do którego się łączymy. Domyślne hasło o które zostaniemy poproszeni to raspberry, a ssh to sposób na bezpieczne łączenie się ze zdalnymi komputerami.
Po poprawnym zalogowaniu się na RPi, zostaniemy przywitani "znakiem zachęty":
pi@raspberrypi ~ $
oraz informacją, że powinniśmy przeprowadzić jeszcze konfigurację poprzez:
sudo raspi-config
Uruchomimy w ten sposób programik pozwoli nam w prosty sposób zmodyfikować podstawową konfigurację. Główny ekran wygląda następująco:
Kilka opcji wydaje się być szczególnie przydatnych na samym początku:
Expand Filesystem - opcja ta spowoduje, że po restarcie systemu partycje zostaną rozszerzone do rozmiaru karty pamięci, a nie do rozmiaru zdefiniowanego w zainstalowanym obrazie systemu.
Internationalisation Options - tutaj możemy dostosować np. strefę czasową poprzez "Change Timezone".
Advanced Options - tutaj warto skorzystać z opcji "Update" żeby zaktualizować raspi-config
Do nawigacji używamy klawiszy strzałek, klawisza "Tab" oraz "Enter". Po dokonaniu stosownych zmian najlepiej uruchomić system ponownie poprzez polecenie
sudo reboot
Dodatkowe informacje
Raspbian korzysta z systemu repozytoriów takich jak repozytoria Debiana, można więc używać identycznych narzędzi to aktualizacji systemu i instalacji dodatkowego oprogramowania:
sudo aptitude update # aktualizujemy listę dostępnego oprogramowania
sudo aptitude safe-upgrade # aktualizujemy zainstalowane uprzednio pakiety
sudo aptitude install byobu # instalujemy pakiet __byobu__ w celu uzyskania "potężniejszego terminala"
Obsługa sensora odległości HC-SR04
Jednym z łatwiejszych czujników do obsługi jest czujnik odległości HC-SR04. Działa on na tej samej zasadzie którą wykorzystują nietoperze do poruszania się w ciemności. Polega ona na emisji fali dźwiękowej, odebrania jej echa od odbitego przedmiotu i wyznaczaniu odległości na podstawie czasu jaki fala potrzebowała na powrót.
Dzielnik napięcia
Sensor odległości HC-SR04 został przygotowany do pracy z napięciem 5V. RPi co prawda ma piny w złączu GPIO służące jako zasilanie 5V, jednak jeśli chodzi o odczyt sygnałów nie wolno podawać na piny wejściowe napięć w logice 5V. W drugą stronę (tzn jeżeli Raspberry ma wysłać sygnał do urządzenia pracującego w logice 5V) jest to bezpieczne i dopuszcalne. My jednak musimy odczytywać odpowiedź z sensora, więc musimy zastosować jakiś konwerter.
Na szczęście w naszym przypadku, można łatwo obejść ten problem stosując zwykły dzielnik napięcia. Potrzebne będą nam do tego dwa dodatkowe rezystory w proporcji 1:2. Ja akurat pod ręką miałem 1K i 2,2K. Schemat logiczny oraz widok płytki stykowej podłączenia sensora wraz z dzielnikiem napięcią znajduje się poniżej.
Więcej o komunikacji między układami pracującymi w logice 3.3V i 5V możesz przeczytać tutaj
Kod w Pythonie
Gdy już przygotowaliśmy Raspbiana oraz zbudowaliśmy dzielnik napięcia możemy przystąpić do pracy z czujnikiem. Do obsługi czujnika wybrałem język programowania Python gdyż jest on łatwy i przystępny dla początkujących.
Wysłanie sygnału
Sensor HC-SR04 został zaprojektowany w taki sposób, że wysyła sygnał dźwiękowy kiedy na wejściu zostanie mu dostarczony odpowiedni stan logiczny (wysoki) przez odpowiednio długi czas (>10us). Robimy to w następujący sposób:
GPIO.output(GPIO_wyzwalacz, True) # ustawienie stanu wysokiego na odpowiednim pinie
time.sleep(0.00001) # odczekanie 10us
GPIO.output(GPIO_wyzwalacz, False) # zmiana stanu tego samego pinu na niski
Rejestracja czasu
Wiemy, że pin wyjściowy z sensora będzie wskazywał stan niski (0) dopóki nie otrzyma sygnału zwrotnego czyli echa. Wiemy też, że będzie wskazywał stan wysoki (1) tak długo ile wynosi czas od nadania do powrotu. Możemy więc pokusić się o dwie pętle while. Pierwsza pętla będzie odczytywała czas początkowy tak długo jak na pinie jest stan niski. Jeżeli rozpocznie się stan wysoki, przejdziemy do pętli drugiej z czasem ostatniego odczytu stanu niskiego. W pętli drugiej mamy analogiczną sytuację - czas zakończenia będzie odczytywany tak długo jak na pinie utrzymuje się stan wysoki. Jeżeli powróci z powrotem do niskiego, w zmiennej stop przechowywana będzie ostatnia odczytana wartość.
while GPIO.input(GPIO_rejestrator)==0:
start = time.time()
while GPIO.input(GPIO_rejestrator)==1:
stop = time.time()
Wyznaczenie odległości
Jeżeli mamy już dwie wartości czasu, możemy wyznaczyć odległość dokonując kilku prostych kalkulacji:
czas_trwania = stop-start
odleglosc_calkowita = czas_trwania * 34320 # prędkość dźwięku w cm/s
odleglosc = odleglosc_calkowita / 2 # czas obejmował drogę w obydwie strony, więc należy podzielić go na 2
Wypisywanie uzyskanej odległości w terminalu
Ostatnim elementem jest np. wypisanie uzyskanej wartości w terminalu. Można się dodatkowo pokusić o stworzenie instrukcji warunkowej która poinformuje nas o tym, że coś się zbliżyło, czyli, że np. ktoś przeszedł przez drzwi.
print "Odleglosc to : %.1f [cm]" % odleglosc
if odleglosc < 50:
print "Ktos przeszedl!"
Sprawy techniczne
Aby powyższy kod dało się wykonać, konieczne jest zaimportowanie dwóch modułów Pythona
import time
import RPi.GPIO as GPIO
Moduł time pozwala nam na łatwe pobieranie czasu z systemu operacyjnego. Moduł RPi.GPIO jest z kolei specjalnym modułem do pracy z GPIO. Aby rozpocząć pracę z GPIO musimy zadecydować czy będziemy korzystać z numeracji pinów RPi czy mikroprocesora. Ja wybrałem pierwszą opcję, gdyż pozwoli to na stosowanie tej samej numeracji przy różnych wersjach RPi:
GPIO.setmode(GPIO.BOARD)
Następnie przypisujemy nazwy numerom pinów i decydujemy czy mają pracować w trybie wejścia (input) czy wyjścia (output):
GPIO_wyzwalacz = 16
GPIO_rejestrator = 18
GPIO.setup(GPIO_wyzwalacz,GPIO.OUT)
GPIO.setup(GPIO_rejestrator,GPIO.IN)
Aby ustawić pin wyjścia w stan niski, czyli logiczne 0 - fałsz (False):
GPIO.output(GPIO_wyzwalacz, False)
Przeciwieństwem będzie stan wysoki, czyli logiczne 1 - prawda (True)
Rezultat działania
Poniżesze wideo prezentuje Raspberry razem z HC-SR04 w akcji:
Założenie jest takie, że przy odległości odczytanej z czujnika mniejeszej niż 30 cm ma zostać zapalona dioda. Jak widać jeżeli przeszkodę (kołyskę) ustawimy na granicznej wartości, odczyty nie są stabilne powodując, że dioda się raz zapala a raz gaśnie.
Problem z pomiarem czasu w Pythonie
Po uruchomieniu powyższego kodu pierwszą rzeczą którą można zauważyć to dość spory rozrzut wyników. Generalnie 8-9/10 pomiarów wskazuje mniej więcej tą samą odległość. Jednakże zdarzają się jeden bądź dwa pomiary odstające od średniej dość znacząco. A wiadomo, że obiekt ani czujnik się nie poruszył. Czy wynika to ze słabej jakości czujnika? Nie. Problem leży w sposobie w jakim nasz skrypt jest wykonywany. A raczej w sposobie w jakim Python zarządza pamięcią. W skrócie: język programowania z raczej "luźnym" zarządzaniem pamięcią działający na dodatek wewnątrz "dużego" systemu operacyjnego zawsze będzie podatny na małe, ale jednak, wariacje w czasie wykonywania. A w naszym przypadku akurat czas jest fundamentem z którego wyliczamy odległość.
Jeżeli wynik pomiaru jest dla nas absolutnie krytyczny to być może warto skorzystać ze wskazówki twórcy modułu GPIO dla Pytnona: "If you are after true real-time performance and predictability, buy yourself an Arduino http://www.arduino.cc !", która mówi nam w zasadzie tyle, że jeżeli zależy nam na powtarzalności i pracy ze stabilnymi pomiarami czasu to powinniśmy zainwestować w Arduino.
Ale tak na prawdę to jeszcze nie jest aż tak źle. Do niektórych zastosowań możemy pokusić się o zastosowanie pewnych sztuczek. Możemy np. dokonać 10-pomiarów, odrzucić wyniki skrajne a resztę uśrednić. Zwiększy to nieco stabilność pomiaru.
Listing całego kodu użytego w przykładzie:
#!/usr/bin/python
import time # dla operacji czasowych
import RPi.GPIO as GPIO # do pracy z GPIO
import sys # do wyswietlenia wersji Pythona
# Podstawowe informacje:
print "Wersja Pythona:"
print sys.version
print "Wersja Raspberry Pi:"
print GPIO.RPI_REVISION
print "Wersja modulu RPi.GPIO:"
print GPIO.VERSION
# uzywanie numeracji pinow z plytki RPi
GPIO.setmode(GPIO.BOARD)
# zgodnie z rysunkiem
GPIO_wyzwalacz = 16
GPIO_rejestrator = 18
GPIO_dioda = 22
# Przygotowanie pinow
GPIO.setup(GPIO_wyzwalacz,GPIO.OUT) # Trigger
GPIO.setup(GPIO_rejestrator,GPIO.IN) # Echo
GPIO.setup(GPIO_dioda,GPIO.OUT) #dioda
# Ustawienie wyzwalacza na 0V
GPIO.output(GPIO_wyzwalacz, False)
GPIO.output(GPIO_dioda, False)
# Czas dla modulu:
time.sleep(1)
try:
while True:
print "Ctrl+C aby przerwac!"
# Wysylanie impulsu 10us do wyzwalacza:
GPIO.output(GPIO_wyzwalacz, True)
time.sleep(0.00001)
GPIO.output(GPIO_wyzwalacz, False)
#start = time.time()
while GPIO.input(GPIO_rejestrator)==0:
start = time.time()
while GPIO.input(GPIO_rejestrator)==1:
stop = time.time()
# Obliczanie czasu pomiedzy wyslaniem sygnalu a jego odebraniem
czas_trwania = stop-start
#print czas_trwania
# Powyzszy czas pomnozony przez predkosc dzwieku w cm/s da nam
# odleglosc_calkowita jaka przebyl impuls
odleglosc_calkowita = czas_trwania * 34320
# Odleglosc od obiektu bedzie wiec polowa tej odleglosci:
odleglosc = odleglosc_calkowita / 2
#time.sleep(1)
print "Odleglosc to : %.1f [cm]" % odleglosc
if odleglosc < 30:
print "Ktos przeszedl!"
GPIO.output(GPIO_dioda, True)
time.sleep(0.1)
else :
GPIO.output(GPIO_dioda, False)
time.sleep(0.1)
except KeyboardInterrupt:
print "Pa!"
GPIO.cleanup()