Kursy • Poradniki • Inspirujące DIY • Forum
Opisywany robot powstał tylko jako prezentacja możliwego podejścia - nie jest ukończoną konstrukcją. W tej chwili nie posiada możliwości autonomicznego sterowania. Jest więc raczej zdalnie sterowaną platformą, którą można rozwijać poprzez dodanie czujników otoczenia lub analizę obrazu z zamontowanej kamery.
Robot z kamerą - lista elementów
Na początek wymienię elementy składowe omawianego robota.
- Raspberry Pi 2
- Kamerka dedykowana do RPi
- Moduł WiFi (USB)
- RPi Motor Hat, opisywany w recenzji produktów od MSX elektronika
- Silniki Pololu (2 szt.) wraz z kołami
- Kulka podporowa
- Akumulator Li-Po 7.4V
Podstawa robota została wydrukowana na drukarce 3D. To nadal wstępny prototyp:
Po zmontowaniu robot prezentuje się następująco:
Oprogramowanie
Jako system operacyjny wybrałem Raspbiana. Jest to popularna dystrybucja Linuxa dedykowana dla Raspberry Pi, oparta o Debiana. Więcej informacji można znaleźć na stronie projektu.
Pierwszym etapem po instalacji była konfiguracja Wi-Fi - wykonywałem ją z poziomu konsoli, wykorzystując bezprzewodową klawiaturę (na zdjęciu widoczny jeszcze moduł USB od klawiatury). Po skonfigurowaniu WiFi, dalsze prace można było prowadzić przez ssh, więc klawiatura nie była już więcej potrzebna.
Wykorzystanie kamery w robocie
W budowanym robocie wykorzystałem kamerkę dedykowaną, ale na wszelki wypadek testowałem również kamerkę podłączaną przez USB. Obie działały bez problemu, chociaż dedykowana była dużo lżejsza oraz nie zajmowała portu USB.
Po podłączeniu kamery musimy sprawdzić, czy system ją widzi. Najprościej wykonać polecenie:
1 |
ls /dev/video* |
Jeśli zobaczymy urządzenie /dev/video0 to znaczy, że nasza kamerka została wykryta prawidłowo. Moja kamerka USB została wykryta automatycznie, natomiast w przypadku kamerki dedykowanej, konieczne okazało się zainstalowanie modułu jądra bcm2835-v4l2:
1 |
modprobe bcm2835-v4l2 |
Mając kamerę, musimy zastanowić się nad wyborem programu do jej obsługi. Możemy oczywiście napisać własny np. wykorzystując bibliotekę OpenCV, ale na początek najłatwiej jest po prostu udostępnić obraz z kamery przez WiFi.
Na szczęście nie musimy pisać odpowiedniego programu - znajdziemy w internecie gotowe rozwiązania. W pakietach dostarczanych z Raspbianem odnajdziemy program Motion. Pozwala on na udostępnienie obrazu z kamerki przez www, czyli spełnia nasze oczekiwania, jednak nie polecam go - jest strasznie zasobożerny, użycie CPU sięga 100%, a nawet na RPi 2 liczba klatek na sekundę jest zaskakująco niska - 2-3 klatki.
Jeśli zainstalowaliśmy pakiet motion najlepiej jest go odinstalować. Jeśli będzie uruchamiany w tle, może powodować błędy w dalszej konfiguracji.
Instalacja mjpg-streamer
Znacznie lepiej jest wykorzystać program mjpg-streamer. Program działa znacznie szybciej na Raspberry i nie powoduje problemów z obciążeniem procesora, a liczba klatek na sekundę jest znacznie większa.
Niestety nie jest on dostępny jako gotowy pakiet (albo ja takiego nie znalazłem), konieczne jest pobranie go ręcznie. Gotową wersję, dostosowaną do Raspberry pobrać można poleceniem:
1 |
wget http://lilnetwork.com/download/raspberrypi/mjpg-streamer.tar.gz |
Gdy mamy już pogram musimy go skompilować. Najpierw zainstalujemy pakiety, które są do tego niezbędne:
1 2 |
sudo apt-get install libjpeg8-dev sudo apt-get install imagemagick |
Teraz już możemy rozpakować pobrane archiwum oraz wykonać kompilację:
1 2 3 4 |
wget http://lilnetwork.com/download/raspberrypi/mjpg-streamer.tar.gz tar xf mjpg-streamer.tar.gz cd mjpg-streamer/mjpg-streamer make |
Nie jestem entuzjastą kompilacji na Raspberry, ale ten projekt jest na tyle mały, że zajmuje to tylko chwilkę. Teraz możemy uruchomić nasz serwer udostępniający obraz z kamery:
1 |
./mjpg_streamer -i "./input_uvc.so -y" -o "./output_http.so -p 8090 -w ./www" & |
Aby sprawdzić, czy wszystko działa otwieramy przeglądarkę www i wpisujemy adres IP Raspberry. Port na którym działa serwer to 8090, podaliśmy taki w linii poleceń.
W moim przypadku Raspberry Pi miało adres 192.168.1.10, więc otworzyłem w przeglądarce http://192.168.1.10:8090 zobaczyłem wtedy następującą stronę:
Dalej musimy wybrać zakładkę Stream, ponieważ później będziemy zainteresowani samym obrazem. Oczywiście warto popatrzeć na pozostałe możliwości jakie daje mjpg-streamer.
Jak widzimy w podpowiedzi, obraz jest dostępny pod adresem /?action=stream. Możemy to sprawdzić wpisując w przeglądarce adres: http://192.168.1.10:8090/?action=stream
Sterownik silników
Kolejnym elementem bez którego nie zbudujemy robota jest układ sterowania. Do budowy prostego robota wręcz idealnie nadaje się moduł RPi Motor HAT produkowany przez firmę MSX Elektronika.
Podstawowe cechy modułu to:
- dwa popularne mostki TB6612 - datasheet
- układ PCA9685 jako 16-kanałowy PWM - datasheet
- 4 wolne wyjścia PWM np. do sterowania serwomechanizmami
- przetwornica impulsowa do zasilania modułu oraz RPi
Jak widzimy, moduł pozwala nie tylko sterować silnikami, daje też możliwość zasilania całego robota. Dzięki wbudowanej przetwornicy wystarczy podłączyć akumulator, a moduł będzie zasilał własną logikę oraz płytkę Raspberry Pi.
Moduł może być też zasilany z 5V, czyli z płytki RPi - dzięki temu możemy również robota zasilać z USB. Jest to bardzo wygodne rozwiązanie podczas programowania.
Na początek powinniśmy zapoznać się ze schematem płytki oraz sterowaniem silnikami. Poniżej możemy zobaczyć jak mostek H jest połączony ze źródłem sygnału PWM:
Układ PCA9685 jest wyposażony w 16 wyjść PWM oznaczonych CH0-CH15. Jak widzimy wyjścia CH0, CH1, CH14 i CH15 są dostępne na złączu, więc możemy je wykorzystać do rozszerzania możliwości naszego robota, np. do sterowania serwomechanizmami.
Pozostałe wyjścia PWM są podłączone do mostków TB6612. Ciekawostką jest wykorzystanie PWM również do sterowania kierunkiem pracy mostka, czyli jako zwykłe wyjście GPIO. Jest to o tyle dobre rozwiązanie, że RPi ma mało wolnych wyprowadzeń, a w ten sposób wykorzystując i2c można sterować wszystkimi wejściami TB6612. Jako przykład zobaczmy kanał A mostka. Do jego sterowania wykorzystywane są 3 wejścia: PWM_A, IN_A1, IN_A2:
- PWM_A - jest podłączone do wyjścia CH2
- IN_A1 - CH4
- IN_A2 - CH3
PWM_A odpowiada za wypełnienie PWM wyjściwowego sygnału (a więc prędkość obrotową silnika). Linie IN_A1 oraz IN_A2 ustalają kierunek obrotów silnika, są zwykłymi liniami GPIO.
Program w Pythonie
Wykorzystamy Pythona. Programowanie w nim jest bardzo łatwe, a programy nie muszą być kompilowane, co ułatwia tworzenie programu, pozwala też na łatwe eksperymentowanie. Programować można zdalnie logując się przez ssh do Raspberry.
Nie bez znaczenia jest również gotowa biblioteka obsługująca PCA9685. Jej kod znajdziemy tutaj. Sterownik PCA9685 znajdziemy w pliku Adafruit_PWM_Servo_Driver.py.
Pierwszy krok to sprawdzenie, czy i2c działa. Wykorzystamy do tego program i2cdetect, który znajdziemy w pakiecie i2c-tools (jeśli go nie zainstalowaliśmy, najwyższy czas to zrobić).
Aby sprawdzić, czy mamy działający interfejs i2c piszemy:
1 |
ls /dev/i2c* |
Jeśli zobaczymy coś w rodzaju: /dev/i2c-1, oznacza to, że mamy interfejs i2c gotowy do działania. Brak interfejsu może wynikać z braku odpowiedniego modułu. Aby go załadować musimy wydać komendę:
1 |
sudo modprobe i2c-dev |
Teraz powinniśmy i2c odnaleźć bez problemu. Wygodniej jest automatycznie ładować odpowiedni moduł, bez konieczności wydawania polecenia modprobe. Aby moduł był ładowany podczas startu systemu, możemy dodać odpowiedni wpis do pliku /etc/modules:
1 |
sudo sh -c 'echo "i2c-dev" >> /etc/modules' |
Interfejs jest gotowy, możemy sprawdzić, czy Raspberry jest w stanie wykryć płytkę sterownika. Wydajemy polecenie i2cdetect 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
pi@raspberrypi ~ $ i2cdetect 1 WARNING! This program can confuse your I2C bus, cause data loss and worse! I will probe file /dev/i2c-1. I will probe address range 0x03-0x77. Continue? [Y/n] 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: 60 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: 70 -- -- -- -- -- -- -- |
Jak widzimy, moduł jest dostępny pod adresem 60 (hexadecymalnie). Czas napisać prosty skrypt w Pythonie, który uruchomi silnik. Napierw zobaczmy kod, później omówimy jego treść:
1 2 3 4 5 6 7 8 |
from Adafruit_PWM_Servo_Driver import PWM pwm = PWM(0x60) pwm.setPWMFreq(1000) pwm.setPWM(2, 0, 1000) pwm.setPWM(3, 4096, 0) pwm.setPWM(4, 0, 0) |
Pierwsze, co rzuca się w oczy, to mała ilość linijek programu. Tylko 5 + jedna od importu biblioteki.
W pierwszej linijce importujemy klasę PWM z biblioteki Adafruit_PWM_Servo_Driver. Klasa ta odpowiada za obsługę układu PCA9685, w który jest wyposażony nasz moduł. Kolejna linijka to utworzenie obiektu klasy PWM. Jako parametr podajemy adres układu - odczytaliśmy go wcześniej, więc podajemy 0x60. Następnie ustalamy częstotliwość pracy - 1kHz.
Nie jest to najlepsza wartość, może powodować piszczenie silników, ale na początek w zupełności wystarczy.
Ostatnie 3 linijki to właściwe sterowanie silnikiem. Najpierw ustawiamy wypełnienie PWM. Podajemy 1000 jako przykładową wartość - zakres jest od 0 do 4095, powinniśmy więc uzyskać ok. 25% wypełnienia. Następnie ustalamy kierunek pracy silnika. Podanie jako wypełnienia wartości (0, 0) daje wypełnienie 0%, więc na linii CH4 uzyskamy stan niski.
Aby uzyskać ciągły stan wysoki musimy zamienić kolejność parametrów i podajemy (4096, 0). W ten sposób na wyjściu CH3 uzyskamy stały stan wysoki.
Rozwijanie programu
Gdy wiemy jak sterować silnikiem możemy napisać skrypt, za pomocą którego będziemy mogli sterować całym robotem. Skrypt będzie uruchamiany z dwoma parametrami, każdy z zakresu od -4095, do 4095. Pierwszy parametr będzie ustalał kierunek i prędkość lewego silnika, a drugi prawego. Kod programu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#!/usr/bin/python from Adafruit_PWM_Servo_Driver import PWM import sys pwm = PWM(0x60) pwm.setPWMFreq(1000) def left(speed): if speed > 0: pwm.setPWM(2, 0, speed) pwm.setPWM(3, 4096, 0) pwm.setPWM(4, 0, 0) elif speed < 0: pwm.setPWM(2, 0, -speed) pwm.setPWM(3, 0, 0) pwm.setPWM(4, 4096, 0) else: pwm.setPWM(2, 0, 0) pwm.setPWM(3, 0, 0) pwm.setPWM(4, 0, 0) def right(speed): if speed > 0: pwm.setPWM(7, 0, speed) pwm.setPWM(6, 4096, 0) pwm.setPWM(5, 0, 0) elif speed < 0: pwm.setPWM(7, 0, -speed) pwm.setPWM(6, 0, 0) pwm.setPWM(5, 4096, 0) else: pwm.setPWM(7, 0, 0) pwm.setPWM(6, 0, 0) pwm.setPWM(5, 0, 0) left(int(sys.argv[1])) right(int(sys.argv[2])) |
Program zawiera sporo powtórzeń, można więc go trochę udoskonalić. Po pierwsze wypadałoby zebrać definicje kanałów PWM do stałych (albo chociaż zmiennych). Po drugie sterowanie lewym i prawym silnikiem jest prawie identyczne, lepiej byłoby więc nie duplikować kodu.
Poniżej znacznie krótsza wersja (chociaż nie wiem czy czytelniejsza):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#!/usr/bin/python from Adafruit_PWM_Servo_Driver import PWM import sys pwm = PWM(0x60) pwm.setPWMFreq(1000) left_ch = (2, 3, 4) right_ch = (7, 6, 5) def set_speed(ch, speed): if speed > 0: in1, in2 = 4096, 0 elif speed < 0: in1, in2 = 0, 4096 else: in1, in2 = 0, 0 pwm.setPWM(ch[0], 0, abs(speed)) pwm.setPWM(ch[1], in1, 0) pwm.setPWM(ch[2], in2, 0) set_speed(left_ch, int(sys.argv[1])) set_speed(right_ch, int(sys.argv[2])) |
Mając ten program możemy zdalnie sterować robotem z poziomu konsoli:
W tym momencie moglibyśmy podłączyć czujniki i rozwinąć skrypt o autonomiczną pracę naszego robota. Możemy też popracować nad zdalnym sterowaniem obecnej platformy.
Sterowanie robotem z przeglądarki
Używanie konsoli nie jest najwygodniejszą opcją do sterowania robotem. Może też być uciążliwe, gdybyśmy chcieli sterować robotem przykładowo przez smartfon lub tablet. Zamiast tego spróbujemy sterować robotem z poziomu przeglądarki.
Skoro zaczęliśmy programować w Pythonie, możemy wykorzystać serwer www oparty o ten język. Ja wybrałem projekt bottle - na stronie projektu znajdziemy dokumentację oraz poradnik użycia.
Postaram się pokazać jak łatwo jest w Pytonie stworzyć prostą aplikację webową (sterującą robotem). Najpierw piszemy bardzo prosty skrypt (podobny do przykładu ze strony Bottle):
1 2 3 4 5 6 7 |
from bottle import route, run, template @route('/robocik/left/<speed:int>') def index(speed): return template('<b>Left {{speed}}</b>', speed=speed) run(host='192.168.1.10', port=8080) |
Ostatnia linijka to uruchomienie serwera na podanym adresie i porcie. Znacznik @route określa ścieżkę, pod którą będzie dostępna nasza aplikacja. Fragment w nawiasach oznacza parametr (speed, typu int). Nasza aplikacja będzie więc dostępna pod adresem:
http://192.168.1.10:8080/robocik/left/1000
Gdzie 1000 to przykładowa wartość parametru. Program jako odpowiedź wyśle napis przekazany do funkcji template. Rezultat wygląda następująco:
Funkcja index(speed) jest wywoływana za każdym razem, kiedy otwieramy podany wcześniej adres. Możemy w niej wysterować silnik - wcześniej już napisaliśmy odpowiednie funkcje. Nowy skrypt wygląda więc następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#!/usr/bin/python from Adafruit_PWM_Servo_Driver import PWM import sys from bottle import route, run, template pwm = PWM(0x60) pwm.setPWMFreq(1000) left_ch = (2, 3, 4) right_ch = (7, 6, 5) def set_speed(ch, speed): if speed > 0: in1, in2 = 4096, 0 elif speed < 0: in1, in2 = 0, 4096 else: in1, in2 = 0, 0 pwm.setPWM(ch[0], 0, abs(speed)) pwm.setPWM(ch[1], in1, 0) pwm.setPWM(ch[2], in2, 0) @route('/robocik/left/<speed:int>') def index(speed): set_speed(left_ch, speed) return template('<b>Left {{speed}}</b>', speed=speed) run(host='192.168.1.10', port=8080) |
Tak krótki skrypt wystarczy do sterowania silnikiem przez przeglądarkę internetową. Pozostaje jeszcze dodać sterowanie prawym silnikiem oraz dodać stronę główną, tak żeby nie było konieczności wchodzenia ręcznie na strony /left i /right.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#!/usr/bin/python from Adafruit_PWM_Servo_Driver import PWM import sys from bottle import route, run, template pwm = PWM(0x60) pwm.setPWMFreq(1000) left_ch = (2, 3, 4) right_ch = (7, 6, 5) def set_speed(ch, speed): if speed > 0: in1, in2 = 4096, 0 elif speed < 0: in1, in2 = 0, 4096 else: in1, in2 = 0, 0 pwm.setPWM(ch[0], 0, abs(speed)) pwm.setPWM(ch[1], in1, 0) pwm.setPWM(ch[2], in2, 0) @route('/robocik/left/<speed:int>') def left(speed): set_speed(left_ch, speed) return template('<b>Left {{speed}}</b>', speed=speed) @route('/robocik/right/<speed:int>') def right(speed): set_speed(right_ch, speed) return template('<b>Right {{speed}}</b>', speed=speed) @route('/robocik/') def index(): return template('index') run(host='192.168.1.10', port=8080) |
Nasz skrypt jest już gotowy, jednak jeśli spróbujemy otworzyć adres strony głównej sterowania robotem, czyli http://192.168.1.10:8080/robocik/ pojawi się błąd - brakuje szablonu index.
Okazuje się, że funkcja template() może przyjmować dwa rodzaje parametrów: kod HTML, który zostanie bezpośrednio przekazany do przeglądarki, albo nazwę pliku z szablonem strony. Pliki szablonów powinny być przechowywane w podkatalogu views i mieć rozszerzenie .tpl. Jednak są to w rzeczywistości zwykłe pliki HTML. Możemy taki plik utworzyć, a w nim umieścić kod, który będziemy chcieli wyświetlać w przeglądarce:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Remote RPi-Bot</title> <link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css"> <script src="//code.jquery.com/jquery-1.10.2.js"></script> <script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script> <script> function setSpeed(left, right) { $("#sliderL").slider('value', left); $("#sliderR").slider('value', right); } function updateLeft() { var value = $("#sliderL").slider("value"); $("#valueL").text(value); $.get("http://192.168.1.10:8080/robocik/left/"+value); } function updateRight() { var value = $("#sliderR").slider("value"); $("#valueR").text(value); $.get("http://192.168.1.10:8080/robocik/right/"+value); } $(function() { $("#sliderL").slider( { value: 0, min: -4095, max: 4095, change: updateLeft, animate: true }); $("#sliderR").slider( { value: 0, min: -4095, max: 4095, change: updateRight, animate: true }); $("#stop").click(function() { setSpeed(0, 0); }); }) </script> </head> <body> <div> <img src="http://192.168.1.10:8090/?action=stream" /> </div> <div id="sliderL"></div> <div id="valueL">0</div> <div id="sliderR"></div> <div id="valueR">0</div> <div><button id="stop">STOP!</button></div> </body> </html> |
Końcowy efekt - przesuwając suwaczki zmieniamy prędkość poszczególnych silników:
Podsumowanie
Opisywany robot powstał przy okazji recenzowania modułów firmy MSX. Nie jest to w pełni ukończony projekt, raczej wstępny opis który można dalej rozbudować. Bez odpowiednich czujników nazywanie tej konstrukcji robotem jest niewątpliwie pewnym nadużyciem - w sumie jest to zdalnie sterowany pojazd wykorzystujący Raspberry Pi jako kontroler.
Aby zmienić go w prawdziwego robota, należałoby podłączyć czujniki lub dodać przetwarzanie obrazu. Jednak powyższy opis miał na celu pokazanie jak łatwo można przygotować zdalnie sterowaną platformę przy wykorzystaniu modułu RPi Motor HAT, Raspberry Pi oraz języka Python. Mam nadzieję, że pomysły w nim opisane przydadzą się innym osobom w ich konstrukcjach.
Dajcie znać w komentarzach, jeśli jesteście zainteresowani podobną tematyką!
Autor: Piotr (Elvis) Bugalski
Redakcja: Damian (Treker) Szymański
To nie koniec, sprawdź również
Przeczytaj powiązane artykuły oraz aktualnie popularne wpisy lub losuj inny artykuł »
druk 3d, kamera, Raspberry PI, robot
Trwa ładowanie komentarzy...