Ta strona używa ciasteczek (plików cookies), dzięki którym może działać lepiej. Dowiedz się więcejRozumiem i akceptuję
Testy SSLW przypadku problemów z działaniem bloga proszę o kontakt na adres web@forbot.pl lub na forum.

Kurs budowy robotów – #6 – Światłolub (sterowanie latarką)

Roboty 04.02.2017 Damian (Treker)

Gdy Arduino nie było jeszcze popularne, dużo początkujących szukało poradników opisujących budowę robotów bez mikrokontrolerów.

Popularnym wyborem był wtedy światłolub, czyli robot, który jechał w kierunku światła. W praktyce oznaczało to możliwość zdalnego sterowania za pomocą latarki!

Nawigacja serii artykułów:
« poprzednia częśćnastępna część »

Kup zestaw elementów i zacznij naukę w praktyce! Przejdź do strony dystrybutora »

W związku z taką popularnością światłolubów nie mogłem pominąć tego projektu podczas pisania kursu budowy robotów. Oczywiście nie będziemy rezygnować z mikrokontrolera, również tym razem sercem naszego pojazdu będzie Arduino UNO!

Czujniki dla światłoluba

Aby robot mógł podążać w kierunku najsilniejszego źródła światła potrzebne są dwa czujniki. Jeden z nich należy zamocować po lewej stronie robota, a drugi po prawej.

Porównanie odczytów pozwoli wykryć kierunek, z którego pada silniejsze światło.

Oczywiście najlepiej w roli prostych czujników oświetlenia sprawdzą się fotorezystory. Elementy te zmieniają swój opór w zależności od ilości padającego na nie światła. Do tej pory korzystaliśmy z nich m.in. podczas kursu podstaw elektroniki (poziom 2) oraz kursu programowania Arduino.

Działanie fotorezystora w praktyce.

W zestawach do kursu budowy robota znajdują się gotowe czujniki analogowe z fotorezystorami, więc nie trzeba przejmować się dobieraniem wartości rezystora do dzielnika napięcia itd.

Zestaw elementów do budowy robota

Gwarancja pomocy na forum dla osób, które kupią poniższy zestaw!

Części pozwalające wykonać ćwiczenia z kursu budowy robotów dostępne są w formie gotowych zestawów! W komplecie znajdują się elementy mechaniczne (w tym koła i silniki), baterie oraz shield!


Kup w Botlandzie »

Podłączenie czujników

Czujniki, których użyjemy do budowy światłoluba widoczne są na poniższym zdjęciu. W zależności od momentu zakupu zestawu mogą one delikatnie różnić się kształtem lub kolorem. Na pewno będą jednak miały na swoim pokładzie fotorezystor!

Czujniki z fotorezystorami.

Płytki z czujnikami należy zamocować z przodu robota. Można to zrobić w różnych konfiguracjach. Dla lepszego efektu warto jednak rozstawić je stosunkowo daleko od siebie. Podczas dalszych testów będzie wykorzystywane następujące ustawienie (na dwóch wysokich dystansach):

Prowadząc przewody należy uważać,
aby nie zaplątały się w koła i nie blokowały ich ruchów.

Czujniki podłączamy do gniazd, których używaliśmy podczas poprzedniego odcinka kursu, czyli ADC_L oraz ADC_R. Podczas wpinania wtyczki należy się upewnić, czy robimy to we właściwą stronę. Najlepiej sprawdzić, czy przewód od masy (GND) trafi we właściwe miejsce na płytce.

Podczas tego projektu będziemy wykorzystywać czujniki w najprostszej formie, czyli bez założonej zworki (między diodą, a fotorezystorem). Można ją jednak na chwile założyć dla testu. Jeśli całość jest podłączona poprawnie, to dioda będzie świecić na niebiesko:

Pamiętaj, aby podczas dalszych eksperymentów opisywanych
w tym artykule zworka między diodą, a fotorezystorem była zdjęta!

Test czujników przez UART

Zacznijmy od prostego programu, który sprawdzi działanie czujników. Oczywiście bazujemy na szkicach, które powstały wcześniej. Czujniki są podłączone pod te same piny, w które wcześniej podpinaliśmy krańcówki. Dodajemy nowe definicje, aby nie myliły nam się czujniki (i ich strony):

