Skocz do zawartości

Przeszukaj forum

Pokazywanie wyników dla tagów 'DIY robot'.

  • Szukaj wg tagów

    Wpisz tagi, oddzielając przecinkami.
  • Szukaj wg autora

Typ zawartości


Kategorie forum

  • Elektronika i programowanie
    • Elektronika
    • Arduino i ESP
    • Mikrokontrolery
    • Raspberry Pi
    • Inne komputery jednopłytkowe
    • Układy programowalne
    • Programowanie
    • Zasilanie
  • Artykuły, projekty, DIY
    • Artykuły redakcji (blog)
    • Artykuły użytkowników
    • Projekty - DIY
    • Projekty - DIY roboty
    • Projekty - DIY (mini)
    • Projekty - DIY (początkujący)
    • Projekty - DIY w budowie (worklogi)
    • Wiadomości
  • Pozostałe
    • Oprogramowanie CAD
    • Druk 3D
    • Napędy
    • Mechanika
    • Wydarzenia
    • Sprzedam/Kupię/Zamienię/Praca
    • Inne
  • Ogólne
    • Ogłoszenia organizacyjne
    • Dyskusje o FORBOT.pl
    • Na luzie

Kategorie

  • Quizy o elektronice
  • Quizy do kursu elektroniki I
  • Quizy do kursu elektroniki II
  • Quizy do kursów Arduino
  • Quizy do kursu STM32L4
  • Quizy do pozostałych kursów

Szukaj wyników w...

Znajdź wyniki, które zawierają...


Data utworzenia

  • Rozpocznij

    Koniec


Ostatnia aktualizacja

  • Rozpocznij

    Koniec


Filtruj po ilości...

Data dołączenia

  • Rozpocznij

    Koniec


Grupa


Imię


Strona

