Jeżdżący robot
Od jakiegoś czasu interesuję się modelami zdalnie sterowanymi. Spod mojej ręki wyszło kilka quadrokopterów, a po bezdrożach szaleję od czasu do czasu terenowym samochodem zdalnie sterowanym. O ile jednak pojazdy zdalnie sterowane są ciekawe, to jeszcze ciekawsze są pojazdy autonomiczne – takie, które mogą samodzielnie poruszać się i podejmować decyzje. Postanowiłem więc zbudować jeżdżącego robota.
Założenia
Na wstępie założenia. Zdecydowałem, że robot musi spełniać następujące wymagania:
Robot będzie jeździł na czterech kołach.
Zawieszenie powinno wystarczyć do pokonywania niewielkich przeszkód – to wymaga też odpowiedniego prześwitu pomiędzy podwoziem a ziemią;
Robot musi być programowalny w relatywnie prostym języku;
Konieczne jest zdalne sterowanie – w przypadku, gdyby wymknął się spod kontroli muszę mieć możliwość przejęcia kontroli i powrotu
Nadwozie musi pozwalać na montaż dodatkowych komponentów – serw lub czujników.
Wybieramy części
Pierwsza decyzja, którą musiałem podjąć, dotyczyła podwozia. W internetowych sklepach można znaleźć dużo komponentów, z których można złożyć własne podwozie – od fragmentów ram, czy wręcz gotowych zestawów konstrukcyjnych przez silniki, koła aż do zębatek, z których można samodzielnie składać odpowiednie przekładnie. Ponieważ jednak bardziej interesowało mnie zaprojektowanie części elektronicznej i oprogramowanie robota, zdecydowałem się na gotowe podwozie o nazwie Multi-Chassis 4WD DAGU DG012-ATV.
Mamy tu do czynienia z podwoziem czterokołowym z dosyć wysokim zawieszeniem oraz – co ciekawe – napędem na cztery koła: każde koło obracane jest niezależnym silnikiem. Trzeba uczciwie powiedzieć, że samo podwozie jest rewelacyjne – rama ma dziesiątki otworów, które śrubami M3 pozwalają przykręcić coś praktycznie w dowolnym miejscu; całość jest też dosyć lekka, ale jednocześnie wytrzymała, z 2,5mm grubości aluminium. Co więcej, w ramie znajduje się dedykowany otwór na serwo (rozmiaru, na przykład, TowerPro MG-995). Początkowo cieszyłem się, że będzie można w modelu bardzo łatwo zamontować serwomechanizm, ale szybko mina mi zrzedła: otwór wykorzystywany jest bowiem w innych wersjach podwozia – w wariancie ATV jest on bowiem bardzo skutecznie zasłonięty dwoma przednimi silnikami.
Kolejnym krokiem był wybór jednostki sterującej robotem. Wahałem się pomiędzy Raspberry Pi a Arduino, ale mój wybór padł dosyć szybko na ten drugi, ponieważ Raspberry charakteryzuje się znacznie większym poborem prądu, zajmuje więcej miejsca i na moje potrzeby jest po prostu za bardzo zaawansowane: sterowanie prostego robocika przy pomocy komputera z Linuksem wydało mi się niepotrzebną komplikacją. Dlatego też zaopatrzyłem się w oryginalne Arduino Leonardo – jest niewielkie, funkcjonalne i można je programować przy pomocy zwykłego kabla microUSB, których chyba w każdym domu walają się dziesiątki.
Teraz musiałem rozwiązać problem sterowania silnikami przy pomocy Arduino. Wśród wielu sterowników silników znalazłem DFRobot TB6612 Quad Motor Driver – idealny na moje potrzeby shield do Arduino pozwalający na sterowanie czterema silnikami szczotkowymi.
W kwestii zdalnego sterowania nie miałem większego wyboru – od lat jestem użytkownikiem aparatury FrSky Taranis, a w szufladzie po moich eksperymentach zostało kilka ośmiokanałowych odbiorników FrSky X8R, co w zupełności wystarczyło na moje potrzeby. Taranis to prawdziwy scyzoryk szwajcarski wśród aparatur modelarskich – w pełni programowalny, w odpowiednich warunkach pozwalający na wykorzystanie do 32 kanałów i w relatywnie przystępnej cenie (w porównaniu do podobnych aparatur firm typu Futaba czy Spektrum). Jeżeli ktoś wiąże plany z budową zdalnie sterowanych modeli, czy robotów, może zaopatrzyć się w młodszego brata Taranisa, czyli QX-7, który tylko w niewielkim stopniu ustępuje modelowi X9D.
Aby uczynić projekt nieco bardziej ciekawym, postanowiłem zamontować na robocie czujnik odległości US-015. Ten tani sensor pozwala wykrywać przeszkody w zakresie od 2cm do 4m. Aby zwiększyć możliwości robota, umieściłem go na serwie TowerPro MG-995. Do tak lekkiego montażu jest to nieco przesadzone rozwiązanie, ale dzięki temu robot będzie rozwojowy – na tak dużym serwie będzie można sporo zamocować.
Na koniec zasilanie – mojemu robotowi mocy dostarcza akumulator LiPo Gens Ace 1800 mAh o imponującej wydajności prądowej 40C (czyli możemy z niego przy napięciu 7.4V bezpiecznie pociągnąć 1.8*40 = 72A). Oprócz tego skorzystałem z małego modułu BEC (Battery Eliminator Circuit), który obniża napięcie do 5V – a takie właśnie potrzebne mi jest do zasilenia serwa.
Oprócz tego przyda się trochę drobnicy:
Przewodów połączeniowych m-m, m-ż i ż-ż
Kabli
Śrub M3 o różnych długościach oraz nakrętek
Dystansów aluminiowych i plastikowych
Zipów (trytek)
Płytek aluminiowych – na przykład fragmentów obudowy starego sprzętu
Kawałka kątowego profilu aluminiowego do montażu czujnika odległości
Nie obędziemy się też bez wtyczki 2.1mm do zasilenia Arduino.
Narzędzia
Podczas budowy robota przydały się:
Wiertarka z wiertłami 3mm (śruby) 4mm (otwory do trytek) i 7-8mm (otwory na przewody)
Do wiercenia otworów w aluminium przydaje mi się frez do wiertarki (technicznie nie używa się go do prac ręcznych, ale kto bym się tym przejmował). Oprócz tego korzystam też z małych kamieni szlifierskich, które przyspieszają obróbkę aluminium
Zestaw pilników
Lutownica
Mała wkrętarka (opcjonalnie – przyspiesza montaż niektórych elementów)
Montaż
Na początku montujemy podwozie – zgodnie z instrukcją. Fabrycznie silniki dostarczone są z wtyczkami JST, ale przewody są zbyt krótkie, by wygodnie je podłączyć do sterownika – a poza tym, jeżeli nie chcemy modyfikować sterownika silników – potrzebujemy na końcu każdego kabla gołego przewodu. Pierwotnie skorzystałem z przewodów żeńsko-żeńskich, które przeciąłem na pół i wykorzystałem jako przedłużacze do wtyczek. Znacznie bardziej praktyczne okazało się jednak usunięcie wtyczek i przedłużenie przewodów – w ten sposób można znacznie elastyczniej rozłożyć komponenty robota.
Kolejnym etapem jest przygotowanie dodatkowej płytki, na której zamontujemy Arduino i serwo – na fabrycznym podwoziu jest na to zbyt mało miejsca. Z większego fragmentu aluminium wyciąłem brzeszczotem płytkę o wymiarach 18cm x 13,5cm – są to dokładne wymiary obrysu robota.
W tak przygotowanym górnym podwoziu wycinamy otwór na serwo, nie zapominając o małych otworkach o średnicy 3mm, przy pomocy których przykręcimy serwo do płytki. Ja na początku nawierciłem kilka otworów wiertłami o różnych średnicach, potem skorzystałem z frezu by wyciąć aluminium, później małego kamienia szlifierskiego i na koniec – pilników. Serwo pasuje w wycięty otwór idealnie.
Górny pokład z dolnym połączymy przy pomocy dystansów aluminiowych – podniosą go one nad linię kół, zostawiając jednocześnie miejsce na dosyć spory akumulator. Długo zachodziłem w głowę, w którym miejscu przykręcić dystanse – w oryginalnej obudowie dosyć dużo miejsca zajęte jest przez silniki i pod płytką przykrywającą silniki nie ma miejsca na łby śrubek. Okazało się jednak, że zmieszczą się one pomiędzy silnikiem a przednią i tylną krawędzią podwozia – jest tam akurat tyle miejsca, żeby zmieściły się łby śrub. Dzięki temu montaż jest pewny – górny pokład nie będzie się giął ani chwiał, jest też sporo miejsca na umieszczenie akumulatora.
Przykręcamy do dolnej płytki dystanse, płytkę do podwozia (przeprowadzając wcześniej przez otwory przewody od silników) i w zasadzie całą dolną część mamy gotową.
Plan rozłożenia komponentów na górnym pokładzie jest na poniższym rysunku.
Aby schować plątaninę przewodów (bez której się nie obędziemy), Arduino umieściłem na półeczce podniesionej ponad górny pokład na plastikowych dystansach. Pod spodem można swobodnie zmieścić wszystkie przewody. Pamiętajmy, żeby ostrożnie dokręcać plastikową ramkę, na której leży Arduino, do aluminiowej półki – bardzo łatwo pęka pod naciskiem.
No to czas na plątaninę przewodów. Mamy kilka obszarów do okablowania:
Zasilanie 7.4V (Arduino i silniki)
Zasilanie 5V (Odbiornik RC, serwo)
Przesył danych (odbiornik RC, czujnik odległości i serwo do Arduino)
Na początek lutujemy przewód do wtyczki T-dean – będzie to główny przewód zasilający. Do niego lutujemy wtyk 2.1mm – zasilimy nim Arduino. Następnie lutujemy przewody do BECa i na koniec dwa dodatkowe, luźne przewody, które podłączymy do sterownika silników.
Wtyczkę z BECa podłączamy do nieużywanego kanału w odbiorniku RC – we wszystkich odbiornikach, z których korzystałem, piny 5V i GND są pozwierane, więc nie ma znaczenia, którymi z nich doprowadzimy zasilanie. Aby oszczędzić sobie lutowania, możemy też dzięki temu łatwo zasilić serwo – wystarczy w innym wolnym kanale wpiąć dwa przewody i podłączyć je do linii 5V i GND serwa. Bez obaw – odbiornik jest do tego przystosowany, w latających modelach zasila się w ten sposób serwa sterujące powierzchniami sterowymi.
Mocujemy teraz półkę z Arduino i chowamy plątaninę kabli pod nią. Kiedy to zrobimy, możemy zacząć łączyć piny danych.
Shield sterujący silnikami korzysta z pinów Arduino 3, 4, 5, 6, 7, 8, 11 i 12. Do pozostałych możemy więc podłączać wszystkie komponenty. Najpierw odbiornik RC – wykorzystamy tylko trzy kanały; łączymy je więc przewodami: kanał 1, 2 i 3 z pinami 0, 1 i 2 Arduino. Czujnik odległości podłączamy do pinów 9 (TRIG) i 10 (ECHO). Z uwagi na to, że pobiera on śladowe ilości prądu, możemy zasilić go bezpośrednio z Arduino – osobnymi przewodami łączymy piny 5V i GND.
Serwo mamy już zasilone, ale nikt nim nie steruje. Pojedynczym przewodem łączymy je z pinem 13 Arduino; pamiętajmy, że jest to też pin sterujący diodą LED na płytce.
Część obliczeniowa shielda sterującego silnikami zasilana jest bezpośrednio z Arduino, ale osobno musimy dostarczyć zasilanie do silników. Do tego celu wykorzystujemy dodatkowy kabel, który przylutowaliśmy wcześniej. Z drugiej strony przykręcamy przewody silników (uważając na polaryzację i kolejność silników). Na koniec pozostaje już tylko wpięcie wtyczki do Arduino, by prawidłowo je zasilić.
Schemat połączeń wygląda następująco (orientacyjny, komponenty na schemacie są podobne, ale nie takie same, jakich użyłem):
Gotowy robot prezentuje się następująco:
Programowanie
Możemy teraz zacząć programować naszego robota. Na początek napiszmy prosty programik, który sprawdzi, czy silniki są prawidłowo podłączone:
Sterowanie silnikami
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
const int E1 = 3; // Motor1 Speed const int E2 = 11; // Motor2 Speed const int E3 = 5; // Motor3 Speed const int E4 = 6; // Motor4 Speed const int M1 = 4; // Motor1 Direction const int M2 = 12; // Motor2 Direction const int M3 = 8; // Motor3 Direction const int M4 = 7; // Motor4 Direction void M1_advance(char Speed) { digitalWrite(M1, HIGH); analogWrite(E1, Speed); } void M2_advance(char Speed) { digitalWrite(M2, LOW); analogWrite(E2, Speed); } void M3_advance(char Speed) { digitalWrite(M3, LOW); analogWrite(E3, Speed); } void M4_advance(char Speed) { digitalWrite(M4, HIGH); analogWrite(E4, Speed); } void M1_back(char Speed) { digitalWrite(M1, LOW); analogWrite(E1, Speed); } void M2_back(char Speed) { digitalWrite(M2, HIGH); analogWrite(E2, Speed); } void M3_back(char Speed) { digitalWrite(M3, HIGH); analogWrite(E3, Speed); } void M4_back(char Speed) { digitalWrite(M4, LOW); analogWrite(E4, Speed); } void setup() { for (int i = 3; i < 9; i++) pinMode(i, OUTPUT); for (int i = 11; i < 13; i++) pinMode(i, OUTPUT); pinMode(LED_BUILTIN, OUTPUT); } void loop() { digitalWrite(LED_BUILTIN, HIGH); M1_advance(100); delay(500); M1_advance(0); M2_advance(100); delay(500); M2_advance(0); M3_advance(100); delay(500); M3_advance(0); M4_advance(100); delay(500); M4_advance(0); digitalWrite(LED_BUILTIN, LOW); delay(2000); // Delay 2S } |
Sterownik silników używa czterech pinów do sterowania kierunkiem obrotów silników i kolejnych czterech pinów do sterowania prędkością obrotu. Funkcje M_Advance i M_Back pozwalają łatwo sterować silnikami – powyższy programik powinien uruchomić kolejne silniki na pół sekundy, a potem zrobić przerwę na dwie sekundy – i tak w kółko.
Obsługa odbiornika RC
Odbiornik RC wysyła na każdym kanale sygnał PWM. Skrót ten rozwija się do Pulse Width Modulation – czyli modulacja szerokością pulsu. 55 razy na sekundę na przewodzie sygnału wzbudzane jest i wygaszane napięcie 5V. Czas trwania takiego wzbudzenia przekłada się na wartość sygnału: 1ms oznacza wartość minimalną, 1,5ms – wartość środkową, zaś 2ms – wartość maksymalną. Musimy więc napisać kawałek kodu, który rozpozna wzrost i spadek napięcia i zmierzy czas pomiędzy nimi. W tym celu skorzystamy z wygodnej biblioteki EnableInterrupt ( https://github.com/GreyGnome/EnableInterrupt ) – na potrzeby tego programiku wypinamy serwo z pinu 13 Arduino!
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 |
#include "EnableInterrupt.h" volatile int microsStart = 0; void pin0Rising() { microsStart = micros(); disableInterrupt(0); enableInterrupt(0, pin0Falling, FALLING); } void pin0Falling() { int microsEnd = micros(); int diff = microsEnd - microsStart; if (diff > 1500) { digitalWrite(LED_BUILTIN, HIGH); } else { digitalWrite(LED_BUILTIN, LOW); } disableInterrupt(0); enableInterrupt(0, pin0Rising, RISING); } void setup() { enableInterrupt(0, pin0Rising, RISING); pinMode(LED_BUILTIN, OUTPUT); } void loop() { } |
Powyższy kod działa w następujący sposób:
Na początku ustawia przerwanie, które wystąpi w momencie wzrostu napięcia na pinie 0 – powinna się wtedy wywołać funkcja pin0Rising.
Kiedy to się dzieje, zapamiętujemy liczbę mikrosekund od uruchomienia programu, zdejmujemy przerwanie dla wzrostu napięcia, a w zamian ustawiamy przerwanie dla spadku napięcia na pinie 0.
Kiedy to nastąpi (i wywoła się funkcja pin0Falling), po raz drugi sprawdzamy, ile upłynęło mikrosekund od startu programu; kiedy odejmiemy od tej wartości liczbę zapamiętaną w poprzednim przerwaniu, otrzymamy czas trwania pulsu w mikrosekundach.
Teraz możemy zrobić z otrzymaną wartością co chcemy – testowy programik zapala diodę na Arduino, gdy długość sygnału przekracza 1,5ms (położenie środkowe).
Mierzenie odległości
Trzeci programik „proof-of-concept” testuje działanie czujnika odległości. Również i dla tego typu czujników istnieje gotowa biblioteka, NewPing ( http://playground.arduino.cc/Code/NewPing ). Używa się jej bardzo łatwo – wystarczy poinformować ją tylko, na którym pinie podłączyliśmy pin sygnału wzbudzającego test odległości (TRIG) oraz pin odpowiedzi (ECHO).
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 |
#include <NewPing.h> #define ULTRASONIC_TRIGGER_PIN 9 #define ULTRASONIC_ECHO_PIN 10 #define MAX_DISTANCE 400 NewPing sonar(ULTRASONIC_TRIGGER_PIN, ULTRASONIC_ECHO_PIN, MAX_DISTANCE); void setup() { pinMode(LED_BUILTIN, OUTPUT); } void loop() { digitalWrite(LED_BUILTIN, HIGH); delay(50); digitalWrite(LED_BUILTIN, LOW); delay(50); int ping = sonar.ping_cm(); if (ping < 30) digitalWrite(LED_BUILTIN, HIGH); else digitalWrite(LED_BUILTIN, LOW); delay(200); } |
Tym razem jest jeszcze łatwiej niż poprzednio – tworzymy obiekt NewPing o nazwie sonar, przekazując mu odpowiednie informacje. Teraz możemy korzystać z dostarczanych przez niego metod (na przykład ping_cm), aby otrzymać precyzyjną informację o odległości od przeszkody.
Przykładowy programik miga cyklicznie diodą Arduino; mignięcia są krótkie, jeżeli najbliższa przeszkoda na drodze czujnika jest dalej niż 30cm, a długie – gdy jest bliżej.
Program robota
Poniżej znajduje się kompletny kod robota pozwalający na jego zdalne sterowanie. Dodatkowo robot co pewien czas testuje obszar przed sobą i uniemożliwia ruch silników do przodu, gdy znajduje się zbyt blisko przeszkody.
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
#include <NewPing.h> #include "EnableInterrupt.h" #include "NewPing.h" // Motor constants // Motor1 Speed #define MOTOR_1_SPEED_PIN 3 // Motor2 Speed #define MOTOR_2_SPEED_PIN 11 // Motor3 Speed #define MOTOR_3_SPEED_PIN 5 // Motor4 Speed #define MOTOR_4_SPEED_PIN 6 // Motor1 Direction #define MOTOR_1_DIR_PIN 4 // Motor2 Direction #define MOTOR_2_DIR_PIN 12 // Motor3 Direction #define MOTOR_3_DIR_PIN 8 // Motor4 Direction #define MOTOR_4_DIR_PIN 7 // Ultrasonic constants #define ULTRASONIC_TRIGGER_PIN 9 #define ULTRASONIC_ECHO_PIN 10 #define MAX_DISTANCE 400 // Servo constants #define SERVO_PIN 13 // AI constants #define DISTANCE_THRESHOLD_2 80 #define DISTANCE_THRESHOLD_1 40 // Ultrasonic control NewPing sonar(ULTRASONIC_TRIGGER_PIN, ULTRASONIC_ECHO_PIN, MAX_DISTANCE); // Motor control functions void M1_advance(byte Speed) ///<Motor1 Advance { digitalWrite(MOTOR_1_DIR_PIN, HIGH); analogWrite(MOTOR_1_SPEED_PIN, Speed); } void M2_advance(byte Speed) ///<Motor2 Advance { digitalWrite(MOTOR_2_DIR_PIN, LOW); analogWrite(MOTOR_2_SPEED_PIN, Speed); } void M3_advance(byte Speed) ///<Motor3 Advance { digitalWrite(MOTOR_3_DIR_PIN, LOW); analogWrite(MOTOR_3_SPEED_PIN, Speed); } void M4_advance(byte Speed) ///<Motor4 Advance { digitalWrite(MOTOR_4_DIR_PIN, HIGH); analogWrite(MOTOR_4_SPEED_PIN, Speed); } void M1_back(byte Speed) ///<Motor1 Back off { digitalWrite(MOTOR_1_DIR_PIN, LOW); analogWrite(MOTOR_1_SPEED_PIN, Speed); } void M2_back(byte Speed) ///<Motor2 Back off { digitalWrite(MOTOR_2_DIR_PIN, HIGH); analogWrite(MOTOR_2_SPEED_PIN, Speed); } void M3_back(byte Speed) ///<Motor3 Back off { digitalWrite(MOTOR_3_DIR_PIN, HIGH); analogWrite(MOTOR_3_SPEED_PIN, Speed); } void M4_back(byte Speed) ///<Motor4 Back off { digitalWrite(MOTOR_4_DIR_PIN, LOW); analogWrite(MOTOR_4_SPEED_PIN, Speed); } // PWM control volatile int microsYStart = 0; volatile int microsXStart = 0; volatile int microsZStart = 0; volatile int yMicros = 0; volatile int xMicros = 0; volatile int zMicros = 0; int UScounter = 0; int USlock = 0; // Pin 0 interrupt handling void pin0Rising() { microsYStart = micros(); disableInterrupt(0); enableInterrupt(0, pin0Falling, FALLING); } void pin0Falling() { yMicros = micros() - microsYStart; disableInterrupt(0); enableInterrupt(0, pin0Rising, RISING); } // Pin 1 interrupt handling void pin1Rising() { microsXStart = micros(); disableInterrupt(1); enableInterrupt(1, pin1Falling, FALLING); } void pin1Falling() { xMicros = micros() - microsXStart; disableInterrupt(1); enableInterrupt(1, pin1Rising, RISING); } // Pin2 interrupt handling void pin2Rising() { microsZStart = micros(); disableInterrupt(2); enableInterrupt(2, pin2Falling, FALLING); } void pin2Falling() { zMicros = micros() - microsZStart; disableInterrupt(2); enableInterrupt(2, pin2Rising, RISING); } void setup() { for (int i = 3; i < 9; i++) pinMode(i, OUTPUT); for (int i = 11; i < 13; i++) pinMode(i, OUTPUT); pinMode(ULTRASONIC_TRIGGER_PIN, OUTPUT); pinMode(ULTRASONIC_ECHO_PIN, INPUT); pinMode(SERVO_PIN, OUTPUT); enableInterrupt(0, pin0Rising, RISING); enableInterrupt(1, pin1Rising, RISING); enableInterrupt(2, pin2Rising, RISING); } void loop() { // Eval motor signals int yValue = (yMicros - 1500) / 2; int xValue = (xMicros - 1500) / 2; if (yValue > 260 || yValue < -260) yValue = 0; if (xValue > 260 || xValue < -260) xValue = 0; int leftEngines = constrain(xValue + yValue, -250, 250); int rightEngines = constrain(yValue - xValue, -250, 250); // Check for obstacles every 10th iteration UScounter++; if (UScounter >= 10) { UScounter = 0; int frontDistance = sonar.convert_cm(sonar.ping_median(5)); if (frontDistance != 0 && frontDistance < DISTANCE_THRESHOLD_2) { if (frontDistance > DISTANCE_THRESHOLD_1) USlock = 1; else USlock = 2; } else USlock = 0; } if (USlock == 1) { leftEngines = constrain(leftEngines, -250, 128); rightEngines = constrain(rightEngines, -250, 128); } if (USlock == 2) { leftEngines = constrain(leftEngines, -250, 0); rightEngines = constrain(rightEngines, -250, 0); } if (abs(leftEngines) < 20) { M3_advance(0); M4_advance(0); } else if (leftEngines > 0) { M3_advance(leftEngines); M4_advance(leftEngines); } else { M3_back(-leftEngines); M4_back(-leftEngines); } if (abs(rightEngines) < 20) { M1_advance(0); M2_advance(0); } else if (rightEngines > 0) { M1_advance(rightEngines); M2_advance(rightEngines); } else { M1_back(-rightEngines); M2_back(-rightEngines); } } |
Kilka szczegółów, które pomogą przeanalizować powyższy programik:
- xValue i yValue przechowują położenie drążka w poziomie i pionie w zakresie od -250 do 250;
- USCounter (UltraSonicCounter) to zmienna, przy pomocy której co 10 cykl wyzwalany jest test odległości.
- DISTANCE_THRESHOLD_2 i DISTANCE_THRESHOLD_1 to odległości w centymetrach, przy których podejmowane jest odpowiednie działanie:
- Jeżeli przeszkoda jest pomiędzy 80 a 40 cm od przodu robota, zmniejszana jest maksymalna prędkość obrotu silników do przodu o połowę (robot zwalnia).
- Jeżeli przeszkoda jest bliżej niż 40 cm od przodu robota, maksymalna prędkość obrotu silników do przodu jest zerowana (silniki mogą kręcić się tylko do tyłu, więc robota można obrócić lub cofnąć, ale nie – pojechać do przodu)
- Zmienna USLock decyduje o sposobie ograniczania silników: 1 – spowalniane, 2 – blokowany ruch do przodu, 0 – brak ograniczeń
- sonar.ping_median powoduje, że test odległości wykonywany jest kilkukrotnie, a następnie zwracana jest mediana (wartość środkowa) – ogranicza to błędy pomiaru.
Uczymy się na błędach
Powiedzenie mówi, że człowiek uczy się na błędach, a mądry człowiek – na błędach innych. Oto kilka błędów, które popełniłem – możecie się ich ustrzec.
- Podwozie jest bardzo fajne, ale za drugim razem wybrałbym raczej wersję na gąsienicach. Mało realne jest jeżdżenie takim podwoziem po terenie – piasek, kurz i pył lądują we wnętrzu robota, a autonomiczne poruszanie się po nierównym terenie na razie wykracza poza moje możliwości programistyczne. Poza tym wewnątrz byłyby tylko dwa silniki, co pozwoliłoby na montaż serwa w przeznaczonym dla niego miejscu.
- Podwozie z dwoma silnikami zamiast czterech pozwoliłoby zastosować prostszy sterownik oraz zwolniłoby 4 dodatkowe piny – obecnie mam wykorzystane wszystkie. Alternatywnie można skusić się na Arduino Mega zamiast użytego przeze mnie Leonardo.
- Razem z podwoziem dostarczony jest koszyk na baterie AA. Jest beznadziejny – trzeba się trochę napracować, żeby baterie prawidłowo stykały. A kiedy wszystko się uda, okaże się, że podczas obrotu silniki pobierają tak duży prąd, że powstaje przysiad napięcia i zarówno Arduino jak i odbiornik RC na chwilę tracą zasilanie. Wydajny akumulator Lipo rozwiązuje takie problemy.
- Czujnik odległości radzi sobie dobrze z dużymi obiektami, natomiast słabo z niewielkimi, o nierównym kształcie lub z materiału (ten rozprasza ultradźwięki)
Podsumowanie
W ten sposób zbudowaliśmy platformę, którą możemy teraz oprogramowywać do jazdy samodzielnej. Oprócz kanałów 1 i 2, przy pomocy których sterujemy robotem, pozostaje nam jeszcze kanał 3. Możemy nim przekazać informację, czy robot ma reagować na zdalne sterowanie, czy jechać samodzielnie – w ten sposób odzyskamy nad nim kontrolę, gdy zajdzie taka potrzeba.
(Oksymoron) To zdalnie sterowany czy robot?
Dlaczego oksymoron? Robot może być autonomiczny, może być zdalnie sterowany, może być czymś pośrednim (jak mój – wspomaga sterowanie). Według Wikipedii: “Robot – mechaniczne urządzenie wykonujące automatycznie pewne zadania. Działanie robota może być sterowane przez człowieka, przez wprowadzony wcześniej program, bądź przez zbiór ogólnych reguł, które zostają przełożone na działanie robota przy pomocy technik sztucznej inteligencji.”
Czyli nie jest sterowany w czasie rzeczywistym
Curiosity też nie jest sterowany w czasie rzeczywistym…
Witam jak przegramowac go by sterowac nim za pomoca bluetooth na bibliotece AFMotor.
Czy autko wysterowane do przodu jedzie faktycznie prosto? Robiłem kiedyś analogiczny temat (napęd tylko na przód) i niestety miałem z tym problem, nawet po zastosowaniu enkoderów ciężko było zsynchronizować obroty silników.