Tym razem wejścia będą odczytywane analogowo, więc NIE deklarujemy ich jako wejścia typu INPUT_PULLUP. Bazowy szkic programu powinien wyglądać następująco:

Na początku warto sprawdzić, czy czujniki działają poprawnie. Posłużymy się komunikacją przez UART, aby co 250 ms wysyłać do komputera informacje o aktualnym stanie czujników:

Po jego uruchomieniu w monitorze portu szeregowego powinny ukazać się aktualne odczyty. Im ciemniej nad danym czujnikiem (zasłonięcie ręką), tym wskazywana wartość będzie większa.

Podgląd wartości czujników światła.

Wiemy, że światłolub, który lubi światło… powinien jechać w stronę gdzie jest go więcej, czyli tam gdzie czujnik pokazuje niższą wartość. Oczywiście można porównywać powyższe wartości, jednak na takim podglądzie będą one cały czas mało czytelne.


Zdecydowanie łatwiej będzie, jeśli obliczymy różnicę odczytanych wartości. Nowa pętla loop() powinna wyglądać następująco:

Teraz odczyty będą łatwiejsze do interpretacji. Jeśli wynik jest dodatni, to znaczy, że po prawej stronie robota jest jaśniej (i tam mamy pojechać). Jeśli wynik będzie ujemny, to skręcimy w lewo. Co więcej im większa będzie liczba (pomijając znak) tym większa różnica będzie między czujnikami, czyli można skręcać szybciej.

Podgląd różnicy między czujnikami.

Łatwo skojarzyć to z osią liczbową, na której wartości ujemne są po lewej stronie, a dodatnie po prawej. Gdy nasza różnica będzie w okolicy 0, to robot może jechać prosto, a gdy różnica będzie odbiegać od 0, tym reakcja pojazdu musi być mocniejsza.

Zachowanie robota uzależnione od wyliczonej różnicy (wartości na osi przykładowe).

Podążanie za światłem „w miejscu”

Na początku najlepiej rozpocząć od testu w miejscu. Niech robot obraca się w kierunku silnego źródła światła. Tak najłatwiej będzie wykryć ewentualne pomyłki typu zamiana stron.

Przykładowa realizacja tego zadania widoczna jest poniżej. Oprócz pomiaru wartości, w pętli loop() liczymy różnicę i w zależności od jej wartości kręcimy się w lewo lub w prawo:

Pora chwycić w dłoń latarkę, telefon z latarką lub inne źródło punktowego światła i zacząć testy! Przykładowy efekt działania tego programu widoczny jest poniżej:

Wersja 1: Prosty program Światłoluba

Wystarczy teraz rozbudować trochę warunek, aby robot podążał już sam za światłem. Tak, jak było zaznaczone na osi liczbowej, dla wartości bliskich zera można jechać prosto.

Tutaj przyjąłem zakres dla jazdy prosto od -50 do 50.
Oczywiście polecam testować też inne wartości!

Działanie robota widoczne jest poniżej:

Zadanie dodatkowe 6.1

Dodaj procedurę startową, w której robot szybko miga diodą podłączoną do pinu 13, a dopiero po oświetleniu z bliska jednego czujnika zaczyna 3 razy pikać buzzerem i dopiero rusza. W ramach rozwiązania zadania oprócz kodu umieszczajcie w komentarzach swoje filmy!

Wersja 2: Płynna jazda Światłoluba

Powyższy kod był prosty, ale robot nie zachowywał się najlepiej. Na zmiany światła reagował nagłymi obrotami. Oczywiście program można rozbudować o kolejne warunki, które wprowadzają kilka stanów pośrednich. Powinno to złagodzić jazdę robota.

Na tym etapie kursu możemy zrobić to zdecydowanie lepiej!

Skrajne wartości zmiennej różnica pojawiają się dość rzadko. Oczywiście zależy to również od warunków, w których testuje się robota. Przy bardzo ciemnym pomieszczeniu i bardzo mocnej latarce na pewno częściej osiągniemy wartości skrajne.