Znaleziono 3 wyniki

  1. Chciałem przedstawić wam mój projekt samochodziku "PiCar". To pojazd z funkcjami prostej jazdy autonomicznej opartej na wizji komputerowej, oraz zdalnego sterowania padem do konsoli (Xbox) z transmisją na żywo z kamerki pokładowej. Ponadto, ważnym elementem tego projektu jest moja własnoręcznie zaprojektowana płytka PCB, która reguluje napięcie płynące z baterii, zasila RPi 5, steruje silnikami i serwami i monitoruje napięcie baterii. Nazwa wzięła się z wykorzystania Raspberry Pi 5 jako komputera pokładowego. Na zdjęciu widać PiCar'a obok znacznika, który potrafi śledzić. Pod tym linkiem można znaleźć repozytorium tego projektu: https://github.com/ghemyn/picar Można tam zobaczyć pliki do których się odnoszę w tym poscie. Aby ułatwić czytanie, podzieliłem ten post na następujące sekcje: "Hardware - Płytka PCB" "Hardware - Silniki i serwa + koła" "Software - Autonomiczna jazda" "Software - Zdalne sterowanie" Hardware - Płytka PCB Powstała, aby pozbyć się osobno połączonych, niechlujnie wyglądających modułów i zastąpić je jedną dopracowaną płytką PCB. Osiągnęła swój cel ale obstawiam, że powstaną kiedyś kolejne wersje, które robią to lepiej Najważniejsze funkcję opisuję poniżej: Pliki projektowe płytki PCB można znaleźć w repozytorium tego projektu. Mikrokontroler CH32V003 jest głównym kontrolerem całej płytki PCB. Odpowiada za sterowanie sterownikami silników, generowanie sygnałów do serw i monitorowania napięcia baterii. Komunikuje się z Raspberry Pi 5 za pomocą magistrali I2C. Firmware jest napisany w C (w repo: /pwm_board/firmware/CH32V003A4M6), chociaż wersja znajdująca się na repozytorium jeszcze nie jest w pełni kompatybilna z tą nową płytką PCB. Przedtem CH32V003 było na innej płytce odpowiedzialnej tylko za sterowanie serwami. Napięcie wejściowe z baterii jest najpierw filtrowane przez kondensator low-ESR oraz ograniczane do 15 V za pomocą diody TVS SMBJ15A. Następnie układ MP2338 skutecznie obniża napięcie z zakresu 9–12,6 V do około 5,15 V, które jest podawane prosto do Raspberry Pi 5 poprzez kabel USB-C. Pin GND układu MP2338 jest połączony z szerokimi ścieżkami miedzi oraz przelotkami, żeby poprawić odprowadzanie ciepła z tak małego układu. Układ TB6612FNG jest wykorzystywany do sterowania oboma głównymi silnikami. Logika zasilania jest sterowana sygnałami PWM generowanymi przez mikrokontroler. Częstotliwość sygnału PWM może być regulowana z software'u o czym opowiadam więcej w sekcji "Hardware - Silniki i serwa". Płytka umożliwia jednoczesne generowanie czterech niezależnych hardware'owych sygnałów PWM. Serwa są zasilane napięciem 4,8 V generowanym przez drugi układ MP2238, skonfigurowany analogicznie do konwertera zasilającego RPi 5 (Oczywiście poza napięciem wyjściowym). Sekcja serw posiada 3-pinowe złącza dla każdego kanału, co ułatwia podpinanie i odpinanie kabli. Zrobiłem zapas kanałów, mimo że wykorzystuję tylko jeden, żeby zapewnić możliwość rozbudowy tego projektu w przyszłości, np. umożliwienia regulacji obrotu kamery w kilku osiach. Hardware - Silniki i serwa + koła Napęd tylnych kół PiCara realizowany jest przez dwa silniki DC N20-BT32 micro 100:1 320RPM - 9V sterowane dwukanałowym mostkiem H TB6122FNG. Umożliwia regulację prędkości za pomocą sygnału PWM generowanego przez wspomniany mikrokontroler CH32V003. Częstotliwość sygnału PWM może być regulowana z software'u, zależnie od tego czy użytkownik woli, żeby silniki były cichsze, czy żeby były mocniejsze. Im niższa częstotliwość, tym silniki pracują wydajniej, ale generują głośniejszy ton powstały przez przełączanie prądu. Skręt przednich kół realizowany jest za pomocą mikro-serwa, które przez prosty układ mechaniczny steruje kątem ich obrotu, dając efekt podobny, jak w samochodzie. Serwo jest sterowane sygnałem PWM o częstotliwości 50Hz również generowanym przez mikrokontroler. Przednie koła są przykręcone krótkimi śrubkami M3 do mocowań, które następnie wczepiają się do łożysk kulkowych, dokładnie tak, jak działają łączenia w LEGO technic. Bolec o odpowiedniej średnicy z przecięciami po bokach wchodzi w otwór łożyska i trzyma się wystarczająco mocno. Tylne koła są nasunięte na wały silników: Układ przedniego koła widziany w Fusion360: Użyte koła to Koła 60x8mm - czarne - Pololu 1420 Software - Autonomiczna jazda Autonomiczna jazda, póki co jest prostym algorytmem wizji komputerowej, który wyszukuje na obrazie z kamerki Raspberry Pi Camera HD v2 8MPx, specjalnego znacznika z trzema kluczowymi kolorami: czerwonym, zielonym i niebieskim. Obraz pochodzący z kamerki, jest najpierw filtrowany bilateralnie (Bilateral Filter), aby pozbyć się drobnych szczegółów i zakłóceń w późniejszym wyszukiwaniu znacznika. Następnie zmieniana jest gamma, żeby zwiększyć kontrast i powstały obraz jest binaryzowany, żeby SimpleBlobDetector z biblioteki OpenCV mógł wykryć trzy koła. Ostatecznie algorytm sprawdza czy koła znajdują się "wewnątrz" potencjalnie znalezionego znacznik - tzn. czy nie są przesunięte od uśrednionego środka o więcej niż o odchylenie standardowe. Jeśli nie są, to mamy pewność, że znacznik został znaleziony. Poniżej znajduje się wizualizacja, jak znacznik jest widziany przez algorytm: Kod jest dostępny na githubie Gdy znacznik jest znaleziony, pojazd jedzie do przodu i skręca tak, żeby środek znacznika znalazł się na środku widoku kamery. To w zupełności wystarczyło, aby efektywnie śledzić taki znacznik. Mam plan w przyszłości wykorzystać model wizji komputerowej "DepthAnythingV2", który oszacowuje głębię korzystając tylko z jednej kamerki w połączeniu z jednym czujnikiem ToF. Dzięki takiemu połączeniu będę w stanie uzyskać przybliżone (wystarczająco dokładne) informacje o odległości wszystkiego, co jest widoczne na przedniej kamerce. Te dane będę mógł wykorzystać do wykrywania kształtów po konturach i autonomicznego omijania przeszkód. Software - Zdalne sterowanie PiCar może być sterowany przez prosty interfejs webowy oparty na WebSocketach (interfejs znajduje się w repo: /frontend/index.html). Dostępne są dwa suwaki, jednak lepszą (i zdecydowanie fajniejszą) metodą sterowania jest gamepad (na przykład do XBox'a One), który strona wykrywa automatycznie. Przyspieszanie i hamowanie odbywa się za pomocą triggerów, a skręcanie lewym drążkiem analogowym. Interfejs wyświetla też obraz na żywo z kamery podłączonej do Raspberry Pi 5 z bardzo małym opóźnieniem (nie więcej niż 20ms). Strumień wideo jest udostępniony przez WebRTC, np. przy użyciu MediaMTX. Używany przeze mnie plik konfiguracyjny znajduje się w /car/mediamtx.yml. Po uruchomieniu transmisja z MediaMTX jest dostępna pod adresem ip raspberry_pi_5_ip:8889/rpi. Interfejs wygląda tak: Niezbyt atrakcyjny... Wiem. Podsumowanie Projekt nie jest w swojej finalnej postaci i obecnie jest w trakcie sporej aktualizacji jeśli chodzi o płytkę PCB, ale ciężko powiedzieć czy kiedykolwiek będzie gotowy bo zawsze będzie co poprawić Pliki ramy wydrukowanej na drukarce 3D, projekty płytek PCB, oraz kod można znaleźć na githubie w repozytorium projektu: https://github.com/ghemyn/picar/
  2. Cześć, pasjonaci elektroniki i robotyki! Chciałbym się z Wami podzielić moim najnowszym projektem DIY, który ożywił moją fascynację mechanicznymi konstrukcjami – SpyderBotem. To czteronożny robot, którym steruję bezprzewodowo za pomocą Raspberry Pi Pico 2 W, wykorzystując przeglądarkę internetową, a nawet podłączony kontroler do gier retro SNES! Pomysł narodził się z chęci stworzenia mobilnej platformy, którą mógłbym kontrolować intuicyjnie, jednocześnie pogłębiając wiedzę z zakresu programowania mikrokontrolerów i komunikacji sieciowej. Chociaż droga do w pełni działającego pająka była pełna wyzwań, satysfakcja z każdego postępu była ogromna. Wiele kluczowych komponentów, które posłużyły mi w tym projekcie, znalazłem w sklepie Botland.pl, co znacząco ułatwiło budowę. [Potrzebne elementy] Oto lista komponentów, które były niezbędne do zbudowania mojego SpyderBota. Jest to mieszanka specjalistycznych modułów i bardziej ogólnych części, które udało mi się zebrać. > 2 x DFRobot Pająk KIT - zestaw montażowy: To one stanowiły bazę dla ruchomych nóg i korpusu pająka. W skład zestawów wchodzą również 2 x silniki DC, które napędzają ruch. Zestawy są świetnie przemyślane, a instrukcja montażu prosta i klarowna, co pozwoliło mi szybko złożyć mechaniczne części. > 1 x Pololu Dwukanałowy Sterownik Silników DRV8835: Niezastąpiony do precyzyjnego sterowania dwoma silnikami DC. Jego niewielkie rozmiary i wydajność idealnie pasowały do kompaktowej budowy robota. > 1 x Przetwornica Step-Up U3V16F5 - 5V 2A - Pololu 4941: Absolutnie kluczowa dla zapewnienia stabilnego zasilania 5V dla Raspberry Pi Pico W i pozostałej elektroniki, bezpośrednio z akumulatora Li-ion. Bez niej, napięcie z baterii byłoby zbyt niskie. > 1 x Ładowarka Li-Pol TP4056 (pojedyncza cela 1S 3,7V microUSB z zabezpieczeniami): Prosta i skuteczna do ładowania baterii Li-ion. > 1 x Kontroler do gier retro SNES - fioletowe przyciski: Wykorzystany do intuicyjnego sterowania robotem. > 4 x Tuleja dystansowa mosiężna - 25mm > 4 x Tuleja dystansowa mosiężna - 30mm (pochodzące z zestawu podwozia 4WD): Wykorzystałem je do stworzenia stabilnej konstrukcji, montując płytki PCB na odpowiedniej wysokości. > Goldpin wtyk i gniazdo: Do wygodnego łączenia modułów. > 1 x Płytka stykowa - 170 otworów biała: Niezastąpiona na etapie prototypowania. > 2 x Czerwone diody LED justPi: Do sygnalizacji statusu pracy, np. połączenia Wi-Fi. > 1 x Koszyk na baterie (4xAA): Główne źródło zasilania dla 2 silników DC. > 1 x Obudowa od iPhone 5: Kreatywne wykorzystanie do stworzenia korpusu/miejsca na elektronikę robota. > 1 x Bateria Li-ion (pozyskana z iPhone 5): Główne źródło zasilania dla Raspberry Pi Pico W. > Śrubki M3 6mm: Do mocowania wszystkich elementów. > Raspberry Pi Pico 2 W: Mózg robota, odpowiedzialny za komunikację Wi-Fi i sterowanie silnikami. [Budowa mechaniczna i montaż elektroniki] Budowę SpyderBota rozpocząłem od mechanicznego złożenia nóg i ramy pająka, zgodnie z instrukcją zestawów DFRobot. Kluczowym krokiem było zaadaptowanie obudowy iPhone'a 5 – stała się ona idealnym, lekkim korpusem, w którym zamknąłem całą elektronikę. Na górnej części obudowy zamontowałem Raspberry Pi Pico W oraz przetwornicę step-up i ładowarkę TP4056. Sterownik silników DRV8835 znalazł swoje miejsce na spodzie, bliżej silników, co zminimalizowało długość przewodów. Tuleje dystansowe zapewniły odpowiednie oddzielenie warstw i sztywność konstrukcji. Całość jest dość kompaktowa i estetyczna, jak na robota DIY! [Schemat połączeń i oprogramowanie] Sercem SpyderBota jest Raspberry Pi Pico W, który pełni rolę serwera WWW. Dzięki niemu, robot jest dostępny poprzez sieć Wi-Fi, co umożliwia sterowanie z dowolnego urządzenia z przeglądarką. Pico W jest zasilane z baterii Li-ion poprzez przetwornicę step-up, gwarantującą stabilne 5V. Dwa silniki DC są podłączone do sterownika DRV8835, który z kolei jest zasilany niezależnie z koszyka na baterie AA. Po wielu testach ustaliłem, że Motor A to silnik prawy, natomiast Motor B to silnik lewy. To bardzo ważne dla prawidłowego mapowania kierunków ruchu robota. Dioda LED na Pico W służy do sygnalizacji statusu połączenia Wi-Fi – miga podczas próby połączenia, a świeci światłem ciągłym po pomyślnym nawiązaniu. Oprogramowanie napisane w MicroPythonie obsługuje zapytania HTTP z przeglądarki, przekładając je na odpowiednie komendy dla silników (np. /start_forward_fast czy /stop). [Testy, debugowanie i sterowanie gamepadem] Najbardziej ekscytującą częścią projektu, a zarazem źródłem największych wyzwań, było zaimplementowanie sterowania za pomocą kontrolera gier retro SNES poprzez Gamepad API w przeglądarce. Początkowe mapowanie przycisków okazało się sporym orzechem do gryzienia, ponieważ indeksy przycisków Gamepad API mogą różnić się w zależności od producenta i modelu kontrolera. Wiele godzin spędziłem na debugowaniu, naciskając każdy przycisk i obserwując reakcję robota oraz logi w konsoli Thonny. Dzięki temu udało mi się ustalić następujące, potwierdzone i działające mapowania dla mojego kontrolera SNES-style: > Przycisk X (Indeks 0): Robot porusza się do przodu z dużą prędkością. > Przycisk A (Indeks 1): Robot porusza się do tyłu z dużą prędkością. > Przycisk B (Indeks 2): Prawy silnik (Motor A) porusza się do przodu. > Przycisk Y (Indeks 3): Robot wykonuje skręt w lewo. > Przycisk L1 (Indeks 4): Robot wykonuje skręt w prawo. > Przycisk Select (Indeks 8): Lewy silnik (Motor B) porusza się do tyłu. > Przycisk Start (Indeks 9): Robot porusza się do przodu (zapewne z domyślną prędkością ustawioną dla komendy /start_forward_fast). > Przycisk X (Indeks 14): Robot ZATRZYMUJE WSZYSTKIE ruchy. Ciekawostka: Okazało się, że mój kontroler raportuje przycisk "X" pod dwoma różnymi indeksami (0 i 14)! Musiałem dostosować logikę w kodzie JavaScript, aby indeks 14 zawsze wywoływał zatrzymanie, dając mu priorytet nad ruchem do przodu. To typowy przykład problemu, który można rozwiązać tylko przez praktyczne testowanie. Niestety, niektóre przyciski takie jak "R", "R1", "L" (jeśli jest inny niż L1) oraz przyciski kierunkowe (D-pad) nie wywołały żadnej reakcji na indeksach testowych (5, 6, 7, 10-13, 15-17). Możliwe, że mój kontroler ich nie raportuje, lub ich indeksy są poza standardowym zakresem. Dalsze eksperymenty z osiami analogowymi kontrolera (jeśli są dostępne) to kolejny krok do pełniejszej kontroli. # SPYDER-BOT BEST WORKING SCRIPT (GAMEPAD CONTROLS NEED TO BE CONFIGURED PROPERLY AND DEBUGGED) import network import time import rp2 import sys from machine import Pin, PWM import socket # --- Wi-Fi Credentials --- try: from secrets import SSID, PASSWORD, COUNTRY except ImportError: print("Create a 'secrets.py' file with SSID, PASSWORD, and COUNTRY.") sys.exit() # --- Broadcast IP address --- def broadcast_ip(ip_address): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.sendto(f"PICO_IP:{ip_address}".encode(), ('255.255.255.255', 5005)) s.close() # --- LED Feedback --- led = Pin('LED', Pin.OUT) # --- Motor Pin Setup --- MOTOR_A_AIN1_PIN = 20 MOTOR_A_AIN2_PIN = 21 MOTOR_B_BIN1_PIN = 7 MOTOR_B_BIN2_PIN = 6 motor_a_ain1 = Pin(MOTOR_A_AIN1_PIN, Pin.OUT) pwm_a_ain2 = PWM(Pin(MOTOR_A_AIN2_PIN)) pwm_a_ain2.freq(6000) motor_b_bin1 = Pin(MOTOR_B_BIN1_PIN, Pin.OUT) pwm_b_bin2 = PWM(Pin(MOTOR_B_BIN2_PIN)) pwm_b_bin2.freq(6000) MOTOR_A_IS_REVERSED = False MOTOR_B_IS_REVERSED = True # --- SPEED CONSTANTS --- LOWEST_CRAWL_SPEED = 41100 SLOW_SPEED = 48200 MEDIUM_SPEED = 57500 FAST_SPEED = 65535 # --- CALIBRATION --- MOTOR_A_CALIBRATION_FORWARD = 1.05 MOTOR_A_CALIBRATION_REVERSE = 0.91 MOTOR_B_CALIBRATION_FORWARD = 1.05 MOTOR_B_CALIBRATION_REVERSE = 0.91 def run_motor(name, fixed_pin, pwm_pin, direction, speed): calibrated_speed = speed if name == "A": if direction == "forward": calibrated_speed = int(speed * MOTOR_A_CALIBRATION_FORWARD) elif direction == "reverse": calibrated_speed = int(speed * MOTOR_A_CALIBRATION_REVERSE) elif name == "B": if direction == "forward": calibrated_speed = int(speed * MOTOR_B_CALIBRATION_FORWARD) elif direction == "reverse": calibrated_speed = int(speed * MOTOR_B_CALIBRATION_REVERSE) calibrated_speed = max(0, min(65535, calibrated_speed)) if direction == "forward": fixed_pin.value(0) pwm_pin.duty_u16(calibrated_speed) elif direction == "reverse": fixed_pin.value(1) pwm_pin.duty_u16(65535 - calibrated_speed) elif direction == "brake": fixed_pin.value(1) pwm_pin.duty_u16(65535) else: # coast fixed_pin.value(0) pwm_pin.duty_u16(0) def stop_all(): run_motor("A", motor_a_ain1, pwm_a_ain2, "coast", 0) run_motor("B", motor_b_bin1, pwm_b_bin2, "coast", 0) # --- CONTINUOUS MOVEMENT --- def start_walk_forward(speed): dir_a = "reverse" if not MOTOR_A_IS_REVERSED else "forward" dir_b = "reverse" if not MOTOR_B_IS_REVERSED else "forward" run_motor("A", motor_a_ain1, pwm_a_ain2, dir_a, speed) run_motor("B", motor_b_bin1, pwm_b_bin2, dir_b, speed) def start_walk_reverse(speed): dir_a = "forward" if not MOTOR_A_IS_REVERSED else "reverse" dir_b = "forward" if not MOTOR_B_IS_REVERSED else "reverse" run_motor("A", motor_a_ain1, pwm_a_ain2, dir_a, speed) run_motor("B", motor_b_bin1, pwm_b_bin2, dir_b, speed) def start_turn_left(speed): # To turn left continuously: motor A backward, motor B forward dir_a = "reverse" if not MOTOR_A_IS_REVERSED else "forward" dir_b = "forward" if not MOTOR_B_IS_REVERSED else "reverse" run_motor("A", motor_a_ain1, pwm_a_ain2, dir_a, speed) run_motor("B", motor_b_bin1, pwm_b_bin2, dir_b, speed) def start_turn_right(speed): # To turn right continuously: motor A forward, motor B backward dir_a = "forward" if not MOTOR_A_IS_REVERSED else "reverse" dir_b = "reverse" if not MOTOR_B_IS_REVERSED else "forward" run_motor("A", motor_a_ain1, pwm_a_ain2, dir_a, speed) run_motor("B", motor_b_bin1, pwm_b_bin2, dir_b, speed) def connect_wifi(ssid, password, country): wlan = network.WLAN(network.STA_IF) wlan.active(True) rp2.country(country) wlan.connect(ssid, password) for _ in range(30): if wlan.isconnected(): break led.toggle() time.sleep(1) if wlan.isconnected(): led.value(1) return wlan.ifconfig()[0] led.value(0) return None def run_web_server(ip): addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] s = socket.socket() s.bind(addr) s.listen(1) print(f"Web server running at http://{ip}") while True: cl, addr = s.accept() request = cl.recv(1024).decode() print(f"Request: {request}") # Movement commands if "/start_forward_slow" in request: start_walk_forward(SLOW_SPEED) cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) elif "/start_forward_medium" in request: start_walk_forward(MEDIUM_SPEED) cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) elif "/start_forward_fast" in request: start_walk_forward(FAST_SPEED) cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) elif "/start_reverse_slow" in request: start_walk_reverse(SLOW_SPEED) cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) elif "/start_reverse_medium" in request: start_walk_reverse(MEDIUM_SPEED) cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) elif "/start_reverse_fast" in request: start_walk_reverse(FAST_SPEED) cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) elif "/stop" in request: stop_all() cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) # Continuous turn (hold) elif "/start_turn_left" in request: start_turn_left(FAST_SPEED) cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) elif "/start_turn_right" in request: start_turn_right(FAST_SPEED) cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) # Single motor debug elif "/motor_a_forward" in request: run_motor("A", motor_a_ain1, pwm_a_ain2, "reverse", SLOW_SPEED) run_motor("B", motor_b_bin1, pwm_b_bin2, "coast", 0) cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) elif "/motor_a_reverse" in request: run_motor("A", motor_a_ain1, pwm_a_ain2, "forward", SLOW_SPEED) run_motor("B", motor_b_bin1, pwm_b_bin2, "coast", 0) cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) elif "/motor_b_forward" in request: run_motor("B", motor_b_bin1, pwm_b_bin2, "forward", SLOW_SPEED) run_motor("A", motor_a_ain1, pwm_a_ain2, "coast", 0) cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) elif "/motor_b_reverse" in request: run_motor("B", motor_b_bin1, pwm_b_bin2, "reverse", SLOW_SPEED) run_motor("A", motor_a_ain1, pwm_a_ain2, "coast", 0) cl.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK".encode()) else: # Serve the control page HTML (same as before, just removed burst buttons) response_html = """\ HTTP/1.1 200 OK Content-Type: text/html <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>SpyderBot Control</title> <style> body { font-family: sans-serif; text-align: center; margin: 20px; background-color: #f0f0f0; } h1 { color: #333; } h2 { color: #555; margin-top: 30px; margin-bottom: 10px; } .button-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-bottom: 25px; } button { flex: 1 0 130px; max-width: 160px; padding: 15px 20px; font-size: 1.1em; cursor: pointer; border: none; border-radius: 8px; transition: background-color 0.3s; user-select: none; } button:active { filter: brightness(0.85); } .move-btn { background-color: #007bff; color: white; } .move-btn:hover { background-color: #0056b3; } .turn-btn { background-color: #ffc107; color: #333; } .turn-btn:hover { background-color: #e0a800; } .stop-btn { background-color: #dc3545; color: white; max-width: 320px; } .stop-btn:hover { background-color: #c82333; } a { text-decoration: none; } </style> </head> <body> <h1> SpyderBot Remote Control</h1> <h2>Debug: Single Motor Test</h2> <div class="button-row"> <a href="/motor_a_forward"><button class="move-btn">Motor A Forward</button></a> <a href="/motor_a_reverse"><button class="move-btn">Motor A Backward</button></a> <a href="/motor_b_forward"><button class="move-btn">Motor B Forward</button></a> <a href="/motor_b_reverse"><button class="move-btn">Motor B Backward</button></a> </div> <h2>Move Forward (Hold to move)</h2> <div class="button-row"> <button class="move-btn" onmousedown="startMove('forward_slow')" onmouseup="stopMove()" ontouchstart="startMove('forward_slow')" ontouchend="stopMove()">Slow</button> <button class="move-btn" onmousedown="startMove('forward_medium')" onmouseup="stopMove()" ontouchstart="startMove('forward_medium')" ontouchend="stopMove()">Medium</button> <button class="move-btn" onmousedown="startMove('forward_fast')" onmouseup="stopMove()" ontouchstart="startMove('forward_fast')" ontouchend="stopMove()">Fast</button> </div> <h2>Move Backward (Hold to move)</h2> <div class="button-row"> <button class="move-btn" onmousedown="startMove('reverse_slow')" onmouseup="stopMove()" ontouchstart="startMove('reverse_slow')" ontouchend="stopMove()">Slow</button> <button class="move-btn" onmousedown="startMove('reverse_medium')" onmouseup="stopMove()" ontouchstart="startMove('reverse_medium')" ontouchend="stopMove()">Medium</button> <button class="move-btn" onmousedown="startMove('reverse_fast')" onmouseup="stopMove()" ontouchstart="startMove('reverse_fast')" ontouchend="stopMove()">Fast</button> </div> <h2>Hold to Turn</h2> <div class="button-row"> <button class="turn-btn" id="hold-left">⟲ Hold Left</button> <button class="turn-btn" id="hold-right">⟳ Hold Right</button> </div> <p><a href="/stop"><button class="stop-btn">STOP ALL MOTION</button></a></p> <p>Note: Hold Forward/Backward buttons to move continuously. Release to stop.<br>Hold turn buttons to turn continuously.</p> <script> function startMove(speed) { fetch('/start_' + speed); } function stopMove() { fetch('/stop'); } // Hold-to-turn logic const leftBtn = document.getElementById("hold-left"); const rightBtn = document.getElementById("hold-right"); function startTurn(direction) { fetch('/start_turn_' + direction); } function stopTurn() { fetch('/stop'); } // Mouse controls leftBtn.addEventListener("mousedown", () => startTurn('left')); leftBtn.addEventListener("mouseup", stopTurn); rightBtn.addEventListener("mousedown", () => startTurn('right')); rightBtn.addEventListener("mouseup", stopTurn); // Touch controls (mobile) leftBtn.addEventListener("touchstart", (e) => { e.preventDefault(); startTurn('left'); }); leftBtn.addEventListener("touchend", (e) => { e.preventDefault(); stopTurn(); }); rightBtn.addEventListener("touchstart", (e) => { e.preventDefault(); startTurn('right'); }); rightBtn.addEventListener("touchend", (e) => { e.preventDefault(); stopTurn(); }); // Gamepad API integration window.addEventListener("gamepadconnected", (e) => { console.log("Gamepad connected:", e.gamepad); }); // SNES button mapping (adjust for your specific gamepad if needed) const buttonMap = { 2: () => fetch("/start_forward_fast"), // B button 1: () => fetch("/start_reverse_fast"), // A button 4: () => fetch("/start_turn_left"), // L1 button 0: () => fetch("/start_turn_right"), // X button 9: () => fetch("/stop"), // 'Start' button button → STOP ALL }; # // working controls # //[0 = X button], [1 = A button], [2 = B button], [3 = Y button], [4 = L1], [8 = 'Select' button], [9 = 'Start' button], [14 = X button (dual with 0)] const activeButtons = new Set(); // To track currently pressed buttons function pollGamepad() { const gp = navigator.getGamepads()[0]; // Get the first connected gamepad if (gp) { gp.buttons.forEach((btn, i) => { const isPressed = btn.pressed; const wasPressed = activeButtons.has(i); if (isPressed && !wasPressed) { activeButtons.add(i); if (buttonMap[i]) { buttonMap[i](); // Execute the mapped function } } else if (!isPressed && wasPressed) { activeButtons.delete(i); // Stop motion on release for continuous movement buttons if ([0, 1, 14, 15].includes(i)) { fetch("/stop"); } } }); } requestAnimationFrame(pollGamepad); // Continue polling } pollGamepad(); // Start the gamepad polling loop </script> </body> </html> """ cl.send(response_html.encode()) cl.close() # --- MAIN --- if __name__ == '__main__': ip = connect_wifi(SSID, PASSWORD, COUNTRY) if ip: print(f"Connected! IP: {ip}") broadcast_ip(ip) run_web_server(ip) else: print("Failed to connect to Wi-Fi.") [Podsumowanie i dalsze plany] SpyderBot to dla mnie dowód na to, że nawet z prostych komponentów można stworzyć zaawansowanego robota, a nauka przez praktykę jest najskuteczniejsza. Rozwiązanie problemów z mapowaniem gamepada było frustrujące, ale ostatecznie bardzo pouczające. Najlepszą częścią tego projektu (i wielu innych potencjalnych robotów, podwozi, czy elementów dostępnych w Botland.pl) jest to, że nie podąża się za sztywną instrukcją, budując robota niczym meble z Ikei, gdzie każdy egzemplarz wygląda identycznie. Wręcz przeciwnie! Istnieje ogromna swoboda i pole do popisu dla kreatywności, by stworzyć całkowicie spersonalizowanego robota. Ta właśnie możliwość modyfikacji, adaptacji i tworzenia czegoś własnego stanowi zarówno część wyzwania, jak i ogromną część zabawy – to właśnie w niej tkwi prawdziwa wartość i przyjemność z majsterkowania! W przyszłości planuję rozbudować robota o dodatkowe funkcje, takie jak detekcja przeszkód za pomocą ultradźwięków, sterowanie prędkością proporcjonalne do wychylenia gałek analogowych na gamepadzie, a może nawet transmisję wideo! Mam nadzieję, że mój projekt zainspiruje Was do tworzenia własnych, unikalnych konstrukcji. Pamiętajcie, że nawet jeśli nie wszystko pójdzie gładko od razu, to wytrwałość w debugowaniu przynosi największą satysfakcję! Szczególne podziękowania dla Botland.pl! Bez szerokiego wyboru komponentów, które znalazłem w Waszym sklepie, ten projekt nie byłby możliwy do zrealizowania. Dziękuję za wsparcie społeczności DIY!
  3. Cześć, w tym roku bardzo wcześnie zabrałem się za kupno prezentów świątecznych dla rodziny - tak mam już kupione wszystkie prezenty (szczególnie duzo dla wnuczki). Aby przeciwdziałać wypaleniu zawodowemu postanowiłem postanowiłem zrobić także sobie prezent. Mój wybór padł na robota balansującego ponieważ od dawna chodził mi po głowie pomysł budowy takiego robota. Części postanowiłem kupić na AliExpress, aby nie wyszedł za drogi. Nabyłem następujące podzespoły: 1) Podwozie z silnikami: https://pl.aliexpress.com/item/1005006776833931.html?spm=a2g0o.order_list.order_list_main.100.65991c24Nn77Nu&gatewayAdapt=glo2pol 2) Sterownik silników L298N: https://pl.aliexpress.com/item/1005004521836030.html?spm=a2g0o.order_list.order_list_main.41.65991c24Nn77Nu&gatewayAdapt=glo2pol 3) MPU 6050 (akcelometr i gyro): https://pl.aliexpress.com/item/1005006995849520.html?spm=a2g0o.order_list.order_list_main.94.65991c24Nn77Nu&gatewayAdapt=glo2pol 4) Arduino UNO R3 (chiński klon) miałem w szufladzie 5) Ultradźwiękowy czujnik odległości HC-SR04 (miałem razem z mocowaniem) 6) Serwomechanizm dwu-osiowy do zamocowania czujnika ultradźwiękowego i kamery (na razie nie obsługiwany) 7) Kamera - na razie nie obsługiwana 7) Pojemnik na dwa ogniwa i ogniwa 18650 (dwie sztuki) oraz kawałki pleksy, kabelki,śrubki, kołki dystansowe także miałem W ciagu jednego dnia złożyłem tak wyglądającego robota: Część podzespołow na razie nie jest podłączona i obsługiwana - mam zamiar to zrobić w nastepnym etapie. Nie są obsługiwane: 1) serwomechanizm z zamocowaną kamerą i czujnikiem odległości 2) kamera Na obudowie robota jest miejsce na płytkę z mikrokontrolerem ESP32 do którego mam zamiar podłączyć kamerę (na ESP32 można uruchomić bibliotekę OpenCV) i zrobić prostą obróbkę obrazu. Jeśli chodzi o oprogramowanie (dla płytki Arduino UNO R3) to korzystałem z tego repozytorium na Githubie: https://github.com/rppelayo/arduino-self-balancing-robot/tree/master W tym repozytorium oprócz samego szkicu są także pliki potrzebnych bibliotek. Biblioteki te należy skopiować do katalogu "libraries" naszej wersji "Arduino IDE". I tak należy utworzyć w "libraries" podkatalogi: 1) "MPU6050" - wrzucamy pliki nagłówkowe i żródła C z MPU6050 w nazwie 2) "I2Cdev" - wrzucamy pliki: I2Cdev.cpp i I2Cdev.h 3) "LMotorController" - wrzucamy pliki: LMotorController.h i LMotorController.cpp 4) "PID_v1" - wrzucamy pliki PID_v1.h i PID_v1.cpp Robot jest zasilany z dwóch ogniw 18650 połączonych szeregowo co daje napięcie około 7,4 V (napięcie to jest także podawane na gniazdko zasilające Arduino UNO R3). Napięcie 7,4 V jest także podawane na zasilanie sterownika silników L298N. Silniki są podłączone do wyjść sterownika L298N. Pinologia podłączeń jest następująca: 1) Sterownik silników L298N: "ENA"- pin 9 Arduino, "ENB"-10, "IN1"-3, "IN2"-4, "IN3"-5,"IN4"-6 Pod tym linkiem można sprawdzić jak są rozmieszczone wyprowadzenia na płytce kontrolera silników: https://projecthub.arduino.cc/lakshyajhalani56/l298n-motor-driver-arduino-motors-motor-driver-l298n-7e1b3b a w następnym linku opis sterownika: https://botland.com.pl/sterowniki-silnikow-moduly/8227-dwukanalowy-sterownik-silnikow-l298n-modul-wb291111-iduino-st1112-5903351241236.html?cd=18298825651&ad=&kd=&gad_source=1&gclid=CjwKCAiA6aW6BhBqEiwA6KzDc54vIlaPMLV-OkuUD1sdPH5eRLeIe25a7LllSrCWE2T_prWmjEVndxoCdS0QAvD_BwE Dla modułu MPU6050 podłączenia do Arduino UNO są następujące: "SCL" z płytki MPU do pinu SCL Arduino "SDA" z lytki MPU do pinu SDA Arduino "INT" z płytki MPU -do pin 2 Arduino "VCC" z płytki MPU do 5V Arduino GND MPU do GND Arduino Pod linkiem poniżej opis MPU6050: https://circuitdigest.com/microcontroller-projects/interfacing-mpu6050-module-with-arduino Dla czujnika ultradźwiękowego podłączenia są następujące: "VCC" do 5V Arduino GND do GND "Trig" do pinu 12 Arduino "Echo" do pinu 11 Arduino Miałem trochę problemów z uruchomieniem oprogramowania: 1) Na początku nie podłączyłem pinu "INT" z płytki MPU6050 do Arduino UNO, a program wykorzystywał odbiór danych z MPU właśnie za pomocą przerwania 2) Jak już podłączyłem pin "INT" do Arduino to generacja przerwania zewnętrznego nie działała - było to spowodowane faktem, że oryginalny kod był pisany dla Arduino Leonardo i tam pin 0 obsługiwał przerwanie zewnętrzne. Dla Arduino UNO tylko piny numer 2 i 3 obsługują zewnętrzne przerwania. Po podłączerniu pinu "INT" do pinu 2 Arduino UNO i zmianie kodyu w szkicu odbiór danych z MPU zaczął działać poprawnie 3) Okazało się, że kąt odchyłki od pionu był liczony w innej płaszczyźnie w MPU niż myślałem, po obróceniu płytki MPU na obudowie o 90 stopni zaczęło to działać poprawnie: 4) ostatnia rzecz musiałem dla drugiego silnika zamienić miejscami piny sterujące IN3 i IN4, aby silnik kręcił się w dobrą stronę Poniżej zmodyfikowany przeze mnie kod programu, który mi zadziałał poprawnie: #include <PID_v1.h> #include <LMotorController.h> #include "I2Cdev.h" #include "MPU6050_6Axis_MotionApps20.h" #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE #include "Wire.h" #endif #define MIN_ABS_SPEED 20 #define LOG_INPUT 1 MPU6050 mpu; // MPU control/status vars bool dmpReady = false; // set true if DMP init was successful uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU uint8_t devStatus; // return status after each device operation (0 = success, !0 = error) uint16_t packetSize; // expected DMP packet size (default is 42 bytes) uint16_t fifoCount; // count of all bytes currently in FIFO uint8_t fifoBuffer[64]; // FIFO storage buffer // orientation/motion vars Quaternion q; // [w, x, y, z] quaternion container VectorFloat gravity; // [x, y, z] gravity vector float ypr[3]; // [yaw, pitch, roll] yaw/pitch/roll container and gravity vector //PID double originalSetpoint = 175.8; double setpoint = originalSetpoint; double movingAngleOffset = 0.1; double input, output; int moveState=0; //0 = balance; 1 = back; 2 = forth double Kp = 50; double Kd = 1.4; double Ki = 60; PID pid(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT); #define interruptPin 2 double motorSpeedFactorLeft = 0.7; double motorSpeedFactorRight = 0.7; //MOTOR CONTROLLER int ENA = 9; int IN1 = 3; int IN2 = 4; int IN3 = 5; int IN4 = 6; int ENB = 10; //LMotorController motorController(ENA, IN1, IN2, ENB, IN3, IN4, motorSpeedFactorLeft, motorSpeedFactorRight); LMotorController motorController(ENA, IN1, IN2, ENB, IN4, IN3, motorSpeedFactorLeft, motorSpeedFactorRight); //timers long time1Hz = 0; long time5Hz = 0; volatile bool mpuInterrupt = false; // indicates whether MPU interrupt pin has gone high void dmpDataReady() { mpuInterrupt = true; } void setup() { // join I2C bus (I2Cdev library doesn't do this automatically) #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE Wire.begin(); TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz) #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE Fastwire::setup(400, true); #endif pinMode(interruptPin, INPUT_PULLUP); // initialize serial communication // (115200 chosen because it is required for Teapot Demo output, but it's // really up to you depending on your project) Serial.begin(115200); while (!Serial); // wait for Leonardo enumeration, others continue immediately // initialize device Serial.println(F("Initializing I2C devices...")); mpu.initialize(); // verify connection Serial.println(F("Testing device connections...")); Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed")); // load and configure the DMP Serial.println(F("Initializing DMP...")); devStatus = mpu.dmpInitialize(); // supply your own gyro offsets here, scaled for min sensitivity mpu.setXGyroOffset(220); mpu.setYGyroOffset(76); mpu.setZGyroOffset(-85); mpu.setZAccelOffset(1788); // 1688 factory default for my test chip // make sure it worked (returns 0 if so) if (devStatus == 0) { // turn on the DMP, now that it's ready Serial.println(F("Enabling DMP...")); mpu.setDMPEnabled(true); // enable Arduino interrupt detection Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)...")); //attachInterrupt(0, dmpDataReady, RISING); attachInterrupt(digitalPinToInterrupt(interruptPin), dmpDataReady, RISING); mpuIntStatus = mpu.getIntStatus(); // set our DMP Ready flag so the main loop() function knows it's okay to use it Serial.println(F("DMP ready! Waiting for first interrupt...")); dmpReady = true; // get expected DMP packet size for later comparison packetSize = mpu.dmpGetFIFOPacketSize(); //setup PID pid.SetMode(AUTOMATIC); pid.SetSampleTime(10); pid.SetOutputLimits(-255, 255); } else { // ERROR! // 1 = initial memory load failed // 2 = DMP configuration updates failed // (if it's going to break, usually the code will be 1) Serial.print(F("DMP Initialization failed (code ")); Serial.print(devStatus); Serial.println(F(")")); } } void loop() { // if programming failed, don't try to do anything if (!dmpReady) return; // wait for MPU interrupt or extra packet(s) available while (!mpuInterrupt && fifoCount < packetSize) { //no mpu data - performing PID calculations and output to motors pid.Compute(); motorController.move(output, MIN_ABS_SPEED); Serial.println(output); } // reset interrupt flag and get INT_STATUS byte mpuInterrupt = false; mpuIntStatus = mpu.getIntStatus(); // get current FIFO count fifoCount = mpu.getFIFOCount(); // check for overflow (this should never happen unless our code is too inefficient) if ((mpuIntStatus & 0x10) || fifoCount == 1024) { // reset so we can continue cleanly mpu.resetFIFO(); Serial.println(F("FIFO overflow!")); // otherwise, check for DMP data ready interrupt (this should happen frequently) } else if (mpuIntStatus & 0x02) { // wait for correct available data length, should be a VERY short wait while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount(); // read a packet from FIFO mpu.getFIFOBytes(fifoBuffer, packetSize); // track FIFO count here in case there is > 1 packet available // (this lets us immediately read more without waiting for an interrupt) fifoCount -= packetSize; mpu.dmpGetQuaternion(&q, fifoBuffer); mpu.dmpGetGravity(&gravity, &q); mpu.dmpGetYawPitchRoll(ypr, &q, &gravity); #if LOG_INPUT // Serial.print("ypr\t"); // Serial.print(ypr[0] * 180/M_PI); // Serial.print("\t"); // Serial.print(ypr[1] * 180/M_PI); // Serial.print("\t"); // Serial.println(ypr[2] * 180/M_PI); #endif input = ypr[1] * 180/M_PI + 180; } } Uwaga! - kod zadziałał poprawnie robot utrzymywał równowagę podczas ruchu, ale musiałem podczepić z tyłu taśmą dwie baterie 18650 jako przeciw wagę dla baterii zasilających. Bez tej przeciw wagi robot nie potrafił utrzymywać równowagi - przechylał się po czasie do przodu, gdzie znajdował się pojemnilk na baterie 18650. Jak wyważę robota poprawnie (dodam regulowaną przeciw wage) i wkomponuje obsługę czujnika odległości HC-SR04, tak, aby robot omijał przeszkody to zrobię film i podam link do niego w tym poście. Ogólnie robot wyszedł dość ciężki i duży, a to nie pomaga w utrzymywaniu równowagi. Pozdrawiam
×
×
  • Utwórz nowe...