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:

  1. 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.

  2. W następnym kroku należy rozpakować pobrany plik:

     unzip 2013-09-10-wheezy-raspbian.zip
    
  3. 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
    
  4. 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.

  5. 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:

Router Netii
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:

config1

Kilka opcji wydaje się być szczególnie przydatnych na samym początku:

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

Płytka stykowa

Schemat logiczny

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()