Kursy • Poradniki • Inspirujące DIY • Forum
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.
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.
Gotowe zestawy do kursów Forbota
Komplet elementów Gwarancja pomocy Wysyłka w 24h
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!
Zamów w Botland.com.pl »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!
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):
#define R_LIGHT_SENSOR A0
#define L_LIGHT_SENSOR A1
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:
#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
void setup() {
//Konfiguracja pinow od mostka H
pinMode(L_DIR, OUTPUT);
pinMode(R_DIR, OUTPUT);
pinMode(L_PWM, OUTPUT);
pinMode(R_PWM, OUTPUT);
//Konfiguracja pozostalych elementow
pinMode(BUZZER, OUTPUT);
digitalWrite(BUZZER, 0); //Wylaczenie buzzera
}
void loop() {
}
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
}
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:
#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
void setup() {
//Konfiguracja pinow od mostka H
pinMode(L_DIR, OUTPUT);
pinMode(R_DIR, OUTPUT);
pinMode(L_PWM, OUTPUT);
pinMode(R_PWM, OUTPUT);
//Konfiguracja pozostalych elementow
pinMode(BUZZER, OUTPUT);
digitalWrite(BUZZER, 0); //Wylaczenie buzzera
Serial.begin(9600);
}
void loop() {
int odczytLewy = analogRead(L_LIGHT_SENSOR);
int odczytPrawy = analogRead(R_LIGHT_SENSOR);
Serial.print("Lewa strona: ");
Serial.print(odczytLewy);
Serial.print(" | ");
Serial.print("Prawa strona: ");
Serial.println(odczytPrawy);
delay(250);
}
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
}
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.
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:
void loop() {
int odczytLewy = analogRead(L_LIGHT_SENSOR);
int odczytPrawy = analogRead(R_LIGHT_SENSOR);
Serial.print("Roznica: ");
Serial.println(odczytLewy - odczytPrawy);
delay(250);
}
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.
Ł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.
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:
#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
void setup() {
//Konfiguracja pinow od mostka H
pinMode(L_DIR, OUTPUT);
pinMode(R_DIR, OUTPUT);
pinMode(L_PWM, OUTPUT);
pinMode(R_PWM, OUTPUT);
//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;
if (roznica > 0) { //Jesli obliczona roznica jest wieksza od zera
leftMotor(30); //To skręcamy w prawo
rightMotor(-30);
} else { //Jesli obliczona roznica jest mniejsza od zera
leftMotor(-30); //To skręcamy w lewo
rightMotor(30);
}
}
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
}
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!
#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
void setup() {
//Konfiguracja pinow od mostka H
pinMode(L_DIR, OUTPUT);
pinMode(R_DIR, OUTPUT);
pinMode(L_PWM, OUTPUT);
pinMode(R_PWM, OUTPUT);
//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;
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);
}
}
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
}
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:
#define ROZNICA_MIN -300
#define ROZNICA_MAX 300
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:
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ść
}
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()!
//Przeliczenie odczytow z czujnikow na zmiane predkosci silnikow
int zmianaPredkosci = map(roznica, ROZNICA_MIN, ROZNICA_MAX, -40, 40);
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:
//Dodajemy lub odejmujemy wyliczoną zmianę od prędkosci bazowej
leftMotor(30+zmianaPredkosci);
rightMotor(30-zmianaPredkosci);
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:
#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 ROZNICA_MIN -300
#define ROZNICA_MAX 300
void setup() {
//Konfiguracja pinow od mostka H
pinMode(L_DIR, OUTPUT);
pinMode(R_DIR, OUTPUT);
pinMode(L_PWM, OUTPUT);
pinMode(R_PWM, OUTPUT);
//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;
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, -40, 40);
//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
}
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!
Nawigacja kursu
Autor kursu: Damian (Treker) Szymański
Powiązane wpisy
arduino, kursBudowyRobotow, robot, światłolub
Trwa ładowanie komentarzy...