W moim przypadku różnica najczęściej wahała się w granicy +/- 300.

Dodałem więc dwie definicje, które określają dopuszczalne wartości zmiennej:

Po zbliżeniu latarki bardzo blisko robota możliwe było przekroczenie tych zakresów. Aby uniknąć problemów warto zabezpieczyć się przed tym odpowiednimi warunkami. Tuż po obliczeniu wartości zmiennej różnica dodałem fragment sprawdzający, czy nie wychodzi ona poza przyjęty zakres:

Dzięki temu, w skrajnych przypadkach, gdy zmienna przyjmie wartość poza zakresem (np. 350), to zostanie ona ustalona na bezpieczne 300. Mechanizm ten przyda się też podczas testów robota.


Jak już było mówione, wartość zmiennej różnica informuje nas o „sile” z jaką robot powinien zacząć skręcać. Najlepiej więc wykorzystać tę informację i bezpośrednio przełożyć ją na obroty silników. Idealnie sprawdzi się tutaj funkcja map()!

Dzięki tej linijce w zmianaPredkosci zapisana będzie wartość z zakresu od -40 do 40. Wartość taką można już przełożyć na prędkości silników. Teraz wystarczy wyliczoną zmianę dodać do prędkości jednego z silników, a odjąć od drugiego:

Wpisane tutaj „30” jest prędkością z jaką robot będzie jechał prosto (gdy odczyty z czujników będą do siebie zbliżone). Zarówno ta „30”, jak i zakres od -40 do 40 dobrałem podczas testów.

Podczas dobierania parametrów prędkości pamiętaj, aby w skrajnym przypadku
ich suma nie przekroczyła 100
(bo 100% to maksymalna prędkość silników).
Tutaj maksymalnie wykorzystamy 30 + 40 = 70% prędkości silników.

Cały kod programu dostępny jest poniżej:

Działanie układu w praktyce przedstawione jest na poniższym filmie. Jak widać zachowanie robota jest teraz znacznie łagodnejsze. Odpowiednie skalowanie różnicy w odczytach czujników pozwala odpowiednio dobierać „siłę skrętu” z jaką powinien reagować robot. Teraz można nawet pokusić się o jazdę slalomem po torze przeszkód!

Zadanie dodatkowe 6.2

Sprawdź, jaki wpływ na zachowanie robota mają zmiany:

  • zakresu zmiennej różnica (w artykule było od -300 do 300),
  • zakresu prędkości dodawane/odejmowanej do silników (w artykule było od -40 do 40).

Jak i dlaczego obliczana jest prędkość silników?

Może nie każdy będzie od razu wiedział, dlaczego obliczoną wartość dodałem do jednego silnika, a odjąłem od drugiego. Najlepiej rozpatrzeć to na przykładzie. Załóżmy, że zdecydowanie silniejsze światło jest po prawej stronie robota, co sprawiło, że zmienna różnica przyjęła wartość 300.

Po jej przeskalowaniu, w zmiennej zmianaPredkosci znajdzie się wartość 40. Jeśli teraz dodamy ją do prędkości lewego silnika (30+40=70), a odejmiemy od prędkości prawego silnika (30-40=-10), to robot skręci w prawo – czyli dokładnie tam gdzie powinien.

Analogicznie dla przeciwnej sytuacji (silne światło po lewej stronie) w zmiennej różnica mamy -300, co po przeskalowaniu da nam -40. Dodając tę wartość do prędkości silnika lewego otrzymamy teraz 30 + (-40) = -10. Natomiast dla silnika prawego 30 – (-40) = 70, czyli robot skręci w lewo.

Przy okazji warto wspomnieć, że zastosowane tu rozwiązanie
jest uproszczonym regulatorem proporcjonalnym („P” w skrócie „PID„).

Podsumowanie

Jeśli wszystko poszło dobrze, to każdy z Was ma już na swoim koncie najpopularniejszego robota, poczciwego światłoluba! Chyba każdy przyzna, że zdalne sterowanie pojazdem za pomocą latarki, to niecodzienne rozwiązanie.

