Przeszukaj forum
Pokazywanie wyników dla tagów 'Freestyle'.
Znaleziono 165 wyników
-
Cześć. 🙂 Przedstawiam wam Small Autonomous Amateur Rover "Romek" - czyli owoc mojej, z założenia kosmetycznej, metamorfozy robota, którego można poznać w jednym z forbotowych kursów. (A ponieważ robiłem tylko w wolnych chwilach, których wielu nie mam, to niczym ciąża - zacząłem z końcem listopada, mamy narodziny z końcem sierpnia). Krótki zarys historyczny: Kilka lat temu miałem fazę, zakupiłem w Botlandzie kilka zestawów do kursów oraz tu i ówdzie trochę rzeczy do szuflady "przydasiów". Życie jednak weryfikuje i przez kilka lat zbierało kurz, aż w zeszłym roku przeszedłem w końcu kurs budowy robotów i kurs Arduino poziom I. Jednak chciałem przede wszystkim sterować robotem przez BT, do tego chciałem pozbyć się w jakiś sposób wężykowania. Odnalazłem info, że komunikacja z BT to w zasadzie komunikacja przez port szeregowy - zapiąłem HC-05, zainstalowałem aplikację Bluetooth RC Car i napisałem switch case pod dane wysyłane z tej apki. Pierwsza rozbudowa "forbociaka" - moduł HC-05 i konwerter poziomów logicznych. Robot jeździł sterowany przez BT i tyle. Podjąłem decyzję, że dokonam modyfikacji zarówno w kodzie, jak i wyglądzie - z tym ostatnim poszedł też warunek, tylko rzeczy z szuflady, żadnych nowych inwestycji - własny czelendż ruszył. Posiadałem podwozie typu T100, które miało silniki z enkoderami - pomyślałem, że super, bo te silniczki z kursu to mogłyby mieć problem z ruszeniem cięższej konstrukcji, ponadto różny typ osi sprawił, że nawet nie próbowałem z nimi rzeźbić. Początkowa radość minęła wraz z przeczytaniem specyfikacji - maksymalny prąd pracy dołączonych silników to 4,5A, zdecydowanie za dużo na shield od Forbota. Przekopywałem szufladę dalej, znalazłem w niej silniczki N20, ale to maluszki, no i oś za mała. Znalazłem także silniki DFRobot o odpowiedniej osi i punktach montażowych. Przeczytałem notę - 7,5V, prąd pracy 50mA, maks. prąd 600 mA, przekładnia 99:1 - zostały wybrane. Nie chciałem mieć elektroniki na zewnątrz, więc potrzeba było też obudowy. Na wstępie wybrałem Kradex Z125 na body i Kradex Z130 na czujnik odległości. Jednak wraz z rozbudową, wybrana obudowa okazała się za ciasna - ledwo się zamykała, a nie założyłem jeszcze wszystkiego, co chciałem. Dodatkowo chciałem odzyskać pewne piny Arduino, które zajmował shield, a których nie potrzebowałem (przycisk i odbiornik IR). Podjąłem decyzję o zmianie obudowy - miałem jeszcze Kradex Z112, która była szersza i nieco wyższa. Poprzednia obudowa wchodziła pomiędzy gąsienice, przy tej musiałem zastosować dystanse, żeby zamocować budę do podwozia i nie kolidować z gąsienicami. Dodałem płaski koszyk na 6 baterii AA, krańcówki dla jazdy autonomicznej, połączone po dwie równolegle - żeby pokryć jak najwięcej czoła i jednocześnie nie odchylać blaszek pod dużym kątem i nie dodawać wąsów z trytytek - i wyłącznik kołyskowy, żeby załączać zasilanie z zewnątrz. Kanapkę Arduino+shield rozdzieliłem i połączyłem je przewodami, odzyskując wspomniane wcześniej piny cyfrowe 2 i 3 oraz uzyskując dostęp do pinu 3,3 V. Obudowę przed dalszymi pracami pomalowałem. Żeby robot nie był tylko sterowanym autkiem, dodałem oczywiście czujnik odległości HC04-SR, czujnik temperatury i wilgotności DHT22 oraz dzielnik napięcia - zbudowany z rezystora 510k i dwóch 68k - dla pomiaru napięcia baterii. Profesjonalny dzielnik amatorski. 😉 Na koniec dodałem LED-y w oprawkach. 4 białe jako światła czołowe, każdy z rezystorem 1k. 2 zielone i 2 czerwone jako światła pozycyjne/nawigacyjne - również z rezystorami 1k. 2 żółte drogowe (już nie żółte, jeszcze nie pomarańczowe) jako światła ostrzegawcze, oczywiście z rezystorami 1k. 1 niebieski jako światło sygnalizujące jazdę autonomiczną, z rezystorem 330 Ohm. Niebieski LED jest zasilany i załączany bezpośrednio z Arduino. Pozostałe LED-y zasilane są z baterii i załączane przez tranzystory NPN BC548. Ostatecznie wyszło trochę makaronu z przewodów, ale nie jest ciasno, obudowa zamyka się dobrze, a ja mam w razie W spis przewodów, a te z górnej części, które są wpięte w Arduino są także opisane na wtykach. :) Ponieważ żółte ledy miałem tylko typu clear i ich światło było widoczne tylko na wprost soczewki, dodałem dyfuzory zrobione ze... słomki. 😄 Romek po montażu waży ok. 1290 g i prezentuje się tak: Oprogramowanie: Założyłem, że chcę się pozbyć delay(), żeby nie kolidować z DHT oraz żeby robot reagował natychmiast na polecenia. Czasami musiałem się naklikać zanim robot zareagował. Czujnik odległości był jedną z prostszych rzeczy. Zastosowałem bibliotekę New Ping, dla której utworzyłem obiekt: NewPing sonar(trigPin, echoPin, MAX_DISTANCE); Sama funkcja wygląda tak: int sonarDystans() { int dystans = sonar.ping_cm(); // Wykonanie pomiaru i zapisanie wyniku w zmiennej distance w centymetrach return dystans; } A jej wykorzystanie na przykład tak: if (sonarDystans() <= doPrzeszkody) { //Jeżeli w zadanej odległości jest przeszkoda obecnyStan = ZATRZYMAJ; //Przejdź do zatrzymania czasStanu = millis(); //Ustaw czas dla timera serwo.attach(SERWO_PIN); //Podłącz serwo } Najwięcej problemów miałem z przepisaniem logiki jazdy autonomicznej. Poziom mojej wiedzy to kurs robota, kurs Arduino I i na coś tam liźnięte o DHT i millisach. Tutaj nawet zatrudniłem forumowiczów do pomocy: Ostatecznie oryginalną logikę wymieniłem na maszynę stanów, gdzie w danym stanie wywołuję funkcję, a dopiero w funkcji mam opisane zachowanie robota. switch (obecnyStan) { case PROSTO: prosto(); break; case ZATRZYMAJ: zatrzymaj(); break; void zatrzymaj() { zatrzymajSilniki(); if (millis() - czasStanu >= 500UL) { //Odczekaj zadany czas obecnyStan = PATRZ_PRAWO; //Przejdź do wykrywania przeszkody po prawej czasStanu = millis(); } } W przypadku krańcówek musiałem dodać flagę dla kolizji, gdyż bez tego po wjechaniu w przeszkodę robot się zatrzymywał. Zapewne zanim zdążył przejść dalej program ponownie docierał do miejsca, gdzie natrafiał co zrobić po załączeniu krańcówki. Dodanie flagi sprawiło, że problem zniknął. boolean kolizjaPrawo = false; if (digitalRead(prawySensor) == LOW && kolizjaPrawo==0) { //Jeżeli przeszkoda po prawej kolizjaPrawo = 1; obecnyStan = COFAJ_PRAWY; czasStanu = millis(); } void cofajPrawy() { lewySilnik(-70); prawySilnik(-70); if (millis() - czasStanu >= 800UL) { obecnyStan = ZATRZYMAJ2; //Przejdź do wykrywania przeszkody po prawej czasStanu = millis(); kolizjaPrawo = 0; } } Samo wejście w jazdę autonomiczną również realizowane jest zmianą flagi. W switch case dle odczytu z seriala/BT poczyniłem toggleswitch. case 'A': jazdaAutonomiczna = !jazdaAutonomiczna; if (!jazdaAutonomiczna) { zatrzymajSilniki(); ekspander.digitalWrite(LEDjazda, 0); serwo.detach(); } break; Nie ukrywam, że obsługę DHT podejrzałem w drugiej części kursu do Arduino, zanim tam dotrę, ale jak już wiemy, unikam delay(). Na podstawie obsługi czujnika z kursu i tego co wiem o millisach, napisałem funkcję po swojemu. Dodałem też korektę odczytów, gdyż względem czujnika referencyjnego temperatura była zawyżana o wilgotność zaniżana. Najpierw próbowałem korekty procentowej, ale ostatecznie wprowadziłem sztywną korektę. Względem czujnika referencyjnego odczyty nie odbiegają o więcej niż 0,5 stopnia i 0,5%. W tym miejscu dodałem także odczyt z ADC, gdzie doprowadziłem wyjście z dzielnika napięcia dla baterii. Dla napięcia baterii 9,66 V napięcie doprowadzone z dzielnika wynosiło ok. 0,95 V. Po wstępnym obliczeniu mnożnika, ostateczną wartość dobrałem empirycznie. Rozbieżność nie przekracza 0,1 V, średnio jest to 0,06 V. W funkcji tej następuje także wysyłanie danych na serial. void humitemp () { //Funkcja czujnika DHT22 i pomiar napięcia float wilgotnosc = dht.getHumidity(); //Pobierz info o wilgotności do zmiennej float temperatura = dht.getTemperature(); //Pobierz info o temperaturze do zmiennej float wilgKorekta = wilgotnosc + 1.11; //Korekta dla odczytu wilgotności float tempKorekta = temperatura - 2.35; //Korekta odczytu temperatury int adc_val = analogRead(bateria); //Odczytaj wartość ADC float Ubat = adc_val * 0.0485340314; //Wylicz wartość napięcia czasDHT = millis(); //Pobierz aktualny czas Arduino roznicaDHT = czasDHT - odmierzDHT; if (roznicaDHT >= 2000UL) { //Jeżeli różnica wyniesie 2000 ms lub więcej odmierzDHT = czasDHT; //Zapisz aktualny czas arduino do zmiennej if (dht.getStatusString() == "OK") { //Jeżeli czujnik zwróci wartość ok, wykonaj poniższe Serial.print(tempKorekta); Serial.print(" °C"); Serial.print("|"); Serial.print(wilgKorekta); Serial.print(" %"); Serial.print("|"); Serial.print(Ubat); Serial.println(" V"); } } } Wspomniałem wcześniej, że odzyskałem niektóre piny z Arduino. Zależało mi zwłaszcza na pinie PWM dla świateł pozycyjnych. W prototypie używałem pinu ekspandera w trybie włącz-wyłącz, a pulsowanie osiągałem przez zastosowany kondensator, jednak nie dawało to pełnej kontroli. Uwolniony pin PWM wykorzystałem właśnie do świateł pozycyjnych. Zmienna kierunek ma wartość 1 lub -1, zatem wartość PWM jest zwiększana lub zmniejszana. Kiedy wartość PWM osiąga 0 lub 255 wartość zmiennej kierunek zostaje przemnożona przez -1 powodując zmianę jej wartości na przeciwną. void swiatlaPuls() { aktualnyCzasPWM = millis(); // Obsługa narastania i opadania PWM if (aktualnyCzasPWM - poprzedniCzasPWM >= interwalZmianyPWM) { poprzedniCzasPWM = aktualnyCzasPWM; wartoscPWM += kierunek; //Zmniana wartości PWM zależna od wartości kiereunku if (wartoscPWM >= 255 || wartoscPWM <= 0) { if (aktualnyCzasPWM - poprzedniCzasAnimacji >= interwalZmianyKierunku) { poprzedniCzasAnimacji = aktualnyCzasPWM; kierunek *= -1; // Zmiana kierunku narastania/opadania } } analogWrite(pulsLED, wartoscPWM); } } Dla świateł ostrzegawczych chciałem osiągnąć efekt szybkich mignięć wplecionych we włączanie i wyłączanie (włącz, zapulsuj, wyłącz, powtórz). Pożądane zachowanie uzyskałem stosując switch case i zmienną dla licznika i zmieniając zachowanie świateł w zależności od jej wartości. void swiatlaStrobo() { czasLEDstrobo = millis(); switch(licznikStrobo) { case 0: digitalWrite(stroboLED, 1); if (czasLEDstrobo - czasStrobo >= 700UL) { digitalWrite(stroboLED, 0); czasStrobo = czasLEDstrobo; licznikStrobo ++; } break; case 13: digitalWrite(stroboLED, 0); if (czasLEDstrobo - czasStrobo >= 500UL) { digitalWrite(stroboLED, 0); licznikStrobo ++; } break; default: if (czasLEDstrobo - czasStrobo >= 50UL) { czasStrobo = czasLEDstrobo; stanStrobo = !stanStrobo; digitalWrite(stroboLED, stanStrobo); licznikStrobo ++; } break; } if (licznikStrobo > 13) { licznikStrobo = 0; } } Na koniec moja świeżynka. Poznałem operator trójargumentowy, zauważyłem, że dla mnie - tego amatora przed monitorem - działa podobnie do if else, zatem przepisałem logikę odpowiedzialną za silniki. Dotychczas używałem oryginalnej z kursu, po poznaniu tego operatora powstało coś takiego: void lewySilnik (int VL) { if (VL == 0) return; //Jeżeli wartość VL jest równa zero to nie wykonuj funkcji int kierunekLewySilnik = (VL > 0) ? 0 : 1; //Jeżeli wartość VL jest większa od 0 to ustaw wartość 0 (jazda do przodu), jeżeli mniej to ustaw 1 (do tyłu) int vlMap = map(abs(VL), 0, 100, 0, PWM_MAX); //Przemapuj zakres PWM do zakresu 0-100, dla ujemnego VL zwróć wartość absolutną digitalWrite(L_DIR, kierunekLewySilnik); //Jazda do przodu, albo do tyłu :) analogWrite(L_PWM, vlMap); //Ustaw zadaną prędkość } Komunikacja - software: Robot jest częściowo zgodny z aplikacją o której wspomniałem na początku (sterowanie, załączanie świateł co drugi kllik), ale gotowa apka nie do końca spełnia moje oczekiwania. Można też sterować przez aplikację typu BT serial monitor, ale to też nie do końca to - choć tutaj już odbieram dane. Postanowiłem wyrzeźbić - to dobre słowo - aplikację w APP Inventorze. O ile w sekcji bloczkowej jakoś to poszło, to graficznie naprawdę była to rzeźba. Predefiniowane rozdzielczości są przedpotopowe, a i responsive nie do końca daje to, czego człowiek oczekuje. Ale, potrzebowałem jednej apki, więc postanowiłem się przemęczyć, zamiast podejmować naukę tworzenia od podstaw w jakimś ludzkim środowisku. W pierwszej wersji odbierałem temperaturę i wilgotność, napis "connect" był tylko etykietą. W drugiej odbieram jeszcze wartość napięcia. Przyciski kierunkowe to przód, tyłu, obroty w miejscu w prawo, w lewo. Dodatkowo w narożnikach przyciski przywołują jazdę po łuku w daną stronę (prawy przód, lewy tył itp). Przycisk H wyzwala impuls buzzerem. Przycisk Światła uruchamia światła. Zmienna w robocie sprawia, że po kolei uruchamiane są: światła pozycyjne i ostrzegawcze, z drugim wywołaniem dochodzą światła frontowe, z trzecim wywołaniem gaszone są pozycyjne i ostrzegawcze, pozostają frontowe. Czwarte wywołanie resetuje zmienną i gasi wszystkie światła. Przycisk Auto włącza lub wyłącza tryb jazdy autonomicznej. Podsumowanie Zdaję sobie sprawę, że kod nie jest idealny, zwłaszcza, że wiele rzeczy znałem po łebkach, niektórych uczyłem się w trakcie. Ale jestem zadowolony - coś, co miało być tylko dodaniem sterowania BT zmieniło się w duży jak dla mnie projekt, który robiony po godzinach ostatecznie działa. 😄 Co dalej? Wyciągnę zasilanie jakoś zasilanie na zewnątrz. Zastosowana obudowa sprawia, że aby ją odkręcić, muszę ściągać gąsienice w celu uzyskania dostępu do śrub. To ze zwykłej wymiany baterii, czy przyszłych zmian w oprogramowaniu, zamienia rutynę w procedurę. 😉 W drugiej części kursu Arduino widziałem czujnik ruchu, więc pewnie też go jakoś zatrudnię - zamysł taki, że w trybie auto będzie sobie oczekiwał i uruchamiał jazdę po wykryciu ruchu. A ponieważ szuflada zawiera jeszcze jednego HC-05, jakiegoś klona Nano i wyświetlacz OLED, mam w planie zrobić sprzętowy kontroler dla tego robota. 😄 Co to za antenka? To tylko ozdoba - zabieram Romka ze sobą na ERC w tym roku - będzie maskotką. 😄 Jaki koszt konwersji? Dla mnie żaden, jeżeli uznajemy, że co w szufladzie to 0 zł. Jeżeli ktoś by chciał iść w moje ślady, to myślę, że kilka stówek. Podwozie jest najdroższe. Ale jak już ktoś ma wydawać pieniądze na konwersję, to lepiej na budowę od podstaw i zrezygnować z tego shielda, na rzecz sterownika, który uciągnie silniki 4,5A - wtedy nie trzeba szukać silników, które ogarnie shield i parę zł zostaje. 😉 Osobiście, gdyby nie postanowienie zrobienia tego bez dodatkowych nakładów, to pewnie też bym dokupił kilka rzeczy, rezygnując z shielda forbotowego, ale czelendż to czelendż. Dotarłeś tutaj? No to zobacz krótki filmik. 🙂 Podjazd miał nachylenie ok. 30 stopni. A tutaj cały potworek 😅 //Załączenie bibliotek #include "DHT.h" //Czujnik temperatury i wilgotności #include "Servo.h" #include "Adafruit_MCP23008.h" //Ekspander portów #include <NewPing.h> //Obsługa HC-SR04 //Ekspander Adafruit_MCP23008 ekspander; //Deklaracje i zmienne dla DHT i pomiaru napięcia #define DHT22_PIN 7 //Pin sygnału z DHT DHT dht; //Utworzenie obiektu dla czujnika #define bateria A3 unsigned long czasDHT = 0; unsigned long odmierzDHT = 0; unsigned long roznicaDHT = 0; //Deklaracje serwomechanizmu Servo serwo; #define SERWO_PIN 11 //Pin sygnału sterującego PWM int idle = 84; //Neutralna pozycja dla serwa //Deklaracje mostek H #define L_PWM 5 //Pin prędkości lewego silnika #define L_DIR 4 //Pin kierunku lewego silnika #define R_PWM 6 //Pin prędkości prawego silnika #define R_DIR 9 //Pin kierunku prawego silnika #define PWM_MAX 210 //Maksymalne wypełnienie dla silnika 7,5V przy zasilaniu 9V //Deklaracje i definicje do obsługi buzzera #define BUZZER 10 unsigned long aktualnyCzasBuzzer = 0; unsigned long czasStartuBuzzer = 0; unsigned long czasTrwaniaBuzzer = 200; // Czas trwania impulsu w milisekundach boolean buzzerON = false; //Deklaracje i zmienne czujnika odległości i wykrywania przeszkód #define LEDjazda 0 //LED informujący o jeździe autonomicznej na 0 pinie ekspandera!!! #define lewySensor A0 //Krańcówka po lewej #define prawySensor A1 //Krańcówka po prawej #define trigPin 12 //Pin nadawczy (szary) #define echoPin 13 //Pin odbiorczy (biały) #define MAX_DISTANCE 200 // Maksymalny zasięg w centymetrach NewPing sonar(trigPin, echoPin, MAX_DISTANCE); //Inicjalizacja obiektu sonar z użyciem biblioteki NewPing const int doPrzeszkody = 25; //Odległość wykrywania przeszkody w cm //Wyliczenie stanów dla jazdy autonomicznej enum States { PROSTO, PATRZ_PRAWO, OBROT_PRAWO, PATRZ_LEWO, OBROT_LEWO, ZATRZYMAJ, ZATRZYMAJ2, COFAJ, COFAJ_LEWY, COFAJ_PRAWY, }; boolean jazdaAutonomiczna = false; boolean kolizjaPrawo = false; boolean kolizjaLewo = false; static States obecnyStan = PROSTO; //Domyślny stan unsigned long czasStanu = 0; //Zmienna dla timera stanu unsigned long czasDotyku = 0; //Zmienna dla timera krańcówek static unsigned long czasObrotu = 1100; //Zmienna dla czasu trwania obrotu //Definicje i zmienne dla świateł #define frontLED 1 //LEDy frontowe na 1 pinie ekspandera!!! #define stroboLED 2 #define pulsLED 3 int trybLED = 0; unsigned long czasStrobo = 0; unsigned long czasLEDstrobo = 0; int stanStrobo = 0; int licznikStrobo = 0; int stanPuls = 0; unsigned long poprzedniCzasPWM = 0; unsigned long poprzedniCzasAnimacji = 0; int wartoscPWM = 0; int kierunek = 1; // 1 - narastanie, -1 - opadanie unsigned long interwalZmianyPWM = 10; unsigned long interwalZmianyKierunku = 750; unsigned long aktualnyCzasPWM = 0; void setup() { //Konfiguracja ekspandera ekspander.begin(); ekspander.pinMode(LEDjazda, OUTPUT); //Konfiguracja świateł ekspander.digitalWrite(LEDjazda, LOW); pinMode(stroboLED, OUTPUT); digitalWrite(stroboLED, stanStrobo); pinMode(pulsLED, OUTPUT); analogWrite(pulsLED, stanPuls); ekspander.pinMode(frontLED, OUTPUT); ekspander.digitalWrite(frontLED, LOW); //Konfiguracja pinów mostka H pinMode(L_DIR, OUTPUT); pinMode(L_PWM, OUTPUT); pinMode(R_DIR, OUTPUT); pinMode(R_PWM, OUTPUT); //Konfiguracja pozostałych elementów pinMode(bateria, INPUT); //Odczyt ADC dla pomiaru napięcia baterii pinMode(lewySensor, INPUT_PULLUP); pinMode(prawySensor, INPUT_PULLUP); Serial.begin(9600); //Uruchomienie sprzętowej komunikacji UART dht.setup(DHT22_PIN); //Podłączenie czujnika do zadeklarowanego pinu serwo.attach(SERWO_PIN); //Podłączenie serwa do zadeklarowanego pinu serwo.write(idle); //Ustawienie serwa w domyślnej pozycji delay(200); //Opóźnienie dla ustabilizowania i przeczekania stanów nieustalonych serwo.detach(); //Odłączenie serwa //Konfiguracja buzzera i info o zakończeniu setupu pinMode(BUZZER, OUTPUT); digitalWrite(BUZZER, 1); ekspander.digitalWrite(LEDjazda, 1); delay(100); digitalWrite(BUZZER, 0); ekspander.digitalWrite(LEDjazda, 0); delay(100); digitalWrite(BUZZER, 1); ekspander.digitalWrite(LEDjazda, 1); delay(100); digitalWrite(BUZZER, 0); ekspander.digitalWrite(LEDjazda, 0); delay(100); } void loop() { humitemp(); //Wykonuj funkcje DHT if(jazdaAutonomiczna) { jazdaAuto(); } switch (trybLED) { case 0: analogWrite(pulsLED, 0); digitalWrite(stroboLED, 0); ekspander.digitalWrite(frontLED, 0); break; case 1: swiatlaPuls(); swiatlaStrobo(); ekspander.digitalWrite(frontLED, 0); break; case 2: swiatlaPuls(); swiatlaStrobo(); swiatlaFront(); break; case 3: analogWrite(pulsLED, 0); digitalWrite(stroboLED, 0); swiatlaFront(); break; case 4: trybLED = 0; break; } if (buzzerON) { klakson(); } if (Serial.available() > 0) { //Jeżeli są dostępne dane BT char jazda = Serial.read(); //Odczytaj te dane i przypisz do zmiennej switch(jazda) { //Wykonaj case zgodnie z odebranymi danymi case 'F': //Do przodu lewySilnik(95); prawySilnik(95); break; case 'B': //Do tyłu lewySilnik(-95); prawySilnik(-95); break; case 'S': //Zatrzymaj zatrzymajSilniki(); break; case 'L': //Obrót w lewo lewySilnik(-80); prawySilnik(80); break; case 'R': //Obrót w prawo lewySilnik(80); prawySilnik(-80); break; case 'G': //Lewy łuk do przodu lewySilnik(60); prawySilnik(95); break; case 'I': //Prawy łuk do przodu lewySilnik(95); prawySilnik(60); break; case 'H': //Lewy łuk do tyłu lewySilnik(-60); prawySilnik(-95); break; case 'J': //Prawy łuk do tyłu lewySilnik(-95); prawySilnik(-60); break; case 'V': //Impuls buzzerem buzzerON = !buzzerON; czasStartuBuzzer = millis(); break; case 'X': trybLED++; break; case 'A': jazdaAutonomiczna = !jazdaAutonomiczna; if (!jazdaAutonomiczna) { zatrzymajSilniki(); ekspander.digitalWrite(LEDjazda, 0); serwo.detach(); } break; } } } void lewySilnik (int VL) { if (VL == 0) return; //Jeżeli wartość VL jest równa zero to nie wykonuj funkcji int kierunekLewySilnik = (VL > 0) ? 0 : 1; //Jeżeli wartość VL jest większa od 0 to ustaw wartość 0 (jazda do przodu), jeżeli mniej to ustaw 1 (do tyłu) int vlMap = map(abs(VL), 0, 100, 0, PWM_MAX); //Przemapuj zakres PWM do zakresu 0-100, dla ujemnego VL zwróć wartość absolutną digitalWrite(L_DIR, kierunekLewySilnik); //Jazda do przodu, albo do tyłu :) analogWrite(L_PWM, vlMap); //Ustaw zadaną prędkość } void prawySilnik (int VR) { if (VR == 0) return; //Jeżeli wartość VL jest równa zero to nie wykonuj funkcji int kierunekLewySilnik = (VR > 0) ? 1 : 0; //Jeżeli wartość VR jest większa od 0 to ustaw wartość 1 (jazda do przodu), jeżeli mniej to ustaw 0 (do tyłu) int vrMap = map(abs(VR), 0, 100, 0, PWM_MAX); //Przemapuj zakres PWM do zakresu 0-100 digitalWrite(R_DIR, kierunekLewySilnik); //Jazda do przodu, albo do tyłu :) analogWrite(R_PWM, vrMap); //Ustaw zadaną prędkość } void zatrzymajSilniki() { analogWrite(L_PWM, 0); //Zatrzymaj lewy silnik analogWrite(R_PWM, 0); //Zatrzymaj prawy silnik } void humitemp () { //Funkcja czujnika DHT22 i pomiar napięcia float wilgotnosc = dht.getHumidity(); //Pobierz info o wilgotności do zmiennej float temperatura = dht.getTemperature(); //Pobierz info o temperaturze do zmiennej float wilgKorekta = wilgotnosc + 1.11; //Procentowa korekta dla odczytu wilgotności float tempKorekta = temperatura - 2.35; //Korekta odczytu temperatury int adc_val = analogRead(bateria); //Odczytaj wartość ADC float Ubat = adc_val * 0.0485340314; //Wylicz wartość napięcia czasDHT = millis(); //Pobierz aktualny czas Arduino roznicaDHT = czasDHT - odmierzDHT; if (roznicaDHT >= 2000UL) { //Jeżeli różnica wyniesie 2000 ms lub więcej odmierzDHT = czasDHT; //Zapisz aktualny czas arduino do zmiennej if (dht.getStatusString() == "OK") { //Jeżeli czujnik zwróci wartość ok, wykonaj poniższe Serial.print(tempKorekta); Serial.print(" °C"); Serial.print("|"); Serial.print(wilgKorekta); Serial.print(" %"); Serial.print("|"); Serial.print(Ubat); Serial.println(" V"); } } } int sonarDystans() { int dystans = sonar.ping_cm(); // Wykonanie pomiaru i zapisanie wyniku w zmiennej distance w centymetrach return dystans; } void swiatlaPuls() { aktualnyCzasPWM = millis(); // Obsługa narastania i opadania PWM if (aktualnyCzasPWM - poprzedniCzasPWM >= interwalZmianyPWM) { poprzedniCzasPWM = aktualnyCzasPWM; wartoscPWM += kierunek; //Zmniana wartości PWM zależna od wartości kiereunku if (wartoscPWM >= 255 || wartoscPWM <= 0) { if (aktualnyCzasPWM - poprzedniCzasAnimacji >= interwalZmianyKierunku) { poprzedniCzasAnimacji = aktualnyCzasPWM; kierunek *= -1; // Zmiana kierunku narastania/opadania } } analogWrite(pulsLED, wartoscPWM); } } void swiatlaStrobo() { czasLEDstrobo = millis(); switch(licznikStrobo) { case 0: digitalWrite(stroboLED, 1); if (czasLEDstrobo - czasStrobo >= 700UL) { digitalWrite(stroboLED, 0); czasStrobo = czasLEDstrobo; licznikStrobo ++; } break; case 13: digitalWrite(stroboLED, 0); if (czasLEDstrobo - czasStrobo >= 500UL) { digitalWrite(stroboLED, 0); licznikStrobo ++; } break; default: if (czasLEDstrobo - czasStrobo >= 50UL) { czasStrobo = czasLEDstrobo; stanStrobo = !stanStrobo; digitalWrite(stroboLED, stanStrobo); licznikStrobo ++; } break; } if (licznikStrobo > 13) { licznikStrobo = 0; } } void swiatlaFront() { ekspander.digitalWrite(frontLED, 1); } void jazdaAuto() { //Funkcja jazdy autonomicznej ekspander.digitalWrite(LEDjazda, 1); //Włącz LED informujący o jeździe autonomicznej switch (obecnyStan) { case PROSTO: prosto(); break; case ZATRZYMAJ: zatrzymaj(); break; case ZATRZYMAJ2: zatrzymaj2(); break; case PATRZ_PRAWO: //Wykrywanie przeszkód po prawej patrzPrawo(); break; case OBROT_PRAWO: //Skręt w prawo obrotPrawo(); break; case PATRZ_LEWO: //Wykrywanie przeszkód po lewej patrzLewo(); break; case OBROT_LEWO: //Skręt w lewo obrotLewo(); break; case COFAJ: cofaj(); break; case COFAJ_LEWY: cofajLewy(); break; case COFAJ_PRAWY: cofajPrawy(); break; } if (digitalRead(lewySensor) == LOW && kolizjaLewo==0) { //Jeżeli przeszkoda po lewej kolizjaLewo = 1; obecnyStan = COFAJ_LEWY; czasStanu = millis(); } if (digitalRead(prawySensor) == LOW && kolizjaPrawo==0) { //Jeżeli przeszkoda po prawej kolizjaPrawo = 1; obecnyStan = COFAJ_PRAWY; czasStanu = millis(); } } void prosto() { if (sonarDystans() <= doPrzeszkody) { //Jeżeli w zadanej odległości jest przeszkoda obecnyStan = ZATRZYMAJ; //Przejdź do zatrzymania czasStanu = millis(); //Ustaw czas dla timera serwo.attach(SERWO_PIN); //Podłącz serwo } else { //Jedź prosto lewySilnik(90); prawySilnik(90); } } void zatrzymaj() { zatrzymajSilniki(); if (millis() - czasStanu >= 500UL) { //Odczekaj zadany czas obecnyStan = PATRZ_PRAWO; //Przejdź do wykrywania przeszkody po prawej czasStanu = millis(); } } void zatrzymaj2() { zatrzymajSilniki(); if (millis() - czasStanu >= 500UL) { obecnyStan = PATRZ_LEWO; //Przejdź do wykrywania przeszkody po prawej czasStanu = millis(); } } void patrzPrawo() { serwo.write(25); //Ustw serwo w prawo if (millis() - czasStanu >= 400UL) { //Patrz przez zadany czas dla ustabilizowania serwa if (sonarDystans() > doPrzeszkody) { //Jeżeli nie ma przeszkód bliżej niż zadana odległość obecnyStan = OBROT_PRAWO; //Skręć w prawo czasStanu = millis(); //Ustaw czas dla timera } else { //Jeżeli jest przeszkoda obecnyStan = PATRZ_LEWO; //Przejdź do wykrywania przeszkody po lewej czasStanu = millis(); //Ustaw czas dla timera } } } void obrotPrawo() { lewySilnik(70); prawySilnik(-70); serwo.write(idle); //Ustaw serwo na wprost if (millis() - czasStanu >= czasObrotu) { //Skręcaj przez zadany czas serwo.detach(); obecnyStan = PROSTO; //Przejdź do jazdy na prosto czasStanu = millis(); //Ustaw czas dla timera } } void patrzLewo() { serwo.write(155); //Obróć serwo w lewo if (millis() - czasStanu >= 450UL) { //Patrz przez zadany czas dla ustabilizowania serwa if (sonarDystans() > doPrzeszkody) { //Jeżeli nie ma przeszkody w zadanej odległości obecnyStan = OBROT_LEWO; //Skręć w lewo czasStanu = millis(); //Ustaw czas dla timera } else { //Jeżeli jest przeszkoda obecnyStan = COFAJ; //Włącz alarm czasStanu = millis(); //Ustaw czas dla timera } } } void obrotLewo() { lewySilnik(-70); prawySilnik(70); serwo.write(idle); //Ustaw serwo na wprost if (millis() - czasStanu >= czasObrotu) { //Skręcaj przez zadany czas serwo.detach(); obecnyStan = PROSTO; //Przejdź do jazdy prosto czasStanu = millis(); //Ustaw czas dla timera } } void cofaj() { serwo.write(idle); //Ustaw serwo na wprost lewySilnik(-80); //cofaj prawySilnik(-80); if (millis() - czasStanu >= 800UL) { //Po upływie zadanego czasu zatrzymajSilniki(); //Zatrzymaj się obecnyStan = ZATRZYMAJ; //Przejdź do jazdy prosto czasStanu = millis(); //Ustaw czas dla timera } } void cofajLewy() { lewySilnik(-70); prawySilnik(-70); if (millis() - czasStanu >= 800UL) { obecnyStan = ZATRZYMAJ; //Przejdź do wykrywania przeszkody po prawej czasStanu = millis(); kolizjaLewo = 0; } } void cofajPrawy() { lewySilnik(-70); prawySilnik(-70); if (millis() - czasStanu >= 800UL) { obecnyStan = ZATRZYMAJ2; //Przejdź do wykrywania przeszkody po prawej czasStanu = millis(); kolizjaPrawo = 0; } } void klakson() { aktualnyCzasBuzzer = millis(); if (aktualnyCzasBuzzer - czasStartuBuzzer < czasTrwaniaBuzzer) { digitalWrite(BUZZER, HIGH); } else { digitalWrite(BUZZER, LOW); buzzerON = false; } }
- 3 odpowiedzi
-
- 10
-
Freestyle Robot pubga v1.0- czyli otto diy po mojemu
Leoneq opublikował temat w Projekty - DIY roboty
witam (do admina) na wstępie chciałem zaznaczyć, że jedyne co mam z gotowego projektu z neta to modele do wydrukowania (po co tworzyć coś na nowo jak już jest) jeżeli projekt nie zalicza się do akcji rabatowej, mogę usunąć linki i opublikować "po prostu". Był taki pomysł, żeby zbudować robota. Linefollower? jak już to na poważnie, a mi sie nie chce. Jakiś manipulator? nie mam napędów. Autko? to też chcę na poważnie zrobić, coś więcej niż lego. Wybór padł na robota, który miał po prostu zająć czymś kolegów i nauczycieli w szkole - czyli takie cudo na pokazy. Miało to być małe, do plecaka, znalazłem projekt otto diy. Spodobał mi się, więc wydrukowałem, złożyłem na płytce stykowej i ledwie zamknąłem obudowę przez kable 20cm - ale robot działał, wydawał zabawne odgłosy i tańczył. Zadanie swoje spełnił, mogłem go teraz zostawić i się skupić na zvsie (którego opublikuję po dokończeniu obudowy). Ale co to za skończony projekt na płytce stykowej? Nie mogłem tego tak zostawić, to dla mnie wręcz porażka. (Aby zapobiec spamowi, drukowałem na Aflawise U30 - dysza 0.4, chłodzenie petsfang, 60mm/s warstwa 0.3mm. PLA od printme, 200*C/60*C. Ta żółta czapka się źle wydrukowała, to troszkę uzupełniłem ubytki lutownicą i filamentem, co jak widać średnio wyszło.) Myślałem, czego mi w nim brakuje. Oczywiście sztuczki robota robione w kółko szybko się znudziły, to musiałem wymyślić jakieś sterowanie. Mam dużo odbiorników IR, to sprzężyłem taki jeden z robotem, spisałem kody z pilota do ledów i zrobiłem mapowanie przycisków. Następnie wziąłem się za lutowanie płytki pod niego. Kilka godzin lutowania, plątanina kabli z drugiej strony, ale działa. (mistrz painta w akcji): Płytka w procesie projektowania, a bardziej patrzenia czy wszystko jest ok Tutaj podczas wgrywania programu, stabilizator ze starego monitora okazał się być zużyty Gotowa płytka: Na płytce za zasilanie odpowiada stabilizator 7805 (oczywiście z radiatorem i kapką pasty termoprzewodzącej), z kondensatorem 100uF. W środku stara dobra atmega 328, z kwarcem 16Mhz i brzęczykiem z diodą led. Najwięcej jest wyprowadzeń, bo cała listwa 4x3 dla serw, złącze od HC-SR04, VS1838B, interfejsu UART a nawet złącze programatora. Cała płytka ma 60x30mm. Źródło napięcia to 2 akusy lipo na 7.4V (tylko takie miałem) o pojemności 1800mAh. Wyjęte z tableta - tam dużo fajnych rzeczy można znaleźć... Początkowo miałem dać 2 akusy 18650, ale one były za duże pod moją płytkę. Jedno 18650 ze step upem to samo. Więc zostały liposy, które niestety nie są zbyt ciężkie i robot ma dość nisko środek ciężkości (powinien być "na środku", pierwotnie tam miały siedzieć 4 paluszki aa) Zostało napisać kod. Skorzystałem z gotowych bibliotek pod otto, gdzie miałem gotowe sterowanie serwami i czujnikiem. Był za to ogromny kłopot z bibliotekami, bo te gryzły się z timerami których używała biblioteka od otto. Na chińskim githubie znalazłem jakimś cudem bibliotekę od IR która nie używa timerów, biblioteka od dźwięków była łatwiejsza do znalezienia ale musiałem wykonywać modyfikacje w niej, tak czy siak. Sam kod nie jest skomplikowany, po funkcjach startujących peryferia co chwilę sprawdzamy czy odebraliśmy jakiś kod z pilota - jeżeli tak, wykonujemy funkcję z nim związaną. W przeciwnym wypadku wykonujemy kod danego trybu, a jest ich 3: (wiem, na zdjęciu jest ich więcej ale uznałem że są niepotrzebne dla mnie): tryb swobodny, czyli nie robimy nic, tryb omijania przeszkód, tutaj za dużo tłumaczyć nie trzeba, tryb śledzenia: po wykryciu dłoni, robot idzie w jej kierunku. A oto cały kodzik, może kiedyś go dam na githuba bo szkoda by się u mnie marnował: //---------------------------------------------------------------- //-- Otto IR mod //-- Does many things, controlled by an IR remote. //-- (insert github here) //-- CC BY SA (http://ottodiy.com) //-- 02 June 2019 by Leoneq ;3 //----------------------------------------------------------------- // INCLUDES --------------------------- #include <Servo.h> //servo library #include <Oscillator.h> //important library #include <US.h> //ultrasonic library #include <Otto.h> //otto library #include <motoIRremote.h> //timer-free ir library #include <TimerFreeTone.h> //timer-free tone library // PINS ------------------------------- #define PIN_LEFT_HIP 2 #define PIN_RIGHT_HIP 3 #define PIN_LEFT_ANKLE 5 #define PIN_RIGHT_ANKLE 4 #define PIN_IR 6 #define PIN_LED 13 // TRIMS ------------------------------ #define TRIM_LEFT_HIP -9 #define TRIM_RIGHT_HIP -9 #define TRIM_LEFT_ANKLE 0 #define TRIM_RIGHT_ANKLE -20 // OBJECTS ---------------------------- Otto Otto; //This is Otto! IRrecv ir(PIN_IR); decode_results IR_RES; // VARIABLES -------------------------- int WALK_TIME = 1000; int WALK_DIR = 1; int WALK_HEIGHT = 20; int speed = 3; int US_DIST = 0; bool obstacleDetected = false; String IR_CODE = ""; String IR_SYMBOL = ""; #define UP "f700ff" #define DOWN "f7807f" #define OFF "f740bf" #define ON "f7c03f" #define ONE "f720df" #define TWO "f7a05f" #define THREE "f7609f" #define FOUR "f710ef" #define FIVE "f7906f" #define SIX "f750af" #define SEVEN "f730cf" #define EIGHT "f7b04f" #define NINE "f7708f" #define TEN "f708f7" #define ELEVEN "f78877" #define TWELVE "f748b7" #define THIRTEEN "f728d7" #define FOURTEEN "f7a857" #define FIFTEEN "f76897" #define W "f7e01f" #define FLASH "f7d02f" #define STROBE "f7f00f" #define FADE "f7c837" #define SMOOTH "f7e817" /* * There are 5 modes attached to: * W - avoid obstacles (1) * FADE - follow hand! (2) * SMOOTH - free mode. Do anything what you want! (3) */ byte OTTO_MODE = 5; void setup() { Serial.begin(115200); //enable serial for debugging ir.enableIRIn(); //enable ir sensor Otto.init(PIN_LEFT_HIP,PIN_RIGHT_HIP,PIN_LEFT_ANKLE,PIN_RIGHT_ANKLE,true); //init otto! Otto.setTrims(TRIM_LEFT_HIP,TRIM_RIGHT_HIP, TRIM_LEFT_ANKLE, TRIM_RIGHT_ANKLE); //set trims //why not in one line? here, in code it looks better. Serial.println("Otto IR Mod by Leoneq :3"); Serial.println("Summer 2019"); Serial.println("-----------------------------------"); Serial.println("Ready."); Otto.sing(S_connection); //let us know, that otto woke up! Otto.home(); //set initial position delay(100); Otto.sing(S_happy); //when you are happy, otto should be too ;) //end of setup function } void checkIR() { if (ir.decode(&IR_RES)) //if we get something { IR_CODE = String(IR_RES.value, HEX); //we save received data into string Serial.print("Received data: 0x"); Serial.println(IR_CODE); //lets write it on serial checkIRcode(); //we must know what button is pressed delay(250); //little delay ir.resume(); } } void loop() { checkIR(); //check for new ir signals switch(OTTO_MODE) { case 1: //avoid obstacles avoid(); break; case 2: //follow hand mode follow(); break; case 3: //free mode free(); break; } } void free() { //nothing! } void follow() { obstacleDetected = obstacleDetector(); if(obstacleDetected) { Otto.walk(1,1000,FORWARD); } else { Otto.home(); } } void dance() { //soon } void sing() { //soon } void avoid() { obstacleDetected = obstacleDetector(); if(obstacleDetected) { Otto.sing(S_surprise); Otto.walk(5,1000,BACKWARD); delay(1000); Otto.sing(S_happy); Otto.turn(3,1000,RIGHT); delay(2000); } else { Otto.walk(1,1000,FORWARD); } //end of avoid() function } bool obstacleDetector() { int distance = Otto.getDistance(); Serial.print("Distance: "); Serial.println(distance); if(distance<15) { return true; } else { return false; } } void up() { IR_SYMBOL = "UP"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); speed--; Serial.print("Changing speed! The new speed is: "); Serial.print(speed); } void down() { IR_SYMBOL = "DOWN"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); speed++; Serial.print("Changing speed! The new speed is: "); Serial.print(speed); } void off() { IR_SYMBOL = "OFF"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.print("See you later!"); Otto.home(); Otto.playGesture(OttoSleeping); Otto.sing(S_sleeping); } void on() { IR_SYMBOL = "ON"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("Turning on..."); Otto.sing(S_happy); Otto.playGesture(OttoHappy); Otto.home(); } void _one() { IR_SYMBOL = "1"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("Moonwalking!"); Otto.sing(S_superHappy); Otto.moonwalker(3,speed*300,30,1); Otto.home(); } void _two() { IR_SYMBOL = "2"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("jump!"); Otto.jump(3, speed*300); } void _three() { IR_SYMBOL = "3"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("Am I taller?"); Otto.updown(1, speed*300, 22); } void _four() { IR_SYMBOL = "4"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("I'm happy, because you are with me!"); Otto.sing(S_happy); Otto.playGesture(OttoLove); } void _five() { IR_SYMBOL = "5"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("I'm so sad, help me :("); Otto.sing(S_confused); Otto.playGesture(OttoSad); Otto.sing(S_sad); } void _six() { IR_SYMBOL = "6"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("mmmphm!"); Otto.sing(S_fart1); Otto.playGesture(OttoFart); Otto.sing(S_fart3); Otto.playGesture(OttoWave); } void _eight() { IR_SYMBOL = "8"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("Going forward!"); Otto.walk(3, speed*300, 1); Otto.home(); } void _fourteen() { IR_SYMBOL = "14"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("Going backwards!"); Otto.walk(3, speed*300, -1); Otto.home(); } void _ten() { IR_SYMBOL = "10"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("Rotating left, please wait..."); Otto.turn(3, speed*300, 1); Otto.home(); } void _twelve() { IR_SYMBOL = "12"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("Rotating right, please wait..."); Otto.turn(3, speed*300, -1); Otto.home(); } void _eleven() { IR_SYMBOL = "11"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("I am standing up!"); Otto.home(); } void _w() { IR_SYMBOL = "W"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("Obstacle avoid mode: ACTIVATED"); OTTO_MODE = 1; } void _flash() { IR_SYMBOL = "FLASH"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("Follow mode: ACTIVATED"); OTTO_MODE = 2; } void _strobe() { IR_SYMBOL = "STROBE"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("Free mode: ACTIVATED"); OTTO_MODE = 3; } void _seven() { IR_SYMBOL = "7"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("Standing one foot!"); Otto.bend(1, 2000, 1); } void _nine() { IR_SYMBOL = "7"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); Serial.println("Standing one foot!"); Otto.bend(1, 2000, -1); } void checkIRcode() { if(IR_CODE == UP) up(); else if(IR_CODE == DOWN) down(); else if(IR_CODE == OFF) off(); else if(IR_CODE == ON) on(); else if(IR_CODE == ONE) _one(); else if(IR_CODE == TWO) _two(); else if(IR_CODE == THREE) _three(); else if(IR_CODE == FOUR) _four(); else if(IR_CODE == FIVE) _five(); else if(IR_CODE == SIX) _six(); else if(IR_CODE == SEVEN) _seven(); else if(IR_CODE == EIGHT) _eight(); else if(IR_CODE == NINE) _nine(); else if(IR_CODE == TEN) _ten(); else if(IR_CODE == ELEVEN) _eleven(); else if(IR_CODE == TWELVE) _twelve(); else if(IR_CODE == THIRTEEN) IR_SYMBOL = "13"; else if(IR_CODE == FOURTEEN) _fourteen(); else if(IR_CODE == FIFTEEN) IR_SYMBOL = "15"; else if(IR_CODE == W) _w(); else if(IR_CODE == FLASH) _flash(); else if(IR_CODE == STROBE) _strobe(); //else if(IR_CODE == FADE) _fade(); //else if(IR_CODE == SMOOTH) _fade(); //we cant do switch case with string else IR_SYMBOL = "UNKNOWN"; Serial.print("The symbol is: "); //lets write symbol on serial Serial.println(IR_SYMBOL); } Efekty prac są podane na zdjęciach. Film dorzuce później, jak na yt wrzucę. Robot został nazwany pubga, na pamięć mojego kolegi z 8. klasy, który co chwile mówił do nauczycieli "a pogramy w pubga? pubga, w pubga!" przez chyba cały czerwiec XD W przyszłości chyba nie będę miał na niego za dużo czasu, bo technikum itd. Może na jakiś długi weekend, zamontuję do niego esp, jakieś dodatkowe czujniki albo miotacz ognia. Kto wie. Miłych wakacji, Leoneq ;3 -
No cóż - trochę mi to czasu zajęło. Ale dziś mam zaszczyt przedstawić Księżniczkę i Krasnoluda. Z czystym sumieniem umieszczam to w dziale "roboty" - tym razem lalki mają przynajmniej częściową autonomię czyli jak najbardziej zasługują na miano robota. Ale może chronologicznie. W pewnym momencie stwierdziłem, że teatralne lalki (klasyczne czy z dodatkiem elektroniki) to trochę za mało, i postanowiłem zrobić jakąś postać w pełni zdalnie sterowaną i z możliwością wykonania jakiegoś mniej lub bardziej skomplikowanego programu. Wybrałem Piękną Księżniczkę i zacząłem robić założenia. Wyszło mi coś takiego: Konstrukcja głowy podobna jak w poprzednich lalkach (wypróbowana, sprawdzona). Jedno serwo do napędu oczu, dwa do pochylenia i obrotu głowy. Zrezygnowałem z pełnego ruchu głowy (tak jak w mlle Woni) ze względu na trudności zarówno w animacji, jak i wykonaniu mechanizmu w domowych warunkach, a efekt nie jest wart zachodu. Silniki krokowe do napędu Zasilanie z dwóch akumulatorów 18650. Implikowało to zastosowanie stepperów EasyDrive (napięcie zasilania) i niewielkich silników NEMA14 ESP32 jako procesor Bezprzewodowy pad (z Botlandu) do sterowania. Na początek poszło podwozie. Teoretycznie wszystko było jak trzeba: dwa koła napędowe osadzone w osi lalki po bokach i dwie kulki podporowe (z przodu i z tyłu). Taki układ powinien zapewnić stabilność... Jako że po zmontowaniu podwozie jeździło całkiem zacnie, zabrałem się za resztę. I tu pierwsza trudność: serwa HD-1370A teoretycznie powinny mieć na tyle duży moment, aby podnieść lekką rękę lalki. Nie wziąłem jednak pod uwagę bezwładności - próby skończyły się rozwaleniem zębatek w serwie i zastosowaniem trochę porządniejszych (z metalowymi trybami). Dalej było tylko gorzej. Szybkie podniesienie ręki kończyło się wpadnięciem serwa w oscylacje. Jako-tako opanowałem to, zmniejszając maksymalną dopuszczalną prędkość ruchu serwa. Kontroler (pad) okazał się absolutną pomyłką. Nie ma mowy o żadnej precyzji, w końcu użyłem go e trybie cyfrowym (czyli jak joysticka w C64). W związku z tym musiałem jakoś uprościć sterowanie; zrezygnowałem z bezpośredniego sterowania oczami, głowa wykonuje tylko podstawowe gesty (tak/nie oraz spojrzenia w ośmiu kierunkach). Do regulacji prędkości silników napędowych użyłem przycisków R1/R2 - nie jest to idealne rozwiązanie, ale pozwalające na względnie dokładną kontrolę ruchu lalki. No i to nieszczęsne podwozie - pomysł był dobry, ale wykonanie absolutnie do niczego. Nie wiem co mi się przyśniło żeby silniki napędowe umocować na sztywno do korpusu a resorowanie dać na kulkach podporowych; w rezultacie lalka kołysze się w trakcie ruszania, zatrzymywania czy zmiany prędkości. Na szczęście byłem zadowolony przynajmniej z głowy. A jako, że lalka może wykonywać różne, nawet dość długie sekwencje ruchów - efekt był całkiem niezły. Przy okazji okazało się, że dodatkowy sterownik (w założeniu miał służyć wyłącznie do wyzwalania kolejnych sekwencji) co prawda działa bardzo dobrze, ale jest absolutnie bezużyteczny, więc nawet nie dokończyłem fragmentu programu obsługi. Zresztą - program w tej postaci jaka jest nadaje się tylko do napisania od zera. Tak więc Księżniczka dostała sukienkę, perukę, rzęsy i makijaż i stanęła sobie na półce aby ładnie wyglądać. Na pewno zabiorę się za przeróbkę zarówno mechanizmu, jak i programu (poczynając od wywalenia w diabły tego nieszczęsnego pada), ale to raczej zadanie na przyszłość. Teraz postanowiłem podejść do sprawy nieco inaczej. Przede wszystkim - chciałem spróbować RPi Pico jako głównego kontrolera. Do tego chciałem podłączyć RPi Zero 2 W, do programowania Pico i do obsługi kamery (jednym z zadań lalki było patrzenie w twarz rozmówcy). Poza tym musiałem przemyśleć sprawę podwozia. Zastosowałem co prawda identyczne silniki i sterowniki jak w Księżniczce, ale tym razem całość zasilana jest z akumulatora Parkside V20 2Ah. Postanowiłem rozwiązać sprawę podwozia nieco inaczej. Teraz to kulki podporowe były połączone na stałe z korpusem, a silniki umieściłem na wahaczu. Dwie sprężyny dbają o prawidłowy kontakt kół z podłożem nawet przy jeździe po nierównej podłodze. I taka konstrukcja jeździ już prawie dobrze (dlaczego prawie - o tym na zakończenie). Ręce rozwiązałem w nieco inny sposób. Oprócz uniesienia ręki lalka ma możliwość skręcenia dłoni (dłoń jest przymocowana do osi, którą obraca małe serwo w przedramieniu), a otwieranie i zamykanie palców wziąłem z mlle Woni. Całość wygląda w ten sposób: Trochę inaczej rozwiązałem głowę. Tym razem zrezygnowałem z naturalistycznego wyglądu. Ponieważ pierwotnie miał być to Troll a nie Krasnolud - naturalne wydało mi się wydrukowanie twarzy i dłoni z szarego filamentu (w końcu trolle mają coś wspólnego ze skałami, a te są najczęściej szare). Trudności techniczne zmusiły mnie do zmiany podejścia (w porę stwierdziłem, że nie mam szans na prawidłowe odwzorowanie postaci trolla bez jakichś cilikonowych odlewów, na których się absolutnie nie znam), ale w końcu Krasnoludów jest wiele gatunków, a mój jest właśnie jaskiniowy i trudni się kamieniarstwem 🙂 Wracając do głowy: spróbowałem inaczej rozwiązać kwestię skłonu (przekładnia zębata). Dodatkowo zrezygnowałem z ruchu oczu na boki; w to miejsce wprowadziłem ruch pionowy (tak jak w "zamykających oczy" lalkach). Co prawda pozwala to na dodatkowy efekt (włączenie odpowiedniej funkcji powoduje, że Krasnolud może sobie mrugać w losowych odstępach czasu), ale ogólnie ruch oczu na bioki daje więcej możliwości animacyjnych. Ideałem byłoby umożliwienie ruchu zarówno w poziomie jak i w pione - ale to zarezerwowałem sobie do następnej konstrukcji. Pionowy ruch oczu zrealizowałem nietypowo - za pomocą paska zębatego. Zdjęcie jest trochę niewyraźne, ale wnętrze głowy wygląda w ten sposób: Niestety - musiałem z przyczyn absolutnie ode mnie niezależnych zrezygnować z kamery. Po prostu okazało się, że Zero 2 W jest absolutnie niedostępny, a zwykły Zero W nie jest w stanie w jakimś sensownym czasie znaleźć twarzy na obrazie z kamery. Próbowałem zrobić po prostu streaming - uznałem jednak, że to mało przydatna funkcja, a pracujący serwer bardzo skutecznie zagłuszał sygnał radiowy sterowania. W związku z tym wyrzuciłem kamerę, w jej miejsce włożyłem czujnik ToF i postanowiłem, że zrobię przy okazji program "spacer" (czyli typowe unikanie przeszkód). Więcej czasu poświęciłem sterownikowi. Co prawda wygląda on nieco prowizorycznie, ale bardzo dobrze spełnia swoja funkcję. Użyłem tym razem precyzyjnych joysticków (dzięki kol. @Sabre). Dzięki temu mogę bardzo precyzyjnie sterować ruchami robota - zarówno napędami, jak i głową. Niestety, znów natrafiłem na problem wpadanie ręki w oscylacje. Wydawało mi się, że zastosowanie mocnego serwa (użyte przeze mnie ma 5 kG*cm) poskutkuje wymuszeniem prawidłowego ruchu ręki. Niestety - nic z tego; przy szybszych ruchach ręce znów wpadały w oscylacje. I znów poradziłem sobie z tym problemem naokoło - spowalniając ruch serwa. Jak widać na filmie - w przeciwieństwie do Księżniczki ręce Krasnoluda poruszają się z większą precyzją. Zresztą kod sterownika zamieszczam tutaj - może nikt nie będzie chciał budować takiego samego, ale część rozwiązań może się przydać (działająca biblioteka nRF24 chociażby). geopilot.zip W międzyczasie (już gdy miałem całą konstrukcję mniej więcej złożoną) pojawił się całkiem używalny polski głos dla syntezatora RHVoice. Z bólem serca pożegnałem więc Milenę i Kedrigerna. Co prawda syntezator jest strasznie mulasty (samo wczytanie np. ukraińskiego głosu zajmuje mojemu Zero W kilkanaście sekund), ale kolejne kwestie wypowiadane są już z prawie niezauważalnym opóźnieniem. Dodatkowo mogłem wprowadzić drugi język (ukraiński - ważne, bo Krasnolud lubi czasem zawitać do przedszkola do którego uczęszczają również dzieci z Ukrainy). A tak wyglądają Księżniczka i Krasnolud w realu: Niestety - z przyczyn prawnych nie mogę zamieścić nagrań z przedszkola, ale sam byłem zdziwiony jak dzieci szybko orientują się w obsłudze dość skomplikowanego w sumie kontrolera. Przy okazji "spaceru"... okazało się, że wysoko umieszczony czujnik (tam, gdzie miała być kamera - czyli w kasku) nie łapie przeszkód znajdujących się blisko podłogi. Poradziłem sobie z tym zupełnie inaczej. Ponieważ robot w czasie spaceru porusza się symulując chód (uruchamiane są silniki naprzemiennie, a głowa i ręce kontrują obrót tułowia), całość jest synchronizowana żyroskopem (po osiągnięciu 8 stopni odchylenia od kierunku następuje przełączenie silników i obrót w drugą stronę) wystarczyło sprawdzić, czy robot rzeczywiście się obraca; jeśli tak - oznacza to napotkanie przeszkody której nie wykrył czujnik ToF. I jeszcze o popełnionych błędach i niedociągnięciach... Nie mówię tu o błędach w programie (w ostatniej chwili okazało się, że jest tam dość krytyczny błąd, nie mam dzisiaj już czasu sprawdzać ale robot musi być gotowy do spotkania z dziećmi od początku września). Z tego też powodu na razie nie zamieszczam kodu samego robota. Kulki podporowe są fajne, ale dla dość ciężkiego i robota poruszającego się po niespecjalnie równym podłożu nawet calowe kulki są za małe, aby robot potrafił np. wjechać na dywan. Na razie nie mam pomysłu na rozwiązanie tego problemu, ale liczę na Wasze podpowiedzi. Wydrukowane opony mają za małą przyczepność, i koła wpadają czasem w poślizg. Można to zobaczyć na filmie, kiedy robot w czasie spaceru próbuje cofnąć się od nieistniejącej przeszkody. Tu muszę poeksperymentować z oponami; w Księżniczce użyłem oryginalnych silikonowych opon zdjętych z tanich kół. Niestety - tu koła mają nieco większą średnicę. Będę musiał po prostu wypróbować kilka możliwości - na razie myślę o wydrukowaniu węższych kół i zrobieniu opon bez bieżnika z TPE zamiast TPU. Jeśli o czymś zapomniałem - jestem do dyspozycji 🙂
-
Freestyle Robot gąsienicowy PENETRATOR
Arturo opublikował temat w Projekty - DIY w budowie (worklogi)
Cześć. Chciałem wam pokazać projekt nad którym pracuję: PENETRATOR Trochę konkretów: aluminiowy kadłub napęd: dwa silniki elektryczne szczotkowe 9V zasilanie 2 x 18650 + 2 x 18650 ukryte pod kadłubem kontroler: Raspberry Pi 3 A+ wizja: moduł kamery 1080p emitery IR umożliwiające kamerze pracę w ciemnościach łączność: WiFi (access point i serwer HTTP) język programowania : Python 3 sterowanie i obraz: autorska aplikacja webowa (w przeglądarce) umożliwiająca wyświetlanie obrazu z kamery i sterowanie robotem W PLANACH: mechaniczne ramię 7DOF oparte na serwomechanizmach MG996R obracanie kamery w 2 płaszczyznach przy pomocy serwomechanizmów mam też ochotę pobawić się sztuczną inteligencją, stąd wybór raspberry a nie np. esp8266 Przy okazji, podziękowania dla Forbot.pl za podstawy które wprowadziły mnie do świata robotyki- 10 odpowiedzi
-
- 6
-
- gąsienicowy
- robot
-
(i 2 więcej)
Tagi:
-
Ech, miało być dużo wcześniej... Niestety okazało się, że co prawda pandemia zmusiła mnie (a właściwie nas, bo moją kochaną też) do siedzenia w domu, ale dodała mi (nam) tyle roboty, że nie bardzo miałem czas na ukończenie projektu. W każdym razie mam zaszczyt wreszcie przedstawić dyplomowaną wiedźmę, pannę (z gatunku tych starszych) Wolhę Nikołajewnę Androinę (dla znajomych Wonia, dla dalszych znajomych mlle Wonia). I od razu wyjaśnienie: tak, mimo wyglądu jest to skończony projekt. Podobnie jak Maestro Zitaurus, Wonia to konstrukcja lalki. Wszelkie włosy, ubrania to zupełnie inna historia: po pierwsze lalkę można ubrać i ucharakteryzować różnie; raz może być złą czarownicą - innym razem dobrą babcią... po drugie pracownie krawieckie są na razie niedostępne i nic nie zapowiada, aby w najbliższej przyszłości coś się zmieniło. Ale zacznę od początku. Wspomniany już Ziutek co prawda sprawdził się na scenie, ale kilka "drobiazgów" znakomicie utrudniało animację. Mechaniczny (bezpośredni) napęd głowy nie pozwolił na wygodne umieszczenie joysticka, a umieszczenie przycisku sterowania dłonią na rękojeści czempuritu bardzo skutecznie uniemożliwiło animację obu rąk jednocześnie. Konstrukcja dłoni też pozostawiała wiele do życzenia - umieszczenie serwomechanizmu w przedramieniu co prawda pozwoliło na zastosowanie typowego SG90, ale przeniesienie napędu na palce było zbyt skomplikowane. Postanowiłem więc skonstruować następną jawajkę - tym razem pozbawioną tych wad. Na pierwszy ogień poszła konstrukcja dłoni. Kiedyś kupiłem na próbę subminiaturowe serwa PowerHD 1370A - okazało się, że świetnie nadają się do tego typu konstrukcji. Są ciche, pobierają niewiele prądu, a rachityczny moment nie przeszkadza przy pracy praktycznie bez obciążenia (dłonie i oczy). Poza tym niewielkie wymiary pozwoliły na umieszczenie serwa bezpośrednio w dłoni i osadzenie palców bezpośrednio na orczyku serwa. Większy problem miałem z opracowaniem napędu głowy. Tym razem postanowiłem, że napęd będzie całkowicie elektryczny i sterowany wyłącznie joystickiem. Dodatkowo chciałem spróbować typowej dla animatroniki konstrukcji umożliwiającej przechyły głowy nie tylko w przód, ale również na boki. Niestety - narzucające się rozwiązanie z przegubami kulowymi (takimi jak stosowanie w drążkach kierowniczych pojazdów RC) było niemożliwe do zrealizowania; dostępne w handlu były po prostu za duże, a samodzielne ich wykonanie w domowych warunkach nie wchodziło w grę. Zrobiłem kilka prób, ale okazało się, że w wykonanych na drukarce przegubach luzy są zbyt duże, aby umożliwić jakikolwiek sensowny ruch - szczególnie w lalce, która wierci się i skacze na wszystkie strony. Już miałem zrezygnować z przechyłów na boki i zastosować sprawdzony sposób z przechyłem tylko w przód... ale w międzyczasie kupiłem zupełnie w innym celu komplet różnych sprężynek. Spróbowałem więc rozwiązania, gdzie sprężyna ściąga głowę do neutralnego położenia, a serwomechanizmy pracują jedynie w trybie pociągania za drążek. I to mnie uratowało - luzy są skutecznie kasowane przez sprężynę, a za napęd mogły posłużyć takie same serwa, jak w dłoniach. Obrót głowy zrealizowałem za pomocą jeszcze jednego takiego samego serwa przekładni zębatej. Jako że podstawowe sprawy z mechaniki miałem już rozwiązane, przyszła kolej na elektronikę. Tym razem lalka powinna działać w dwóch trybach: pierwszy to bezpośrednie sterowanie za pomocą joysticka i przełączników na rękojeści lalki, drugi to typowy w konstrukcjach Hensona sposób animacji przez dwie osoby: pierwsza robi to bezpośrednio w typowy dla jawajki sposób, druga (pomocnik) zdalnie steruje głową i zamykaniem dłoni. Jako głowny kontroler posłużył mi Arduino Pro Mini sterujący bezpośrednio serwami, do komunikacji ze zdalnym manipulatorem użyłem modułów nRF24L01+. Rękojeść jest tym razem pionowa, wewnątrz ukryte są Arduino, transceiver i przetwornica zasilająca elektronikę. Również w rękojeści znajduje się joystick sterujący głową, klawisze dłoni oraz przełącznik sterowania oczami. Klawisze dłoni mają różną wysokość ułatwiającą oddzielne manipulowanie dłońmi jednym palcem, przełącznik oczu został umieszczony tak, aby małym palcem można było bez problemu skierować wzrok lalki w odpowiednią stronę. Rękojeść jest dopasowana do konkretnej osoby - stąd widoczne na filmie trudności w animacji (film nagrywałem sam, a rozstaw joysticka i przycisków po prostu nie pasuje do mojej dłoni). Ponieważ musiałem zrealizować sterowanie trzema serwami za pomocą pojedynczego dwuwymiarowego joysticka, musiałem użyć pewnego tricku. Przesunięcie joysticka w dół powoduje skłon główy do przodu, przesunięcie na boki obrót, przy czym pochylenie głowy na bok realizowane jest przez jednoczesne przesunięcie joysticka w bok i w dół. Co prawda przy takim sterowaniu nie jest możliwe pochylenie głowy na bok bez obrócenia jej, ale taki taki efekt można uzyskać poprzez zaangażowanie drugiego animatora-pomocnika (zdalne sterowanie pozwala na dowolne ustawienie głowy). I tu ciekawostka. Któryś z kolei raz składając rękojeść omyłkowo przekręciłem joystick o 90°. I to był strzał w dziesiątkę: przy takim układzie można osiągnąć dużo większą precyzję w sterowaniu głową. Kontrola ruchu kciuka w linii prostopadłej do palców (a więc równoległej do osi rękojeści) jest dokładniejsza, a ta dokładność jest potrzebna przede wszystkim przy obrocie głowy. No i oczywiście musiałem popełnić jeden błąd - zasilanie. Początkowo zakładałem akumulator LiPo 2000 mAh i moduł przetwornicy MT3608. Wszystko pięknie działało dopóki nie poskładałem tego do kupy... okazało się, że zestaw akumulator-przetwornica nie wyrabia się szczególnie przy starcie urządzenia. Co prawda układ bardzo ładbnie startował przy odłączonych serwach, ich podłączenie już po starcie umożliwiało dalszą pracę ale i tak Arduino działał niestabilnie, a przy uruchomieniu wszystkich serw układ potrafił się zresetować. Wybrałem więc rozwiązanie najprostsze: zastosowałem dwie przetwornice (oddzielna na elektronikę, oddzielna na serwa) i największy akumulator LiPo z oferty Botlandu. Ustawienie drugiej przetwornicy na 6V pomogło przy okazji serwomechanizmom. Na załączonych zdjęciach widoczne są obie przetwornice - jedna w rękojeści do zasilania elektroniki, druga z tyłu lalki pod akumulatorem (do serw). Teraz przyszła kolej na sterownik dla pomocnika. W teorii urządzenie jest proste - Arduino, transceiver (tu użyłem wersji z anteną) kilka klawiszy, joystick... no i właśnie na joysticku się zaciąłem. Próbowałem różnych mocowań potencjometrów, ale urządzenie wychodziło mi jakieś wielkie i nieporęczne, poza tym operowanie czymś takim było piekielnie niewygodne. Chciałem sobie uprościć robotę, kupić zwykły joystick analogowy od peceta i na nim zamontować trzeci potencjometr - niestety, nie mogłem trafić na żadną sensowną ofertę w sieci. W końcu gdzieś na Thingiverse znalazłem szkic podobnej konstrukcji, no i po paru godzinach spędzonych nad OpenSCAD-em miałem gotowy projekt: Ostatecznie całość wygląda tak jak poniżej na zdjęciu. A, i jak zwykle jakiś błąd musiałem popełnić. Skorzystałem tu z gotowej obudowy Kradexu, wymierzyłem, znalazłem akumulator który się tam mieści i... zamawiając go w Botlandzie kliknąłem na sąsiedni link. Stąd takie dziwne umieszczenie akumulatora na zewnątrz - po prostu ten model nie zmieścił się w obudowie 🙂 I tyle miałem przed pandemią. Co więc tak opóźniło opublikowanie projektu? Otóż zarówno w lalce, jak i w sterowniku zrezygnowałem z wbudowania ładowarki. Ponieważ oba akumulatory mają identyczne złącza JST, postanowiłem zrobić uniwersalną ładowarkę z możliwością przełączania prądu ładowania. Ustrojstwo niesamowicie proste: w zwykłym module TP4056 usunąłem rezystor 1.2k i w miejsce tego podłączyłem drabinkę z kilku rezystorów oraz przełącznik obrotowy, a do wyjścia podłączyłem gniazdo JST. Niestety, przed pandemią nie zdążyłem zmontować ładowarki, a nie mogłem przecież opisać niekompletnego urządzenia! Jeśli kogoś to interesuje, zamieszczam kody lalki i sterownika: lalka.zip O ile kod lalki jest dość typowy, to w kodzie sterownika zastosowałem pewien trick którego nie widziałem na razie w żadnych konstrukcjach - diodę sygnalizującą połączenie radiowe z lalką. Może się komuś przyda... Schematów nie zamieszczam, bo w rzeczywistości oba urządzenia (sterownik i lalka) to płytka Arduino z podłączonymi do odpowiednich pinów transceiverem, przyciskami, diodami i tak dalej, a cała pinologia jest zamieszczona w źródłach. Na koniec wyjaśnienie - skąd akurat takie imię? Miłośnicy fantasy pewnie już wiedzą o kogo chodzi, ale na wszelki wypadek podaję link do książki 🙂
-
KTR-X1 8x8 To pojazd zdalnie sterowany do wykonywania zadań w bardzo trudnym terenie, charakteryzuje się prostotą konstrukcji, niskim środkiem ciężkości oraz uniwersalnością. KTR-X1 8x8 zbudowany jest z dwóch sekcji połączonych przegubem, dzięki temu każda sekcja jest niezależna, dodatkowo ruchome wahacze dają ogromne możliwości w pokonywaniu przeszkód w terenie. Pojazd obojętnie w jakim terenie się porusza,oraz jak stoi, zawsze ma doskonałą przyczepność na wszystkie 8 kół. Dzięki temu że KTR-X1 jest hybrydą można bez obaw wybrać się na dalekie wyprawy gdyż prądu nam nie zabraknie. Specyfikacja: Podwozie: rama stalowa z profila zamkniętego 30x30x1,5 spawana Wahacze: profil zamknięty 40x80x2 wspawane bloki łożyskowe Osie: stal utwardzana Poszycie: blacha aluminiowa 0.8mm wzmacniania kątownikami Przegub: blacha stalowa 10mm plus łożyska Przeniesienie napędu: przekładna łańcuchowa przełożenie1,5:1 Napęd: 4 silniki 24V / 450W każdy (moc znamionowa) 123 RPM Układ skrętu: pompa hydrauliczna 120 bar plus dwa siłowniki Sterownik silnika: Sabertooth 4x32A Sterownik pompy: Sabertooth 2x32A Zasilanie: Akumulatory kwasowe 12V 40Ah 2x Układ ładowania: silnik spalinowy 200c OHV plus alternator 28V 55A Koła: 145/70/6 Pilot sterowania: HK-T6 Oświetlenie: led 2x9W przód, 2x2W tył Układ chłodzenia: 2x wentylator 4800 RPM Przyłącza: szybkozłącze montażowe plus gniazdo zasilania Spalanie: 0,5-1,2l/mth Masa: 140Kg Prędkość max: 8km/h Wymiary: szer 90cm, dłu 192cm, wys 68cm, prześwit 27cm Dalsze plany: Pług do śniegu obrotowy (zrobiony) Zamiatarka (dalszy etap) Kosiarka (dalszy etap)
-
Jakiś czas temu opisałem budowę pojazdu zdalnie sterowanego. Przypomnę, efekt końcowy wyglądał mniej więcej następująco: Już w poprzednim poście wypunktowałem wszystkie wady powyższego projektu - zaczynając od sterczących wszędzie kabli, anten mocowanych na taśmę izolacyjną, ogromnego Leonardo z shieldem sterującym silnikami w charakterze kontrolera, a wszystko na DIY płytce aluminiowej: działać działało, ale wyglądało... no, nazwijmy rzecz po imieniu, tragicznie. Postanowiłem więc zabrać się za temat trochę bardziej poważnie - a że w międzyczasie wyposażyłem się w drukarkę 3D, miałem spore pole do popisu. Zmienił się też charakter projektu. Zamiast czujnika zbliżeniowego zdecydowałem się zamontować kamerkę z przekaźnikiem wideo - ze zdalnie sterowanego pojazdu powstał więc w mały robot inspekcyjny. Na pierwszy ogień poszły komponenty. Leonardo wyleciało do innego projektu, gdzie miało o wiele więcej sensu, a w jego miejsce trafił Teensy 3.6 (pod linkiem 3.5 dostępny w Botlandzie - różnią się głównie mocą obliczeniową). Shield zastąpiłem dwoma osobnymi dwukanałowymi sterownikami silników DRV8833. Zasilanie szło z dwucelowego akumulatora LiPo, więc konieczny był też zakup przetwornic step-down, ale nauczony doświadczeniem kupiłem dwie: jedna zasilała Teensy i odbiornik RC, zaś druga - dwa mikro serwa, które sterowały położeniem kamerki. Sterowniki silników dostały napięcie bezpośrednio z akumulatora, bo obsługiwały je bezpośrednio, podobnie nadajnik video. Drugim krokiem było zaprojektowanie płytki. Miałem już dosyć sterczących wszędzie kabli, więc zacisnąłem zęby, siadłem do Eagle'a i zacząłem dziergać płytkę. Pomierzyłem wszystko dokładnie, zamówiłem w JCLPCB i światło dzienne ujrzał taki oto kawałek włókna szklanego: Przede wszystkim zaprojektowałem od razu otwory, którymi mogłem przeprowadzić spod spodu przewody od silników - odpowiednie pady znalazły się zaraz obok, co znacząco ułatwiło lutowanie. Pamiętałem też o tym, by ścieżki, którymi będzie płynął największy prąd zasilający silniki i nadajnik video, były odpowiednio szersze od pozostałych. Nie są to wprawdzie dramatycznie wysokie wartości, ale po co ryzykować. Oprócz dedykowanych złącz na dwa serwa, wyprowadziłem jeszcze dwie dodatkowe serie pinów 5V+GND+SGN w razie, gdybym chciał podłączyć jeszcze więcej serw albo na przykład czujnik odległości. Teraz trzeba było zaprojektować obudowę. Zdecydowałem się na prosty, prostopadłościenny kształt - elegancki, funkcjonalny i - co najważniejsze - dopasowany dokładnie do podwozia, które miałem do dyspozycji. Co ważne, okazało się, że przy odrobinie wysiłku projekty w Fusion 360 można synchronizować z płytkami zaprojektowanymi w Eagle - dzięki temu mogłem "wywiercić" otwory dokładnie w tych miejscach, w których było to potrzebne. Potem pozostało wydrukowanie wszystkiego - długie godziny - i zmontowanie. Dzięki temu, że miałem pod ręką płytkę, montaż był banalnie prosty. Wszędzie przylutowałem żeńskie złącza goldpinowe, serwa przypiąłem kątowymi męskimi pinami (dzięki temu nie miałem problemu ze zmieszczeniem wtyków w obudowie), wszystkie płytki wskoczyły w swoje sloty i to właściwie był koniec. I dobrze, bo mam straszną alergię na lutowanie... Programowania też nie było zbyt dużo, ponieważ mogłem wykorzystać całkiem sporo kodu z poprzedniego projektu. Całość możecie ściągnąć z mojego repozytorium na GitLabie. Jest jednak jedna istotna różnica: zdecydowałem się zrezygnować z odbierania danych z odbiornika RC poprzez PWM, ponieważ FrSky umożliwia odbiór kompletu danych poprzez port szeregowy - SBUS. Pewnym problemem jest to, że jego standard jest... nieco niestandardowy (jest to podobno "odwrócony" UART, czyli tam, gdzie w UART jest 1, w tym formacie jest 0 i vice versa), ale na szczęście istnieje odpowiednia biblioteka, która automatycznie odczytuje dane, przy czym uwaga: nie działa ona na wszystkich mikrokontrolerach zgodnych z Arduino (ale na szczęście działa na Teensy). Co ciekawe, nie nadziałem się w tym projekcie na żadne większe problemy. Było jednak kilka mniejszych, na przykład montaż akumulatora - pierwotnie miał on znajdować się w dedykowanej rynience, ale się w niej najzwyczajniej w świecie nie zmieścił. Nie zależało ni jednak jakoś specjalnie na tym, by się tam ona znajdowała, więc skończyło się na najzwyklejszym rzepie. Musiałem też raz przeprojektować montaż anten odbiornika RC (powinny one znajdować się względem siebie pod kątem 90 stopni dla najlepszego odbioru), ale ogólnie jestem z niego bardzo zadowolony, bo anteny wskoczyły w odpowiednie sloty i trzymają się w nich pod wpływem grawitacji - nie musiałem montować ich na klej, śrubami czy w inny tego typu sposób. Po złożeniu całości okazało się też, że robot bardzo mozolnie porusza się do przodu i dziwnie przy tym terkocze. Okazało się, że oryginalne felgi umieszczały opony bardzo blisko podwozia, przez co opony o nie tarły. Niewiele myśląc, zwymiarowałem felgi dokładnie i wydrukowałem własne - z odpowiednim dystansem. Na powyższych zdjęciach widać już te wydrukowane. Poniżej możecie zobaczyć robocika podczas pierwszych testów w moim starym mieszkaniu. Moja półtoraroczna córka nazywa go "brum brum", ale finalnie ochrzciłem go zgodnie z przyjętą jakiś czas temu konwencją, więc otrzymał imię "Lynx", czyli "Ryś". Koncepcja opierała się na nazywaniu pojazdów zwierzętami, które mniej więcej oddają sposób ich poruszania się - na przykład zbudowane przeze mnie wcześniej quadrokoptery po starcie musiały zawsze w jakiś sposób wylądować (planowo lub nie), uznałem, że tak naprawdę nie latają, tylko kicają, więc otrzymały nazwy "Pchła" (najmniejszy, kupiony w sklepie), "Zając", "Królik" i "Kangur" (ostatni mierzył pół metra - od śmigła do śmigła, a latał na śmigłach 12,8"). Ot, taka ciekawostka. Ale projekt koniec końców się udał! Czego życzę również wszystkim innym amatorom budowania elektronicznych projektów DIY.
- 5 odpowiedzi
-
- 12
-
Cześć! Chciałbym podzielić się z Wami projektem rozwijanym przeze mnie równolegle do AgeBota (który zresztą na tejże platformie bazuje) o nazwie Veides. A co to? Głównym celem Veidesa jest umożliwienie każdemu korzystania z dobrodziejstw Sztucznej Inteligencji bez konieczności posiadania zaawansowanej wiedzy na temat działania poszczególnych algorytmów z tej dziedziny, co pozwala skupić się na tworzeniu rozwiązań, a nie rozwiązywaniu problemów. A do czego to? Jednym z założeń Veidesa jest to, aby mógł współpracować z dowolnymi urządzeniami (robotami, robotycznymi ramionami, wszelkimi DIY, Smart Home), którym przyda się trochę inteligencji. Nie ma wymogu posiadania warstwy sprzętowej, więc wszystko zależy od fantazji. Wystarczy połączenie z internetem. A co to potrafi? Jednym z głównych elementów systemu są tzw. przepływy. Określają one wysokopoziomowe zachowanie Waszego robota, urządzenia czy oprogramowania (dalej "agent"), a definiuje się je za pomocą graficznego edytora w konsoli: Przepływy można zdefiniować jako bazę wiedzy każdego agenta, która potem ewoluuje w zależności od środowiska, w jakim agent się znajduje czy w jakie interakcje z nim wchodzi. Uczy się, trochę jak dziecko 🙂 Idąc nieco w przód, wymarzyłem sobie świat, w którym to robot przyrządza i podaje mi rano kawę. Przygotowanie kawy dla nas jest czymś względnie prostym, natomiast dla robota już niekoniecznie. Z jego punktu widzenia jest to dosyć skomplikowana sekwencja czynności do wykonania, gdyż musi posiadać wiedzę na temat środowiska zaczynając od tego gdzie jest czajnik czy ekspres, przez to gdzie są kubki (oraz czy mam jakieś preferencje co do kubka) i ile czasu kawę należy parzyć (jeśli użyje czajnika). Być może znacząco uprościłem, ale myślę, że to oddaje obraz złożoności tego zadania. Jeśli dołożymy zmieniające się, dynamiczne środowisko gdzie ktoś może przestawić czajnik/ekspres lub na drodze do nich pojawi się niespodziewana przeszkoda, to jeszcze bardziej zwiększamy poziom skomplikowania. I tutaj wkracza Veides. Rolą platformy w tym wszystkim jest koordynacja wykorzystywania zgromadzonej wiedzy oraz zdobywania nowej. Nasze zadanie to - w przypadku robota - reagowanie w pożądany sposób "sprzętem" na przychodzące akcje, czyli konkretne instrukcje do zrobienia: Jedź prosto 3 metry Obróć się w lewo o 30 stopni itd... Analogicznie w przypadku innych urządzeń i oprogramowania. Dzięki systemowi możemy lepiej skupić się na tym co chcemy osiągnąć. Powyższą wizję powoli wdrażam. Na początek AgeBot jest w stanie uczyć się pewnych zachowań, może także zapamiętać twarze i przypisać do nich imiona oraz oczywiście posiada wiele innych możliwości, np. reagowanie na krytyczny stan baterii - w czym pomaga Veides. Następnym modułem systemu jest wbudowany czat tekstowy, dzięki któremu mamy możliwość komunikowania się z agentem za pomocą języka naturalnego. Komunikacja jest dwustronna, tzn. można ten kanał wykorzystać do wydawania poleceń ("Skocz po piłkę") oraz do tego, aby agent informował nas o ważnych zdarzeniach ("Chodźcie, Adaś skacze!"). Istnieje także możliwość "poproszenia" o kilka zadań, które wykonają się sekwencyjnie, dodatkowo można te zadania "odkładać" na później (przykład w tym filmie) czy ustawić sobie budzik. Polecenia nie muszą być wykonywane natychmiast. Jednym z kolejnych kroków w rozwoju modułu komunikacji będzie m.in. komunikacja głosowa. Kolejny moduł to kokpit, w którym możemy na żywo obserwować działanie agentów dostosowane do naszych potrzeb: A jak się tego używa? Do projektu spisana jest dokumentacja zawierająca najważniejsze informacje (m.in. tutorial jak zacząć), które w miarę możliwości uzupełniam. Zarówno dokumentacja, konsola oraz moduł czatu wspierają obecnie jedynie język angielski. Veides dostarcza SDK dla Pythona, C oraz dla aplikacji ROS paczkę zawierającą node (gotowy przykład znajduje się na GitHubie). Bilbioteki SDK dla innych technologii (NodeJS, Java (również w kontekście Androida), Arduino oraz ROS2) zostaną zaimplementowane w niedalekiej przyszłości, jeśli będzie taka potrzeba. A kiedy to? Veides jest aktualnie w fazie alfa, jednak minimalna ilość funkcjonalności jest już dostępna niepublicznie. Użytkownikom forum chcącym sprawdzić jak Veides działa w praktyce, chętnie dam dostęp do systemu na miesiąc (poproszę zgłosić się w wiadomości prywatnej), a jeśli projekt się spodoba, po miesiącu zastanowimy się co dalej 🙂 W zamian jedyne o co bym prosił to podzielenie się opinią o projekcie. Takie możliwości jak nauka zachowań czy twarzy na razie są dostępne tylko dla AgeBota, ale oczywiście zostanią wraz z rozwojem platformy udostępnione. A co dalej? Projekt rozwijam systematycznie w ramach hobby, co sprawia mi mnóstwo frajdy. Mam wiele pomysłów bezpośrednio dotyczących rozwoju AgeBota jak i tych dotyczących ekosystemu Veidesa, które powoli wcielam w życie. Na razie konkretnych dat nie jestem w stanie podać, ale na pewno w tym wątku będę Was na bieżąco informował o wszelkich postępach. Zachęcam także do śledzenia wątku AgeBot. Jeśli macie jakieś pytania, opinie, wątpliwości - zapraszam do dyskusji 🙂
-
Robot "Kopernik" to projekt robota-asystenta targowego 😉 Robot powstawał dla i we współpracy z moim Technikum Łączności, z Zespołu Szkół Łączności im. M. Kopernika w Poznaniu. Podstawowym celem było (z mojej strony) stworzenie platformy sprzętowej opartej o RaspberryPi, która umożliwi prostą "rozmowę" - w przyszłości, kiedy drzwi otwarte/targi edukacyjne będą mogły odbywać się już normalnie, platforma ma stanowić urozmaicenie tego typu imprez i stanowić stoiskowe "eye-catcher" 🙂 Worklog Pozwolę sobie nie opisywać początkowych założeń, ścieżki rozwoju itd.. - kwestie te aktualizowałem "na bieżąco" w worklog'u (link poniżej), a tutaj przedstawię jedynie efekt końcowy 🙂 Konstrukcja mechaniczna Zagadnienie chyba najlepiej wytłumaczą zdjęcia (na części z nich robot nie ma osłon - chodzi oczywiście o pokazanie podzespołów wewnątrz konstrukcji); nie ma też w sumie wielkiej filozofii - stelaż v-slot, profile 20x20 (jednym z założeń robota miała być możliwość łatwej rozbudowy i zmian), w odpowiednich miejscach wstawione poprzeczki celem montażu wszystkich elementów. W podstawie znajdują się koła meblowe, całość (z wyjątkiem strony dolnej i tyłu) jest zabudowana plexiglassem i polipropylenem, wcześniej odpowiednio dociętym/nawierconym. Oprócz konstrukcji nośnej, stelaż pełni też funkcję "szafy", do której schować mogłem wszystkie moduły i podzespoły (o których za chwilę). Jest też pneumatyka - robot miał mieć możliwość wykonywania jakiś prostych ruchów, dlatego są dwie "dłonie" zawieszone wahliwie na łożysku poruszane siłownikami pneumatycznymi (takie odniesienie do zawodu "technik automatyk" - miałem zadbać też o to, aby robot nawiązywał do oferty edukacyjnej Szkoły 😉). Jeśli chodzi o wysokość całkowitą, jest to około 800mm: Głowa Jeśli chodzi o głowę, jest ona zamocowana na silniku krokowym z drukowaną 3D przekładnią planetarną. "Twarz" stanowi dysk polipropylenu, znajdują się na niej dwa microserwa jako "brwi" - SG-90; "oczy" - pierścienie WS2812B z dwoma kamerami RaspberryPi Camera; "nos" - czujnik Sharpa i "usta" - głośnik (aktualnie niepodłączony, jego funkcję pełnią dwa głośniki umieszczone przy podstawie). Całość (chodzi o głowę) ma średnicę 200mm, a jest zwieńczona aluminiową półkulą. Ponownie moduł głowy najlepiej zilustrują zdjęcia 😉 Elektronika i oprogramowanie W kwestii elektroniki, "sercem" czy też modułem nadrzędnym jest RaspberryPi 3B+ - to na nim uruchomiony jest główny program, który "czeka" na moment, kiedy ktoś przyłoży rękę pod czujnik Sharp'a - wówczas pobrana zostaje próbka dźwiękowa, która zostaje przekazana do modułu Python SpeechRecognition - wykorzystuję default'owe API od Google (to darmowe, które nie wymaga rejestracji - https://pypi.org/project/SpeechRecognition/ oraz https://realpython.com/python-speech-recognition/ ). Na ten moment dostępnych jest dwanaście fraz, każda licząca dwa słowa - typu "oferta edukacyjna", "termin rekrutacji" czy "aktualna godzina" - kiedy otrzymam w zwrocie to, co rozpoznał moduł SpeechRecognizer, obliczam https://pl.wikipedia.org/wiki/Odległość_Levenshteina - dzięki temu mogę stwierdzić, która fraza jest najbardziej zbliżona do wyniku detekcji mowy. Oczywiście jest to rozwiązanie silnie nieoptymalne, ale całkiem skuteczne - nie ukrywam bowiem, że moja implementacja rozpoznawania mowy jest daleka od idealnej 😉 Jeśli fraza wymaga odpowiedzi, wywołuję polecenie espeak, który syntetyzuje odpowiedź. Jest też opcja przewidziana dla frazy "aktualna godzina" - wówczas osobny podprogram pobiera czas systemowy i syntetyzuje odpowiedź (na filmie poniżej było już po północy 😉) oraz "widoczny kolor" - aby zademonstrować jakoś możliwość wykorzystania kamery, osobny podprogram może zrobić zdjęcie, obliczyć wartości kanałów R,G,B i stwierdzić, czy pokazany przez mnie przedmiot (na przykład kartka papieru) jest czerwona, zielona czy niebieska. Jeśli zaś chodzi o warstwę sprzętową - w robocie znajdują się w sumie cztery płytki Arduino, które: a) pierwsze Arduino Nano - sterowanie matrycą LED 8x32, jest to zapętlone wyświetlanie napisów typu "Cześć, jestem <<Kopernik>>, opowiem Ci o ZSŁ..." czy animacji pulsu serca oraz losowego szumu - wykorzystałem przykład z dedykowanej temu modułowi biblioteki; b) drugie Arduino Nano - sterowanie diodami LED WS2812B - jest to płytka uniwersalna z dwoma rzędami złącz ARK - pozwala na sterowanie trzech zestawów takich LED'ów (2x pierścienie - oczy i pasek "w korpusie"), ma też optoizolowane wejścia - trzy, Raspberry podając kombinację trzech bitów może sterować efektami świetlnymi na osiem różnych sposobów; c) trzecie Arduino Nano - sterowanie drukarką termiczną (o tym za chwilę); d) Arduino Uno z shieldem "Sensor Shield" - komunikuje się z RPi po USB, przez UART - to Arduino obsługuje Sharp'a jako urządzenie wejściowe, jeśli chodzi o urządzenia wyjściowe - steruje przekaźnikami (elektrozawór dłoni i elektromagnesy), szyją - silnikiem krokowym, brzęczykiem czy serwami 😉 Uprzedzając pytania - oczywiście można było zmniejszyć liczbę Arduino i wykorzystać je optymalniej, ale prosze pamiętać, że konstrukcja miała pozostać rozwojowa - stąd budowa modułowa i "rozbicie" funkcjonalności na kilka płytek Arduino (inną kwestią jest popularność Arduino w środowisku szkolnym (znacznie większa niż STM32) oraz fakt, że jednoczesne sterowanie WS2812B i serwami z jednego Arduino wymaga pewnych sztuczek 😉). Jeśli chodzi o zasilacze, w sumie jest ich cztery - trzy 12Vdc i jeden 5V dla RPi? Dlaczego tak dużo? Znów możliwość rozwoju - ponadto wolałem oddzielić od siebie zasilanie drukarki termicznej, logiki i elementów wykonawczych (miałem pewne problemy z zakłóceniami elektromagnetycznymi). Oczywiście właściwe napięcia uzyskuję później za pomocą przetwornic step-down. Drukarka termiczna Chciałbym jeszcze zwrócić uwagę na moduł drukarki termicznej - na odpowiednią komendę RPi wyzwala za pomocą impulsu Arduino Nano sterujące drukarką termiczną i rozpoczyna się proces drukowania ulotki zawierającej informacje o szkole, zawodach, kod QR z adresem WWW naszej szkoły - takie "zaproszenie" do zabrania 😉 Jest zamocowana "w korpusie", ale tutaj również najlepiej załączę kilka zdjęć: Szczegóły Myślę, że omówiłem wszystkie najważniejsze aspekty projektu - pozwolę sobie na dołączenie jeszcze kilku zdjęć 😉 Zapraszam za zadawania pytań i/lub konstruktywnej krytyki 😉 Film Podziękowania Szczególnie podziękować muszę: a) firmie Nettigo, która pomogła mi nabyć w preferencyjnej cenie drukarkę termiczną z akcesoriami - dziękuję 😉 b) Igus Polska za udostępnienie jako sample przegubów widełkowych do siłowników pneumatycznych; c) mojej Szkole, Zespołowi Szkół Łączności im. M. Kopernika w Poznaniu, za wsparcie merytoryczno-finansowe; d) Kolegom z Forum, szczególnie Koledze @ethanak za wskazówki w sprawie syntezy mowy; e) Kolegom z Szkoły, którzy doradzali mi w kwestii design'u; a szczególnie @k_ijada za pomoc w opracowaniu dokumentacji - ponieważ to projekt szkolny, potrzebne były schematy, opisy itd... dla osób, które w przyszłości "zaopiekują się" projektem 🙂 Co dalej? Robot jest już w Szkole, teraz trafił "pod skrzydła" Kolegi-informatyka, który zajmie się "lepszym" rozpoznawaniem mowy - w planach jest rozszerzenie bazy pytań i możliwość "domyślenia się", o co chodzi autorowi pytania. Następnie ma być rozwijany przez innych chętnych uczniów, a możliwości jest bardzo dużo - od zmiany dłoni na proste manipulatory z większą liczbą stopni swobody z "ciekawszymi" chwytakami, przez dodanie OpenCV i skorzystanie z kamer obecnych "w głowie", po dodanie monitora/ekranu wyświetlającego tekstowo to, co mówi robot. Z swojej części projektu jestem zadowolony, myślę, że nie wyszła ona najgorzej - jest to już coś, co można gdzieś ewentualnie zaprezentować, a jednocześnie zostawiłem "otwartą furtkę" do dalszego rozwoju/poprawek 🙂 Pozdrawiam Wiktor Nowacki
- 2 odpowiedzi
-
- 11
-
Cześć, ponieważ jestem w klasie maturalnej i wkrótce wychodzę z Technikum, otrzymałem zadanie - zbudować robota (nazwa jeszcze niewybrana), który będzie stanowił uatrakcyjnienie drzwi otwartych, targów edukacyjnych i tym podobnych imprez. Założenia podstawowe: Konstrukcja prototypowa, z możliwością szerokiego rozwoju, niekoniecznie ukończona i zamknięta w 100% (tak, aby w przyszłości inni mogli rozwijać projekt dalej) - ale już działająca Funkcja informacyjna, odpowiadanie na podstawowe (oczywiście przy rozsądnym stopniu dokładności, bardziej demonstracyjnie) pytania typu: "Gdzie leży szkoła?" Rozsądny budżet, projekt miał być stosunkowo "low-cost" Nawiązanie do oferty szkoły tak, aby zaprezentowane zostało jakieś nawiązanie do automatyki, elektroniki czy informatyki (o co przy budowie robota nietrudno 😉) - w pozostałych kwestiach (sprzętowych, elektronicznych i programowych) pełna dowolność Projekt miał być zespołowy, ale ze względu na pandemię zabrałem go do domu 😉 Dobrze, to uwzględniając ramy z pierwszego akapitu, dodałem własne założenia: Konstrukcja oparta na V-slot'ach - łatwo się z nich prototypuje, nie ma problemu, aby coś przenieść, dodać, usunąć - co jest też ważne, że budując np. dedykowany prostopadłościan z plexi nie jestem w stanie przewidzieć, co będą potrzebować osoby rozwijające projekt dalej 😉 Skrzynka o wymiarach 240x240x500mm Wygląd - jak widać poniżej, dość futurystyczny, możliwie zbliżony do wyobrażeń "nie-technicznych" - jednocześnie pamiętając o kryterium "low-cost" nie chciałem zbytnio konstrukcji upodabniać do wzorców naturalnych - po pierwsze było by to zbyt kosztowne, po drugie miałem na uwadze argument "doliny niesamowitości" Rozpoznawanie mowy i odpowiadanie na pytania oparte o interfejs Google i syntetyzator e-speak - zatem jako mózg robota wybrałem RaspberryPi 4B Ręce, chociaż to bardziej "kończyny górne" - również skręcone z V-slot, zakończone elektromagnesami (może wstawię tylko magnesy, jeszcze nie wiem), zawieszone wahliwie - z tyłu popychane są siłownikiem pneumatycznym (powietrze to nie problem), ciśnienie jest zredukowane do minimum tak, że bez problemu zatrzymam siłownik dłonią (w celach bezpieczeństwa) Twarz - z tyłu zakończona półkulą fi200mm; z przodu znalazło się miejsce na serwa SG-90 jako "brwi"; kamery RPi wraz z pierścieniami LED jako "oczy" - podłączona będzie tylko jedna, może uda mi się napisać proste przetwarzanie obrazu na zasadzie "Jaki kolor kartki widzisz przed sobą?" (oczywiście nic nie stoi na przeszkodzie, aby w przyszłości jakiś entuzjasta wykonał stereowizję 😉), Sharp jako czujnik odległości "nos" i głośnik jako "usta" - chociaż jak widać na zdjęciu, na ten moment wykorzystuję gotowe "kolumny" (umieszczone na dole) - a głośnik w twarzy pełni funkcję wyłącznie estetyczną Silnik krokowy z przekładnią w "szyi" tak, aby można było obracać nią kilkanaście stopni lewo/prawo Drukarka termiczna, aby na zawołanie "Wydrukuj zaproszenie" drukować "paragon" z informacjami o szkole i kodem QR jej strony internetowej Matryca LED u góry z informacją "Cześć, jestem Nicolaus" Pamiętając o modułowości, między moduły o Raspberry Pi dodam Arduino (jedno do drukarki, drugie do matrycy LED, trzecie do WS2818B oraz czwarte do pozostałych urządzeń) - mogę to zrobić oszczędniej, ale pamiętam o pozostawieniu możliwości rozwoju Poniżej kilka zdjęć (z różnych stadiów rozwoju projektu) - jak mówiłem, wygląd jest dość futurystyczny - proszę pamiętać, że środkowa "luka" zostanie zapełniona płytą z elektroniką, podobnie węże też podpięte są prototypowo): A tu film jak działają dłonie: oraz rozpoznawanie mowy z Google i synteza e-speak (na laptopie, ale udało mi się przenieść całość na RPi): Robot powstaje dość szybko, jak na moje tempo wręcz błyskawicznie - chciałbym (przynajmniej elektronicznie-sprzętowo) dokończyć go w przeciągu dwóch tygodni 🙂 Pozdrawiam wn2001
-
Freestyle Drewniak - robot mobilny pokonujący labirynt
Kubeck opublikował temat w Projekty - DIY roboty
Witam serdecznie 🤗 Chciałbym przedstawić Państwu robota mobilnego, o wdzięcznej nazwie "Drewniak". Praca ta została wykonana na potrzeby projektu "Roboty Mobilne" na Politechnice Opolskiej. Poniżej krótka prezentacja robota na filmie, w dalszej części szczegółowy opis. 1. Cel projektu. Celem projektu było stworzenie robota mobilnego zdolnego do pokonania labiryntu. Postawione wymagania przed realizacją projektu to: całkowita autonomiczność robota, brak możliwości wpływu na trajektorię podczas jazdy robota, pokonanie labiryntu w obie strony w czasie poniżej dwóch minut. Zakres prac koniecznych do wykonania w celu realizacji projektu obejmuje: zaprojektowanie konstrukcji mechanicznej oraz elektronicznej, budowa robota, stworzenie oprogramowania robota, wykonanie testów potwierdzających poprawność działania konstrukcji. Na poniższym rysunku przedstawiono zwymiarowany schemat labiryntu, który musi zostać pokonany przez projektowanego robota. 2. Budowa układu mechanicznego, napędy Do stworzenia platformy jezdnej robota zdecydowano się na użycie materiałów drewnianych. Podwozie wykonane zostało z płyty pilśniowej oraz przymocowanych do nich drewnianych listew. Rzeczywiste wymiary platformy jezdnej robota to: 18 cm długości, 12 cm szerokości, oraz 5,5 cm wysokości, mierząc od podłoża do platformy podwozia. Robot napędzamy jest dwoma silnikami DC o następujących parametrach: napięcie zasilania od 5 V do 7 V, pobór prądu w stanie „bez obciążenia” około 180 mA, prędkość obrotowa za przekładnią: ok. 80 obr/min, moment obrotowy za przekładnią: ok. 0,5 kg*cm (0,049 Nm). Dodatkowo, kompletny zespół napędowy wyposażony jest w przekładnię o przełożeniu 48:1, a także koła wykonane z materiałów sztucznych o średnicy 65 mm oraz szerokości 26 mm. Ostatnim elementem zestawu napędowego robota jest prototypowe koło samonastawne, stworzone z zestawu klocków „Lego”, zapewniające trzeci punkt podparcia robota. Na poniższych rysunkach ukazano podwozie robota wraz z przymocowanymi napędami. 3. Budowa układu elektronicznego 3.1. Układ zasilania Na układ zasilania konstrukcji robota składają się cztery ogniwa litowo-jonowe, pozyskane ze starej baterii laptopa. Pojemność jednego ogniwa wynosi 2200 mAh. Ogniwa połączono równolegle. Uzyskano w ten sposób źródło zasilania o napięciu równym od 3 do 4,2 V, w zależności od stanu naładowania ogniw, oraz pojemności ponad 8000 mAh. W celu zabezpieczenia ogniw przed nadmiernym rozładowaniem oraz możliwości ich ładowania, zastosowano układ ładowarki akumulatorów jedno-celowych z zabezpieczeniem o oznaczeniu TP4056. Napięcie ładowania ogniw wynosi 4,2 V, maksymalny prąd ładowania to 1000 mA. Zabezpieczenie przed rozładowaniem ogniw poniżej napięcia równego 2,5 V, oraz przed poborem prądu ponad 3 A. Zastosowane rozwiązanie pozwala na ciągłe pobieranie prądu równego 3 A przez niespełna 3 godziny, bez uwzględniania strat. Aby podnieść napięcie zasilania do poziomu napięcia równego 7 V (maksymalne napięcie zasilania zastosowanych silników), użyto regulowanej przetwornicy impulsowej Step-Up o oznaczeniu XL6009E1. Jej napięcie wejściowe wynosi od 3 do 30 V, natomiast możliwe do regulacji napięcie wyjściowe od 5 do 35 V. Maksymalny prąd wyjściowy równa się 3 A, a sprawność układu zawiera się na poziomie 80-94%. Napięcie wyjściowe z przetwornicy zostało doprowadzone do sterownika silników oraz układu z mikroprocesorem. 3.2. Sterownik silników W celu precyzyjnej kontroli prędkości oraz kierunku obrotu silników napędowych, zastosowano dwukanałowy sterownik silników prądu stałego o oznaczeniu L298N. Pozwala on na pobór prądu maksymalnego przez silniki do 2 A na kanał, przy maksymalnym napięciu zasilania równym 12 V. Sterownik posiada wbudowany regulator napięcia 5 V, do zasilania części logicznej. 3.3. Czujniki odległości Aby możliwe było określenie położenia robota mobilnego, zdecydowano się na wykorzystanie dwóch analogowych czujników odległości firmy Sharp. Pierwszy z nich, o oznaczeniu GP2Y0A41SK0F i zakresie pracy od 4 do 30 cm, został umieszczony na prawym boku platformy jezdnej, pozwalając tym samym na określenie jej odległości od prawej ściany. Drugi czujnik, o oznaczeniu GP2Y0A21YK0F i zakresie pracy od 10 do 80 cm, został umieszczony na przodzie platformy jezdnej. Jego zadaniem jest wykrywanie przeszkody w postaci ściany przez robotem w momencie, gdy tunel zakręca w lewo pod kątem większym bądź równym 90 stopni. Czujniki zasilane są napięciem z zakresu od 4,5 do 5,5 V, ich średni pobór prądu to 30 mA. Wyjściem jest sygnał analogowy, którego wartość zależna jest od odległości pomiędzy wykrytym obiektem a sensorem. Im obiekt znajduje się bliżej, tym napięcie na wyjściu jest wyższe. 3.4. Mikrokontroler sterujący Jako jednostkę sterującą układem wybrano mikrokontroler AVR Atmega328 w obudowie do montażu przewlekanego DIP. Jest to ośmiobitowy mikrokontroler, posiadający 23 programowalne układy wejścia-wyjścia. Oferuje on 32 KB programowalnej pamięci Flash, 1 KB pamięci EEPROM, a także 2 KB pamięci SRAM, natomiast maksymalna częstotliwość taktowania wynosi 20 MHz przy użyciu zewnętrznego rezonatora kwarcowego lub 8 MHz przy użyciu wbudowanego. Na potrzeby projektu stworzono płytkę PCB metoda termotransferu, zawierającą opisywany mikrokontroler, wraz z niezbędnymi do jego poprawnej pracy elementami, tj. stabilizator napięcia 5 V, kondensatory i rezystory filtrujące zasilanie, rezonator kwarcowy o częstotliwości 16 MHz, dwie kontrolne diody LED, złącza na moduł Bluetooth, złącza na czujniki odległości, złącza do wyprowadzeń sterownika silnika. 4. Oprogramowanie mikrokontrolera Program sterujący mikrokontrolerem napisany został w języku C++ z użyciem środowiska Arduino. Następnie, po jego skompilowaniu, wgrano wsad do pamięci mikroprocesora. Użycie środowiska Arduino pozwoliło na łatwiejsze zaprojektowanie oprogramowania z racji dostępności wielu bibliotek, które w łatwy sposób można zaimportować do projektu. Działanie programu można zobrazować za pomocą poniższego schematu blokowego. W kodzie programu, na samym początku zadeklarowano odpowiednie pliki nagłówkowe, zdefiniowano stałe oraz zmienne globalne, które wykorzystywane są podczas działania oprogramowania. Zadeklarowano również obiekt FastPID, odpowiedzialny za użycie biblioteki regulatora PID. W funkcji setup() znalazły się inicjalizacje kolejnych modułów mikrokontrolera, tj. modułu ADC, mierzącego analogowe napięcie, oraz modułu PWM, pozwalającego wygenerować sygnał o zmiennym wypełnieniu. Ustalono również stany na pinach wyjściowych oraz zainicjalizowano moduł obsługujący komunikację szeregową UART, w celu przesyłania podstawowych informacji o pracy mikrokontrolera. W funkcji loop(), czyli w głównej pętli programu, na samym początku algorytmu sprawdzany jest czas, w celu odmierzenia 25 milisekund, za pomocą wbudowanej funkcji millis(). Następnie, po jego upływie, dokonuje się odczyt wartości analogowej napięcia z czujników Sharp. Wyznaczana jest odległość w milimetrach dla czujnika umiejscowionego z prawej strony robota, za pomocą własnej funkcji. Kolejno obliczany jest uchyb, a następnie dane przekazywane są do funkcji regulatora PID. W kolejnej części funkcji loop() sprawdzane jest, czy przed robotem nie ma przeszkody w postaci ściany. Odległość z przodu robota nie została przeliczona na jednostki miary, a jedynie ustalono pewną stałą „granicę”, po której przekroczeniu platforma jezdna skręca maksymalnie w lewo. Jeśli „granica” nie została przekroczona, wykonuje się regulacja za pomocą PID. Dodatkowo zadeklarowano przesyłanie podstawowych informacji, m.in. wartości z obu modułów ADC, wyznaczonej odległości, uchybu, za pomocą interfejsu UART, w celu kontrolowania działania programu mikrokontrolera. Wlutowane w płytkę diody również sygnalizują w odpowiedni sposób o poprawnym działaniu programu. Program mikrokontrolera nie może się zakończyć. Działa nieprzerwanie, aż do zaniku zasilania. Dzięki takiemu rozwiązaniu, robot jest w pełni autonomiczny. 5. Wnioski Celem projektu było stworzenie robota mobilnego zdolnego do pokonania labiryntu bez ingerencji użytkownika w tor jazdy, podczas jego pokonywania. Cel ten został osiągnięty, jednak podczas pracy natknięto się na kilka problemów. Przede wszystkim użyte silniki napędowe okazały się być średniej jakości. Po ustawieniu identycznego wypełnienia dla sygnału PWM dla obu silników, ich prędkości obrotowe znacznie się różniły. Jednak zastosowanie regulatora PID pozwoliło na odpowiednie kontrolowanie toru jazdy platformy robota mobilnego. Parametry regulatora PID zostały dobrane podczas przeprowadzanych testów. Wzmocnienie członu proporcjonalnego ustalono na wartość 1.30, wzmocnienie członu różniczkującego na wartość 2.15, natomiast wzmocnienie członu całkującego na wartość 0. W konstrukcji tego typu, gdzie zachodzi potrzeba szybkiego działania regulatora, człon całkujący mógłby wprowadzić niechciane opóźnienie w układzie regulacji. Robot pokonuje labirynt w czasie niecałych 30 sekund, co jest bardzo dobrym wynikiem. Stworzenie własnej konstrukcji, układu elektronicznego oraz oprogramowania wymagało dużego nakładu pracy, jednak końcowy efekt daje wiele satysfakcji. Ostateczna forma robota mobilnego została przedstawiona na poniższym rysunku. Projekt i wykonanie: Jakub Kinder / Politechnika Opolska 2019. Wszelkie prawa zastrzeżone! Kopiowanie, powielanie i wykorzystywanie zdjęć bez pisemnej zgody autora zabronione!- 8 odpowiedzi
-
- 6
-
- Robot Mobilny
- labirynt
-
(i 1 więcej)
Tagi:
-
Cześć! Przedstawiam wam mój owoc lockdownu. Robot a3p jest czterokołową platformą wyposażoną w obrotową wieżyczkę z działkiem na sprężone powietrze, który przy wykorzystaniu openCV jest w stanie rozpoznawać i namierzać rozmaite cele. Mechanika Większość części została wykonana z PLA w technologii FDM przez Janka @Holgin, któremu serdecznie dziękuję. Montaż całej konstrukcji nośnej wymaga tylko 2 śrub. Reszta części montowana jest na wcisk (jaskółczy ogon). Konstrukcja jest prawidłowo sztywna oraz niewrażliwa na drgania mechaniczne, co świadczy o dobrej precyzji wykonania części. Koła montowane są na przesuwnych płytkach, co pozwala na regulację prześwitu. Konstrukcja kół zapewnia robotowi minimalną amortyzację. Warto wspomnieć o oponach, które również zostały wykonane na drukarce 3D (fiberflex). Minimalna szybkość druku i kolejne zerwania spowodowały, że był to zdecydowanie najtrudniejszy element do wykonania. Elektronika Platforma zasilana jest pojedynczą baterią LiPo 4S. Na potrzeby projektu wykonany został dedykowany moduł zarządzania mocą. Płytka oparta jest o STM32 i pozwala na selektywne włączanie 4 sekcji zasilania (3v3, 5v, 12v i vbat). Moduł dokonuje róznież pomiaru napięcia każdego ogniwa baterii oraz mierzy prąd płynący przez sekcję vbat (tam podłączone są silniki). Za projekt PCB ponownie dziękuję @Holgin. Za kontrolę peryferiów odpowiadają 2 raspberry pi (3B+ dla pojazdu, Zero W dla wieżyczki). Każde koło napędzane jest szczotkowym silnikiem DC (12V, 90RPM). Ruch wieżyczki realizowany jest za pomocą silnika krokowego (obrót) oraz serwa modelarskiego (góra-dół). Zakres ruchów to 360° w poziomie oraz 40° w pionie. Działko pneumatyczne zostało zaadaptowane z gotowego chińskiego airsoftu (napęd to szczotkowy silnik DC). Kamery to: Raspberry Pi Camera v2 (przód) oraz IR-CUT OV5647 (wieżyczka). Odległość do celu mierzona jest poprzez analizę odległości oraz kąta pomiędzy 2 plamkami lasera. Realizowane jest to za pomocą dwóch wskaźników laserowych i pozwala mierzyć dystans w zakresie 1-4 m. Oprogramowanie Komunikacja pomiędzy modułami oparta jest o framework ROS (Melodic). Za analizę obrazu odpowiedzialna jest biblioteka OpenCV. Robot może rozpoznawać obiekty dzięki gotowym sieciom ssd_mobilenet_v2 lub yolov3. Do wykrywania prostych okręgów wykorzystana została transformacja kołowa Hougha. Robot jest kontrolowany za pomocą desktopowego GUI (Qt5). W zakładce Live view widoczny jest obraz z obu kamer, aktualna liczba klatek dla kamery wieżyczki oraz odległość do celu. Możliwa jest tutaj zmiana trybu kamery oraz wydanie rozkazu strzału. W zakładce Settings mamy kontrolę nad sekcjami zasilania oraz możemy podejrzeć aktualną pozycję wieżyczki względem ramy robota. Kod projektu można znaleźć tutaj. Kod modułu zarządzania mocą tutaj. Zachęcam do pytań i oceny projektu. Pozdrawiam, Tomek
- 5 odpowiedzi
-
- 15
-
Zbudowałem jeżdzącego robota na bazie standardowego podwozia, który ma jeździć zdalnie rozpoznawać obrazy Cieszę się bo było to pierwszy kontakt z technologią Tensorflow Lite i widzę jak fajne możliwości daje to na urządzeniach drobnych, niepodłączonych do internetu. Oryginalny wpis z mojego bloga znajdziecie tu: https://lukaszkuczynski.github.io/Machina-ze-slaboscia/ Github z projektem jest tu: https://github.com/lukaszkuczynski/rpi80b3 Dlaczego samochód? Od kiedy kupiłem pojedynczy silnik krokowy musiał odczekać swoje. Przeleżał dobre kilka tygodni w pudełku. Ale potem przemyślałem sprawę i zanim zaprojektowałem kolejny system z sensorem pomyślałem - jakie miałem myślenie w dzieciństwie? Zrobić autko, którym da się sterować. Zdalnie sterować. A dziś radio jest już wszędzie, wszystkie nasze mniejsze i większe urządzenia “chodzą po BT / WIFI”. A gdzieś na półce leżał czarny i zakurzony mały komputerek - Raspberry 3B. Machina działa w dwóch trybach pracy. Możliwe jest zdalne sterowanie i obserwowanie w kamerze gdzie jadę. Brak jakiejkolwiek stabilizacji więc obraz jakiego doświadczam wygląda mniej więcej tak. Jest tak zapewne dlatego że mocowanie to kawałek kartoniku. Gdy tylko dobiorę się do drukarki 3D albo z kolegą zaprojektuje mały stelaż będzie na pewno lepiej. Mogę też badać działanie uczenia maszynowego. Oczywiście nie trenowałem sieci neuronowej samemu bo choć bawiłem się już ML to nie miałem doświadczenia z badaniem obrazów - Computer Vision. Dlatego podstawiłem gotowy model w Tensorflow, który jest już wytrenowany na przykładowych obiektach. Oparty jest na zbiorze COCO . Aktualnie program jest tak ustawiony że gdy wykryty zostanie obiekt typu pomarańcz to pojazd wykonuje kilkusekundowy krótką jazdę wprost. Nie ma tu na razie logiki, która dostosowywała by tor jazdy do tego gdzie dokładnie ta pomarańcz stoi. Program do sterowania zdalnego to prosta aplikacja we Flasku. Komunikacja z silnikami odbywa się poprzez nadawanie sygnału PWM. Z kamerą mam dwie opcje do wyboru. Pierwszą jest zwyczajne przekierowanie obrazu na streaming HTML. Drugą jest detekcja obrazu. Jak to już wcześniej zaznaczyłem Tensorflow odwala tutaj brudną robotę. Detekcja polega na prostym klasyfikowaniu obrazów pochodzących z kamery z gotowym modelem zaczytanym z repozytorium. W moim wypadku jest tutaj klasyczny model COCO z 90 klasami obiektów. Cele na przyszłość: więcej czujników - np detekcja przeszkód korzystając z ładniejsza obudowa 🙂 mniej baterii / zasilić wszystko z 1 źródła po detekcji obiektu zmierzać do obiektu, mądrze regulacja prędkości ładniejsza aplikacja webowa
- 2 odpowiedzi
-
- 10
-
- Raspberry Pi
- tensorflow
-
(i 1 więcej)
Tagi:
-
Witam 🙂 Od około półtora roku interesuję się sztuczną inteligencją, zwłaszcza zastosowaną w robotyce. Jest to dla mnie niezwykle ciekawy temat 🙂 Kilka miesięcy temu zacząłem pracować nad moim prywatnym projektem, który cały czas jest w fazie rozwoju. Pewne elementy, takie jak uczenie się poprzez rozmowę z człowiekiem są gotowe, więc postanowiłem nagrać poniższe filmy: AgeBot podłączony jest do internetu. Platforma to gotowa konstrukcja - AlphaBot-PI. Użyłem jej, aby szybciej skupić się na oprogramowaniu. W ostatnim czasie domykałem widoczne na filmach możliwości robota i w międzyczasie zacząłem pracę nad wykorzystaniem kamery (aktualny stan to ~30%). W bliskiej przyszłości platformę mam zamiar wymienić na coś bardziej zaawansowanego lub zbudować swoją, ponieważ ta zaczyna mnie powoli ograniczać (pod względem hardware'u). Jej specyfikacja dostępna jest w internecie (np. na stronie firmy Waveshare), więc nie będę kopiował 😉 Jak już zauważyliście - używam Raspberry PI. Oprogramowanie na malinkę napisałem w C++, pozostała część to C++ oraz Python. Systemem jest Raspbian. Do zautomatyzowania instalacji niektórych zależności w systemie (akurat w przypadku samej malinki) używam narzędzia Ansible, a o te krytyczne dla działania aplikacji dba manager pakietów (apt, buduję paczki .deb). Do przetwarzania tekstu użyłem biblioteki Tensorflow, natomiast w procesie uczenia się, robotowi asystuje wnioskowanie. Kamera i przetwarzanie obrazu otworzy wiele nowych drzwi, ułatwi również pracę przy kolejnej domenie - planowaniu zadań. Sam robot jest stosunkowo małą częścią projektu, ale o tym... w przyszłości 😉 Każdy feedback mile widziany 🙂 Pozdrawiam PS: W ciągu kilku dni napiszę posta z trochę bardziej szczegółowymi informacjami odnośnie przetwarzania tekstu - czatowania z robotem
- 27 odpowiedzi
-
- 5
-
- AI
- Raspberry Pi
-
(i 1 więcej)
Tagi:
-
Witam wszystkich! Około rok temu, w oczekiwaniu na coroczny konkurs techniczny organizowany w mojej szkole postanowiłem wykonać robotyczne ramię sterowanie kontrolerem w formie rękawicy. Ramię miało powtarzać ruchy dłoni oraz nadgarstka w czasie rzeczywistym. Głównym celem było stworzenie działającego prototypu dzięki któremu zwiększę swoją wiedzę w dziedzinie elektroniki, robotyki oraz programowania. Ramię Projektowanie Zabrałem się więc za projekt. Na samym początku rysowałem oraz testowałem różne konstrukcje przekazania momentu do odpowiednich stawów aż zdecydowałem się na linki w pancerzach. W mojej opinii jest to najlepsze rozwiązanie w konstrukcjach amatorskich z ograniczonym budżetem. Serwomechanizmy TowerPro SG90 wymieszane ze swoimi klonami, plecionka wędkarska 0.23mm, oraz wężyki silikonowe 3mm/1mm powinny załatwić sprawę. Powrót określonej części palca realizowany za pomocą gumek recepturek . Przeszedłem więc do programu fusion 360 i tak prezentują się efekty mojej pracy. Składanie Po otrzymaniu przesyłki z serwami od naszych wschodnich kolegów przystąpiłem do drukowania poszczególnych części projektu. Wszystkie elementy drukowałem na delikatnie zmodyfikowanym enderze 3 z PLA ora PETG. Serwa zostały przykręcone oraz przyklejone na klej cyjanoakrylowy. następnie przystąpiłem do przedziewania wężyków silikonowych i podklejania linek do odpowiedniej części dłoni. Szybko okazało się że wężyk, który zamówiłem kompletnie nie nadaje się do takiego zastosowania ze względu na duże tarcie oraz małą sztywność powodującą ściskanie się wężyka zamiast zginania palca w odpowiedzi na ciągnięcie linki przez serwo. Dlatego aby konstrukcja działała chociaż trochę lepiej owinąłem wszystkie wężyki koszulkami termokurczliwymi co w większości pozwoliło pozbyć się problemu ściskania. Jednakże dodało to dodatkowy milimetr średnicy każdemu z nich. Coś za coś. Zostało tylko dodać gumki i cała konstrukcja mechaniczna skończona. Kontroler Jako czujniki zgięcia zastosowałem potencjometry. Początkowo miały to być zwyczajne montażowe jednak niezliczone wady tego rozwiązania ujawniły się natychmiast po złożeniu pierwszego prototypu. W poszukiwaniu innej opcji trafiłem w Internecie na podobną konstrukcję również wykorzystującą potencjometry jednakże z otworem umożliwiającym przedzianie przez nie wału. Zakupiłem więc wystarczającą ilość sztuk i podczas oczekiwania na przesyłkę zaprojektowałem naprędce projekt drugiej rękawicy który okazał się finalnym. Dodatkowo do podłączenia potencjometrów użyłem wyjątkowo giętkich przewodów odpornych na wielokrotne zginanie( skrętka nie poradziła sobie w tej roli). Elektronika Mózgiem całego układu jest atmega 8 , otoczona elementami pasywnymi niezbędnymi do jej właściwego działania oraz do właściwego działania przetwornika ADC. Płytkę zaprojektowałem w programie Eagle, a wykonałem na zrobionym samodzielnie ploterze wykorzystując czarny pisak permanentny laminat zamiast kartki. Po kąpieli w wytrawiaczu płytka była gotowa do montażu. Oczywiście nie obyło się bez błędów wynikających z mojego małego doświadczenia oraz zwyczajnej nieuwagi. Drugim niezwykle ważnym układem był multiplekser 16 kanałowy wykonany z trzech multiplekserów 8 kanałowych. Takie rozwiązanie było zdecydowanie tańsze oraz niemiałem żadnych problemów z dostępnością części. Ponownie odpaliłem Eagle-a i w ruch ruszył mój ploter. Pierwsza płytka wykonana na elementach SMD okazała się całkowitą porażką natomiast druga na THT wyszła dokładnie tak jak powinna. Starałem się aby w całej części elektronicznej projektu jak najmniej elementów było lutowane "na stałe". Dlatego używałem złączek IDC, ARC oraz oczywiście goldpinów. Dzięki temu całość uzyskała budowę modułową niezwykle ułatwiającą przenoszenie oraz wszelakie naprawy. Program Program został napisany w języku C na mikrokontrolery AVR w środowisku Eclipse. Gdyby nie książka: " Mikrokontrolery AVR - podstawy programowania" Pana Mirosława Kardasia nie sądzę aby udało by mi się nauczyć się programować AVR-y oraz napisać mój program w tak krótkim czasie. Program jest niezwykle prosty. Co 20 ms czyli z częstotliwością odświeżania właściwą dla serw SG90 przestawiane są multipleksery za pomocą odpowiednich bitów, a wartość odczytana z potencjometru przez przetwornik ADC jest zamieniana funkcją map()( przekopiowaną z biblioteki arduino) na wartości odpowiadające odpowiedniemu wypełnieniu sygnału PWM. Dzieje się to ze względu na właściwość użytych przeze mnie serwomechanizmów. Przy odpowiedniej wartości wypełnienia PWM przyjmują one odpowiednią pozycję( w moim przypadku 0 stopni to około 1ms wypełnienia natomiast 180 stopni to około 2ms wypełnienia). Następnie 16 programowo generowanych kanałów PWM przyjmuje przekonwertowane dane i wysyła je złączem IDC na serwomechanizmy. Lista części: Serwomechanizmy SG90(klony + oryginalne) - ok. 80zł Plecionka, wężyk + rurka termokurczliwa- ok. 60zł Potencjometry APC ( z otworem ) - ok.20 zł Atmega 8 , złączki, elementy pasywne- ok.30zł Multipleksery CD4051BE - ok.6 zł Koszty druku( filament + prąd)- ok.40zł Wady: Konstrukcja zawiera ich bardzo wiele : mała sztywność całego układu złe wężyki ( zbyt wiotkie oraz zbyt grube) nieoryginalne serwa mające ogromne problemy właściwie ze wszystkim nieprzemyślana konstrukcja dłoni (zbyt wiele kleju zbyt mało połączeń rozłącznych) gumki zamiast sprężyn jako powrót po ściśnięciu brak prawidłowej filtracji zasilania!!! niedziałający nadgarstek(spowodowany przez: brak pinów na mikrokontrolerze, brak czasu , za słabe serwa , zbyt mało miejsca na prawidłowe działanie, nieprzemyślana budowa itp. Podsumowanie Jestem niezwykle zadowolony z całokształtu mojej pracy. Mimo wielu błędów( a raczej dzięki nim) nauczyłem się bardzo bardzo wiele. Od programowania, projektowania, trochę mechaniki oraz wiele więcej. Jeśli mógłbym doradzić coś osobą początkującym takim jak ja chcącym wykonać podobną konstrukcję to: proszę was kupcie oryginalne serwa! Mimo ich 3 razy większego kosztu działają nieporównywalnie lepiej do chińskich odpowiedników a ich właściwości także są o wiele lepsze. Na swoim przykładzie przekonałem się że niema czegoś takiego jak za trudny projekt jest tylko za mało czasu i pieniędzy. Wszystkiego da się nauczyć w trakcie pracy jednakże będzie to skutkować błędami tak jak w moim przypadku. Bogatszy w doświadczenie zacząłem już tworzyć drugi projekt ręki , która to min. będzie realizowała ruch palców w bok. Pozdrawiam wszystkich serdecznie i dziękuję za uwagę!
-
Witam chce wam przedstawić mojego pierwszego robota o nazwie LEM Od dłuższego czasu w moim domu leżały nieużywane moduły zdalnego sterowania do bram wjazdowych. Stwierdziłem, że jest to marnowanie ich potencjału, w mojej szkole miał odbyć się cykliczny konkurs mam talent gdzie uczniowie z całej szkoły pokazywali co potrafią. Ten projekt był pierwszym tego typu ''talentem'' w mojej szkole i wywołał nie małe zainteresowanie. Więc tak jak napisałem wyżej za rozum robota odpowiadał moduł 12 kanałowy z wbudowanymi przekaźnikami. Nie było to najlepsze rozwiązanie, ale chciałem wykorzystać to co mam. Planując możliwości robota uparłem się na gąsienice, nie chciałem montować kół, bardziej fascynowały mnie właśnie gąsienice. Te napędzane były przez dwie niezależne głowice od wkrętarek 12v dzięki czemu lem miał możliwość skręcania. Głowice zakończone zębatkami od przerzutek rowerowych zdawały się nie najlepiej ale wystarczająco dobrze dużo lepiej zdałyby egzamin np. takie silniki z przekładnią. Ramię robota zginane jest też za pomocą silników od wkrętarek z zamontowaną śrubą trapezową. Tak działały dwa zgięcia ''ręki'' natomiast sam chwytak działał przy pomocy osi z napędu dvd. Napęd ten był nietypowy ponieważ wykorzystywał silnik dc a nie silnik krokowy jak można to spotkać w większości napędów. Nie ukrywam, ułatwiło mi to zadanie. Najwięcej czasu zajęło mi zrobienie gąsienic skradają się one z 5 łańcuchów rowerowych. co drugi nit w łańcuchu musiałem wybić i zastąpić prętem m3 ręcznie dociętym na 6cm łącząc tym samym dwa łańcuchy równolegle. Pomiędzy nimi wsunąłem dystanse zrobione z rurek od namiotu, nadały się idealne! Następnie ze starej opony rowerowej powycinałem małe elementy które zostały przewiercone i przyczepione na trytytki. Pod spód mojej maszyny doczepiłem ledy wydobyte kosztem lampki nocnej ale można je kupić również oddzielnie w formie pasków LED. Jednak to nie jedyne oświetlenie lema z przodu posiada on dwa reflektory 10W. Są to nieco zmodyfikowane lampki rowerowe, rozebrałem obudowę lampki i włożyłem tam własne ogniwo led. Te grzało się tak bardzo, że niezbędne okazało się zamontowanie z tyłu radiatory chłodzące. na powyższym zdjęciu można zaobserwować rurę u dołu robota jest to niedopracowany odkurzacz działający na wentylatorze komputerowym, jednakże działało to dość słabo dlatego traktuje to bardziej jako ciekawostkę. Ciekawy może też być tył robota. Lem nosi na swoich barkach odznakę ZSRR i znak wysokiego napięcia. Kontroler to przerobiony pad do gier z wymienionymi przyciskami i doklejonym pilotem do ledów. Robot działał na 9 akumulatorach 18650 które działały znakomicie. Jeszcze parę bajeranckich przełączników dźwigniowych tego typu lub takich i robot gotowy! film z działania okazał się zbyt duży i nie mogłem go tutaj przesłać wiec wrzuciłem na yt i proszę bardzo wszystko działa! Dziękuje wszystkim za uwagę i chcę was zachęcić tym samym do budowy odważniejszych projektów. Ten robot był prosty w podłączaniu teraz uczę się języka Arduino mam nadzieję że przeczytacie tu jeszcze nie jeden artykuł mojego autorstwa!
- 4 odpowiedzi
-
- 16
-
No wreszcie - po jakimś szale na prezenty, naprawy różnych domowych urządzeń i inne przyziemne sprawy mogłem zabrać się za następnego z rodziny Konserwatorów Płaskich Powierzchni - czyli małego Kopłapowa. Miał on w założeniu służyć do wycierania podłogo w trudniej dostępnych miejscach (np. pod kabiną prysznicową) więc siłą rzeczy musiał byc jak najmniejszy. Założenia były proste: zasilanie 1 x 18650; dwa silniczki N20 do napędu z driverem DRV8825; z przodu pojedyncza gąbka kuchenna; wysokość taka, aby się zmieścił pod brodzikiem; z czujników: zderzak, żyroskop i kilka czujników odbiciowych; Arduino Pro Mini jako "mózg" urządzenia; żadnego skomplikowanego algorytmu - ma się przejechać po podłodze i tyle, z możliwością zdalnego sterowania. Z takimi założeniami zabrałem się do roboty. Sterownik już miałem - kiedyś zrobiłem sobie coś w rodzaju uniwersalnego nadajnika, sprawdził się już w trzech konstrukcjach a więc tę część miałem gotową. Tym razem postanowiłem w maksymalnym stopniu wykorzystać drukarkę. Ponieważ miałem felgi z kół Pololu (opony zostały użyte w innym projekcie) wystarczyło dodrukować jakieś ładne oponki z TPU. Również dolna płyta podwozia wraz z koszykiem na 18650 oraz mocowaniami na ładowarkę, przetwornicę i inne drobiazgi typu wyłącznik została wydrukowana w jednym kawałku. Zadowolony z siebie zmontowałem podwozie, podłączyłem prowizorycznie silniczki pod 5V... i w tym momencie szczęście się skończyło. Okazało się, że robot nawet po lekkim dociążeniu jest jeszcze za lekki i nie da rady przepchnąć gąbki nawet po śliskich kafelkach. Zmartwiony odstawiłem konstrukcję na skraj biurka i zająłem się swoimi sprawami. Na szczęście był to skraj biurka przy którym pracuję i co chwila chcąc nie chcąc spoglądałem na biednego robocika. Jak go wykorzystać... Pomysł przyszedł sam, gdy nastąpiłem bosą nogą na jakąś śrubkę. A gdyby tak wyposażyć go w magnes i kazać zbierać śrubki z podłogi? Wywaliłem uchwyt gąbki, w to miejsce zamontowałem obrotowe kółko (o nim więcej w dalszej części), uchwyt na magnesy i również podłączając bezpośrednio do przetwornicy silniczki puściłem go po podłodze. Okazało się, że śrubki i podobne niewielkie metalowe farfocle zbiera doskonale! Tak więc robocik zmienił swoje przeznaczenie, miałem już gotowe podwozie i mogłem zabrać się za dalsze projektowanie. Oczywiście - potrzebne były nowe założenia. Przede wszystkim - wysokość nie była już krytyczna (robot miał zbierać śrubki porozrzucane po podłodze i nie wjeżdżać do łazienki). Z uwagi na to zrezygnowałem z czujników odbiciowych, w ich miejsce postanowiłem zamontować obrotowy laserowy czujnik TOF o kącie 180°. Pozostał oczywiście żyroskop. Z uwagi na przykre doświadczenia z MPU6050 przy okazji zakupów w Botlandzie zamówiłem moduł L3G, czujnik VL53L0X kupiłem już wcześniej, tak więc zabrałem się za dalsze projektowanie. I tu mała dygresja: odrzuciłem MPU6050 z uwagi na to, że potrafił zawiesić Arduino po kilkudziesięciu sekundach od włączenia (a z tego co wyczytałem w sieci nie tylko ja miałem z tym problemy). Oczywiście: nie doczytałem, że dotyczy to wyłącznie odczytu danych z DMP, odczyt surowych danych z żyroskopu i akcelerometru był prawidłowy. Dopiero po zmontowniu całości przypadkiem natknąłem się na nową wersję biblioteki która (podobno) nie sprawia takich problemów... a szkoda, bo niepotrzebnie wydałem trzy dychy na nowy żyroskop 😞 Ale wróćmy do robocika. Ponieważ laser na serwie i tak musiał dość mocno wystawać nie było konieczności zachowania wysokości reszty konstrukcji. Postanowiłem zrobić więc coś podobnego jak w poprzednim robocie - podłoga ze spienionego PVC (idealna do eksperymentów z uwagi na łatwość obróbki), do niej mocowane serwo i płytka z elektroniką. Z tamtej konstrukcji skopiowałem również zawieszenie zderzaka i przekładnię do serwa obracającego laser. Płytka z elektroniką miała początkowo być wykonana na frezarce - niestety, wskutek pandemii zostałem odcięty od warsztatu kolegi i musiałem zadowolić się płytką uniwersalną. Przyszedł więc czas na najprzyjemniejszą część pracy programowanie. Na zdjęciu wyżej widać przytwierdzony do robota najnowszy model SMARDZ-a. Tym razem SMARDZ ma własne zasilanie (akumulatorek Li-Ion 1000 mAh, ładowarka i przetwornica) co uniezależnia go od akumulatorów robota i - co ważniejsze - przy większych wstrząsach (np. uderzenie w przeszkodę) nie ma możliwości, że zrobi sobie reset. Większych niespodzianek nie miałem z wyjątkiem jednej - ale za to bardzo śmiesznej... Otóż po zmontowaniu wszystkiego na płytce uniwersalnej jak zwykle przedzwoniłem wszystkie połączenia. Po stwierdzeniu że wszystko jest w porządku rozpocząłem testowanie. Wszystko działało - oprócz serwa. Zdjąłem płytkę, wyjąłem Arduino, sprawdziłem połączenia - wszystko było w porządku. Ki diabeł? Zacząłem szukać w programie czy coś mi nie blokuje timera - nie, najprostszy program ruszający serwem z przykładów też nie chciał działać. Co prawda serwo było sprawdzone (przed zamontowaniem musiałem ustawić środkowe położenie, a do tego używałem testera serw) - ale wiadomo, popsuć się mogło. Niestety - inny egzemplarz zachował się tak samo. Co ciekawsze - mały HD-1370A zaczął kręcić się w kółko... Nie bardzo mi się chciało podpinać sondę, ale jakoś chęć zbadania przyczyny owego dziwacznego działania przeważyła nad lenistwem. No i co się okazało? Otóż na nóżce 8 do której podłączone było serwo radośnie trwało 5V. Wtedy sobie przypomniałem, że płytkę Arduino wziąłem z pudełka gdzie leżą wszystkie podejrzane moduły (jako że miał wszystkie piny polutowane) - po prostu wyjście 8 było najprawdopodobniej uszkodzone i zwarte na stałe z Vcc 🙂 Na szczęście połączenia robione Kynarem mają to do siebie że szybko można je zmodyfikować - podłączenie serwa do wolnego pinu obok pomogło! Ech, znowu się okazało, że elementy sprawne działają z reguły lepiej od uszkodzonych! I jeszcze drobiazg - w pewnym momencie jeden z silniczków uległ uszkodzeniu (mechanicznemu). Chciałem kupić dwa w Botlandzie na wypadek gdyby drugi też miał jakieś skłonności samobójcze - niestety, już nie było (tzn. był jeden ale trochę się bałem). Kupiłem więc podobne - działają chyba nawet lepiej. W sumie mam na płytce: Arduino Pro Mini cztery mikroswitche zderzaków (połączone parami, czyli wykrywane jako dwa oddzielne) podpięte do jednego pinu analogowego moduł radiowy nRF24L01 (zasilany z wyjścia VDD żyroskopu) żyroskop L3GD20H driver DRV8835 buzzer służący do wygrywania różnych krzepiących melodyjek związanych z wykonywaną funkcją wyjścia na silniki, serwo i diody WS2812 (wskaźnik wykonywanej funkcji oraz wskaźnik poziomu naładowania akumulatora). Wszystko zasilane z jednego napięcia 5V z przetwornicy MT3608, do pinu analogowego podpięte wejście przetwornicy (kontrola napięcia akumulatora). O dziwo działa bez żadnych dodatkowych kondensatorów z wyjątkiem jednego 100nF przy module radiowym i dwóch przy silnikach. W efekcie robot wygląda tak: Tak więc robot w tej chwili potrafi realizować następujące programy: Sterowanie zdalne (ot, wielki mi program). Jedyne co go różni od samochodzika wnuczka na bateryjkę to fakt, że używa żyroskopu do jazdy prosto (podobnie jak reszta programów). Wałęsak - Bradiaga - najprostszy z możliwych - przejeżdża kawałek i skręca sobie w losowym kierunku. Po spotkaniu z przeszkodą po prostu cofa i zakręca. Wałęsak - Moocher - taki włóczęga bardziej inteligentny. Używa lasera do stwierdzenia czy nie dojeżdża do przeszkody oraz do wyszukania najlepszej (w jego rozumieniu najdłuższej) trasy do dalszej jazdy po spotkaniu z przeszkodą lub gdy mu się zamami zmiana kierunku. Odplamiacz - jedzie sobie po spirali. W przypadku napotkania na przeszkodę wykonuje wcześniej zaplanowany skręt, poprzedzając lekkim cofnięciem jeśli sygnał pochodzi od zderzaków a nie lasera.. Przerywa pracę, gdy nie może skręcić (tzn. w czasie skrętu dostanie sygnał od zderzaków). Polowiec - obrabia prostokątny kawałek podłogi - przejeżdża kawałek, zawraca, znowu przejeżdża, zawraca... jakby orał pole. Również używa oprócz zderzaków lasera jako czujnika przeszkody. Ponieważ zawraca zawsze na zasadzie zastopowania jednego koła (wewnętrznego) przesuwa się nieco za każdym nawrotem. Podobnie jak Odplamiacz przerywa pracę, gdy nie może zawrócić. Patrol - robot stara się ustawić jak najbliżej ściany równolegle do niej, a następnie jeździ od ściany do ściany aż do momentu, kiedy nie może zawrócić. Kod jest w sumie bardzo prosty, jednak chciałbym zwrócić uwagę na dwie rzeczy: Używam funkcji printf do wyświetlania różnych informacji o tym, co robi robot. Ponieważ wszystkie funkcję dotyczące żyroskopu działają na liczbach float, musiałem zmusić Arduino do wyświetlania ich właśnie przez printf. Niestety - nie ma możliwości ustawienia tego dla pojedynczego szkicu, ale globalne ustawienie jest bardzo proste: otóż w katalogu, gdzie znajduje się plik platform.txt dla naszego Arduino (czyli w tym przypadku ARDUINO_HOME/hardware/arduino/avr) trzeba założyć nowy plik o nazwie platform.local.txt, a w nim umieścić skopiowaną z platform.txt linię rozpoczynającą się od ciągu "recipe.c.combine.pattern=" z jedną drobną modyfikacją: na końcu linii należy (po spacji) dodać: -Wl,-u,vfprintf -lprintf_flt W przypadku wersji 1.8.12 (a podejrzewam, że wielu innych) będzie to wyglądać tak: recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {compiler.c.elf.flags} -mmcu={build.mcu} {compiler.c.elf.extra_flags} -o {build.path}/{build.project_name}.elf" {object_files} "{build.path}/{archive_file}" "-L{build.path}" -l m -Wl,-u,vfprintf -lprintf_flt Druga sprawa to laser. Na potrzeby programu zmodyfikowałem nieco bibliotekę Pololu dodając możliwość nieblokującego odczytu danych. Mimo to czujnik zachowywał się dość dziwnie: czasami zwracał bardzo pięknie prawidłowe odległości, a czasami kompletnie wariował. Bliższe przyjrzenie się problemowi poskutkowało stwierdzeniem, że czujnik zwraca nieprawidłowe dane po wgraniu kolejnej wersji programu (ściślej po programowym resecie Arduino). Ponieważ w bibliotece zabrakło funkcji resetowania lasera postanowiłem wypróbować najprostszą możliwość - w funkcji setup przed init() wstawiłem następujący kod: lidar.writeReg(VL53L0X::SOFT_RESET_GO2_SOFT_RESET_N, 0); delay(100); lidar.writeReg(VL53L0X::SOFT_RESET_GO2_SOFT_RESET_N, 1); delay(100); Od tej pory laser przestał mieć fochy i grzecznie zwraca odczytane odległości. I jeszcze jedna uwaga dla kogoś, kto na tej podstawie chciałby zbudować własną wersję robota. Otóż funkcja "Patrol" w teorii działa, w praktyce jednak robot nie jest w stanie ustawić się dokładnie równolegle do ściany. Co najmniej w czasie pierwszego przejazdu powinno się dokonać korekty... niestety, o ile laserowy czujnik jest wystarczająco precyzyjny, o tyle brak enkoderów nie pozwala na pomiar przebytej odległości. Prawdopodobnie i tak dałoby się to zrobić oceniając mniej więcej prędkość robota, i dokonując korekty zanim robot dojedzie do ściany z przodu... ale prawdę mówiąc już mi się nie chciało 🙂 Natomiast uważam zadanie za całkiem ciekawe i pozostawiam sobie na przyszłość - na razie muszę niestety przerwać pracę nad robocikiem i zająć się bardziej przyziemnymi sprawami 😞 Aha, obiecałem jeszcze o kółku. No więc początkowo chciałem wyposażyć robota w kulkę podporową. Bliższa analiza zawartości szuflady wykazała, że mam dwa rozmiary: mianowicie za dużą i za małą. Drukowanie kulki raczej nie wchodziło w grę, natomiast obrotowe kółko można wydrukować na dowolej taniej drukarce. Wygląda to tak: Oprócz trzech drukowanych elementów do zrobienia kółka potrzebne są: 1 x śrubka M3x8 1 x śrubka M3x16 2 x nakrętka M3 2 x łożysko kulkowe 3x10x4 Kółko mocowane jest dwiema śrubkami M2x10 do podwozia. Załączam plik OpenSCAD (gdyby ktoś chciał sobie takie kółko zmodyfikować do własnych potrzeb) oraz pliki STL (gdyby ktoś chciał tylko wydrukować bez modyfikacji). Kółko dopasowane jest wysokością do silników Pololu Micro (lub tańszych N20) mocowanych bezpośrednio pod dolną płytą podwozia oraz kół o średnicy 42mm. Łożyska trzeba wcisnąć we właściwe otwory (ja użyłem zwykłego małego imadełka), gdyby okazały się za luźne należy po prostu w OpenSCADzie zmniejszyć nieco ich średnicę. Nie załączam schematu ani programu sterownika - nie jest specjalnie udany, używam go przede wszystkim dlatego, że nie chce mi się robić innego. Lepiej zastosować np. bezprzewodowy gamepad (w sumie cenowo wyjdzie na jedno). No i na koniec filmik demonstrujący działanie robota - niestety nie we wszystkich programach (Moocher i Patrol wymagają więcej miejsca, a na dziś nie bardzo dysponuję takim które się nadaje do kamery). A - oczywiście winien jestem jeszcze wyjaśnienie, skąd takie dziwne imię Zbignaś. To po prostu skrót od Zbieracz Gwoździków, Nakrętek i Śrubek 🙂 Pora na podsumowanie i wnioski. A więc: Robot powinien być okrągły - konstruktorzy Roomby nieprzypadkowo tak go zaprojektowali. Zderzak powinien obejmować cały obrys robota (nie tylko przód jak w Roombie) oraz całą wysokość (robot nie może wjechać pod mebel jeśli jest za mało miejsca). Nic nie może wystawać ponad zderzak Laser musi obejmować 360°, oś obrotu (niekoniecznie sam laser) powinna być w środku robota (jak to połączyć z poprzednim punktem?) Enkodery nie muszą być super dokładne kwadraturowe, wystarczą zwykłe niewielkiej rozdzielczości, ale powinny być. W załączniku: program robota, zmodyfikowana biblioteka do czujnika, pliki do kółka oraz (może się komuś przydadzą) opona i felga (STL i SCAD, do opony/felgi potrzebny openscad w wersji nightly).Zbignas.zip W razie pytań i pretensji jestem do dyspozycji. Gdyby coś z moich STL-i było potrzebne proszę o informację (tylko nie na priv, błagam).
-
Drodzy Technicy! Typowe ramię robota tylko pozornie naśladuje nasze ludzkie. Mobilność naszych kończyn jest daleko większa gdyż mamy bardziej ruchliwe stawy. A co by było gdyby i robot miał możliwość realizowania wielu stopni swobody w ramach tylko jednego przegubu?... ... To pytanie zadałem sobie po raz pierwszy dawno, dawno temu, u schyłku poprzedniego tysiąclecia, kiedy to komórki były w cebuli i na ziemniaki. W owych straszliwych, bezfejsowych czasach, pacholęciem będąc, usłyszałem o bajecznej chirurgii laparoskopowej. Dziurka od klucza te sprawy... Zafascynował mnie ten temat w stopniu większym niźli przysługiwał memu smarkaczowatemu stanowi. Pooglądałem sobie narzędzia jakie są używane i byłem niezmiernie zbulwersowany, że wśród nich brak jest takich, które by działały wystarczająco dobrze... Było dla mnie oczywistym, że przegub, który występuje w tego typu instrumentach, winien pracować podobnie jak nadgarstek, gałka oczna czy staw ramienny lub biodrowy. W takim brzuchu czy innym sercu jest klaustrofobicznie ciasno, więc należy mieć możliwość działania swobodnie w każdym kierunku bez przeszkód. Istniejące konstrukcje dają wolność w wymiarze albo w lewo albo w prawo i dopiero przy następnym przegubie mamy ponownie jedynie słuszną dowolność albo w lewo albo w prawo… No ale tam, w tych trzewiach, przecie nie ma miejsca na taką gburowatość w ruchach! O jakże potężną moc ma dziecięcy gniew!... Zanurkowałem w bezkresnym bałaganie mojego pokoju i cudem odnalazłem w miarę całą i nadal względnie kulistą piłeczkę pingpongową i zacząłem kombinować… O dziwo udało mi się znaleźć rozwiązanie. Działało! Ale niestety nie działało dość dobrze… Konstrukcja żerowała na wytrzymałości bardzo finezyjnych, delikatnych i trudnych w wykonaniu elementach. Wiedziałem już wtedy, że jeżeli coś ma być do medycyny to ma być solidne, niezawodne i nade wszystko tanie!… Początkowy gniew, po którym nastał złudny sukces, ostatecznie przeobraził się z wieloletnią, dojmującą rozpacz bezowocnych poszukiwań… Aż tu nagle, razu pewnego, jadłem na kolację jajecznicę. Lubuję się w takiej technologii spożycia rzeczonej jajecznicy, gdy niezmiennie pozostaje ona na patelni a ta z kolei spoczywa uroczyście na gazecie aby zadość uczynić stołowemu, kuchennemu bhp… Pozwalam sobie o tym wspomnieć tylko dlatego, że wystąpił w tamtym momencie ów decydujący czynnik katalizujący me pragnienia – gazeta! A dokładniej skrawek jej, marginesem zwany, wciąż niedoceniany, który aż się prosi aby przelać nań jakąś ważną myśl. Z takiego zaproszenia skwapliwie skorzystałem bo właśnie wtedy napadła mnie bardzo ważna myśl, jakże utęskniona… Otóż rozwiązanie patowego problemu, który dręczył mnie całe dziecieństwo, że o okresie gołowąsowości nie wspomnę, okazało się bezczelnie proste – stos rurek, rozmieszczonych współosiowo kubek w kubek, uzębionych na końcu, przenosi ruch w oś stawu, na którym nanizana jest adekwatna ilość kół zębatych, a te z kolei, przenoszą napęd dalej... Każda rurka, wraz z przypisanym sobie kołem odpowiada za inną funkcję, której sobie życzymy… Wszelkie ruchliwości mogą być realizowane osobno lub jednocześnie - jednakowoż sterowanie ich jest niezależne, z możliwościami (zaletami i wadami) i precyzją układów zębatych… czyli klasyka mechaniki w nieco odświeżonej, cudacznej formie... Po etapie gazetowym nastąpiła już czysta formalność – zasiadłem do warsztatu, pozbierałem kilka rurek, kilka zębatek wypreparowałem z maszyn, które liczyły czasy słusznie minione… Nad ranem dysponowałem ruchliwą ilustracją mojego pomysłu. Oddam pod łaskawy osąd drogich Forumowiczów, czy to drugie podejście, które nastąpiło po wielu latach rozkmin podprogowych, może osuszyć wcześniejsze łzy… Proszę o słowa możliwie merytoryczne i krytyczne. Oczywistym jest, że rozwiązanie to namieszać może w robotyce jako takiej. Pierwotnie skupiłem się na medycynie bo tam jest najtrudniej i tam też najprędzej nowe rozwiązania powinny trafiać. Nota bene stąd wynikają rozmiary prototypu, który naprędce zmajstrowałem.. Docelowo produkcja tych urządzeń miała być realizowana za pomocą lasera femtosekundowego (popularne dziś stenty, przedmioty o podobnych rozmiarach i klasie dokładności są tak wykonywane). Ja wtedy miałem tylko rękę uzbrojoną w pilnik – stąd żałosna dokładność... Wyzwań, którym trzeba by sprostać jest wiele – opracowanie odpowiedniego modułu zęba (audi ma na koncie niezłą propozycję, co najważniejsze przećwiczoną), wybór materiału (metal, ceramika?), w końcu wybór systemu łożyskowania… Owszem, sporo zabawy! Jednakże śmiem twierdzić, że rozwiązanie jakie pozwoliłem sobie zaproponować pod wieloma względami jest nader atrakcyjne – daje nieznane dotychczas, nowe możliwości. Ochoczo przyjmę wszelkie uargumentowane za i przeciw. Szczególnie będę wdzięczny za wskazanie gdzie takie rozwiązanie w przemyśle już występuje. Przyznam się, że na tamten czas, przeprowadziłem gruntowne poszukiwania przynajmniej śladów podobnych koncepcji, gdyż byłem przekonany, że tak prosty mechanizm musi już gdzieś występować. Dopiero później odnalazłem lakoniczny schemat rysunkowy zamieszczony bodaj na stronie pewnej japońskiej uczelni. Niestety, nie było żadnych zdjęć czy filmu dokumentujących prace badawcze nad tego typu konstrukcją. Życzę owocnych rozmyślań i z góry dziękuję za rozpoczęcie dyskusji! Szczegóły zębatki:
-
Witam, przedstawiam autonomicznego robota balansującego. Robot balansujący na Atmega 1284P 20MHz. Obsługuje komunikację oraz zmianę wsadu za pomocą bluetooth. Ponadto istnieje możliwość sterowania robotem za pomocą pilota IR, oraz zmiany nastawów regulatorów. Posiada system autonomicznej jazdy z wykorzystaniem 3 sensorów ultradźwiękowych. Delikatne ruchy robota w stanie spoczynku spowodowane są dużymi luzami w przekładniach silników. Robot radzi sobie bez problemów ze średniej wielkości nachyleniami podłoża. Sterowanie odbywa się poprzez aplikację na system android, która to wyświetla także podstawowe informacje o robocie (napięcie baterii, wielkość całki w regulatorze pochylenia itp). Tryb autonomicznej jazdy opiera się o trzy ultradźwiękowe czujniki odległości. W oparciu o ich wskazania, robot samoistnie podejmuje decyzje co do dalszej drogi. Jest to ostateczna wersja robota która posiada także prócz trybu autonomicznego, tryb zdalnego sterowania na odległość do 100 metrów. Zaimplementowany moduł auto diagnozy potrafi wykryć 32 ostrzeżenia i błędy, np od niskiego napięcia 12v 5v, po jego niestabilność, uślizg kół, luzy na piastach, opory toczenia i przekładni... itp.... itd... Uruchomienie poszczególnych funkcji robota odbywa się poprzez komendy terminala uart, lub wygodniej pilot ir. Wszystkie parametry robota wyświetlane są na 5 pulpitach 4 wierszowego wyświetlacza lub uproszczone w dedykowanej aplikacji na system android. __________ Komentarz dodany przez: Treker Witam na forum, następnym razem proszę pamiętać o zdjęciu w formie załącznika, które widoczne będzie później w katalogu robotów oraz na stronie głównej. W tym przypadku już poprawiłem 🙂
-
Cześć, jesteśmy grupą studentów, którzy na swój projekt inżynierski postanowili stworzyć niedużego robota sterowanego za pomocą interaktywnej maty. Robot sterowany jest za pomocą sekwencji, które wprowadzamy na wyżej wymienionej macie. Możemy dawać mu proste komendy pozwalające na poruszanie się. Alternatywnym trybem jest jazda swobodna pozwalająca na sterowanie robotem w czasie rzeczywistym. Mata jest urządzeniem posiadającym układ przycisków 4x4 i ekran pokazujący aktualnie wpisywaną sekwencję. Posiadając podstawową wiedzę na temat programowania i elektroniki, będziesz mógł skonstruować taki zestaw sam, w zaciszu swojego domu. Bardziej szczegółowe informacje na temat samego projektu znajdziesz w samym poradniku. Repozytorium jest podzielone na pięć folderów. Dzielą się one na: Kod robota - znajduje się tu gotowy kod, dzięki któremu robot będzie w stanie się poruszać. Kod maty - znajduje się tu gotowy kod, dzięki któremu będziemy w stanie wysyłać komendy z maty do robota. Schemat robota - wymagane pliki do zamówienia płytki pcb do robota. Schemat maty - wymagane pliki do zamówienia płytki pcb do robota. Instruktaż - szczegółowy poradnik zawierający wymagane materiały oraz opis budowy robota i maty. Zalecamy z zapoznaniem się dokładnie z poradnikiem przed próbą konstrukcji. Spis treści znajdziesz w pliku README w folderze "Instruktaż". Powodzenia! Chcielibyśmy tutaj jeszcze dodać, że wykonując projekt trzeba liczyć się z pewnymi wydatkami. Orientacyjne koszty na dzień 16 stycznia 2020: Robot - koszt wszystkich części robota to około 130zł, jeśli kupujemy na botlandzie. Można zmniejszyć koszta kupując części na przykład na aliexpress, jednak wydłuży to czas w jakim części do nas przyjdą. Mata - koszt wszystkich części maty wynosi około 205zł, podobna sytuacja jak z częściami u robota, koszty można zmniejszyć kupując części na przykład z allegro lub aliexpress. Zdjęcia końcowe produktów: Robot Mata Więcej informacji na temat projektu oraz poradnik ze wszystkimi potrzebnymi plikami znajdują się w repozytorium PS. Schemat do robota zostanie jeszcze zaktualizowany w ciągu kilku najbliższych dni, ponieważ wykryliśmy błąd. Mogą również nastąpić mniejsze zmiany w samych opisach.
-
Mam zaszczyt przedstawić: Roko Kopłapow - czyli Robot Koncepcyjny, Konserwator Płaskich Powierzchni. Co mniej więcej ma oznaczać: mopopodobny twór samojezdny, którego zadaniem jest sprawdzenie wszelkich koncepcji przed zaprojektowaniem i skonstruowaniem prototypu robota do czyszczenia podłogi na mokro. Niestety - nie wszystko poszło po mojej myśli. Niektóre koncepcje okazały się absolutnie nietrafione, inne wymagają dopracowania i ponownego sprawdzenia. Sam robot jednak doskonale się sprawdził pokazując mi, w którą stronę mają iść dalsze prace. Jakie były założenia: robot ma służyć do przetarcia podłogi na mokro; ma być sterowany pojedynczym Arduino Pro Mini; miło by było aby realizował wszystkie funkcje, ale równie dobrze może pracować jako "inteligentne podwozie ze szczotkami", czyli jako nośnik różnej maści przystawek; Robot powinien umieć poruszać się po podłodze bez jej zmywania (czyli podnieść mechanizm szczotek choćby w celu przejechania przez próg); Robot powinien umieć znaleźć stację dokującą lub miejsce parkingowe. Co się nie sprawdziło: Podnoszone szczotki Założenie całkiem fajne, ale zbyt duża siła jest potrzebna do podniesienia całego mechanizmu. W dodatku podniesiony mechanizm zajmuje zbyt dużo miejsca. Teoretycznie dało by się to obejść ale robot musiałby być dużo większy, a tego nie chciałem. Podwozie gąsienicowe O ile na normalnym podłożu zachowywało się bez zarzutu - o tyle na mokrej, śliskiej podłodze to absolutna porażka, gąsienice po prostu nie łapią przyczepności. Podwozie Pololu (testowo z pojedynczą nieruchomą gąbką) w ogóle nie chciało skręcać, jeśli napędzana była tylko jedna gąsienica jechał sobie dalej prosto, przy napędzanych dwóch przeciwbieżnie robił jakieś dziwaczne ewolucje nie mające nic wspólnego ze skręcaniem. Podwozie Tamiya (maksymalna długość gąsienic, szerokość zwiększona do 15 cm) - niewiele lepiej. Co prawda przy podniesionych szczotkach dało się tym manewrować, ale jako że podnoszone szczotki też nie zdały egzaminu - zrezygnowałem z gąsienic i zrobiłem kołowe. Żyroskop Teoretycznie wszystko było w porządku, robot bardzo ładnie jechał prawie po prostej mimo wirujących szczotek które rzucały nim na boki, skręcał dokładnie o tyle ile chciałem... przez pierwsze 30 sekund. Tyle czasu udało mi się utrzymać mojego MPU6050 przy życiu. Niestety - po bardziej wnikliwych poszukiwaniach okazało się, że nie tylko ja mam takie problemy, niektóre egzemplarze działają bezbłędnie, inne się wieszają (zawieszając przy okazji Arduino). Tak więc na razie robot obywa się bez żyroskopu, chociaż w przyszłości na pewno się na niego zdecyduję (może taki? Poradzicie?). Stacja dokująca W związku z brakiem podnoszonych szczotek musiałem z niej zrezygnować - robot nie przejedzie przez próg, a stacja dokująca powinna być w innym pomieszczeniu. Szkoda... Na razie zamiast dokowania robot powinien wjechać w wyznaczony kąt pomieszczenia i się wyłączyć, ale do tego muszę wymyśleć jakiś inteligentny wyłącznik (to już w oddzielnym wątku). Wąska wiązka czujnika laserowego Chciałem przy "rozglądaniu się" robota ograniczyć szerokość matrycy. Niestety - tu już nie pozwoliło mi na to oprogramowanie. Mamy do dyspozycji dwie biblioteki do czujnika VL53L1X: jedną uproszczoną (która nie pozwala na ustawienie regionu) i drugą pełną (adaptacja ST, pozwala na wszystko tyle że zajmuje 20 kB, jak na małe Arduinko to nieco za wiele). Być może któregoś pięknego dnia uda mi się dopisać tę możliwość do tej mniejszej biblioteki, ale na razie niespecjalnie mam na to czas, a z szerokością wiązki 27° muszę nauczyć się żyć... Inna możliwość to postawienie drugiego procesorka tylko do obsługi lasera, mógłbym wtedy zmienić serwo na mały silnik krokowy (nawet taki mi w szufladzie leży), tylko czy jest sens? A co się sprawdziło: Przede wszystkim - napęd szczotek. Obie szczotki to po prostu kuchenne gąbki włożone w uchwyty, napędzane są pojedynczym silnikiem z dwustronnym wałem. Szczotki są obrócone wobec siebie o kąt 90°, aby zmniejszyć szerokość robota i pozbyć się martwego pola pomiędzy szczotkami, obracają się przeciwbieżnie. Gąbki można w każdej chwili wyjąć i wymienić na czyste, w dodatku mogą być włożone zarówno miękką, jak i szorstką stroną do podłogi. Druga rzecz bez której dalsza praca nie miałaby sensu to przedni zderzak. Musi być bardzo lekki, znajdować się dość nisko nad podłogą (progi) a jednocześnie mieć wysokość wystarczającą, aby robot nie usiłował wleźć pod jakąś kuchenną czy łazienkową szafkę. Jednoczęściowy zderzak zapewnia wykrycie przeszkody w promieniu 180°, jest na tyle lekki że nie mam nawet żadnych sprężyn - cztery krańcówki wystarczają. Krańcówki są połączone po dwie równolegle z każdej strony, zwierają przez rezystor do masy co umożliwia bezproblemowy odczyt zderzaków przez pojedyncze wejście analogowe Arduino. Z drobiazgów: materiał. Oprócz elementów drukowanych z PLA mam tu: płytę główną podwozia z 3mm PCW. Łatwe w obróbce, w miarę elastyczne i wystarczająco wytrzymałe. podłogę piętra ze spienionego PCW 4mm. Nie musi wytrzymywać dużych obciążeń, jest bardzo lekkie i możliwe do cięcia zwykłym ostrym nożem. koszyki na akumulatory wydrukowane z PET-G z integralną sprężyną. Początkowo obawiałem się że nie będą kontaktować - okazało się, że sprawują się dużo lepiej niż kupowane podwójne (już nie mówię o porażce pod tytułem "koszyk na jeden akumulator"), a przy okazji mam tu porządny przewód (ucięty od pecetowego zasilacza) a nie jakiś chiński włosek. Dla zainteresowanych podaję link na thingiverse. A z rozwiązań: Enkodery na kołach. No - powiedzmy częściowo, trochę za mała rozdzielczość ale działają. Do bardziej precyzyjnych rzeczy pewnie by się nie nadały, ale tu zdają egzamin. Ponieważ nie wyszedł mi napęd gąsienicowy, zacząłem szukać jakichś fajnych kółek. Kiedyś znalazłem na Allegro za jakieś śmieszne pieniądze silniczki z kątową przekładnią (odpowiedniki Pololu), dokupiłem do tego koła Pololu 42x19, zmontowałem jakieś prowizoryczne podwozie... i na tym się skończyło bo jakoś straciłem zainteresowanie projektem. Czyli silniki z kołami już miałem. W pierwszej wersji robot miał się orientować w przestrzeni za pomocą żyroskopu i laserowego czujnika odległości, ale z braku żyroskopu zostałem skazany na enkodery. Co prawda oficjalnie te silniki nie miały możliwości zamontowania enkoderów, ale na szczęście w to nie uwierzyłem 🙂 Postanowiłem umieścić enkodery wewnątrz kół. Co prawda miejsca jest dość mało, musiałem wydrukować nowe felgi, ale jakoś cała konstrukcja się zmieściła. Użyłem popularnego TCRT5000L (zawsze mi kilka w szufladzie leży), dokleiłem uchwyty do silników... i ruszyło! Co prawda enkodery czasami fałszują, ale uczciwa bramka Schmitta powinna im w przyszłości pomóc. Na razie podłączenie wygląda tak: Dla zainteresowanych - zdjęcia i pliki STL/SCAD. Czujnik odległości 360° Niedawno ktoś pytał o coś takiego - o ile pamiętam chodziło o ultradźwiękowy czujnik odległości o zakresie ruchu 360°. Ponieważ moje rozwiązanie działa całkiem nieźle (należałoby tylko maksymalnie zmniejszyć luzy, ale to już zależy od możliwości drukarki) mam tu prostą przekładnię, zakładaną bezpośrednio na serwo i mocowaną tymi samymi śrubami co serwo, z tarczą pozwalającą na zamocowanie czujnika (w moim przypadku laser, ale może to być równie dobrze czujnik ultradźwiękowy). Dwie wersje: większa i kompaktowa (jeśli nie starcza miejsca). Jako osi użyłem śruby z podwozia Tamiya oryginalnie służącej do mocowania kół jezdnych z uwagi na fagment bez gwintu, ale w ostateczności można spróbować ze zwykłą śrubą M3. Tym razem nie ma STL-i jako że każdy ma inne potrzeby, zamiast tego plik dla OpenSCAD-a. Potrzebne będą biblioteki: spur_generator oraz parametric_involute_gear_v5.0. Co do programu: Program jest dość prosty, nie ma sensu publikowanie bo pewnie jutro jak mi się coś przyśni to będzie inny (możliwe że lepszy, ale niekoniecznie). Zresztą tak jak wspominałem - to jest egzemplarz koncepcyjny, wystarczy mi stwierdzenie że "da się to zrobić". Mój SMARDZ niesamowicie ułatwia programowanie, musiałem go tylko trochę inaczej zamocować (dodatkowy uchwyt i przedłużacz) bo przy gwałtowniejszych manewrach zsuwał się z pinów - prawdopodobnie bez czegoś takiego rzuciłbym to po paru godzinach. Robot ma cztery tryby pracy: Zdalne sterowanie - wiadomo o co chodzi. Zrobiłem kiedyś takiego uniwersalnego radiopilota, to już czwarte urządzenie które nim steruję; Jazda losowa - taki typowy wszędołaz, potrafi omijać przeszkody, stara się nie jechać prosto za długo aby się poszwendać w ciekawszych miejscach; Plama - analogicznie jak w Roombach, wyciera podłogę w promieniu do ok. 70 cm od miejsca startu i zatrzymuje się po skończeniu pracy; Sprzątanie - tu program nie jest dopracowany, ale w wersji koncepcyjnej nie ma to większego sensu. Ogólnie robot powinien podjechać do najbliższej ściany (to potrafi), ustawić się do niej równolegle (to też potrafi) i krążyć po pomieszczeniu coraz bliżej środka (tu ma problemy związane z niedokładnością enkoderów). Tak wygląda robot w trybie "plama": Ktoś za chwilę powie: zaraz, przecież to robot do sprzątania na mokro, a gdzie woda? Na razie nie ma. Po prostu: ponieważ nie ma stacji dokującej, przed uruchomieniem robota należałoby ręcznie uzupełnić wodę w zbiorniku. Dużo wygodniejsze jest po prostu spryskanie wodą podłogi w pomieszczeniu przed uruchomieniem robota. A czemu "na razie"? Bo jak wspomniałem na początku, ten robot powinien mieć możliwość współpracy z przystawkami, a taką przystawką może być np. urządzenie do spryskiwania płynem do mycia paneli. Ale taka przystawka powinna mieć swój procesor, robot w tym momencie powinien przestawić się w tryb "inteligentnego podwozia", a to wszystko jest kwestią przyszłości 🙂 Na razie robot spełnił swoje zadanie, popracuję jeszcze trochę nad programem (dopóki się da), i zaczynam zbierać kasę na wersję finalną 🙂 Obiecane pliki STL i SCAD w załączniku:koplapow.zip Jak zwykle jestem gotów odpowiedzieć na pytania - choć tym razem chyba ich nie będzie...
-
Chciałbym zaprezentować Wam, chyba pierwszy na Forbocie, manipulator równoległy typu Platforma Stewarta. Urządzenie powstało w ramach pracy magisterskiej na Wydziale Mechatroniki i Budowy Maszyn Politechniki Świętokrzyskiej pod czujnym okiem dr inż. Pawła Łaskiego. Temat pracy: projekt manipulatora o zamkniętym łańcuchu kinematycznym, o sześciu stopniach swobody z napędem elektrycznym. Projektowanie. Projekt konstrukcji mechanicznej został wykonany w Solid Worksie. Wszystkie układy elektroniczne projektowane były w Eagle, a algorytmy sterowania wyprowadzane z pomocą Matlaba. Poniżej widać projekt konstrukcji mechanicznej. Mechanika: Manipulator posiada 6 stopni swobody. Składa się z nieruchomej podstawy, sześciu jednakowych i symetrycznie rozmieszczonych ramion oraz ruchomej platformy. Podstawa wykonana z polietylenu HDPE, platforma ruchoma z poliamidu. Ramiona składają się z dolnej części w postaci płaskownika z włókna szklanego, górnej w postaci pręta stalowego (w przyszłości będzie pręt z włókna węglowego) i dwóch przegubów kulistych. Urządzenie napędza sześć serwonapędów Tower Pro MG995. Zamocowane one są za pomocą uchwytów z aluminium. Elektronika: Część elektroniczna składa się z trzech modułów. Pierwszy to moduł sterownika głównego. Zbudowany został w oparciu o ATMegę 128 taktowaną kwarcem 16MHz. Zajmuje się on praktycznie jedynie wykonywaniem obliczeń kinematyki. Moduł ten komunikuje się ze światem zewnętrznym za pomocą USARTu. Na płytce wyprowadzone są dwa złącza DB9 służące do połączenia z komputerem w standardzie napięć RS232 i z kontrolerem w standardzie TTL. Ponadto ATMega 128 połączona jest za pomocą drugiego kanału USART ze sterownikiem serwonapędów. Drugi moduł to moduł sterownika serwonapędów. Zbudowany jest w oparciu o ATMegę 8 z kwarcem 8MHz. Sygnał PWM generowany jest programowo za pomocą dwóch timerów. Trzeci moduł to moduł kontrolera. Za jego pomocą użytkownik może sterować manipulatorem. Jego sercem jest ATMega 8 z kwarcem 8MHz. Dwie gałki z potencjometrami służą do zmiany pozycji i orientacji platformy roboczej. Kontroler ma również trzy mikroswitche za pomocą których można zmieniać informacje wyświetlane na LCD oraz funkcje potencjometrów. Na LCD wyświetlane są ustawiana pozycja i orientacja platformy, oraz współrzędne przegubowe serwonapędów. Urządzenie zasilane jest z zasilacza komputerowego DELL. Do pracy wymaga napięcia +5V i zapasu wydajności prądowej ok. 9A. Sterowanie: Manipulator wypracowuje zadaną przez użytkownika pozycję i orientacje platformy roboczej w bazowym układzie współrzędnych. Układ ten związany jest z podstawa manipulatora. Po załączeniu zasilania robot przyjmuje pozycję startową. Za pomocą kontrolera lub komputera do układu sterowania przesyłana jest pozycja i orientacja platformy jaką robot ma wypracować. Układ sterowania wyznacza tor ruchu w postaci linii prostej z punktu aktualnego do docelowego i wykonuje ruch wzdłuż tej prostej z krokiem nie większym niż 1 mm. Po każdym kroku na bieżąco przeliczana jest kinematyka odwrotna i wyliczane nowe wartości współrzędnych przegubowych. Po wyliczeniu układ sterowania wysyła do sterownika serwonapędów współrzędne przegubowe serw do wypracowania. Zadanie odwrotne kinematyki wymaga w każdym cyklu obliczeń rozwiązania sześciu układów trzech równań nieliniowych z trzema niewiadomymi ( po jednym dla każdego ramienia). Układy te rozwiązywane są Metodą Newtona – Raphsona. Obliczenia są dosyć skomplikowane, dlatego mimo użycia kwarcu 16MHz udało się uzyskać tylko 15 – 20 cykli obliczeń na sekundę. Widać na filmach, że manipulator porusza się takimi drobnymi kroczkami. Drugą słabością manipulatora są niewątpliwie niezbyt mocne napędy. Objawia się to niedokładnością w pracy i takimi momentami przestojów. Mimo to konstrukcja spełnia swoje założenia i chyba nie wyszła tak najgorzej. Tutaj kilka pozostałych zdjęć, a w najbliższym czasie postaram sie dodać jakiś filmy z pracy urządzenia. Pojawiły się dwa filmy 🙂
-
Freestyle Samochód z podglądem FPV oparty na RaspberryPi
remoteme_org opublikował temat w Projekty - DIY roboty
O mnie Witam, Jestem Maciej - pracuje jako software architect (nie mam wykształcenia elektronicznego dlatego proszę o wyrozumiałość jeżeli chodzi o połączenia i rozwiązania - z tego też powodu w projekcie nie daje gotowego przepisu na zasilanie ) i żeby do końca nie zgnuśnieć po godzinach tworzę platformę RemoteMe. W platformie chodzi głównie o ułatwienie sterowaniem urządzeniami IoT poprzez internet - bez konieczności posiadania VPNa, publicznego IP. Po stronie RemoteMe tworzycie strony internetowe ( RemoteMe posiada hosting na Wasze pliki ) + zestaw bilbiotek (Javascript, RasbeprryPi (python), Arduino ESP ) które w łatwy sposób pozwolą na komunikowanie się z Waszymi urządzeniami. Co do protokołu to opracowałem własny (bardziej jest to konwencja przesyłu paczek niż protokół jako taki ) (działa przez sockety, websockety, wywoływania RESTowe) - umożliwia on przesył wiadomości do określonego urządzenia, i coś w rodzaju topic- subscribera ( u mnie się to nazwa"variables" zmienne ) Wasze urządzenia rejestrują się w RemoteMe po zarejestrowaniu, możecie do nich wysyłać komendy przez stronę internetową albo z innych urządzeń. Jednym z ostatnich featerów są "Out of the box projects" - polega to na tym, że jednym kliknięciem importujecie projekt na Wasze konto - oczywiście możecie potem wszytko zmieniać wedle własnych pomysłów - właśnie ostatnim takim projektem jest Samochód RaspberryPi z kamerką o którym w tym kursie. Projekt jest częściowo openSourcowy - bilbioteki Javascript, Python, api Restowe są openSource - sam kod serwera i program na RasbeprryPi jest zamknięty. Platformę tworzę sam - po godzinach i udostępniam za darmo 🙂 O kursie Ten kurs – przedstawia budowę samochodu sterowanego przez przeglądarkę z wyświetlaniem obrazu z kamerki. Jest to dość specyficzny kurs (jak pisałem wyżej ) – całość oprogramowanie jest już zrobiona, a dodanie plików do własnego konta w RemoteMe sprowadza się do paru kliknięć. Po tak dodanym projekcie można własnoręcznie i dowolnie edytować wszystkie pliki źródłowe (strona WWW skrypt pythonowy) – zwłaszcza, że same pliki projektu nie są skomplikowane, a kod jest dość czytelny i samo opisujący się (mam przynajmniej taką nadzieję ) Na dole kursu opisana jest bardziej szczegółowo zasada działania samochodu. Jeżeli nie jesteście zainteresowani samą platformą RemoteMe to i tak myślę, że w kursie znajdziecie garść pomysłów na podobny projekt 🙂 Kurs właściwy Na filmie działanie + krok po kroku wszystko z tego kursu. W poprzednim kursie pokazałem jak sterować pozycją kamerki i przechwytywać obraz na przeglądarkę tutaj i sterowanie kamerką tutaj . Teraz rozbudujemy ten projekt i zbudujemy samochód z napędem na 4 koła ( sterowany podobnie jak czołg – żeby skręcić prawa para kół musi się kręcić z inną prędkością niż lewa). Taki efekt otrzymamy na wideo też całość kursu – można zaglądnąć, gdy gdzieś utkniecie Części RaspberryPi z wifi ( np zeroW) link Podwozie z silnikami link Sterownik serwomechanizmów na I2C link Kamera do Rpi + taśma jeżeli potrzebna link Dwa SerwoMechanizmy kompatybilne z uchwytem kamerki kamery link Uchwyt na kamerkę z serwami link Mostek H TB6612FNG link Zasialnie – np akumlatory podłączone do przetwornicy – żeby uzyskać odpowiednie napięcie link Połączenia RaspberryPI steruje serwami poprzez moduł PWM i napędem przez ustawianie stanu pinów na mostku oraz dostarczając sygnału PWM poprzez ten sam moduł, który wysyła sygnał do serwomechanizmów. (Dzięki modułowi do generowanie sygnału PWM nie musimy tych sygnałów generować przez samo RaspberryPi – co jest dość problematyczne, poprostu przez I2C przesyłamy odpowiednie instrukcje do sterownika serw (poprzez bibliotekę pythonową dostarczoną przez adafruit), a ten generuje odpowiedni sygnał PWM dla serwo mechanizmów i do mostka H) Schemat połączeń: Poprzez magistrale I2C RPi steruje kontrolerem serwo mechanizmów, dwa ostanie wyprowadzenia kontrolera są podłączone do mostka H, gdzie wykorzystując sygnał PWM ustawiamy prędkość silników. Do mostka H są również podłączone piny RPi które stanami wysokim i niskim będą określać kierunek obrotu silników napędowych – po szczegóły odsyłam do dokumentacji układu TB6612FNG, równie dobrze można użyć mostka L298N lub podobnego. Zaprojektowałem płytkę PCB, którą możecie użyć pliki eagle,gerber itd tutaj plik pcb.zip Schemat płytki pokrywa się ze schematem powyżej. Wyprowadzenia płytki: Wejście sygnału PWM z kanałów 15 i 14 modułu PWM Wejście zasilania silników do poruszania się Zasilanie układów (PWM, RPi) koniecznie dokładne +5V Wyjście silników napędu, pierwsze dwa wyjścia do jednej pary silników kolejne dwa do drugiej Zasilanie serw i w tym przypadku silników napędu w moim przypadku ~6V (należy sprawdzić w specyfikacji serw i silników, jakie maksymalne napięcie można podłączyć) ZWORKA gdy zepniemy dwa piny napięcie z 5 będzie podawane też do zasilania silników napędu ZWORKA gdy jest podłączona zasilane jest RaspberryPI z połączenia 3 przed podłączeniem zworki należy dokładnie sprawdzić napięcia, żeby nie uszkodzić Najdroższego komponentu czyli właśnie malinki Wlutowujemy kabelki, bo nie będziemy korzystali z konwertera stanów Wlutowujemy kabelki, bo nie będziemy korzystali z konwertera stanów Oczywiście jest jeszcze potrzebne odpowiednie zasilanie, w moim przypadku 6v jako napięcie silników napędu i serw, oraz 5v dla RasbeprryPi i kontrolerów. Poniżej kilka zdjęć poglądowych całości samochodu: Programowanie Przed utworzeniem projektu skonfigurujcie kamerkę i komunikacje I2C używając raspi-config opisane tutaj. Ten projekt jest jednym z gotowych projektów, które możecie prosto zaimplementować kilkoma kliknięciami: KLIK - po zalogowaniu otworzy się projekt. Przejdźcie do zakładki “Build It” i podążajcie krokami, na końcu klik w “Build Project” i otrzymacie: Po kliknięciu “Get QR” pokaże się kod QR, który możecie zeskanować smartfonem, lub po prostu otworzyć stronę przyciskiem “Open” w przeglądarce na komputerze. Narazie nie radzę zmieniać pozycji kamery dopóki nie ustawimy pozycji serw – w niektórych przypadkach możecie uszkodzić swoje serwomechanizmy. Samochód powinien jeździć jak na filmie – może się zdarzyć, że skręca w złą stronę albo serwa są źle skalibrowane, poniżej w omawianiu kodu napisałem jak to naprawić. Omówienie tego, co się stało Jak zauważyliście, tworzenie projektu było mocno zautomatyzowane. Dlatego omówię teraz, co się dokładnie wydarzyło i jak modyfikować Wasz projekt. Przede wszystkim utworzyły się dwie zmienne (zakładka variables): Zmienna cameraPos przesyła pozycje kamery, drive pozycje joysticka. Obie są typem “Small int x2” oznacza to, że wartoscią zmiennej są dwie liczby integer. Strona internetowa zmienia nasze zmienne, a skrypt pythonowy te zmiany rejestruje i odpowiednio reaguje (jak rozwiniemy zmienne zobaczymy, że urządzeniem nasłuchującym na zmiany jest właśnie skrypt pythonowy). Zobaczmy jak wygląda kod Pythonowy (więcej o zmiennych tutaj) Python Skrypt pythonowy został automatycznie wgrany. Oczywiście możemy go podejrzeć i modyfikować ( żeby stworzyć nowy skrypt pythonowy np dla innych projektów zobacz tutaj) . Z informacji jakie teraz są Ci potrzebne to skrypt pythonowy to kolejne urządzenie w remoteMe jest zarządzalne przez skrypt (uruchomiony przez ./runme.sh) , do tego urządzenia możemy wysłać wiadomości, urządzenie reaguje też na zmiany zmiennych, które obserwuje i może te zmienne zmieniać. Żeby otworzyć skrypt pythonowy kliknij python.py i wybierz Edit : Wygląda on następująco. Poniżej omówię ciekawsze fragmenty import logging import socket import math import struct import sys import os os.chdir(sys.argv[1]) sys.path.append('../base') import remoteme import Adafruit_PCA9685 import time import RPi.GPIO as GPIO motorAIn1 = 25 # GPIO25 motorAIn2 = 8 # GPIO8 motorBIn1 = 24 # 24 motorBIn2 = 23 # 23 motors = [[motorAIn1, motorAIn2], [motorBIn1, motorBIn2]] motorsPWM = [14, 15] pwm = None; def motorForward(motorId): GPIO.output(motors[motorId][0], GPIO.LOW) GPIO.output(motors[motorId][1], GPIO.HIGH) def motorBackward(motorId): GPIO.output(motors[motorId][0], GPIO.HIGH) GPIO.output(motors[motorId][1], GPIO.LOW) def motorSoftStop(motorId): GPIO.output(motors[motorId][0], GPIO.LOW) GPIO.output(motors[motorId][1], GPIO.LOW) def setMotor(motorId, speed): if speed == 0: motorSoftStop(motorId) elif speed > 0: motorForward(motorId) elif speed < 0: motorBackward(motorId) speed=-speed logger.info("set speed {} for motor {} ".format(speed,motorId)) pwm.set_pwm(motorsPWM[motorId], 0, int(speed)) def onCameraPosChange(i1, i2): global pwm logger.info("on camera change {} , {}".format(i1, i2)) pwm.set_pwm(1, 0, i1) pwm.set_pwm(0, 0, i2) pass def onDriveChange(x, y): logger.info("on drive change x {} , y {}".format(x, y)) global pwm left=y right=y left+=x right-=x delta=(left+right)/2 left+=delta right+=delta # when your car doesnt drive as suppose try to swich right and left variable below # or remove add minuses next to 2 # another way is to switch cables conencted to motors setMotor(0, 2*left) setMotor(1, 2*right) pass def setupPWM(): global pwm pwm = Adafruit_PCA9685.PCA9685() pwm.set_pwm_freq(80) def setupPins(): global GPIO GPIO.setmode(GPIO.BCM) # Broadcom pin-numbering scheme for motor in motors: for pinId in motor: GPIO.setup(pinId, GPIO.OUT) try: logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', datefmt='%d.%m %H:%M', filename="logs.log") logger = logging.getLogger('application') setupPWM() setupPins() remoteMe = remoteme.RemoteMe() remoteMe.startRemoteMe(sys.argv) remoteMe.getVariables().observeSmallInteger2("cameraPos" ,onCameraPosChange); remoteMe.getVariables().observeSmallInteger2("drive" ,onDriveChange); remoteMe.wait() finally: pass Sterowanie silnikami: def motorForward(motorId): GPIO.output(motors[motorId][0], GPIO.LOW) GPIO.output(motors[motorId][1], GPIO.HIGH) def motorBackward(motorId): GPIO.output(motors[motorId][0], GPIO.HIGH) GPIO.output(motors[motorId][1], GPIO.LOW) def motorSoftStop(motorId): GPIO.output(motors[motorId][0], GPIO.LOW) GPIO.output(motors[motorId][1], GPIO.LOW) def setMotor(motorId, speed): if speed == 0: motorSoftStop(motorId) elif speed > 0: motorForward(motorId) elif speed < 0: motorBackward(motorId) speed=-speed logger.info("set speed {} for motor {} ".format(speed,motorId)) pwm.set_pwm(motorsPWM[motorId], 0, int(speed)) Funkcja setMotor dla motorId 1 lub 2 ustawia prędkość speed (może być ujemna). poprostu najperw na odpowiednich pinach ustawiamy odpowiednio stany (ruch do przodu do tyłu, hamowanie), a następnie w zależności od prędkości ustawiamy odpowiednio wypełnienie PWM korzystając ze sterownika serw. def onCameraPosChange(x, y): global pwm logger.info("on camera change {} , {}".format(x, y)) pwm.set_pwm(1, 0, x) pwm.set_pwm(0, 0, y) pass Funkcja setMotor dla motorId 1 lub 2 ustawia prędkość speed (może być ujemna). poprostu najperw na odpowiednich pinach ustawiamy odpowiednio stany (ruch do przodu do tyłu, hamowanie), a następnie w zależności od prędkości ustawiamy odpowiednio wypełnienie PWM korzystając ze sterownika serw. def onDriveChange(x, y): logger.info("on drive change x {} , y {}".format(x, y)) global pwm left=y right=y left+=x right-=x delta=(left+right)/2 left+=delta right+=delta # when your car doesnt drive as suppose try to swich right and left variable below # or remove add minuses next to 2 # another way is to switch cables conencted to motors setMotor(0, 2*left) setMotor(1, 2*right) pass Powyższa funkcja zostanie wywołana jak zmieni się zmienna drive – np po zmianie joysticka na stronie. x,y to po prostu współrzędne wychylenia joysticka (1024,1024) oznacza wychylenie maksymalnie do góry i w prawo. Na podstawie tych zmiennych wyliczamy prędkość lewej i prawej strony samochodu. Jeżeli samochód skręca, zamiast jechać do przodu, jedzie do tyłu zamiast do przodu: setMotor(0, Y2*left) setMotor(1, X2*right) Dajcie minusy w różnych kombinacjach (w miejsca X i Y) do czasu, aż samochód jedzie do przodu – gdy joystick jest wychylony do góry. (będzie to odpowiadać zamianą miejscami przewodów lewej strony dla miejsca Y i prawej strony dla miejsca X). Następnie, jeżeli prawa i lewa strony są zamienione (samochód skręca w złą stronę ), w funkcji powyżej zamieńcie left i right miejscami. def setupPWM(): global pwm pwm = Adafruit_PCA9685.PCA9685() pwm.set_pwm_freq(80) def setupPins(): global GPIO GPIO.setmode(GPIO.BCM) # Broadcom pin-numbering scheme for motor in motors: for pinId in motor: GPIO.setup(pinId, GPIO.OUT) Po prostu ustawiamy odpowiednie piny (te do sterowania mostkiem H) na wyjścia. I tworzymy obiekt do kontrolowania sterowania serw. (Uwaga żeby sterownik serw działał prawidłowo musimy włączyć komunikacje I2C używając raspi-config więcej tutaj ) remoteMe.startRemoteMe(sys.argv) remoteMe.getVariables().observeSmallInteger2("cameraPos" ,onCameraPosChange); remoteMe.getVariables().observeSmallInteger2("drive" ,onDriveChange); remoteMe.wait() Ustawienie RemoteMe i ustawienie jakie funkcję mają zostać wywołane, gdy zmienne do sterowania kamery i napędu zostaną zmienione. To tyle w samym skrypcie pythonowym, jak widzicie, nie jest on zbyt skomplikowany, po więcej informacji zapraszam tutaj Strona WWW Tak naprawdę zostały stworzone dwie strony WWW — jedna do kalibracji druga — strona do sterowania samochodem i wyświetlania obrazu z kamerki. Otwórzmy stronę do kalibracji: Otworzy się strona internetowa z dwoma suwakami. Ustawmy górny i dolny na środkową pozycję – kamera się poruszy- górny suwak powinien poruszać kamerę w osi x, dolny y. Jeżeli się tak nie dzieje – zamieńcie miejscami przewody serwomechanizmu. Następnie za pomocą suwaków ustalcie maksymalne wychylenie osi x i osi y dla mnie jest to: x : 298 – 830 i centralna pozycja. Ważne, żeby centralna pozycja byłą dokładnie pomiędzy przedziałem u mnie ((298+830) /2 = 564) y: 223 – 723 i podobnie centralna pozycja kamery w osi y powinna być w środku przedziału Zapiszmy liczby gdzieś w notatniku i otwórzmy do edycji stronę do sterowania samochodem: <camera autoConnect="true" showInfo="true" class="cameraView"></camera> <connectionstatus webSocket="true" directConnection="false" camera="true"></connectionstatus> <variable component="cameraMouseTrack" type="SMALL_INTEGER_2" style="display:block" name="cameraPos" xMin="298" xMax="830" invertX="true" yMin="223" yMax="723" invertY="true" requiredMouseDown="true" reset="true" onlyDirect="true"></variable> <div class="joystickButtons"> <div class="buttons"> <variable class="gyroscope" component="gyroscope" type="SMALL_INTEGER_2" name="cameraPos" label="Gyroscope Camera" orientationSupport="true" xMin="298" xMax="830" xRange="19" invertX="true" yMin="223" yMax="723" yRange="20" invertY="false" onlyDirect="true"></variable> <variable class="gyroscope" component="gyroscope" type="SMALL_INTEGER_2" name="drive" label="Gyroscope Drive" xMin="-512" xMax="512" xRange="19" invertX="false" yMin="-512" yMax="512" yRange="20" invertY="false" onlyDirect="true"></variable> <button class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect gyroscope" onClick="toggleFullScreen()">fullscreen </button> </div> <div class="joystickParent"> <variable class="joystick" component="joystick_simple" type="SMALL_INTEGER_2" name="drive" xRange="1024" yRange="1024" onlyDirect="true"></variable> </div> <div style="clear: both;"/> </div> Są to automatyczne komponenty do sterowania samochodem i wyświetlaniem obrazu wideo. Strona, którą otworzyliście zawiera już wszystkie komponenty potrzebne do sterowania waszym samochodem. Jedyne co trzeba zrobić to zmienić zakres ruchów serwo mechanizmów. xMin,xMax,yMin,yMax, zamieńcie na wartości jakie otrzymaliście na poprzedniej stronie z suwakami. Jeżeli chcecie stworzyć własną stronę ze swoimi komponentami najlepiej utworzyć ją od początku pokazane tutaj – pozwoli Wam to dodawać komponenty w przy pomocy kreatora, gdzie ustalicie potrzebne parametry, albo po prostu edytować źródła tej już utworzonej strony – wcześniej warto utworzyć kopię, gdyby coś poszło nie tak (albo skasować pliki i utworzyć projekt jeszcze raz – wtedy nie zapomnijcie wcześniej wykasować też zmienne) Po zmianie wartości x/y/Min/Max możemy otworzyć naszą stronę np w smartfonie, klikamy na index.html, ale tym razem wybieramy opcje get anymous link Następnie, klikamy ikonkę kodu QR, a kod, który się pojawi skanujemy smarfonem. Oczywiście sterowanie działa również poprzez internet – nie tylko w sieci lokalnej, jednak w niektórych przypadkach jest potrzebna dodatkowa konfiguracja więcej o niej tutaj Trochę szczegółów technicznych ( nie obowiązkowe ) RemoteMe serwuję stronę WWW do sterowania Waszym samochodem ( a dodatkowo Was loguje na Wasze konto – stąd token w linku otwieranym przez smartfon ) i uczestniczy w negocjowaniu tworzenia połączenia WebRTC. Połączenie webRTC jest to połączenie bezpośrednie (RaspberryPi – przeglądarka ( w niektórych przypadkach np za NATem jest potrzebny dodatkowo stun sewer)). Po stworzeniu połączenia webRTC stan zmiennych nie jest w ogóle wysyłany do remoteMe (bo pole “onlyDirect” w komponentach jest ustawione na true). Protokołem webRTC przesyłany jest też obraz, a że webRTC tworzy połaczenie point to point opóźnienie jest znikome. Program na RaspberryPi który odpalacie poleceniem ./runme.sh tworzy połączenie websocketowe z platformą RemoteMe oraz zarządza skryptem pythonowym (wysyła do niego wiadomości, przesyła wiadomości ze skryptu dalej do platformy etc ). Działanie skryptu pythonowego jest możliwe dzięki dodatkowym bilbiotekom RemoteMe (znajdują sie w folderze leafDevices/base/ ). Sama strona internetowa po websocketach łączy się do platformy RemoteMe (pozwalają na to skrypty javascriptowe zaimportowane w headerze pliku index.html ze ścieżek /libs/). Ułatwiają i ustanawiają komunikacje z platformą RemoteMe. Same komponenty wstawione w index.html typu : <variable component="**" W funkcjach pliku remoteMeComponents.js są zastępowane na “standardowe” i “bootstrapowe” komponenty htmlowe, dodatkowo do komponentów przypinane są eventy reagujące na akcje użytkownika i wysyłające odpowiednie komunikaty do skryptu pythonowego. Można podejrzeć jak remoteMeComponents.js zamienia i tworzy nowe komponenty – może to być interesujące gdy macie potrzebę stworzenia własnych komponentów, które RemoteMe nie pozwala dodać z kreatora. W większości przypadków akcje Waszych komponentów będą wykonywać zapis zmiennych w postaci RemoteMe.getInstance().getVariables() .setSmallInteger2("cameraPos",123,456,true); która wyśle do skryptu pythonowego informacje o zmianie zmiennej, podobnie sterujemy silnikami ustawiając odpowiednią zmienną. Podsumowanie Tworząc tej projekt chciałem pokazać Wam jak w łatwy sposób sterować Waszym samochodem z widokiem FPV. Projekt można rozbudowywać, lub zmieniać np, gdy korzystanie z innego mostka H niż ja. Zachęcam zatem do eksperymentowania ze źródłami projektu . Gdy jest to Wasz pierwszy projekt, zachęcam do zrobienia na początek projektu z mrugającą diodą i poczytanie dokumentacji na www.remoteme.org Pozdrawiam, Maciej- 15 odpowiedzi
-
- 5
-
- Raspberry Pi
- Samochód
-
(i 2 więcej)
Tagi:
-
Researcher jest robotem badawczym przeznaczonym do badania składu gleby oraz powietrza. Robot jest wyposażony w łamane ramię z łyżką przeznaczoną do pobierania próbek gleby oraz umieszczania próbek w specjalnych pojemniczkach wyposażonych w czujniki składu gleby (np. wilgotności, ph). Próbki mogą być pobierane z głębokości 10 cm dzięki zastosowaniu wiertła zamontowanego na siłowniku liniowym o wysuwie 10cm. Za obroty wiertłem jest odpowiedzialny silnik DC. Element wiertniczy jest zamontowany na zawiesie i mocowany na jedną śrubę, ułatwia to transport robota. Za działanie ramienia odpowiedzialne są 4 serwa, ramię jest wykonane z aluminium, dzięki czemu jest wytrzymałe i lekkie. Aby odciążyć serwo odpowiedzialne za obracanie ramieniem zastosowaliśmy płytę przymocowaną do podwozia, poruszają się po niej kułeczka zamocowane do części ruchomej. Konstrukcja robota jest wykonana w całości z aluminium i stali. Obudowa i podstawka pod elektronikę są wykonane z polisterynu. Robot jest w stanie poruszać się po trudnym terenie i wjeżdżać na krawężniki, umożliwia mu to zastosowanie zawieszenia biegunowego dla dwóch przednich osi. Tylna oś jest zamocowana na amortyzatorach. Za ruch silników odpowiedzialnych jest 6 silników DC zamontowanych do płaskowników aluminiowych za pomocą obejm i śrub. W tylnej części zamontowany wspornik mocujący do czujników wiatru. Robot waży około 13 kilogramów i ma 70 cm długości i ok 40 cm szerokości. Za pracę odpowiada moduł Arduino MEGA 2560, w robocie zastosowaliśmy 4 sterowniki silników jednokanałowych po jednym na każdą stronę robota (po 3 silniki do jednego) 2 pozostałem odpowiadają za ruch siłownikiem oraz silnikiem w module wiertniczym. Zastosowaliśmy czujniki temperatury powietrza i gleby, składu powietrza i gleby. Aby nie uszkodzić robota zastosowaliśmy czujniki odległości dzięki którym robot nie wjeżdża w ściany i większe przeszkody. Do komunikacja pomiędzy robotem a aparaturą używamy modułu radiowego. Do sterowania używamy przerobionej aparatury. Znajduje się w niej Arduino UNO, 3 gałki analogowe, wyświetlacz i płytka dotykowa z przyciskami oraz da przełączniki dźwigniowe do zmiany trybu sterowania. Całego robota budowaliśmy około 10 miesięcy w 3 osobowym zespole, uczestniczyliśmy w licznych zawodach m in East Robo, Mikrobot, Explory. Do sukcesów można zaliczyć 4 miejsce w Konkursie El Robo Mech dzięki któremu zdobyliśmy indeksy na studia. Zastosowaniem robota ma być wykorzystywany do badania gleb w miejscach trudno dostępnych oraz jako robot ratunkowy w wypadku skażeń chemicznych i biologicznych. Robot mógłby zbadać glebę i powietrze na terenie skażonym, a dzięki czujnikom kierunku i prędkości wiatru można będzie oszacować przemieszczanie się chmur z zanieczyszczeniem. Operator robota może odczytywać dane z czujników z wyświetlacza na kontrolerze oraz mieć podgląd z kamery umieszczonej na robocie.
-
Chciałem zaprezentować mój pierwszy edukacyjny projekt o nazwie kodowej KLOSZARD, który stanowi połączenie pojazdu sterowanego i autonomicznego. Swoją nazwę zawdzięcza temu, że zbudowałem go z najtańszych części ze sklepów wysyłkowych, a jego głównym zadaniem jest pałętanie się po mieszkaniu bez większego celu 🙂 Początki Pierwsza wersja powstała na gotowym podwoziu robota 2WD. Posiadała jeden moduł mikrokontrolera (Blue Pro Micro) i czujnik odległości (wpięte na breadboardzie). Była zasilana z czterech baterii AA i uwzględniała sterowanie radiowe (moduł + pilot z czterema przyciskami). Niestety wszystkie te elementy potwornie mnie zawiodły, a przede wszystkim to, że pojazd nie potrafił utrzymać prostego kierunku jazdy. Postanowiłem więc podejść do sprawy bardziej profesjonalnie 🙂 Konstrukcja Obecne czterokołowe podwozie oparte jest na płytach plexi (dociętych na wymiar z portalu aukcyjnego) połączonych kołkami dystansowymi. Posiada cztery silniki z podwójnym wałem (DC 6V z przekładnią 1:48). Na każdym wale umieściłem tarcze z otworami oraz czujniki szczelinowe stanowiące enkodery optyczne. Moduły czujników FC-03 mają wlutowane kondensatory między pin D0 i GND w celu eliminowania błędnych/fałszywych impulsów (rozwiązanie znalezione w sieci). Pierwsze piętro robota wyposażyłem w pięć czujników odległości, koszyk na akumulatory i przełącznik zasilania. Cała konstrukcja zwieńczona jest gustownym zadaszeniem z plexi 🙂 Zasilanie Maszynę zasilają dwa ogniwa litowo-jonowe (2S 7.4V). Część logiczna otrzymuje napięcie 5V dzięki miniaturowej przetwornicy step-up/step-down (Pololu S7V8A). Silniki sterowane są przez dwa masywne moduły z radiatorami oparte na dwukanałowym układzie L298N. Moduł jezdny Na parterze konstrukcji umieściłem Arduino Pro Micro, które nieustannie oblicza prędkość obrotową każdego wału oraz reguluje napięcia silników w celu uzyskania prędkości zadanej przez moduł główny. Dzięki temu koła mogą niezależnie poruszać się z jednakową prędkością. Komunikacja z modułem głównym odbywa się po magistrali I2C. Komenda sterująca zawiera trzy liczby: 1. prędkość lewej strony, 2. prędkość prawej strony (koło przednie i tylne otrzymują tę samą wartość w przedziale od -100 do +100 RPM) oraz 3. długość zadania mierzona w impulsach enkodera (0 = zadanie ciągłe, nieskończone). [DriveModule.ino] #include <Wire.h> #include <PID_v1.h> /**************************************************************************************************/ class MotorController { static const int8_t SMPL_COUNT = 4; // speed calculation every 4 sensor events static const double RPM_CONST = (60000000 * (SMPL_COUNT / 40.0)); // 20 holes, 40 events bool lastState; byte encPin, fwdPin, bckPin, pwmPin; int8_t signedRpm = 0; double desiredRpm = 0, pwmVal, currRpm = 0; PID *Pid; public: bool setpointReached; uint32_t counter = 0, lastMeasurement = 0, setpoint = 0; MotorController(byte encPin, byte fwdPin, byte bckPin, byte pwmPin) { pinMode(this->encPin = encPin, INPUT); pinMode(this->fwdPin = fwdPin, OUTPUT); pinMode(this->bckPin = bckPin, OUTPUT); pinMode(this->pwmPin = pwmPin, OUTPUT); lastState = !digitalRead(encPin); Pid = new PID(&currRpm, &pwmVal, &desiredRpm, 0.4, 1.5, 0, DIRECT); Pid->SetSampleTime(50); // PID calculation every 50 milliseconds setRpm(0); } int8_t speedMeasurement() { // ISR bool state = digitalRead(encPin); if (state != lastState) { lastState = state; counter++; setpointReached = (setpoint && (counter >= setpoint)); if ((counter % SMPL_COUNT) == 0) { uint32_t currTime = micros(); currRpm = RPM_CONST / (currTime - lastMeasurement); lastMeasurement = currTime; return 2; // RPM calculated } return 1; // sensor event occurred } return 0; // nothing happened } int8_t voltageAdjustment() { if (setpointReached) return 2; // task completed if (!Pid->Compute()) return 0; // nothing happened, no adjustment or PID turned off noInterrupts(); uint32_t eventDuration = micros() - lastMeasurement; if (eventDuration > 400000) currRpm = 0; // no event for 400ms, engine stopped interrupts(); analogWrite(pwmPin, pwmVal); return (eventDuration < 3000000) ? // no event for 3 seconds means danger of power overload 1 /* voltage adjustment */ : 3 /* power overload */; } void setRpm(int8_t rpm, byte initPwm = 0) { if (((rpm ^ signedRpm) < 0) || (rpm && !signedRpm)) { // if new RPM has opposite sign or ... noInterrupts(); currRpm = 0; interrupts(); pwmVal = initPwm; } signedRpm = rpm; desiredRpm = abs(rpm); digitalWrite(fwdPin, rpm > 0 ? HIGH : LOW); // setting direction depending on sign of RPM digitalWrite(bckPin, rpm < 0 ? HIGH : LOW); Pid->SetMode(rpm ? AUTOMATIC : MANUAL); // PID switch ON-OFF } }; /**************************************************************************************************/ class VehicleController { static const byte MAX_PWM = 255; static const byte MID_PWM = 100; uint32_t lastTime = 0; static int ascComp(const void *a, const void *b) { return (*(uint32_t *)a - *(uint32_t *)b); } public: bool powerOverload = false, isRunning = false; int8_t taskCompleted = 0; static const int8_t M_LEN = 4; MotorController motor[M_LEN] = { {/*ENC1*/ 12, /*FIN1*/ 4, /*FIN2*/ 2, /*FENA*/ 3}, {/*ENC2*/ 11, /*FIN3*/ 8, /*FIN4*/ 7, /*FENB*/ 9}, {/*ENC3*/ 10, /*BIN1*/ A1, /*BIN2*/ A0, /*BENA*/ 6}, {/*ENC4*/ 13, /*BIN3*/ A3, /*BIN4*/ A2, /*BENB*/ 5} }; void interruptRoutine() { // ISR for (byte i = 0; i < M_LEN; i++) motor[i].speedMeasurement(); } void loopRoutine() { for (int8_t i = 0; i < M_LEN; i++) { switch (motor[i].voltageAdjustment()) { case 0: // nothing happened break; case 1: // voltage adjustment break; case 2: // task completed taskCompleted++; noInterrupts(); motor[i].setpointReached = motor[i].setpoint = 0; interrupts(); break; case 3: // power overload powerOverload = true; setSpeed(0, 0); return; } } if (taskCompleted >= M_LEN) setSpeed(0, 0); } void setSpeed(int8_t left, int8_t right, uint32_t taskLen = 0) { uint32_t now = micros(); isRunning = left || right; noInterrupts(); if (isRunning) { powerOverload = taskCompleted = 0; for (byte i = 0; i < M_LEN; i++) { motor[i].lastMeasurement = now; motor[i].setpointReached = motor[i].counter = 0; motor[i].setpoint = taskLen; } } else { for (byte i = 0; i < M_LEN; i++) motor[i].setpointReached = motor[i].setpoint = 0; } interrupts(); motor[0].setRpm(left, MID_PWM); motor[1].setRpm(right, MID_PWM); motor[3].setRpm(left, MID_PWM); motor[2].setRpm(right, MID_PWM); } } vehicle; /**************************************************************************************************/ void setChangeInterrupt(byte pin) { pinMode(pin, INPUT); *digitalPinToPCMSK(pin) |= bit(digitalPinToPCMSKbit(pin)); PCIFR |= bit(digitalPinToPCICRbit(pin)); PCICR |= bit(digitalPinToPCICRbit(pin)); } volatile int8_t gblLeftSpeed = 0, gblRightSpeed = 0; volatile uint8_t gblTaskLen = 0; void setup() { Wire.begin(44); Wire.onReceive(i2cReceiveEvent); Wire.onRequest(i2cRequestEvent); for (byte i = 10; i <= 13; i++) setChangeInterrupt(i); // PCINT0 int8_t tmpLSpeed = 0, tmpRSpeed = 0; uint8_t tmpTaskLen = 0; while (true) { vehicle.loopRoutine(); if (gblLeftSpeed != tmpLSpeed || gblRightSpeed != tmpRSpeed || gblTaskLen != tmpTaskLen) { tmpLSpeed = gblLeftSpeed; tmpRSpeed = gblRightSpeed; tmpTaskLen = gblTaskLen; vehicle.setSpeed(tmpLSpeed, tmpRSpeed, tmpTaskLen); } } } void i2cReceiveEvent(int howMany) { if (howMany >= 3) { gblLeftSpeed = Wire.read(); gblRightSpeed = Wire.read(); gblTaskLen = Wire.read(); howMany =- 3; } while (howMany--) Wire.read(); } void i2cRequestEvent() { // task ended Wire.write(gblTaskLen ? vehicle.taskCompleted >= vehicle.M_LEN : 1); } ISR(PCINT0_vect) { vehicle.interruptRoutine(); } Moduł zbliżeniowy Pięcioma czujnikami odległości (4x HC-SR04 i 1x US-015) steruje samotna Atmega328P na płytce uniwersalnej (wgrany bootloader wykorzystuje wewnętrzny oscylator 8MHz). Każde żądanie danych (wysłane po I2C) do tego modułu jest sygnałem do wykonania kolejnego pomiaru ze wszystkich czujników (po kolei). [ProximityModule.ino] #include <Wire.h> #include <NewPing.h> NewPing sensor[] = { NewPing(12, 12, 200), NewPing(5, 5, 200), NewPing(13, 13, 200), NewPing(4, 4, 200), NewPing(A3, A3, 200) }; const byte ORDER[] = {0, 2, 4, 1, 3}; const byte CNT = sizeof(ORDER); volatile byte distance[CNT] = {}; volatile bool takeNext = false; void setup() { Wire.begin(40); Wire.onRequest(requestEvent); while (true) { if (!takeNext) continue; takeNext = false; for (byte i = 0; i < CNT; i++) { distance[ORDER[i]] = sensor[ORDER[i]].ping_cm(); delay(8); } } } void requestEvent() { for (byte i = 0; i < CNT; i++) Wire.write(distance[i]); takeNext = true; } Sterowanie Pojazd może być sterowany za pomocą smartfonu z Androidem. Aplikacja napisana w Java’ie (Android Studio) przełącza się na WiFi rozgłaszane przez robota i przesyła drogą sieciową (na ustalony adres IP i port) krótkie instrukcje sterujące. Przy tej okazji po raz pierwszy w życiu doceniłem prostotę protokołu UDP – każda komenda znajduje się w osobnym pakiecie danych 🙂 Nie zamieszczam źródeł aplikacji ponieważ zawierają one mnóstwo nadmiarowego kodu związanego bardziej z interfejsem użytkownika. Moduł główny Przypadkowo wpadłem w posiadanie modułu WiFi NodeMCU v3 z rodziny układów ESP8266. Podczas pierwszych testów w Arduino IDE zauważyłem, że najprostsze szkice wygrywają się do niego potwornie długo. Do tej pory nie znalazłem rozwiązania tej niedogodności, ale w trakcie poszukiwań natrafiłem na firmware obsługujący prosty system plików oraz interpreter języka skryptowego Lua. http://nodemcu.readthedocs.io Koncepcja asynchronicznych callbacków wywoływanych przez zdarzenia i timery (znana mi z JavaScriptu) wydała się genialnym rozwiązaniem symulującym pracę wielozadaniową w środowisku jednowątkowym, dlatego postanowiłem przyjrzeć się temu środowisku. NodeMCU pracuje w standardzie 3.3V. Konwerter poziomów logicznych został ukryty na płytce uniwersalnej bezpośrednio pod modułem 🙂 Pojazd posiada możliwość autonomicznej jazdy aktywowanej z poziomu aplikacji lub wbudowanego w moduł przycisku FLASH. W tym trybie robot ma za zadanie objechać całe mieszkanie tak, aby po jego prawej stronie w odległości do 40 cm zawsze znajdowała się jakaś przeszkoda. W przypadku braku przeszkody robot obraca się w prawo mniej-więcej o 90 stopni i jedzie prosto. Przeszkoda od strony frontowej uruchamia poszukiwanie otwartej przestrzeni przy jednoczesnym obracaniu w lewo. [main.lua] print("Starting Kloszard") local klrd = require "kloszard" local snsr = require "sensor" local autoDrive = { timer = tmr.create() } function autoDrive:onSetup() self.timer:register(100, tmr.ALARM_AUTO, function() local s = klrd.prxRead() for i = 1, 5 do snsr[i]:add(s:byte(i)) end local act = self:action() if act then self.action = act if act == self.main then -- drive forward klrd.drvSend(45, 45) end end end) self:onStop() end function autoDrive:onStart() self.action = function() return self.main end klrd.prxRead() self.timer:start() snsr.sideCheckLock = true end function autoDrive:onStop() self.timer:stop() klrd.drvSend(0, 0) end function autoDrive:onFinish() self:onStop() self.timer:unregister() end function autoDrive:main() -- obstacle ahead if math.min(snsr[2].val, snsr[3].val, snsr[4].val) < 20 then klrd.drvSend(-45, 45) return function() -- finding escape if snsr[2]:gt(20) and snsr[3]:gt(20, 3) and snsr[4].val > 20 then snsr.sideCheckLock = true return self.main end end end -- open space on right side if snsr:rightSideOpen(40) then klrd.drvSend(45, -45, 30) -- turn right return function() if klrd.drvTaskEnded() then return self.main end end end -- right side collision risk if snsr[5]:lt(10) and snsr[5]:get(5) > snsr[5].val then klrd.drvSend(0, 45) -- course correction return function() if snsr[5]:get(5) < snsr[5].val then return self.main end end end end klrd.setup(autoDrive) [sensor.lua] local Queue = { 100, 100, 100, 100, 100, pos = 1, LEN = 5, val = 100 } Queue.__index = Queue Queue.new = function() return setmetatable({}, Queue) end function Queue:add(value) if value == 0 then value = self.val end self[self.pos], self.val = value, value self.pos = self.pos + 1 if self.pos > self.LEN then self.pos = 1 end end function Queue:get(index) if index > self.LEN then index = self.LEN elseif index < 1 then index = 1 end local shift = self.pos - index if shift < 1 then shift = shift + self.LEN end return self[shift] end function Queue:gt(value, len) -- values greater than if not len then len = self.LEN end for i = 1, len do if self:get(i) <= value then return false end end return true end function Queue:lt(value, len) -- values less than if not len then len = self.LEN end for i = 1, len do if self:get(i) >= value then return false end end return true end local M = { sideCheckLock = true } for i = 1, 5 do M[i] = Queue:new() end function M:rightSideOpen(distance) local cnt = 0 for i = 1, self[5].LEN do if self[5][i] > distance then cnt = cnt + 1 end end if self.sideCheckLock then self.sideCheckLock = cnt > 0 return false else self.sideCheckLock = cnt == self[5].LEN return self.sideCheckLock end end return M [kloszard.lua] local M = {} local AUTOBTN_PIN = 3 local CMD_PING = 121 local DRVM_ADDR = 44 -- DriveModule Address local CMD_DRIVE = 122 local PRXM_ADDR = 40 -- ProximityModule Address local CMD_AUTO = 123 local UDP_PORT = 50000 function M.setup(autoDrive) M.autoDrive = autoDrive wifi.setmode(wifi.SOFTAP) wifi.ap.setip({ ip = "192.168.1.1", netmask = "255.255.255.0", gateway = "192.168.1.1" }) wifi.ap.config({ ssid = "KloszardWiFi", pwd = "abcd1234" }) i2c.setup(0, 5, 6, i2c.SLOW) -- SDA pin 5, SCL pin 6 autoDrive:onSetup() M.udpSock = net.createUDPSocket() M.udpSock:listen(UDP_PORT) M.udpSock:on("receive", function(sock, data, port, ip) if data:byte(1) == CMD_PING then sock:send(port, ip, "PONG") elseif data:byte(1) == CMD_DRIVE then M.drvSend(data:byte(2), data:byte(3), data:byte(4)) elseif data:byte(1) == CMD_AUTO then if data:byte(2) ~= 0 then autoDrive:onStart() else autoDrive:onStop() end end end) gpio.trig(AUTOBTN_PIN, "up", function() if autoDrive.timer:state() then autoDrive:onStop() else autoDrive:onStart() end end) M.drvSend(0, 0) end function M.finish() M.autoDrive:onFinish() M.udpSock:close() wifi.setmode(wifi.NULLMODE) end local lastDrvmLeft, lastDrvmRight, lastTaskLen = 0, 0, 0 function M.drvSend(left, right, taskLen) if not taskLen then taskLen = 0 end if lastDrvmLeft == left and lastDrvmRight == right and lastTaskLen == taskLen -- ignore function call if nothing changed then return end lastDrvmLeft, lastDrvmRight, lastTaskLen = left, right, taskLen left = bit.band(left, 0xFF) right = bit.band(right, 0xFF) i2c.start(0) i2c.address(0, DRVM_ADDR, i2c.TRANSMITTER) i2c.write(0, left, right, taskLen) i2c.stop(0) end function M.drvTaskEnded() i2c.start(0) i2c.address(0, DRVM_ADDR, i2c.RECEIVER) local res = i2c.read(0, 1) i2c.stop(0) return res:byte(1) ~= 0 end function M.prxRead() i2c.start(0) i2c.address(0, PRXM_ADDR, i2c.RECEIVER) local res = i2c.read(0, 5) i2c.stop(0) return res end return M WebEspDitor Konieczność ciągłego podłączania kabla USB w celu modyfikacji najdrobniejszych parametrów kodu zmotywowała mnie do napisania dodatkowego skryptu o nazwie WebEspDitor. Jest to prosty serwer/serwis HTTP, który umożliwia edycję plików znajdujących się w pamięci nodeMCU z poziomu przeglądarki internetowej. Skrypt wyświetla również wyniki funkcji print i ostatni błąd aplikacji, w związku z czym powinien być uruchamiany na wstępie jako init.lua (kod aplikacji należy przenieść do pliku main.lua). [init.lua] gpio.mode(3, gpio.INPUT, gpio.PULLUP) -- FLASH BUTTON if gpio.read(3) == 0 then return end -- boot loop protection print("Starting WebEspDitor...") net.createServer():listen(80, function(socket) local htmlTmpl = { -- 1 -- [===[HTTP/1.0 200 OK Content-Type: text/html; charset=UTF-8 Connection: close <!DOCTYPE html><html><head><title>WebEspDitor</title><meta name="viewport" content="width=device-width"><link rel="icon" href="data:;base64,="><style> a{text-decoration:none;color:navy}a:hover{text-decoration:underline}body{ line-height:1.5em;font-family:monospace}</style></head><body> ]===], -- 2 -- [===[<p><a href="/">WebEspDitor</a> [ <a href="/reset">reset</a> ]</p>]===], -- 3 -- [===[<form action="/save/<!FNAME>" method="post" enctype="text/plain"> <textarea name="s" spellcheck="false" style="position:absolute;width:100%; height:100%;margin:0;border:0;padding:4px;resize:none;white-space:pre; box-sizing:border-box">]===], -- 4 -- [===[</textarea><input style="position:absolute;bottom:0;right:0" type="submit" value="Save"></form><style>body{margin:0;overflow:hidden}</style>]===], -- 5 -- [===[<input id="name" type="text"><input type="button" value="create" onclick="location.href='/edit/'+document.getElementById('name').value">]===], -- 6 -- [===[<script>setTimeout(function(){location.href='/'},2000)</script>]===], -- 7 -- [===[<li><a href="/edit/<!FNAME>"><!FNAME></a> (<!FSIZE>, <a href="/delete/<!FNAME>" onclick="return confirm('Are you sure to delete'+ ' file \'<!FNAME>\'?')">del</a>)</li>]===] } local rqst, rspn = { length = 0, totalLen = 0, fname = nil }, { } local function getLine(str, bgnPos) local line, endPos = "", str:find("\r\n", bgnPos, true) if endPos then line = str:sub(bgnPos, endPos - 1) bgnPos = endPos + 2 end return line, bgnPos end local function sendRspn(sck) -- send response table to client if #rspn > 0 then sck:send(table.remove(rspn, 1), sendRspn) else sck:close() end end local function saveRqst(sck, data) -- save incoming content to file if #data > 0 then rqst[#rqst+1] = data end rqst.length = rqst.length + #data if rqst.length >= rqst.totalLen then -- last network frame data = nil rqst[1] = rqst[1]:sub(3) -- enctype text/plain: rqst[#rqst] = rqst[#rqst]:sub(1, -3) -- s=[data]CRLF local fd = file.open(rqst.fname, "w+") if fd then while #rqst > 0 do fd:write(table.remove(rqst, 1)) end fd:close() end collectgarbage("collect") sendRspn(sck) end end socket:on("receive", function(sck, data) rspn[1] = htmlTmpl[1] local line, dataPos = getLine(data, 1) -- 1 2 3 4 5 local parts = {} -- GET /cmd/prm HTTP/1.1 for elm in line:gmatch("[^/ ]+") do parts[#parts+1] = elm end if #parts == 5 then parts[3] = parts[3]:gsub("[^a-zA-Z0-9_.-]", "") end if parts[1] == "POST" then repeat line, dataPos = getLine(data, dataPos) local len = line:match("Content%-Length: (%d+)") if len then rqst.totalLen = tonumber(len) end until #line == 0 end -- EDIT if parts[2] == "edit" and #parts == 5 then local tmp = htmlTmpl[3]:gsub("<!FNAME>", parts[3], 1) rspn[#rspn+1] = tmp tmp = #rspn + 1 local fd = file.open(parts[3], "r") if fd then while true do local chunk = fd:read(512) if chunk then rspn[#rspn+1] = chunk else break end end fd:close() end for i = tmp, #rspn do rspn[i] = rspn[i]:gsub("&", "&"):gsub("<", "<") end rspn[#rspn+1] = htmlTmpl[4] else rspn[#rspn+1] = htmlTmpl[2] -- SAVE if parts[2] == "save" and #parts == 5 then rqst.fname = parts[3] rspn[#rspn+1] = '<p>File "'..parts[3]..'" has been saved.</p>'..htmlTmpl[6] -- DELETE elseif parts[2] == "delete" and #parts == 5 then file.remove(parts[3]) rspn[#rspn+1] = '<p>File "'..parts[3]..'" has been deleted.</p>'..htmlTmpl[6] -- RESET elseif parts[2] == "reset" then local timer = tmr.create() timer:register(500, tmr.ALARM_SINGLE, function() node.restart() end) timer:start() rspn[#rspn+1] = '<p>Waiting for device...</p>'..htmlTmpl[6] -- INDEX else local res = {'<ul>'} for n, s in pairs(file.list()) do if not n:find("init.", 1, true) then res[#res+1] = htmlTmpl[7]:gsub("<!FNAME>", n):gsub("<!FSIZE>", s) end end res[#res+1] = '</ul>' rspn[#rspn+1] = table.concat(res)..htmlTmpl[5] local res = file.getcontents("init.out") if res then rspn[#rspn+1] = '<p>Lua interpreter output:<pre>'..res..'</pre></p>' end end end rspn[#rspn+1] = '</body></html>' socket, htmlTmpl, line, parts = nil collectgarbage("collect") -- FILE UPLOAD if rqst.fname and rqst.totalLen > 0 then sck:on("receive", saveRqst) saveRqst(sck, data:sub(dataPos)) -- NORMAL REQUEST else sendRspn(sck) end end) end) print("Starting main...") do local res, err file.remove("init.out") node.output(function(s) local fd = file.open("init.out", "a+") if fd then fd:write(s) fd:close() end end, 1) if file.exists("main.lc") then res, err = pcall(function() dofile("main.lc") end) else res, err = pcall(function() dofile("main.lua") end) end if not res then print(err) end end Dalszy rozwój Najsłabszymi elementami obecnej wersji KLOSZARDa są plastikowe przekładnie i wały silniczków, które coraz bardziej uginają się pod ciężarem konstrukcji (1,06 kg). Na horyzoncie rozbudowy pojawiła się jednak nadzieja w postaci identycznych silników z przekładnią metalową. Mam nadzieję, że uda się je bezkarnie podmienić 🙂 Drugie miejsce niedoskonałości zajmuje sam w sobie NodeMCU, który cierpi z powodu niedostatku pamięci RAM oraz tendencji do resetowania w przypadku wszelkich problemów zarówno softwarowych jak i hardwarowych. W tym przypadku rozwiązaniem wydaje się przejście na Raspberry Pi Zero (WiFi+BT), czego dodatkowym atutem byłoby całkowite uwolnienie projektowania i debugowania od kabla USB 🙂 Dalsze prace będą ukierunkowane na rozwój oprogramowania (bardziej zaawansowane przetwarzanie danych z modułu zbliżeniowego i jezdnego), a może nawet uda się zaimplementować jakąś skromną formę lokalizowania w przestrzeni.