Jeśli chcecie zobaczyć, jak można zbudować takiego robota bez Arduino
(i bez możliwości regulacji ustawień), to odsyłam do odpowiedniego poradnika.

W kolejnym artykule zajmiemy się równie popularnymi robotami typu LineFollower, które potrafią poruszać się po specjalnie przygotowanej trasie bez niczyjej pomocy! Na razie, w ramach zadania domowego zachęcam każdego do wyregulowania swojego światłoluba, dobrania nowych prędkości i nagrania krótkiego wideo z jazdy Waszej konstrukcji!

Autor kursu: Damian (Treker) Szymański

Kup zestaw elementów i zacznij naukę w praktyce! Przejdź do strony dystrybutora »

Powiadomienia o nowych, darmowych artykułach!

Komentarze

leepa79

22:34, 07.02.2017

#1

Zadanie 6.1 - oczywiście w moim, pokrętnym, stylu :) Zdradzę Wam sekret - staram się wykonać zadania tylko z pomocą tych kursów, tzn. mam przygotowane materiały do nauki, ale póki kursy trwają staram się korzystać tylko z nich. Książki po kursie :) Chociaż ostatnio miałem problem przy kursie Arduino II z default: w case: :) bo nie było przykładu na lekcji. Chyba czas na posiłkowanie się źródłami zewnętrznymi bo po napisaniu tego zadania przed oczami pojawił mi się mem z kotkiem "Andrzej to jeb... :) Zatem przedstawiam kod:

#define L_PWM 5

#define L_DIR 4

#define R_PWM 6

#define R_DIR 9

#define PWM_MAX 165

#define R_LIGHT_SENSOR A0

#define L_LIGHT_SENSOR A1

#define BUZZER 10

#define LED 13

int bip = 0; //Zmienna na potrzeby 'bipania'

void setup() {

//Konfiguracja pinow od mostka H

pinMode(L_DIR, OUTPUT);

pinMode(R_DIR, OUTPUT);

pinMode(L_PWM, OUTPUT);

pinMode(R_PWM, OUTPUT);

pinMode(LED, OUTPUT); //Konfiguracja LED

//Konfiguracja pozostalych elementow

pinMode(BUZZER, OUTPUT);

digitalWrite(BUZZER, 0); //Wylaczenie buzzera

}

void loop() {

int odczytLewy = analogRead(L_LIGHT_SENSOR);

int odczytPrawy = analogRead(R_LIGHT_SENSOR);

int roznica = odczytLewy - odczytPrawy;

migLed();

stopMotors();

while (odczytPrawy < 600 || odczytLewy < 600 ){

if (bip < 3){

BUZZASTER();

bip++;

}else{

JAZDA();

}

}

}

void leftMotor(int V) {

if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia)

V = map(V, 0, 100, 0, PWM_MAX);

digitalWrite(L_DIR, 0); //Kierunek: do przodu

analogWrite(L_PWM, V); //Ustawienie predkosci

} else {

V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku

V = map(V, 0, 100, 0, PWM_MAX);

digitalWrite(L_DIR, 1); //Kierunek: do tyłu

analogWrite(L_PWM, V); //Ustawienie predkosci

}

}

void rightMotor(int V) {

if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia)

V = map(V, 0, 100, 0, PWM_MAX);

digitalWrite(R_DIR, 0); //Kierunek: do przodu

analogWrite(R_PWM, V); //Ustawienie predkosci

} else {

V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku

V = map(V, 0, 100, 0, PWM_MAX);

digitalWrite(R_DIR, 1); //Kierunek: do tyłu

analogWrite(R_PWM, V); //Ustawienie predkosci

}

}

void stopMotors() {

analogWrite(L_PWM, 0); //Wylaczenie silnika lewego

analogWrite(R_PWM, 0); //Wylaczenie silnika prawego

}

void migLed() {

digitalWrite(LED, 1);

delay(200);

digitalWrite(LED, 0);

delay(200);

}

void BUZZASTER() {

digitalWrite(BUZZER, 1);

delay(500);

digitalWrite(BUZZER, 0);

delay(500);

}

//Jazde wrzucilem tu zeby miec czysto w loop podczas 'wymyslania' zadania domowego :)

void JAZDA(){

int odczytLewy = analogRead(L_LIGHT_SENSOR);

int odczytPrawy = analogRead(R_LIGHT_SENSOR);

int roznica = odczytLewy - odczytPrawy;

if (roznica > 50) { //Jesli obliczona roznica jest wieksza

leftMotor(30); //To skręcamy w prawo

rightMotor(-30);

} else if (roznica < -50){ //Jesli obliczona roznica jest mniejsza

leftMotor(-30); //To skręcamy w lewo

rightMotor(30);

} else {

//W pozostałych przypadkach jedziemy prosto

leftMotor(30);

rightMotor(30);

}

}

i film

Treker
Autor wpisu
Administrator

23:11, 11.02.2017

#2

leepa79, dałbym sobie rękę uciąć, że odpisywałem na Twoją wiadomość... i teraz nie miałbym czym pisać artykułów... Robot radzi sobie bardzo ładnie. Faktycznie program napisany trochę pokrętnie, ale ważne, że działa! Główna zmiana jakąś bym wprowadził, to przeniesienie procedury startowej do funkcji setup(), bo ten kod z założenia powinien wykonać się tylko raz.

A jak idzie innym programowanie światłolubów?

Przy okazji niestety muszę powiedzieć, że "coś mnie bierze", a wolę się nie rozchorować na najbliższe 2 tygodnie, więc musiałem sobie trochę dziś odpuścić i nie zdążyłem dorobić wszystkich zdjęć do kolejnego artykułu. Nie chcę publikować wybrakowanego, więc będzie drobna zmiana w kalendarzu publikacji. Postaram się od razu ustalić terminy wszystkich pozostałych artykułów. Będę informował o postępach ;)

leepa79

9:11, 14.02.2017

#3

Światłolub otrzymał dodatkowe 'oczy'. Czujnik odległości HC-SR04 i krańcówki z poprzedniej lekcji.

#define L_PWM 5

#define L_DIR 4

#define R_PWM 6

#define R_DIR 9

#define PWM_MAX 165

#define trigPin A4

#define echoPin A3

#define L_SIDE_SENSOR 7

#define R_SIDE_SENSOR A2

#define R_LIGHT_SENSOR A0

#define L_LIGHT_SENSOR A1

#define BUZZER 10

#define LED 13

#define ROZNICA_MIN -300

#define ROZNICA_MAX 300

int bip = 0; //Zmienna na potrzeby 'bipania'

void setup() {

//Konfiguracja pinow od mostka H

pinMode(L_DIR, OUTPUT);

pinMode(R_DIR, OUTPUT);

pinMode(L_PWM, OUTPUT);

pinMode(R_PWM, OUTPUT);

//Konfiguracja pinów HC-SR04

pinMode(trigPin, OUTPUT);

pinMode(echoPin, INPUT);

//Konfiguracja pinow od czujnikow

pinMode(L_SIDE_SENSOR, INPUT_PULLUP);

pinMode(R_SIDE_SENSOR, INPUT_PULLUP);

//Konfiguracja pozostalych elementow

pinMode(BUZZER, OUTPUT);

digitalWrite(BUZZER, 0); //Wylaczenie buzzera

pinMode(LED, OUTPUT);

randomSeed(analogRead(5)); //Inicjalizacja generatora

}

void loop() {

int odczytLewy = analogRead(L_LIGHT_SENSOR);

int odczytPrawy = analogRead(R_LIGHT_SENSOR);

int roznica = odczytLewy - odczytPrawy;

migLed(); //Miganie diody przed jazda

stopMotors();

while (odczytPrawy < 600 || odczytLewy < 600 ){//Kiedy mocniejsze swiatlo

//Bipnij 3 razy

if (bip < 3){

BUZZASTER();

bip++;

}else{

int odczytLewy = analogRead(L_LIGHT_SENSOR);

int odczytPrawy = analogRead(R_LIGHT_SENSOR);

int roznica = odczytLewy - odczytPrawy;

//Pomiar odleglosci

long czas, dystans;

digitalWrite(trigPin, LOW);

delayMicroseconds(2);

digitalWrite(trigPin, HIGH);

delayMicroseconds(10);

digitalWrite(trigPin, LOW);

czas = pulseIn(echoPin, HIGH);

dystans = czas / 58;

//W przod

leftMotor(50);

rightMotor(50);

int los = random(5, 20) * 10; //Dodajemy losowo od 50 do 200 ms obrotu

//Jesli przeszkoda

if (dystans < 15) {

stopMotors();

delay(150);

leftMotor(-40);

rightMotor(-40);

delay(500);

leftMotor(-35);

rightMotor(35);

delay(140+los);

}

if (digitalRead(L_SIDE_SENSOR) == LOW) {

//Jesli przeszkoda po lewej stronie

//Jedz do tylu i w prawo

leftMotor(-40);

rightMotor(-40);

digitalWrite(BUZZER, 1);

delay(700);

leftMotor(50);

rightMotor(10);

digitalWrite(BUZZER, 0);

delay(800);

//Koniec warunku wracamy do jazdy prosto

}

if (digitalRead(R_SIDE_SENSOR) == LOW){

//Jesli przeszkoda po prawej stronie

//Jedz do tylu w lewo

leftMotor(-40);

rightMotor(-40);

digitalWrite(BUZZER, 1);

delay(700);

leftMotor(10);

rightMotor(50);

digitalWrite(BUZZER, 0);

delay(800);

//Koniec warunku wracamy do jazdy prosto

}

if (roznica < ROZNICA_MIN) { //Jeśli roznica mniejsza od dopuszczalnej

roznica = ROZNICA_MIN; //To ustaw najmniejszą dopuszczalną wartość

} else if (roznica > ROZNICA_MAX) { //Jeśli różnica większa od dopuszczalnej

roznica = ROZNICA_MAX; //To ustaw największą dopuszczalną wartość

}

//Przeliczenie odczytow z czujnikow na zmiane predkosci silnikow

int zmianaPredkosci = map(roznica, ROZNICA_MIN, ROZNICA_MAX, -45, 45);

//Dodajemy lub odejmujemy wyliczoną zmianę od prędkosci bazowej

leftMotor(30+zmianaPredkosci);

rightMotor(30-zmianaPredkosci);

}

}

}

void leftMotor(int V) {

if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia)

V = map(V, 0, 100, 0, PWM_MAX);

digitalWrite(L_DIR, 0); //Kierunek: do przodu

analogWrite(L_PWM, V); //Ustawienie predkosci

} else {

V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku

V = map(V, 0, 100, 0, PWM_MAX);

digitalWrite(L_DIR, 1); //Kierunek: do tyłu

analogWrite(L_PWM, V); //Ustawienie predkosci

}

}

void rightMotor(int V) {

if (V > 0) { //Jesli predkosc jest wieksza od 0 (dodatnia)

V = map(V, 0, 100, 0, PWM_MAX);

digitalWrite(R_DIR, 0); //Kierunek: do przodu

analogWrite(R_PWM, V); //Ustawienie predkosci

} else {

V = abs(V); //Funkcja abs() zwroci wartosc V bez znaku

V = map(V, 0, 100, 0, PWM_MAX);

digitalWrite(R_DIR, 1); //Kierunek: do tyłu

analogWrite(R_PWM, V); //Ustawienie predkosci

}

}

void stopMotors() {

analogWrite(L_PWM, 0); //Wylaczenie silnika lewego

analogWrite(R_PWM, 0); //Wylaczenie silnika prawego

}

void migLed() {

digitalWrite(LED, 1);

delay(200);

digitalWrite(LED, 0);

delay(200);

}

void BUZZASTER() {

digitalWrite(BUZZER, 1);

delay(500);

digitalWrite(BUZZER, 0);

delay(500);

}

i film

Teraz jest samodzielnym zwierzakiem ;)

Treker
Autor wpisu
Administrator

16:21, 16.02.2017

#4

leepa79, super - miło widzieć, że ciągle rozwijasz robota :)

Zobacz powyższe komentarze na forum

FORBOT Damian Szymański © 2006 - 2017 Zakaz kopiowania treści oraz grafik bez zgody autora. vPRsLH.