Skocz do zawartości

Przeszukaj forum

Pokazywanie wyników dla tagów 'arduino'.

  • 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
    • Zawody/Konkursy/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

  1. Witam, założyłem wcześniej wątek, o tym. Dziękuję @farmaceuta @ethanak @SOYER i innym za tłumaczenie, o co chodziło z else/if'ami, ale nadal nie wiem, kiedy ich użyć. Czy ktoś dałby mi jakieś przykłady, czy coś innego. Nadal nie wiem, w jakich przypadkach ich użyć. Bardzo proszę o wytłumaczenie.
  2. Dobra a więc tak robie swój pierwszy projekt czyli mój gameboy i żeby jakoś zasilić arduino kupiłem baterie lipo 1000mah 3,7v i moduł solar charger shield od seeed naładowałem baterie i podłączyłem moduł do arduino wgrałem kod na gameboya i włączyłem zasilanie a dioda na arduino sie zaswieciła na zielono lecz arduino nie wykonuje kodu czy ktoś może mi coś poradzić?
  3. Witam, czy ktoś mógłby wytłumaczyć mi, o co chodzi z else if'ami? Zastosowałem je w moim robocie, ale nie do końca wiem, czym one się różnią od zwykłych, osobnych if'ów. Chciałbym również się dowiedzieć, kiedy je wykorzystywać. Z góry dzięki 😄
  4. Cześć, zbudowałem robota kroczącego składającego się z 12 serw. 4x serwo typu medium (Power HD HD-1160A z plastikowymi przekładniami) i 8x serwo typu micro (MG-90S z metalowymi przekładniami), serwa zasilane są battery Packiem (4x bateria AA 1,5V) 6V oraz Arduino mega, które zasilam 5V. Po wykonaniu dwóch kroków robot się zapada i serwa medium się wyłączają (serwa umieszczone na ramie robota) a serwa micro działają i wykonują dalsze ruchy nogi. Podejrzewam, że problem leży przy zasilaniu robota, battery pack dodaje masy do robota i może być niezbyt optymalnym źródłem prądu. Po podłączeniu woltomierza, miernik pokazywał ~5V zamiast wymaganego 6V. Drugim problemem mogą być serwera medium, jakich używam, przy 6V ruch obrotowy serwa wynosi 2,7 kg*cm a serwa micro nogi mają 2,2 kg*cm również przy 6V. Po ściągnięciu battery packa, robot działał dobrze do pewnego momentu, gdy się zawalił, serwa przestały być „naprężone” co wynikało by z braku odpowiedniego zasilania. Czy ktoś z Was wie, gdzie może leżeć przyczyna problemu? Z góry dziękuje. Film z działania robota oraz zdjęcie nogi w załącznkiu poniżej:
  5. Od dawna interesowały mnie pomiary warunków meteorologicznych w mojej miejscowości, pierwsza stacja meteorologiczna, którą zbudowałem około roku 2010, wykonana była na mikrokontrolerze Atmega32. Do komunikacji z światem wykorzystywała moduł LAN Wiznet 7010a. Stacja ta była oprogramowana w języku BASCOM. Projekt który chcę zaprezentować dzisiaj działa już od roku 2018 i został oprogramowany w środowisku Arduino. Stacja została podzielona na 2 moduły, pierwszy pomiarowy oparty jest na klonie Arduino Nano oraz drugi odbiorczy którego sercem jest ESP8266 NodeMCU v3, służy on również do wyświetlania aktualnych pomiarów na wyświetlaczu LED dot matrix o wymiarach 8x56 punktów. Na pracach stolarskich się nie będziemy skupiać napiszę tylko że klatka meteorologiczna została wykonana z drewna sosnowego i umieszczona na wysokości 2 m. Moduł Pomiarowy Czujniki jakie zastosowałem to dwie sztuki DS18B20 pierwszy zajmuje się pomiarem temperatury przy gruncie na wysokości 5cm, drugi pełni rolę zapasowego czujnika temperatury na wypadek uszkodzenia się głównego czujnika BME280. Do pomiaru prędkości wiatru wykorzystuję wiatromierz firmy Maplin na jeden obrót wiatromierza przypadają 2 impulsy z kontaktronu który jest w nim zamontowany, producent dostarcza również odpowiedni wzór według którego można obliczyć rpm oraz prędkość wiatru w km/h. Dane mierzone przez wiatromierz możemy podzielić na dwie wartości, pierwsza to chwilowa prędkość, druga prędkość w porywach, aby uśrednić wartości mierzone program zlicza impulsy z 5s a następnie dokonuje odpowiednich obliczeń. Zebrane dane przesyłane są do drugiego urządzenia poprzez moduły radiowe które działają na częstotliwości 433,92 MHz. W tym celu zastosowana została biblioteka RCSwitch. Każda mierzona wartość jest wysyłana jako osobna transmisja. aby rozróżnić pomiary z konkretnych czujników mierzona wartość mnożona jest przez 100 a następnie dodawana jest liczba 100 000 dla pierwszego czujnika, 200 000 dla drugiego itd. Przykład kodu który realizuje tę funkcję poniżej: // temperatura sensor BME codetosend = temp * 100 + (1 * 100000); mySwitch.send(codetosend, 24); // wilgotnosc sensor BME codetosend = hum * 100 + (2 * 100000); mySwitch.send(codetosend, 24); Moduł Wewnętrzny Obudowa, która idealnie nadawała się do implementacji wewnętrznego modułu pochodzi z tunera IPTV Motorola VIP1910-9. Przedni panel został wykonany z ciemnego półprzepuszczalnego plastiku który idealnie nadaje się do umieszczenia w nim wyświetlacza. Sercem urządzenia jest układ ESP8266. "Moduł wewnętrzny" został również wyposażony w czujnik temperatury oraz wilgotności DHT22, dodatkowo w celu prezentacji zmierzonych wartości dołączone zostało 7 szt. modułów wyświetlacza LED dot matrix z układem MAX7219. Do obsługi tej matrycy zastosowałem bibliotekę Max72xxPanel.h która współpracuje z biblioteką Adafruit_GFX.h w ten sposób nie byłem zmuszony implementować do rozwiązania własnych czcionek. Matryca ta oprócz modułowej konstrukcji umożliwia również sterowaniem jasnością podświetlania, w tym celu aby uprzyjemnić użytkowanie w porach nocnych odbiornik został wyposażony w fotorezystor dzięki któremu potrafi określić natężenie oświetlenia otoczenia i odpowiednie ustawienie podświetlenia. Na wyświetlaczu w pierwszej kolejności wyświetlam aktualną godzinę oraz temperaturę wewnątrz pomieszczenia oraz wilgotność, po około jednej minucie wyświetlane są informacje odczytane z stacji meteo czyli temperatura wilgotność i ciśnienie, postanowiłem nie wyświetlać tutaj informacji dotyczących prędkości wiatru oraz temperatury przy gruncie. Decyzję tą podjąłem na podstawie użytkowania innego podobnego rozwiązania, akurat jak chcemy odczytać godzinę to wyświetlane są inne informacje. Dodatkowo w godzinach nocnych, które zostały ustawione w sztywnych ramach czasowych między 21:00 a 7:00 informacje odczytane z stacji meteo zostały okrojone tylko do temperatury. W projekcie zostały zastosowane 2 rodzaje animacji pierwsza z nich, przesuwa tekst z prawej strony wyświetlacza na lewą, z możliwością zatrzymania w interesujących momentach. Drugi rodzaj to pionowa animacja. Mikrokontroler również poprzez protokół NTP i bibliotekę time.h pobiera aktualną godzinę i datę. Za odbiór danych z pierwszego układu odpowiedzialny jest moduł radiowy którego obsługą tak jak w poprzednim module zajmuje się biblioteka RCswitch. Poniżej fragment programu który demonstruje w jaki sposób odbierane i dekodowane są dane: rc = mySwitch.getReceivedValue(); // czujnik temperatury powietrza BME280 if (abs(rc)>=50000&& abs(rc)<150000) { rc=(rc-100000)/100; if (rc > -50 and rc < 60) { temp1 = rc; Serial.print("Czujnik BME280 - temperatura: \t"); Serial.println(rc); matrix.drawPixel(55,0,1); matrix.write(); } } // czujnik wilgotności BME280 if (abs(rc)>=150000 && abs(rc)<250000) { rc=(rc-200000)/100; if (rc > 5 and rc <= 100) { hum = rc; Serial.print("Czujnik BME280 - wilgotnowsc: \t"); Serial.println(rc); matrix.drawPixel(55,1,1); matrix.write(); } } Dzięki zastosowaniu zewnętrznej anteny oraz odbiornika opartego na superheterodynie, zasięg w otwartym terenie to około 250 m. Po odebraniu danych z pierwszego układu poprzez moduł radiowy następuje przekazanie ich do serwera z systemem Domoticz. Domoticz to bardzo lekki system automatyki domowej, który pozwala monitorować i konfigurować różne urządzenia, przełączniki, czujniki takie jak temperatura, opady deszczu, wiatr, promieniowanie ultrafioletowe (UV), zużycie energii elektrycznej, zużycie gazu, zużycie wody i wiele więcej. Wykresy dostępne są również na stronie www http://meteo.palowice.net Poniżej film z działania odbiornika, smużenie animacji które występuje na filmiku ludzie oko nie rejestruje. Gdyby kogoś interesował kod to również zamieszczam: meteo.zip
  6. Zapraszam do "blogowania" nowych / mniej znanych mikrokontrolerów, które można programować z poziomu Arduino / PlatformIO. W80x - chińska podróbka... chińskiego ESP32. Również oparta na rdzeniu XT804. Wersja W801 ma WiFi i Bluetooth, W806 jest "bezradiowa". Chyba jedyną przewagą nad ESP32 jest nieco niższa cena - poniżej 14zł z przesyłką z Aliexpress. https://github.com/board707/w80x_arduino Boards Manager URL: https://raw.githubusercontent.com/board707/w80x_arduino/hal-v0.6.0/package_w80x_test_index.json LGT8F328P - bezczelna, chińska aluzja do Atmega328P. Parametry podobne, chyba nawet pin compatible, z niewielkimi poprawkami względem pierwowzoru. Wyróżnia go m.in. selekcja zegara 1, 2, 4, 8, 12, 16 oraz 32 Mhz z poziomu Arduino (co pozwala dopasować wydajność i pobór prądu do zastosowań), 12-bitowe ADC (zamiast 10), wbudowany DAC. Toolchain wydaje się być w pełni użyteczny, zwłaszcza przy prostych projektach (używam od paru lat w stacji lutowniczej DIY). https://github.com/dbuezas/lgt8fx Boards Manager URL: https://raw.githubusercontent.com/dbuezas/lgt8fx/master/package_lgt8fx_index.json CH32 RISC-V MCUs Get Official Arduino Support CH32V003 - Test i recenzja https://github.com/openwch/arduino_core_ch32 Boards Manager URL: https://github.com/openwch/board_manager_files/raw/main/package_ch32v_index.json https://github.com/AlexanderMandera/arduino-wch32v003 Boards Manager URL: https://alexandermandera.github.io/arduino-wch32v003/package_ch32v003_index.json CH582F - toolchain chyba nie jest zbyt aktywnie rozwijany. https://github.com/ElectronicCats/arduino-wch58x Ze względu na gorsze wsparcie i w niektórych przypadkach toolchain na bardzo wczesnym etapie, na początku lepiej wybrać sprawdzone ESP8266, ESP32, RP2040, STM32. Z "dziwactw", najciekawsze wydają mi się moduły w stylu Arduino Pro Mini - LGT8F328P SSOP20, WeAct CH32V003F4U6. Zapewniają nieco więcej możliwości od Pro Mini (choć jest jeszcze zmniejszony klon Raspberry Pi Pico - RP2040-Zero).
  7. chcąc programować mikrokontrolery avr, ale niestety mój programator nie jest zbyt dobry dokładnie usbasp. Słyszałem o możliwości programowania tych mikrokontrolerów za pomocą arduino ale nie wiem jak to zrobić
  8. W tym projekcie chciałbym opisać krok po kroku proces podłączenia licznika samochodowego od Forda Galaxy do naszego Arduino. Potrzebne elementy: Zasilacz 12V Arduino Przewody męsko-żeńskie Licznik samochodowy Zestaw wskaźników od Forda Galaxy posiada 2 wtyczki - czerwoną oraz czarną. Nas w tym projekcie interesuje tylko czerwona wtyczka gdyż znajdują się w niej piny zasilające oraz dostarczające dane do silników krokowych w liczniku. Najpierw zajmijmy się zasilaniem. Do pinu 3 oraz do pinu 4 na liczniku wpinamy 2 przewody i podłączamy je do minusa na naszym zasilaczu a kolejne 2 przewody wpięte w pin 14 oraz w pin 15 podłączamy do +. Jako zasilacz może nam posłużyć zwykły zasilacz komputerowy kub jakikolwiek o napięciu 12V. Dalej zajmijmy się podłączeniem silniczków od wskazówek. obrotomierz - 10 pin prędkościomierz - 27 pin wskaźnik poziomu paliwa - 21 pin wskaźnik temperatury cieczy - 23 pin (pin 1 jest w lewym dolnym rogu wtyczki) Następnie przewody te wpinamy w wejścia cyfrowe do Arduino. W moim przypadku obrotomierz wpiąłem w wejście oznaczone 2, prędkościomierz w wejście nr 3, wskaźnik poziomu paliwa 4 a temp. cieczy w wejście 5. Jeżeli po podpięciu zasilania licznik zadziała (wskazówki ustawią się w położeniu 0 oraz włączy się podświetlenie) to możemy przejść do konfiguracji. Pobieramy oprogramowanie SimHub i instalujemy je. Po uruchomieniu programu przechodzimy do zakładki Arduino a następnie klikamy na zakładkę "My hardware". Wybieramy "Single Arduino" i klikamy "Open arduino setup tool". Następnie definiujemy w jakie wejścia wpięliśmy nasze wskaźniki. Wybieramy z jakiego arduino korzystamy (w moim przypadku jest to UNO) oraz wybieramy port komunikacyjny. Gdy wszystko mamy już zrobione klikamy Upload to arduino i czekamy aż program zostanie wgrany na Arduino. Jeżeli program wgrał się poprawnie przechodzimy do zakładki "Gauges" i kalibrujemy nasz licznik. Wartości liczbowe są indywidualne dla każdego licznika ale to co musimy ustawić do każdego licznika to MAX Tachometer RPM na 7 (jeżeli zakres na tarczy obrotomierza jest inny to podajemy maksymalną liczbę, jeśli jest to 5 to podajemy 5) oraz tachometer cylinders na 6. Warto zaznaczyć opcję "Always use tachometer full range" jednak jeśli sprawia ona problemy możemy ją wyłączyć. Resztę wartości musimy ustawić tak, żeby wskazówka poprawnie wskazywała położenie min i max. Niestety nie ma uniwersalnych wartości i prędkościomierz u mnie wskazuje poprawnie 240 km/h przy wartości 222 (speedo gauge maximum output) jednak w innym liczniku może być to wartość ciut większa lub mniejsza. Na samym końcu wybieramy grę w którą chcemy zagrać z zakładki "Games". Następnie uruchamiamy naszą grę i cieszymy się rozgrywką z naszym licznikiem. Ktoś mi może powiedzieć "Przecież można napisać kod", zgodzę się z tym tylko ja gram od ETS 2 przez Dirt 4 na Forzie kończąc. O wiele łatwiej jest jednym kliknięciem zmienić grę w simhubie niż pisać osobny kod eksportujący dane z telemetrii do Arduino. Jeżeli ktoś potrzebuje tylko licznika do jednej gry to ma to sens jednak w moim przypadku mija się to z celem. Koszt takiego licznika może zamknąć się w okolicach 50 zł. Możemy wykorzystać klona arduino (klon nano możemy kupić za mniej niż 15zł), a licznik możemy znaleźć na portalach aukcyjnych za ok 20zł. Jest to niedrogi i fajny bajer a na dodatek jest bardzo praktyczny. Poniżej znajdują się zdjęcia i gif pokazujący pracę urządzenia.
  9. 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; } }
  10. Czego dowiesz się z tego artykułu? Każdy to robi, natomiast prawie nikt nie zwraca na to uwagi. Nawet czytając ten tekst, organizm czytelnika wykonuje ściśle określoną listę kroków, która pozwala mu na zrozumienie czytanego tekstu. Algorytm to uporządkowany zbiór zadań, które wykonuje się w celu rozwiązania problemu. Czytając poniższy tekst zrozumiesz, że algorytmy są związane z praktycznie wszystkim co robisz na co dzień. Wprowadzę Cię do zapisu algorytmów. W artykule przedstawiono również 2 proste algorytmy zapisane w formie programu dla Arduino. Algorytmy w codziennym życiu. Załóżmy, że chcielibyśmy upiec ciasto, tak jak robiła to nasza babcia, która kierowała się konkretnym przepisem. Zaglądamy do jej zeszytu i czytamy, że potrzebujemy jajka, mąkę, masło i inne składniki. Pierwszym krokiem jest pokrojenie masła. Drugi to dodanie mąki, proszku do pieczenia, cukru i masła do miski. Po kolejnych 10 krokach wyciągamy pyszne ciasto z piekarnika i po ostygnięciu delektujemy się nim. Postępujemy tutaj według jasno określonej listy zadań, którą wymyśliła nasza babcia i dzięki której możemy osiągnąć pożądany cel. Po zjedzeniu ciasta wychodzimy na spotkanie z naszym znajomym. Nasz kolega mieszka na 1 piętrze, a winda nie działa, więc kierujemy się na schody. Wchodzimy na pierwszy stopień i weryfikujemy, czy jesteśmy na 1. piętrze. Jeżeli nie, to wchodzimy o 1 stopień wyżej i powtarzamy to, dopóki nie znajdziemy się na docelowym piętrze. W tym przypadku nasze postępowanie jest wyrażone za pomocą powtarzających się czynności, które kończymy, gdy założony przez nas warunek się spełni. Oczywiście w takich sytuacjach postępujemy automatycznie, ale warto było o tym wspomnieć, bo są to życiowe przykłady algorytmów - przepisów, które prowadzą do rozwiązania problemu, np. upieczenia ciasta, lub wejścia na 1. piętro. Mówimy o algorytmie, gdy jest on: poprawny - działa, jednoznaczny - daje ten sam wynik przy takich samych danych, skończony - nie działa w nieskończoność, efektywny - wykonuje się w sensownym czasie. Trzeba pamiętać, że algorytm ≠ program. Jeżeli algorytm jest zapisany językiem programowania, to wtedy możemy mówić o programie. Zapis algorytmów i przykłady Pamiętaj, że bardzo złym pomysłem jest zapisywanie algorytmów jako jakąś opowieść, bo może to zaburzać interpretację ich działania. Dobrym zwyczajem jest zapisywanie ich technikami, które zostały przedstawione poniżej. Lista kroków Najprostszą i najbardziej naturalną techniką jest zapis algorytmu w postaci ponumerowanych punktów, w których wypisujemy po kolei, co robimy. Zależnie od sytuacji, kroki można pomijać lub do nich wracać. Uproszczony algorytm wykonania ciasta. Autor nie ponosi odpowiedzialności za korzystanie z tego przepisu! pokrój 100 g zimnego masła na małe kosteczki dodaj do miski: 200 g szklanki mąki, pokrojone masło, 13 g proszku do pieczenia, 26 g cukru i rozbite jajko bez skorupki zmieszaj porządnie zawartość miski ciasto włóż do lodówki po 15 minutach wyciągnij ciasto piekarnik nagrzej do 180 stopni Celsjusza wypełnij formę ciastem i dodaj owoce włóż do piekarnika blachę z ciastem na 50 minut sprawdź czy gotowe. Jeżeli tak to przejdź do kroku 12. W przeciwnym razie przejdź do kroku 10 poczekaj minutę przejdź do kroku 9 wyjmij ciasto i daj mu ostygnąć zakończ algorytm Powyższy algorytm jest uproszczeniem. Bezmyślne kierowanie się nim może skutkować tym. Jesteśmy ludźmi i raczej każdy rozumie zwrot dodaj cukier. Algorytm wchodzenia po schodach: sprawdź czy wszedłeś na docelowe piętro. Jeżeli tak, to przejdź do kroku 4. W przeciwnym razie przejdź do następnego kroku wejdź o jeden stopień do góry przejdź do kroku 1 zakończ algorytm W powyższych algorytmach pojawiają się instrukcje warunkowe (jeżeli) i pętle (jeżeli… przejdź do kroku poprzedniego) - jedne z najważniejszych zagadnień programowania. Instrukcją warunkową nazywamy fragment programu, który praktycznie odpowiada na pytanie w formie: tak/nie, prawda/fałsz, 1/0. Na podstawie odpowiedzi może wykonywać jakąś czynność. Pętla to fragment kodu, który powtarza się, jeżeli warunek przypisany do tej pętli jest spełniony. W kolejnych przykładach będę posługiwał się tylko algorytmem wchodzenia po schodach, bo jest on krótki i wystarczy do wyjaśnienia innych technik zapisywania algorytmów. Pseudokod Jak sama nazwa wskazuje, jest to symbolicznie zapisany kod, który w dużej mierze zachowuje zasady języków programowania. W tym artykule wykorzystamy zasady, które opisano w tym dokumencie. Należy pamiętać, że nie istnieje oficjalny standard dla pseudokodu i to jest tylko szkolny przykład. Operacje matematyczne dodawanie – + odejmowanie – - mnożenie – • dzielenie – / dzielenie całkowite – div reszta z dzielenia – mod Przypisanie wartości do zmiennej a ← 3 Instrukcja warunkowa jeżeli a = 3 wykonuj a ← 0 w przeciwnym razie a ← 1 Operatory porównania większe - > większe równe - ≥ mniejsze - < mniejsze równe - ≤ równe - = nierówne - ≠ Operatory logiczne AND - i OR - lub Pętle dopóki a < 10 wykonuj a ← a + 1 lub dla i = 1, 2, 3, … 10 wykonuj a ← a + 1 lub wykonuj a ← a + 1 dopóki a < 10 Przedstawmy teraz algorytm wchodzenia po schodach w postaci pseudokodu. Musimy założyć pewne uproszczenia i dodatki: z góry wiemy ile dane schody mają stopni wejście na stopień traktujemy jako dodanie 1 do zmiennej wskazującej aktualny stopień gdy będziemy na środkowym stopniu to powiemy “połowa za mną…” po wejściu na piętro jesteśmy bardzo szczęśliwi, że nasz algorytm działa, więc mówimy “jestem na piętrze!” wprowadź liczbaStopni aktualnyStopien ← 0 dopóki aktualnyStopien < liczbaStopni wykonuj aktualnyStopien ← aktualnyStopien + 1 jeżeli aktualnyStopien = liczbaStopni div 2 wypisz “Połowa za mną…” wypisz “Jestem na piętrze!” Zapisując algorytm w pseudokodzie, pamiętaj, żeby stosować wcięcia do operacji, które dotyczą użytej wcześniej instrukcji, takiej jak instrukcja warunkowa. Zapewni to czytelność i poprawne działanie kodu, ponieważ pseudokod bazuje na wcięciach. Pseudokod zapewnia szybki zapis algorytmu i możliwość łatwego przekonwertowania go na język programowania. Schemat blokowy W tej technice używamy bloków o konkretnym kształcie, aby wskazać różne instrukcje algorytmu. Podstawowe kształty: owal - miejsce rozpoczęcia/zakończenia algorytmu prostokąt - operacje na zmiennych romb - instrukcja warunkowa równoległobok - wprowadzenie/wypisanie danych I tak jak poprzednio, algorytm wchodzenia po schodach: Dla czytelności polecam wejść w pełny obraz. Schemat blokowy czyta się od punktu START i podąża się zgodnie ze strzałkami, wykonując wszystkie operacje zawarte w mijanych blokach. Spróbuj przeanalizować powyższy algorytm! Dzięki schematowi blokowemu będziesz mógł przedstawić swoje algorytmiczne pomysły w czytelny i klarowny sposób. Należy pamiętać, że schemat blokowy dobrze sprawdza się w algorytmach mniejszych rozmiarów. Spróbuj sobie wyobrazić, co by było, gdyby na schemacie znajdowało się 100 różnych bloków i byłoby między nimi dużo połączeń… Język programowania Wcześniejsze techniki zapisu algorytmów są stosowane do ich przedstawienia w sposób symboliczny. Komputer to maszyna i potrzebuje konkretnych instrukcji, dlatego stosuje się języki programowania, które są jednoznaczne i precyzyjne. Mówiąc chcę dodać dwie liczby trzeba jeszcze sprecyzować, jakie mają wartości, jakiego są typu, gdzie będzie przechowywany wynik itp. Każdy język charakteryzuje się składnią i semantyką, czyli sposobem, w jaki programy napisane w danym języku są konstruowane i interpretowane przez komputer. Składnia odnosi się do struktury i zapisu gramatycznego kodu, czyli do tego, jakie konstrukcje i polecenia są akceptowane w danym języku. Z kolei semantyka odnosi się do znaczenia tych konstrukcji oraz sposobu, w jaki są one rozumiane i wykonane przez interpreter lub kompilator. Poniżej znajduje się zapis algorytmu wchodzenia po schodach w języku C. #include <stdio.h> int main() { int iloscStopni = 10; int aktualnyStopien = 0; while (aktualnyStopien < iloscStopni) { aktualnyStopien = aktualnyStopien + 1; if (aktualnyStopien == iloscStopni / 2) { printf("Polowa za mna!\n"); } } printf("Jestem na pietrze!\n"); return 0; } Tutaj warto zaznaczyć jak korzysta się z funkcji. Nazwę możesz kojarzyć z matematyki, gdzie występuje funkcja f(x). Dokładnie w ten sam sposób działają funkcje w programowaniu. Wywołując ją podajesz jej nazwę i w nawiasach argumenty, które zostaną wykorzystane do pewnych operacji. Funkcja może zwracać pewną wartość lub nie. Np. funkcja printf() nic nie zwraca, ale wypisuje napis w konsoli. Jeżeli zwraca to musimy podać zmienną przechowującą wartość wyniku. Na początku programu musimy pobrać bibliotekę, w której znajdują się funkcje, których będziemy używali w programie. Za to odpowiada pierwsza linijka - #include <stdio.h>. W C operacje pisze się między nawiasami klamrowymi, które oznaczają początek i koniec danej instrukcji. W tym przypadku cały program jest zapisany w funkcji głównej main(), która zawsze się wykonuje przy uruchomieniu programu. Na końcu każdej operacji musi wystąpić średnik! Żeby nie komplikować kodu pobraniem wartości założyłem, że wiemy od samego początku, ile jest stopni na schodach, dlatego równocześnie z deklaracją, że będzie to zmienna stałoprzecinkowa int, przypisujemy jej wartość 10: int iloscStopni = 10;. while() to pętla odpowiadająca pętli dopóki w pseudokodzie. W nawiasach wpisujemy warunek wykonania się kolejnego kroku pętli. Funkcja printf() wyświetla napis zawarty w nawiasach, w cudzysłowie. Porównaj kod zapisany pseudokodem z tym w C. Jestem pewny, że dostrzeżesz podobieństwa. Algorytmika w Arduino Należy pamiętać, że programy, które piszemy na Arduino, działają w nieskończonej pętli. Procesory, mikrokontrolery i inne sterowniki są projektowane tak, aby mogły działać cały czas. Z tego powodu programy w Arduino wykonują się w dwóch funkcjach - setup() i loop(). Dodatkowo warto zaznaczyć, że język wykorzystywany w Arduino IDE jest praktycznie językiem programowania C++. setup() wykonuje się tylko i wyłącznie raz przy starcie całego programu. Używamy tej funkcji, żeby rozpocząć pewne procesy lub ustawić wejścia i wyjścia na odpowiednie tryby. Stąd jej nazwa, która przetłumaczona na j. polski brzmi konfiguracja. Konfigurujemy i przygotowujemy środowisko na późniejsze etapy wykonywania programu. loop() wykonuje się po zakończeniu funkcji setup i działa w pętli - wykonuje się cały czas. Tutaj umieszczamy operacje związane z np. sterowaniem i przechwytywaniem informacji. Przykładowe algorytmy na Arduino Blink Na początku zajmiemy się miganiem diodą LED. Nie musisz nic pisać, aby skorzystać z tego programu! Włącz Arduino IDE -> kliknij zakładkę File -> Examples -> 01.Basics -> Blink. Otworzy się nowe okienko z programem Blink. Zauważysz dużo komentarzy, które są poprzedzone dwoma myślnikami, lub znakami /* i zakończone */. Możesz je usunąć. Zostanie poniższy kod: void setup() { pinMode(LED_BUILTIN, OUTPUT); } void loop() { digitalWrite(LED_BUILTIN, HIGH); delay(1000); digitalWrite(LED_BUILTIN, LOW); delay(1000); } pinMode() ustawia wybrany pin na wejście lub wyjście. Przyjmuje dwa argumenty - numer pinu i wybranie trybu. Arduino ma wbudowaną stałą, która wskazuje na pin wbudowanej w płytkę diody, więc to będzie nasz pierwszy argument. Drugim będzie OUTPUT, czyli ustawienie pinu na wyjście. Ta funkcja wykona się tylko raz, bo znajduje się w setup(). W funkcji loop() jako pierwszy krok ustawiamy pin LEDa na stan wysoki (zapali się) funkcją digitalWrite(), podając pin tej diody i argument HIGH oznaczający stan wysoki. Funkcja delay() powoduje czekanie programu. Wartość w nawiasach to długość czasu w milisekundach. Potem wyłączamy diodę, czekamy sekundę. Funkcja loop() wykonuje się od nowa. Czy jesteś w stanie zapisać algorytm migania diodą w postaci schematu blokowego? Czytanie przycisku Poniżej znajduje się program do zapalania lampki, gdy wciśnięty jest przycisk na pinie 7. Spróbuj go przeanalizować i pochwal się w komentarzu, jak działa! Dodatkowo w ramach ćwiczenia, polecam zapisać go w postaci pseudokodu. Kilka podpowiedzi: zmienne i stałe można deklarować poza głównymi funkcjami. Można wtedy się do nich odwołać w każdym miejscu w programie #define oznacza deklarację stałej, której nie zmienia się przez cały program. Symbolicznie stałe zapisuje się wielkimi literami digitalRead() czyta i zwraca stan, który aktualnie jest na pinie funkcja else() wykonuje się wtedy, gdy instrukcja warunkowa zwróciła fałsz #define PIN_LED 8 #define PIN_BUTTON 7 int buttonState = 0; void setup() { pinMode(PIN_LED, OUTPUT); pinMode(PIN_BUTTON, INPUT); } void loop() { buttonState = digitalRead(PIN_BUTTON); if (buttonState == HIGH) { digitalWrite(PIN_LED, HIGH); } else { digitalWrite(PIN_LED, LOW); } } Podsumowanie Znajomość pojęcia algorytm to kluczowa umiejętność w świecie programowania. Jeśli zaczynasz pisać kod na platformę Arduino, to warto zgłębić tę tematykę. Nie ograniczaj się tylko do zapisu algorytmów - zrozum, jak działają i jak możesz je wykorzystać w praktyce. Gorąco polecam skorzystać z kursu Arduino na Forbocie, abyś mógł szybko przekuć swoją wiedzę na konkretne projekty.
  11. Cześć, może komuś by się chciało napisać coś na temat pisania bibliotek do Arduino? Temat nie poruszany a potrzebny. Baaaaaardzo potrzebny 😁
  12. Cześć, pewnego razu na spotkaniu ze znajomymi okazało się, że na hasło „polej” nikt nie zareagował. Wtedy zrodził się pomysł, aby zaradzić takim przykrym sytuacjom w przyszłości postanowiłem stworzyć robota do nalewania alkoholi wysokoprocentowych. Z założenia robot miał nalewać alkohol do każdego kieliszka jaki się postawi oraz miał być zasilany przy pomocy baterii. Pierwsze prototypy zawierały prostą elektronikę opartą na czujnikach i przekaźnikach – jestem laikiem oraz samoukiem w kwestiach elektroniki. Projekty nie spełniały oczekiwań, ponieważ w normalnym użytkowaniu zawodziły. Około rok temu natknąłem się na Arduino i zacząłem pogłębiać swoją wiedzę w tym kierunku. Wydruki 3D wykonałem na swojej drukarce. Robot spełnił założenia. Poprzez zastosowanie czujnika ultradźwiękowego jest w stanie nalać do każdego kieliszka, a potencjometrem można ustawić ilość nalewanej wódki w zakresie około 10 - 50ml. Do zasilania użyłem 8 akumulatorów AA. Wykonałem obudowę z kilku elementów żeby mieć lepszy dostęp do podzespołów. Rynienka na pompce została stworzona po zalaniu układu... z wyciekiem sobie poradziłem ale dla pewności została 😉 Części których użyłem do budowy: Płytka stykowa 170 pól - 1 szt Stabilizator L7805CV 5V 1,5A – 1 szt Ultradźwiękowy czujnik odległości HC-SR04 2-200cm – 1szt Potencjometr liniowy 1K – 1 szt Wtyk DC 2,1/5,5mm z zaciskami skręcanymi – 1 szt Pompa membranowa - silnik R385+ - 12V - 3W - mini pompa wodna – 1szt Moduł sterownika L298N do silników DC – 1 szt Przełącznik kołyskowy 15x10mm – 1 szt Koszyk na baterie 8xAA (R6) – 1 szt Arduino Nano – 1 szt Uniwersalny Proto Shield do Arduino Nano – 1 szt Serwo TowerPro MG-995 – 1szt. Zawór zwrotny – 2szt. Moduł dioda LED RGB 5050 wspólna katoda – 1 szt. Magnesy neodymowe 10x5 – 8 szt Przycisk monostabilny – 1 szt. Rurka sylikonowa – 0,5m Rezystory Przewody połączeniowe Aby "polewacz" był mobilny jego elementy zaprojektowałem tak, aby zmieściły się w drewnianej skrzyni 30x20x15cm.
  13. Cześć wszystkim, Ostatnio zainteresowałem się pewnym zjawiskiem fizycznym, polegającym na możliwości wykorzystania dźwięku do gaszenia ognia [link] [link]. Postanowiłem samemu zbudować podobne urządzenie, jednak podczas doboru elementów natrafiłem na pewne problemy, których niestety sam nie potrafię rozwiązać. 1. Schemat urządzenia 2. Zasada działania urządzenia Chciałbym, aby zaprojektowane urządzenie, za pomocą głośnika (generowanej fali akustycznej) było w stanie zgasić np. płomień zapalonej świeczki typu tealight [link] czy ogień z podpalonego denaturatu, wypełniającego pojemnik po wyżej wspomnianej świeczce. Z tego względu podstawą takiego urządzenia musi być odpowiedni głośnik. Uważam, że dobrym wyborem okaże się głośnik o impedancji 8 Ω, głośności 91 dB i mocy maksymalnej 200 W. Dzięki temu będę miał zapas na wykonywanie coraz to bardziej skomplikowanych eksperymentów, z możliwością podnoszenia poprzeczki. Owszem zapaloną świeczkę można po prostu zdmuchnąć ale trzeba od czegoś zacząć 😉 [link]. Również zdaję sobie sprawę, że do uzyskania takiego zjawiska potrzebuję odpowiedniej częstotliwości fali akustycznej oraz poziomu natężenia dźwięku. Precyzyjną częstotliwość fali akustycznej mogę wygenerować dzięki Arduino Uno oraz funkcji tone() [link], zaś kontrolę nad poziom natężenia dźwięku uzyskam dzięki wzmacniaczowi z wbudowanym potencjometrem [link]. Idąc dalej wzmacniacz potrzebuje zasilania od 6 V do 12 V. Większe napięcie przy stałej impedancji zapewnia większe natężenie. Większe natężenie to większa moc głośnika. Rozwiązaniem okaże się tutaj moduł z tranzystorem [link] pozwalający na włączanie urządzeń wymagających zasilania od 5 V do 15 V. Co więcej uchronię Arduino Uno przed natężeniem większym niż 20 mA. Wybrałem taki moduł z serii Grove, ponieważ posiadam nakładkę Grove Base Shield v2 [link] rozszerzającą możliwości pinów Arduino Uno oraz dysponuję wyświetlaczem LCD 2 x 16 [link] tej samej firmy, na którym wyświetlałbym daną częstotliwość fali akustycznej oraz poziom natężenia dźwięku. Co więcej planuję dokupić 2 przyciski, dzięki którym sterowałbym częstotliwością fali akustycznej generowanej przez Arduino (zmniejszenie częstotliwości / zwiększenie częstotliwości) [link]. Całość planuję zasilić akumulatorem 12 V ; 1,2 Ah, w połączeniu z przełącznikiem ON/OFF i stabilizatorem LM7805 [link] odpowiadającym za dostarczenie bezpiecznego napięcia 5 V do Arduino wraz z dwoma kondensatorami 1000 μF ; 25 V, których zadaniem jest eliminacja zakłóceń i zapewnienie prawidłowej pracy układu. Zasilanie do Arduino zapewnię poprzez wtyk DC [link]. 3. Problem do rozwiązania Mam nadzieję, że udało mi się zrozumiale wytłumaczyć planowany sposób działania urządzenia. Poniżej zapisałem pytania, na które niestety sam nie potrafię odpowiedzieć, a są według mnie kluczowe do poprawnego zadziałania prototypu: Czy mój tok rozumowania jest prawidłowy? Czy może coś mi umknęło? Czy przedstawiony schemat zakłada poprawne połączenia wejść / wyjść komponentów urządzenia? Czy zasilanie 12 V w połączeniu ze stabilizatorem okaże się wystarczające do uruchomienia głośnika, nie uszkadzając przy tym Arduino? Czy głośnik w połączeniu ze wzmacniaczem będzie generował dźwięk? Czy może czegoś brakuje? Z góry dziękuję za poświęcony czas i pomoc! 😊
  14. Już myślałem, że mam ogarnięty temat RS-485. Jednak nie i znów potrzebuję pomocy. Czy są jakieś zasady / dobre praktyki, które należy stosować do obsługi komunikacji dwukierunkowej w trybie half duplex? Ew. ktoś to robił i może mi pomóc z moją aplikacją (np. jakiś wieczorny calll?) Aktualnie robię w miarę prosty układ obsługujący komunikację RS-485, wykorzystując 2 płytki: UNO R4 i GIGA. Całość ma działać tak: 1. (działa) Wciskam przycisk podpięty do UNO. UNO wysyła komunikat do GIGA. Kod: void sendReadMessage(int command) { char message[BatteryMonitor_Message_Lenght]; int commandValueParameter = 1; //wg przykładów z instrukcji urządzenia każdy read wysyła wartość = 1. int checksum = getChecksum(commandValueParameter); sprintf(message, ":R%02i=%i,%i,%i,\r\n", command, batteryMonitorParameters.deviceAddress, checksum, commandValueParameter); //Active HIGH for Driver Input Enable; Active LOW for Receiver Output Enable. digitalWrite(receiverOutputEnablePinNumber, HIGH); batteryMonitorSerialDevice->print(message); batteryMonitorSerialDevice->flush(); delay(WaitTimeAfterSend); //Active HIGH for Driver Input Enable; Active LOW for Receiver Output Enable. digitalWrite(receiverOutputEnablePinNumber, LOW); } W klasie instancją której operuję jest definicja zmiennej: Stream* batteryMonitorSerialDevice; a przy inicjowaniu podpinam pod nią Serial1 na którym jest poprawnie wpięty konwerter RS-485. 2. (działa) GIGA odbiera i prasuje komunikat. W zależności od treści otrzymanego i przetworzonego komunikatu ustala odpowiedź do wysyłki. Kod: void loop() { returnMessage = ""; switch (readMessage()) { case 0: { returnMessage = ":r00=1,47,1120,100,101,\r\n"; break; } case 50: { returnMessage = ":r50=2,215,2056,200,5408,4592,9437,14353,134,0,0,0,162,30682,\r\n"; break; } default: break; } if (returnMessage.length() > 0) { sendMessage(returnMessage); } delay(100); } int readMessage() { String returnMessage_local = ""; char message[BatteryMonitor_Message_Lenght]; int startIndex = 0; int endIndex = 0; bool receivedFullMessage = false; bool receivedMessage = false; int index = 0; while (Serial1.available() > 0) { message[index] = Serial1.read(); receivedMessage = true; if (message[index] == '\n') { receivedFullMessage = true; break; } index++; } if (receivedMessage and receivedFullMessage) { //parsuję wiadomość i szukam interesującego mnie kodu operacji for (int i = 0; i <= index; i++) { if (message[i] == 'R') { startIndex = i + 1; } if (message[i] == '=') { endIndex = i - 1; i = index + 1; //leave the loop } } for (int i = startIndex; i <= endIndex; i++) { returnMessage_local += message[i]; } if (returnMessage_local == "") { return -1; } else { return returnMessage_local.toInt(); //kod operacji } } return -1; } 3. (zaczynają się problemy) GIGA wysyła komunikat po RS-485. Nie mam żadnych oznak niepoprawnego działania. Kod: void sendMessage(String message) { message.toCharArray(messageToSend, BatteryMonitor_Message_Lenght); digitalWrite(ReceiverOutputEnablePinNumber, HIGH); for (int i = 0; i < message.length(); i++) { Serial1.write(messageToSend[i]); } Serial1.flush(); delay(100); digitalWrite(ReceiverOutputEnablePinNumber, LOW); } 4. (nie działa) UNO czeka na przychodzący komunikat. Jeżeli coś się pojawia to odbiera i wyświetla na serial (USB). Niestety przy kolejnych naciśnięciach przycisku albo nic nie odbiera, albo odbiera śmieci. Ani razu nie dotarła wiadomość zwrócona przez GIGA. void loop() { if (digitalRead(5) == LOW) { //Jeśli przycisk jest wciśnięty readBasicInformation(); SerialDebug_BM_BasicInformation(); //na potrzeby debugowania wyświetla otrzymane informacje na Serial / USB } delay(150); } void readBasicInformation() { char returnMessage[140]; String complexField = ""; char nextCharacter; int index = 0; //funkcja wysyłania z pkt 1 – ta działająca. Na wejściu kod operacji sendReadMessage(BatteryMonitor_Functions_ReadBasicInfo); //wiadomości są zakończone dwoma znakami: \r\n int tmpTimeout = 0; //czekam na wiadomość. Aby nie zawiesić aplikacji symulacja timeout w oczekiwaniu na komunikat. Kod do refactoringu while (batteryMonitorSerialDevice->available() <=0 && tmpTimeout <=30) { delay(150); tmpTimeout++; } //odczyt znak po znaku i wyświetlanie na Serial w celu debugowania while (batteryMonitorSerialDevice->available() > 0) { //tu dostaję śmieci lub nic nie dostaję…. returnMessage[index] = batteryMonitorSerialDevice->read(); Serial.print(returnMessage[index]); index++; } //odczyt wyświetlanie na Serial w celu debugowania Serial.print("Return message full: "); Serial.println(returnMessage); } Co mogę robić źle?
  15. Uczę się używania Arduino i gdy podłączyłem całość wyskakuje mi błąd, co musze zrobic? Szkic używa 1144 bajtów (3%) pamięci programu. Maksimum to 30720 bajtów. Zmienne globalne używają 13 bajtów (0%) pamięci dynamicznej, pozostawiając 2035 bajtów dla zmiennych lokalnych. Maksimum to 2048 bajtów. avrdude: stk500_recv(): programmer is not responding avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0xde avrdude: stk500_recv(): programmer is not responding avrdude: stk500_getsync() attempt 2 of 10: not in sync: resp=0xde avrdude: stk500_recv(): programmer is not responding avrdude: stk500_getsync() attempt 3 of 10: not in sync: resp=0xde avrdude: stk500_recv(): programmer is not responding avrdude: stk500_getsync() attempt 4 of 10: not in sync: resp=0xde avrdude: stk500_recv(): programmer is not responding avrdude: stk500_getsync() attempt 5 of 10: not in sync: resp=0xde avrdude: stk500_recv(): programmer is not responding avrdude: stk500_getsync() attempt 6 of 10: not in sync: resp=0xde avrdude: stk500_recv(): programmer is not responding avrdude: stk500_getsync() attempt 7 of 10: not in sync: resp=0xde avrdude: stk500_recv(): programmer is not responding avrdude: stk500_getsync() attempt 8 of 10: not in sync: resp=0xde avrdude: stk500_recv(): programmer is not responding avrdude: stk500_getsync() attempt 9 of 10: not in sync: resp=0xde avrdude: stk500_recv(): programmer is not responding avrdude: stk500_getsync() attempt 10 of 10: not in sync: resp=0xde Failed uploading: uploading error: exit status 1
  16. Witam. Gdy robię schemat załączony na obrazku i załączam poniższy kod, serwomechanizm działa przez jakiś czas, ale po czasie się wyłącza, a stabilizator się przegrzewa tak, że ledwo co mogę go dotknąć. Wie ktoś, czy powinno tak być, a jeśli nie, to czym to może być spowodowane i jak można temu zaradzić? #include <Servo.h> //Biblioteka odpowiedzialna za serwa Servo serwomechanizm; //Tworzymy obiekt, dzięki któremu możemy odwołać się do serwa void setup() { serwomechanizm.attach(9); //Serwomechanizm podłączony do pinu 9 } void loop() { serwomechanizm.write(0); delay(1000); serwomechanizm.write(120); //Wykonaj ruch delay(1000); serwomechanizm.write(40); delay(1000); }
  17. Dz ień dobry🙂Chciałbym się dowiedzieć czy mogę zasilać z baterii 9V plytkę arduino UNO i silniki 5V? Robięaktualnie projekt z dwoma silnikami i plytka arduino, chcę to zasilac z jednej bateri 9V i chcę to zrobić tak że podłączę do bateri plytkę i stabilizatory napięcia ktore z 9v beda robic 5v (dla silnikow) a stabilizatory do tranzystorow a tranzystory do plytki i silnikow. (Bede sterował napieciemdla silnikow poprzez płytkę) 😀
  18. Dopiero zaczynam z arduino i nie wiem gdzie mam tu błąd. Chcę podłączyć dalmierz ultradźwiękowy brzęczyk i leda który się zaświeci gdy coś się ruszy #define trigPin 12 #define echoPin 11 void setup() { Serial.begin (9600); pinMode(trigPin, OUTPUT); //Pin, do którego podłączymy trig jako wyjście pinMode(echoPin, INPUT); //a echo, jako wejście pinMode(2, OUTPUT); //Wyjście dla buzzera pinMode(4, OUTPUT); } void loop() { zakres(10, 25); //Włącz alarm, jeśli w odległości od 10 do 25 cm od czujnika jest przeszkoda delay(100); } int zmierzOdleglosc() { long czas, dystans; digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); czas = pulseIn(echoPin, HIGH); dystans = czas / 58; return dystans; } void zakres(int a, int b) { int jakDaleko = zmierzOdleglosc(); if ((jakDaleko > a) && (jakDaleko < b)) { digitalWrite(2, HIGH); //Włączamy buzzer } else { digitalWrite(2, LOW); //Wyłączamy buzzer, gdy obiekt poza zakresem } void zakres(int a, int b) { int jakDaleko = zmierzOdleglosc(); if ((jakDaleko > a) && (jakDaleko < b)) { digitalWrite(4, HIGH); } else { digitalWrite(4, LOW); } }
  19. Pisałem niedawno, że marzyłem kiedyś i takim przenośnym urządzeniu, które czytałoby mi książki w czasie spaceru czy jazdy pociągiem. I to nie audiobooki, ale zwyczajne ebooki. Cóż - kilkanaście lat temu było to raczej mało realne. Minęły lata (kilkanaście jak wspomniałem), realia się zmieniły, i chociaż rzadziej wychodzę na spacery czy wsiadam do pociągu - postanowiłem zrealizować swoje marzenie. Co prawda jakieś pierwotne założenia sobie poczyniłem, jednak pozostały z nich tylko dwa: rozmiar nie przekraczający paczki papierosów i ESP32 WROVER jako serce urządzenia. Miałem w planach kartę microSD - ale po drodze stwierdziłem, że na moim ESP mam jakieś 8MB flasha do dyspozycji na FatFS, książka po skompresowaniu zajmuje nie więcej niż pół mega, czyli kilkanaście książek powinienem zmieścić bez dodatkowych urządzeń typu czytnik kart. Kilka problemów musiałem rozwiązać zanim zabrałem się do jakiejkolwiek roboty. Najważniejsza była klawiatura: musiała być intuicyjna (tak żebym nie musiał wyciągać urządzenia z kieszeni żeby znaleźć klawisz pauzy), a jednocześnie odporna na jakieś "samoczynne" wciskanie klawiszy w kieszeni. Postanowiłem umieścić ją razem z gniazdem słuchawek i wyłącznikiem na górnej (najkrótszej) ścianie obudowy; pozwoli to na bezwzrokowe obsługiwanie czytaka, a jednocześnie jest nikłe prawdopodobieństwo że klawisz sam się wciśnie. Trochę musiałem pokombinować z ilością klawiszy (początkowo miało ich być 13, ale po ograniczeniu do 9 okazało się, że wszystkie potrzebne funkcje są dostępne). Trochę kombinowania - i wyszło mi coś takiego: Dla porównania wielkości - moje pudełko na papierosy. Jest to oczywiście tylko próbny wydruk, ale wszystkie klawisze są wlutowane do płytki, a gniazdo słuchawek jest już na swoim miejscu. Drugą niemniej ważną sprawą był rozmiar aplikacji. Tego się obawiałem najbardziej - bo o ile tworząc microlenę mogłem sobie pozwolić na dość duże uproszczenia z uwagi na przewidywalność stosunkowo krótkich komunikatów (vide "poziomica") - o tyle tutaj musiałem zastosować pełne słowniki i wzorce rozpoznawania zwrotów. Dodatkowo microlena nie miała modułu rozpoznawania czasowników - tu musiałem go stworzyć praktycznie od nowa (wersja pecetowa korzysta albo z Morfologika, albo z wbudowanego kilkunastomegabajtowego słownika czasowników). I tu niespodzianka - wszystkie dane potrzebne do przetworzenia tekstu z książki na zapis fonetyczny zmieściły się w ok. 800 kB. Co prawda analizator morfologiczny nie jest tak dokładny jak w wersji PC, ale wystarczający aby nie było rażących błędów prozodii. Przynajmniej słuchając przez pół godziny przygód Anastazji Kamieńskiej nie zauważyłem błędów (poza kompletnie popsutą odmianą liczebników, ale to kwestia błędów w programie, a nie niewystarczających danych). Tak więc testowa aplikacja, zawierająca prowizoryczny serwer www (do uploadu i ogólnie manipulowania "księgozbiorem") oraz syntezator ma ok. 1.8 MB. Ponieważ nie przewiduję jakichś "flashożernych" procedur powinienem zejść poniżej 3 MB. Biorąc pod uwagę zajmujące nieco ponad 5 MB próbki głosu potrzebne Mbroli do syntezy - powinienem móc połowę z 16 MB pozostawić na FatFS. Opiszę jeszcze klawiaturę urządzenia (w trybie odtwarzania): Klawisze po lewej stronie (piątka): dwa klawisze regulacji głośności klawisz pauzy dwa klawisze regulacji prędkości Klawisze po prawej stronie (romb) która godzina/stan baterii (krótkie lub długie wciśnięcie) dwa klawisze przeskoku o zdanie/akapit w tył lub do przodu gdzie jestem? (tytuł, rozdział, akapit) W trybie pauzy można przeskoczyć o akapit lub rozdział lub (poprzez długie naciśnięcie klawisza "pauza") przejść do stanu "stop" (tu jeszcze nie mam opracowanego znaczenia klawiszy). Ponieważ urządzenie będzie miało wbudowany zegarek - wybrałem moduł zawierający pamięć EEPROM, w której mam zamiar trzymać zarówno wszystkie ustawienia programu, jak i informacje o aktualnie czytanej książce (rozdział/akapit/offset). Cóż - zobaczymy co będzie dalej 🙂 Na pewno opiszę efekty moich bojów z wbudowanymi w ROM funkcjami kompresji/dekompresji, muszę tylko doprowadzić ten fragment kodu do postaci umożliwiającej publiczne pokazanie go bez narażania się na inwektywy oraz skrobnąć parę słów opisu typu "dlaczego tak a nie inaczej". Aha: program jest pisany pod Arduino, tak że być może ktoś wykorzysta jakieś jego fragmenty. Postaram się publikować co ciekawsze kawałki. Jeśli ktoś miałby jakieś sugestie - jestem bardzo ciekaw, bo na tym etapie mogę jeszcze dużo zmienić. Stay tuned!
  20. Choć tablice nie są tematem kursu Arduino ani poziomu 1 ani poziomu 2, to warto o nich wspomnieć. W pytaniach na forum kursanci często pytają jak udoskonalić swoje programy – w niektórych przykładach aż prosi się o użycie tablic. Temat jest omówiony co prawda w kursie STM32L4 w części 3 o GPIO, to jednak linkowanie trudnego artykułu dla zupełnie początkujących może budzić lęk. Postanowiłem więc napisać wstęp do tablic w Arduino, który przerodził się w nieco dłuższą serię (powiedzmy kurs). W pierwszej części ogólnikowo przedstawię tematykę oraz sposób poradzenia sobie z kilkoma typowymi problemami. W dalszych częściach zagłębimy się w bardziej wnikliwe zagadnienia, gdzie niezbędna będzie podstawowa wiedza o wskaźnikach. Spis treści #1 – wstęp i praktyka (czytasz ten artykuł) #2 – organizacja pamięci #3 – tablice w pamięci #4 – tablica jest wskaźnikiem #5 – znaki, cstring #6 – argumenty funkcji #7 – przykład #8 – tablice wielowymiarowe #9 – tablice dynamiczne #10 – zakończenie Zmienne i typy danych W kursie Arduino przyrównano zmienne do "szufladek" do których wkładamy jakieś dane. Przeznaczenie tych danych określa typ danych i na pewno kojarzysz już takie hasła jak: char, int, long czy float. Każda zmienna jest w stanie przechować jedną wartość, np. zmienna int może przechować liczbę całkowitą, a zmienna float liczbę zmiennoprzecinkową: int variable_int = 5; // zmienna całkowitoliczbowa float variable_float = 1.5; // zmienna zmiennoprzecinkowa Obiekt jest tu słusznym określeniem, ponieważ w C i C++ istnieją typy danych wbudowane (tzw. fundamentalne lub proste), np. char, int, ipt., a także typy dodane, które mogą reprezentować zbiór (np. strukturę) typów fundamentalnych. Więcej danych Co jednak zrobić, gdy liczba danych zaczyna rosnąć? Wyobraźmy sobie taki scenariusz: projektujemy sterownik skrytek na bagaż. W każdej skrytce ma być umieszczony czujnik zamknięcia drzwiczek, tak by sterownik miał kontrolę nad całą szafą: Gdybyśmy chcieli przyporządkować każdemu czujnikowi (skrytce) jedną zmienną to szybko dojdziemy do 2 słusznych wniosków: napisanie tego jest mozolne i małorozwojowe, trzeba jakoś usystematyzować nazewnictwo. Możliwe, że nasz kod zacznie wyglądać tak: // skrytki w górnym wierszu int locker_0 = 0; // skrytka w zerowej kolumnie int locker_1 = 0; // skrytka w pierwszej kolumnie int locker_2 = 0; // skrytka w drugiej kolumnie int locker_3 = 0; // skrytka w trzeciej kolumnie int locker_4 = 0; // skrytka w czwartej kolumnie // ... Jak się teraz do tego odwołać? Jak sprawdzić czy wybrana skrytka jest zamknięta? Lepiej nie zaprzątać sobie tym głowy. Użyjmy do tego tablicy! Tablice Tablica jest uporządkowanym ciągiem danych tego samego typu zajmujących ciągłe miejsce w pamięci. Wracając do przykładu podanego na wstępie, to tablica jest odpowiednikiem szafki z szufladami. Szuflady możemy ponumerować począwszy od zera, a następnie wkładać i wyciągać z nich obiekty posługując się tymi numerami, czyli indeksami. Dane są uporządkowane - każda skrytka ma swój niezmienny, unikatowy indeks. To że są tego samego typu oznacza, że w obrębie jednej tablicy możemy używać tylko jednego typu danych – to tak jakby uznać, że jedna szafka jest tylko na sztućce, a inna tylko na narzędzia. Do tego, że dane zajmują w pamięci określone miejsce jeszcze wrócimy. Tablice w praktyce Postaram się w skrócie napisać niezbędne minimum, aby móc używać tablic w programach (szczegóły zostaną omówione w dalszych częściach). Zadeklarujmy przykładową tablicę składającą się z 5 elementów: int lockers[5]; Deklaracja ta oznacza zarezerwowanie miejsca na 5 zmiennych typu int. Jak widać różnica polega tylko na dodaniu nawiasu kwadratowego po prawej stronie nazwy zmiennej z liczbą 5 oznaczającą liczbę komórek tablicy. Uwaga! Liczba komórek w takiej tablicy (statycznej) jest liczbą stałą i koniecznie musi być znana na etapie kompilacji, czyli w jakiś sposób jawnie zapisana w programie. Istnieje możliwość utworzenia tablicy (dynamicznej) w czasie działania programu. Jeżeli taką deklarację umieścisz "na górze kodu" poza jakąkolwiek funkcją (w tym setup), to domyślnie każdy element tablicy będzie miał wartość 0. Możliwe jest jednak zainicjowanie tablicy własnymi wartościami – należy je podać w nawiasach klamrowych zgodnie z przykładem: int lockers[5] = {1, 0, 1, 1, 1}; Odczyt wartości tablicy Aby upewnić się czy udało się zapisać w ten sposób dane do tablicy, odczytajmy kilka jej elementów: Serial.println(lockers[0]); Serial.println(lockers[1]); Serial.println(lockers[2]); Należy pamiętać, że indeksowanie w tablicach zaczynamy od 0. W wyniku powinniśmy otrzymać: 1 0 1 Jak można zauważyć, wpisywanie kolejnych indeksów jest niepraktyczne i można się przy tym pomylić, użyjmy więc pętli for do wypisania wszystkich elementów: for(int i = 0; i < 5; ++i) Serial.println(lockers[i]); Należy pamiętać, że ostatni indeks 5 elementowej tablicy to 4, a nie 5. Zapis elementów tablicy Podobnie jak w zwykłych zmiennych, w tablicach też można coś zapisać. Wystarczy przypisać wartość do konkretnego elementu: lockers[0] = digitalRead(3); Podobnie jak przy odczytywaniu wartości, również do zapisu można wykorzystać pętlę: for(int i = 0; i < 5; ++i) lockers[i] = 1; Do czego praktycznego można w Arduino użyć tablice? Sprawdźmy to na przykładzie odczytu wejść Arduino. Praktyczny przykład Załóżmy, że do pierwszych 8 pinów Arduino mamy podpięte przyciski. Przyciski te mogą być przełącznikami krańcowymi umieszczonymi w robocie służące do wykrywania kolizji. Chcemy mieć możliwość zapisania odczytów z czujników do osobnych zmiennych. Jak wiemy napisanie kilku prawie identycznych zmiennych mija się z celem, lepiej użyć tablicę: #define SENSORS_COUNT 8 int sensors[SENSORS_COUNT]; void read_all_sensors() { for(int i = 0; i < SENSORS_COUNT; ++i) sensors[i] = digitalRead(i); } Taki zapis wygląda dużo lepiej. Zwracam od razu uwagę na staranne pisanie kodu: dbanie o wcięcia i nawiasy, tworzenie sensownych nazw i unikanie magicznych liczb zastępując je stałymi lub "przezwiskami" (aliasami dyrektywy #define). Indeksy tablicy można potraktować jako kolejne czujniki na obwodzie robota. W razie zmian w sprzęcie (dołożeniu/usunięciu czujników) łatwo można zmienić program, tak aby pasował do nowego układu. Co jednak, gdyby nie dało się tak dobrać wyprowadzeń Arduino, tak by piny zgadzały się z indeksami tablicy? Przyda się tu koncepcja tablicowania stałych. LUT – Lookup Table Tablicowanie zwane po angielsku Lookup Table pozwana na zastąpienie funkcji tablicą. Argumenty funkcji zamieniane są w indeksy tablicy, zaś wyniki funkcji są w wartościach komórek przyporządkowanych do indeksów. Wygląda to podobnie do opisu funkcji nauczanego w szkole: Po lewej stronie są indeksy tablicy, po prawej wartości elementów kryjących się pod tymi indeksami. Jak to może wyglądać w praktyce? Wróćmy do problemu postawionego w poprzednim przykładzie. Załóżmy, że czujniki w robocie są trochę chaotycznie połączone. Zanotowaliśmy połączenia, ale co z tym zrobić? nr czujnika, pin Arduino 0 , 4 1 , 5 2 , 7 3 , 8 4 , 3 5 , 6 6 , 11 7 , 10 Zainicjujmy tablicę przechowującą numery wyprowadzeń Arduino, do których podpięte są czujniki zgodnie z powyższymi notatkami: int sensors_pins[SENSORS_COUNT] = {4, 5, 7, 8, 6, 3, 11, 10}; Czy liczba w nawiasach kwadratowych jest potrzebna? Nie, gdyż kompilator sam by się domyślił jaki jest rozmiar tablicy na podstawie liczby wartości ujętych w klamrach. Jawne wpisanie liczby elementów pomaga uniknąć problemów, bo kompilator sam może wykryć nieścisłość. Jak się później dowiesz może to być też wykorzystane do innych celów. Warto przypomnieć: liczba elementów tablicy statycznej (czyli takiej której rozmiar się nie zmienia) musi być w jakiś sposób znana na etapie kompilacji. Tak przygotowana tablica umożliwia nabudowanie pewnego poziomu abstrakcji, tak by nie posługiwać się fizycznymi wyprowadzeniami, a zacząć posługiwać się indeksami. Teraz aby odczytać dane z czujników wystarczy nieco poprawić poprzednią funkcję: #define SENSORS_COUNT 8 int sensors[SENSORS_COUNT]; int sensors_pins[SENSORS_COUNT] = {4, 5, 7, 8, 6, 3, 11, 10}; void read_all_sensors() { for(int i = 0; i < SENSORS_COUNT; ++i) sensors[i] = digitalRead(sensors_pins[i]); } I gotowe! W zmiennej sensors są odczyty czujników ułożonych zgodnie z fizycznym rozkładem w robocie (a nie narzucone niekoniecznie oczywistymi połączeniami). Zastosowanie tablicowania pomogło naprawić niedopatrzenie i sprawiło, że program jest bardziej elastyczny i szybszy. Zdarza się, że nie możemy użyć pewnych wyprowadzeń Arduino, bo niektóre pełnią specyficzną rolę (np. realizują komunikację UART) – wtedy tablicowanie może okazać się niezbędne. Kapitanie... do brzegu – zadania z kursu Pisałem na wstępie o zadaniach z kursu i obiecuję, że zaraz jedno zrobimy, na początek coś na rozgrzewkę – napiszmy funkcję, która włączy wszystkie diody w linijce LED. Później dojdziemy do tego jak poradzić sobie z dość trudnym przykładem z czujnikiem ultradźwiękowym. Załóżmy, że wyprowadzenia zapisaliśmy w postaci etykiet o nazwach LED_0 – LED_5. Gdyby nie używać tablic to kod wyglądałby jakoś tak: void turn_on_leds() { digitalWrite(LED_0, HIGH); digitalWrite(LED_1, HIGH); digitalWrite(LED_2, HIGH); digitalWrite(LED_3, HIGH); digitalWrite(LED_4, HIGH); digitalWrite(LED_5, HIGH); } Nie jest źle, ale da się lepiej! Używając pętli i tablicy będzie to wyglądać tak: int LED_pins[] = {LED_0, LED_1, LED_2, LED_3, LED_4, LED_5}; void turn_on_leds() { for(int i = 0; i < LEDS_COUNT; i++) digitalWrite(LED_pins[i], HIGH); } Brak mozolnego kopiowania na pewno uprzyjemni pisanie takich kodów 🙂 Zadanie 2 – prosta animacja Teraz coś nieco trudniejszego – program ma wyświetlać animację, w której zapalane i gaszone są kolejne diody. Tu już bardzo ważne jest poprawne indeksowanie. Bez wykorzystania tablic kod byłby bardzo powtarzalny i nieprzyjemny w modyfikacji. Stosując tablicę i pętlę zajmie to tylko kilka linijek kodu! for(int i = 0; i < LEDS_COUNT; i++) { digitalWrite(LED_pins[i], HIGH); delay(250); digitalWrite(LED_pins[i], LOW); delay(250); } I tyle, kolejne diody są zapalane i gaszone. Kod ten można umieścić wewnątrz pętli loop() aby wykonywał się w nieskończoność zapętlając animację. Zadanie 3 – wyświetlacz słupkowy Wreszcie obiecane zadanie 🥳 Na tapetę weźmy zadanie 9.3 z kursu Arduno 1 i części dotyczącej ultradźwiękowego czujnika przeszkód, którego teść brzmi: Załóżmy, że doszliśmy do momentu, gdzie mamy wyznaczoną odległość od przeszkody, np. w zmiennej: long dist; // wyrażone w cm i chcemy napisać funkcję wizualizującą wartość na linijce 6 diod. Zadanie rozpocznijmy od deklaracji funkcji: void show_dist_on_bargraph(long distance_cm); Znowu przypominam, aby tworzyć nazwy funkcji i zmiennych, które same z siebie coś mówią 😉 po polsku może to być wyzwaniem, bo nie użyjemy polskich znaków, ale po angielsku "feel free". Teraz czas na definicję (opisanie ciała funkcji). Mamy 6 diod więc trzeba jakoś zawęzić nasze wartości. Musimy poczynić pewne założenia: załóżmy, że interesuje nas zakres od 10 do 40 cm, jeżeli wartość jest poza zakresem to ma przyjąć wartości graniczne: 10 albo 40. Do dzieła! Od razu dodajmy kilka stałych, żeby nie używać w kodzie magicznych liczb: #define MIN_DIST 10 #define MAX_DIST 40 void show_dist_on_bargraph(long distance_cm) { if (distance_cm < MIN_DIST) distance_cm = MIN_DIST; if (distance_cm > MAX_DIST) distance_cm = MAX_DIST; int lit_leds = map(distance_cm, MIN_DIST, MAX_DIST, 0, LEDS_COUNT); } Wewnątrz funkcji przekazywana jest domyślnie kopia zmiennej distance_cm (przekazanie przez wartość), więc możemy śmiało ją modyfikować. Uzyskana zmienna lit_leds informuje o indeksie zapalonej diody. Bez znajomości tablic dalsza część kodu może wyglądać w ten sposób: switch(lit_leds) { case 0: digitalWrite(LED_0, HIGH); digitalWrite(LED_1, LOW); digitalWrite(LED_2, LOW); digitalWrite(LED_3, LOW); digitalWrite(LED_4, LOW); digitalWrite(LED_5, LOW); break; case 1: digitalWrite(LED_0, LOW); digitalWrite(LED_1, HIGH); digitalWrite(LED_2, LOW); digitalWrite(LED_3, LOW); digitalWrite(LED_4, LOW); digitalWrite(LED_5, LOW); break; // ... } Już samo dokończenie pisania tego stworka budzi zastrzeżenia... spróbujmy to poprawić! Dysponując tablicą LED_pins zapalenie wybranej diody jest banalnie proste! for(int i = 0; i < LEDS_COUNT; i++) digitalWrite(LED_pins[i], LOW); digitalWrite(LED_pins[lit_leds], HIGH); I zrobione! Odległość jest reprezentowana przez punkt na linijce LED. Takie podejście ma też inną, bardzo ważną zaletę – jest elastyczne. Łatwo można zmienić kod tak by wyświetlać wszystkie LEDy do wybranego punktu włącznie, tzw. bargraph: for(int i = 0; i < LEDS_COUNT; i++) if(i < lit_leds) digitalWrite(LED_pins[i], HIGH); else digitalWrite(LED_pins[i], LOW); // lub for(int i = 0; i < LEDS_COUNT; i++) digitalWrite(LED_pins[i], i < lit_ledds ? HIGH : LOW); Czy nie ładniej? Jeżeli wiemy, że tablica zawiera niezmienne elementy możemy dodać przed typem danych słowo kluczowe const, w ten sposób stworzymy tablicę 6 stałych zabezpieczoną przed modyfikacją: const int LED_pins[] = {LED_0, LED_1, LED_2, LED_3, LED_4, LED_5}; Podsumowanie W tej części przybliżyłem temat tablic. Temat został poruszony od strony praktycznej, więc tę część polecam tym, którzy chcą rozwiązać jakieś zadanie i od razu zobaczyć efekty. W kolejnych częściach przyjrzymy się bardziej wnikliwym tematom.
  21. Witam. Od niedawna zacząłem interesować się programowaniem oraz Arduino, i chciałbym zrobić takie coś (ale kompletnie nie wiem jak to zrobić): Klikam przycisk => silnik kręci się w prawo (3s) => przerwa (2s) => silnik kręci się w lewo (3s) => Stop. I kiedy kliknę przycisk znowu to samo od poczatku. *Arduino Leonardo *Silnik (zwykły, chiński z samochodu RC) Proszę o pomoc, będę bardzo wdzięczny.😉
  22. Cześć, Próbuję przesłać dane z telefonu (używam gotowej aplikacji bluetooth terminal), na arduino. Schemat podłączenia wygląda następująco Oraz Kod SoftwareSerial BTSerial(2, 3); void setup() { Serial.begin(9600); BTSerial.begin(9600); pinMode(2, INPUT); pinMode(3, OUTPUT); } void loop() { if (BTSerial.available()) { char received = BTSerial.read(); Serial.print("Received: "); Serial.println(received); } delay(100); } W momencie gdy przesyłam dowolną wartość z telefonu wyskakuje mi coś takiego: Z tego co się orientuje powinienem otrzymać po prostu znac ASCII Gdy zamieniłem to na INT, pojawia się coś takiego Próbowałem znaleźć podobny problem na internecie, ale nigdzie nic nie widzę, czy to oznacza uszkodzony moduł bluetooth? Pozdrawiam i z góry dziękuję za pomoc
  23. Cześć, przedstawiam projekt dla fanów drinków dla których zachowanie proporcji jest niezmiernie istotne. Postanowiłem, że robot będzie nalewał trzy różne substancje (jak na domowego barmana w zupełności wystarczy). Wykorzystałem w tym celu pompki membranowe, które są stosunkowo tanie, jednak warto użyć zaworów zwrotnych przy każdym z węży ponieważ "na postoju" nie są szczelne i ciecze powoli kapią z dysz robota. Przy innym projekcie zdarzyło mi się, że pompka była nieszczelna, więc w tym zamontowałem je w miejscu w którym ewentualny wyciek nie narobi szkód. We wcześniejszych konstrukcjach ustawiałem lanie zależne od zadanego czasu, jednak po testach (wspierając się wagą kuchenną) stwierdziłem, że odmierzane wartości nie są powtarzalne. Wtedy postanowiłem dodać wbudowaną wagę. Użyłem belki tensometrycznej do 1kg oraz wzmacniacza HX711. Robot zasilany jest zasilaczem wtykowym 12V a jego serce to Arduino Mega - ze względu na zapotrzebowanie na dużą liczbę wejść/wyjść. Do sterowania pompek użyłem gotowego Shielda - sterownika L293D – może sterować czterema silnikami DC i zajmuje mało miejsca w obudowie, a chciałem żeby konstrukcja była zwarta i jak najmniejsza. Początkowo użyłem wyświetlacza 2x16, jednak wyświetlane dane nie były tak przejrzyste, jakbym tego chciał więc zamontowałem wyświetlacz 4x16. Teraz każdy składnik ma swoją nieskróconą nazwę i ma swoją osobną linijkę. Wartości danych substancji ustawia się przy pomocy trzech potencjometrów. Wyskalowałem wartość każdego od 0 do 150g. W przypadku przekroczenia sumy trzech substancji powyżej 250g, zamontowane diody RGB świecą na czerwono (jest to tylko sygnał ostrzegawczy, nie blokuje nalewania). Diody te również „ubarwiają” proces tworzenia drinka (efekt widoczny na załączonym filmie). Po ustawieniu ulubionych proporcji wciskamy START i nalewanie odbywa się automatycznie, przed nalaniem każdego składnika waga samoczynnie się taruje. Można również korzystać z manualnego nalewania oraz tarowania, gdy stworzony drink nie spełnia wszystkich wymagań. Obudowę wydrukowałem na drukarce 3D. Składa się z wielu elementów, żeby był łatwiejszy dostęp do podzespołów oraz żeby pojedynczy wydruk krócej się drukował. Umieściłem z jednej strony okno rewizyjne żeby móc się w razie potrzeby podłączyć do Arduino. Części których użyłem do budowy robota: Arduino ATmega2560 - 1szt. wyświetlacz LCD 4x20 - Niebieski - ze sterownikiem kompatybilnym z HD44780 - QC2004A konwerter I2C do wyświetlacza LCD HD44780 wzmacniacz do belki tensometrycznej HX711 - 1szt. belka tensometryczna 1kg - 1szt pompa do cieczy 12V 110l/h - 7mm – 3szt moduł sterownika silnika L293 UNO MEGA shield - 1szt moduł diody RGB 5V - 2szt, wtyk DC 2,1/5,5mm z zaciskami skręcanymi – 1szt. gniazdo DC 2,1/5,5 do obudowy plastikowe -1szt. zasilacz wtyczkowy UMEC impulsowy 12V 30W 2,5A - 1szt. przełącznik kołyskowy 15x10mm – 1 szt przewody połączeniowe przycisk podświetlany - 1szt. przycisk okrągły monostabilny chwilowy czerwony - 4szt. potencjometr liniowy 1K – 3szt. .zawór zwrotny - 3szt. wężyk silikonowy 1,5m Oto kod, z którym się najdłużej męczyłem, ale działa 😉 #include <HX711.h> #include <Wire.h> #include <LiquidCrystal_I2C.h> #include <AFMotor.h> #define I2C_ADDR 0x3F #define start 52 #define manpom1 50 #define manpom2 46 #define manpom3 44 #define tara 48 #define redl 24 #define bluel 22 #define greenl 26 #define redr 30 #define bluer 28 #define greenr 32 HX711 waga; LiquidCrystal_I2C lcd(I2C_ADDR, 2, 1, 0, 4, 5, 6, 7); int led = 13; float ciezar; int ml1 = 0; int ml2 = 0; int ml3 = 0; int czasMigania = 250; int predkoscNalewania = 200; char odczyt[8]; AF_DCMotor pompa1 (3); AF_DCMotor pompa2 (2); AF_DCMotor pompa3 (1); void setup() { Serial.begin(9600); pinMode(start, INPUT_PULLUP); pinMode(manpom1, INPUT_PULLUP); pinMode(manpom2, INPUT_PULLUP); pinMode(manpom3, INPUT_PULLUP); pinMode(tara, INPUT_PULLUP); pinMode(redl, OUTPUT); pinMode(bluel, OUTPUT); pinMode(greenl, OUTPUT); pinMode(redr, OUTPUT); pinMode(bluer, OUTPUT); pinMode(greenr, OUTPUT); lcd.begin(20, 4); lcd.setBacklightPin(3, POSITIVE); digitalWrite(led, HIGH); lcd.setBacklight(HIGH); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Witaj!"); lcd.setCursor(0, 1); lcd.print("Chlapnij sobie!"); lcd.setCursor(13, 3); lcd.print("Kasztan"); delay(1000); pinMode(led, OUTPUT); waga.begin(A1, A2); waga.set_scale(419341.0 / 200.0); waga.tare(5); pompa1.setSpeed(predkoscNalewania); pompa2.setSpeed(predkoscNalewania); pompa3.setSpeed(predkoscNalewania); } void loop() { pomiar(); ekran(); if ((ml1 + ml2 + ml3) > 250) { czerwona(); delay(500); ciemno (); } else { ciemno(); } if (digitalRead(start) == LOW) { waga.tare(); ekran(); Serial.println("zeruje"); if (ml1 >= 0) { waga.tare(10); pomiar(); ekran(); Serial.println("nalewam promile"); while (ciezar <= ml1) { pomiar(); ekran(); pompa1.run(FORWARD); led1(); } pompa1.run(RELEASE); Serial.println("promile przerwa"); delay(100); if (ml2 >= 0) { waga.tare(10); pomiar(); ekran(); Serial.println("nalewam rozcienczacz"); while (ciezar <= ml2) { pomiar(); ekran(); pompa2.run(FORWARD); led2(); } pompa2.run(RELEASE); Serial.println("rozcienczacz przerwa"); delay(100); if (ml3 >= 0) { waga.tare(10); pomiar(); ekran(); Serial.println("nalewam kwas"); while (ciezar <= ml3) { pomiar(); ekran(); pompa3.run(FORWARD); led3(); } pompa3.run(RELEASE); Serial.println("kwaas przerwa"); delay(100); } } } waga.tare(); pomiar(); kolorowo(); ciemno(); } if (digitalRead(manpom1) == LOW) { pompa1.run(FORWARD); led1(); } else { pompa1.run(RELEASE); } if (digitalRead(manpom2) == LOW) { pompa2.run(FORWARD); led2(); } else { pompa2.run(RELEASE); } if (digitalRead(manpom3) == LOW) { pompa3.run(FORWARD); led3(); } else { pompa3.run(RELEASE); } if (digitalRead(tara) == LOW) { waga.tare(10); } } void pomiar() { ml1 = analogRead(A5); ml1 = map(ml1, 1020, 0, 0, 150); ml2 = analogRead(A4); ml2 = map(ml2, 1020, 0, 0, 150); ml3 = analogRead(A3); ml3 = map(ml3, 1020, 0, 0, 150); ciezar = waga.get_units(); dtostrf(ciezar, 5, 0, odczyt); } void ekran() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("1.Alkohol:"); lcd.print(ml1); lcd.setCursor(0, 1); lcd.print("2.Dodatek:"); lcd.print(ml2); lcd.setCursor(0, 2); lcd.print("3.Kwas:"); lcd.print(ml3); lcd.setCursor(0, 3); lcd.print("Waga:"); lcd.print(odczyt); lcd.print("g"); Serial.println(ml1); Serial.println(ciezar); } void ciemno() { digitalWrite(redl, LOW); digitalWrite(greenl, LOW); digitalWrite(bluel, LOW); digitalWrite(redr, LOW); digitalWrite(greenr, LOW); digitalWrite(bluer, LOW); } void czerwona () { digitalWrite(redl, HIGH); digitalWrite(greenl, LOW); digitalWrite(bluel, LOW); digitalWrite(redr, HIGH); digitalWrite(greenr, LOW); digitalWrite(bluer, LOW); } void zielona () { digitalWrite(redl, LOW); digitalWrite(greenl, HIGH); digitalWrite(bluel, LOW); digitalWrite(redr, LOW); digitalWrite(greenr, HIGH); digitalWrite(bluer, LOW); } void niebieska () { digitalWrite(redl, LOW); digitalWrite(greenl, LOW); digitalWrite(bluel, HIGH); digitalWrite(redr, LOW); digitalWrite(greenr, LOW); digitalWrite(bluer, HIGH); } void kolorowo () { digitalWrite(redl, HIGH); delay(czasMigania); digitalWrite(greenl, HIGH); delay(czasMigania); digitalWrite(bluel, HIGH); delay(czasMigania); digitalWrite(redr, HIGH); delay(czasMigania); digitalWrite(greenr, HIGH); delay(czasMigania); digitalWrite(bluer, HIGH); delay(czasMigania); delay(500); } void led1 () { digitalWrite(redl, HIGH); digitalWrite(greenl, HIGH); digitalWrite(bluel, LOW); digitalWrite(redr, HIGH); digitalWrite(greenr, HIGH); digitalWrite(bluer, LOW); } void led2 () { digitalWrite(redl, LOW); digitalWrite(greenl, HIGH); digitalWrite(bluel, HIGH); digitalWrite(redr, LOW); digitalWrite(greenr, HIGH); digitalWrite(bluer, HIGH); } void led3 () { digitalWrite(redl, HIGH); digitalWrite(greenl, LOW); digitalWrite(bluel, HIGH); digitalWrite(redr, HIGH); digitalWrite(greenr, LOW); digitalWrite(bluer, HIGH); } Link do filmu:
  24. Cześć, chcę zrobić inwerter. W tym celu najpierw chciałem się nauczyć obsługiwać driver IR2110 z dwoma MOSFETami typu N IRFP250 w układzie półmostka. W celu nauki chciałem jako odbiornik podłączyć żarówkę i zmieniać jej jasność poprzez zmianę wypełnienia sygnału PWM z arduino na wejścia HIN i LIN drivera. Niestety po włączeniu układu żarówka świeci się bardzo słabo i można powiedzieć, że praktycznie nie reaguje na zmiany sygnału PWM z arduino. Całość połączyłem tak jak na schemacie w załączniku. Użyłem kondensatorów ceramicznych 100 nF, diody UF4007 i rezystorów 100 Ohm. Dodatkowo wejścia HIN i LIN zwarłem tak, aby móc sterować oboma MOSFETami przy użyciu drivera za pomocą jednego sygnału PWM z arduino. Wypełnienie sygnału PWM reguluje za pomocą potencjometru. Kod arduino: #define width_pin A2 int analog_width = 0; int width; void setup() { Serial.begin(9600); digitalWrite(11, OUTPUT); } void loop() { analog_width = analogRead(width_pin); width = map(analog_width, 1023, 0, 255, 0); analogWrite(11, width); Serial.println(width); } Dla jasności również powiem co konkretnie podłączyłem pod poszczególne wejścia. Vdd - 5V z Arduino HIN i LIN - jeden i ten sam sygnał PWM z Arduino SD - połączone do minusa zasilania Vss - połączone do minusa zasilania HO - bramka górnego MOSFETa LO - bramka dolnego MOSFETa COM - połączone z minusem zasilacza i źródłem dolnego MOSFETa Vcc - 12V z zasilacza Vs - połączone ze źródłem górnego MOSFETa Vb - połączone przez diodę UF4007 z 12V z zasilacza Dodatkowo pomierzyłem napięcia układu: LO i COM - 3,2 V HO i COM - 7,3 V Vdd i Vss - 4,97 V Bramka i źródło dolnego MOSFETa - 3,2 V Źródło i dren dolnego MOSFETa - 6,9 V Bramka i źródło górnego MOSFETa - 0 V Źródło i dren górnego MOSFETa - 0,03 V Czy dobrze połączyłem układ? Co może powodować, że on nie działa i jak go poprawnie połączyć? Czy wymiana górnego MOSFETa na MOSFET typu P naprawi mój problem? Mam nadzieję, że ktoś pomoże mi rozwiązać ten problem bo już długo tkwie w tym samym miejscu :c
  25. Miniaturowy Tetris z pięcioma przyciskami, głośniczkiem Piezo i ekranem Oled 128×64. To wszystko przy zaledwie sześciu dostępnych GPIO jednodolarowego Digisparka/ATtiny85. Moja wersja kodu dodaje kilka ulepszeń, które znacząco zmieniają odbiór całej gry 🙂 (opisane niżej), a koszt budowy całego zestawu wynosi około 15 zł. ATtiny Tetris Gold Multi Button obsługuje narastający poziom trudności (przyspieszanie opadania wraz z usuwaniem kolejnych linii), informację o klocku pojawiającym się w kolejnej turze, podpowiedź o pozycji klocka w dolnej partii planszy, pełną pseudo-losowość doboru klocków, dźwięki i temat muzyczny z oryginalnej gry. Poniżej mój filmik prezentujący ten układ w działaniu: W filmie można zobaczyć krótkie urywki z rozgrywki, proces instalacji szkicu w urządzeniu oraz pełny, pięciominutowy gameplay. Sprzęt Jakiś czas temu zainteresowały mnie klasyczne gry retro dla ATtiny85, ale w repo Attiny-Arduino-Games wszystkie były zaledwie dwu-przyciskowe. Tetris wymagał nieco więcej logiki, ale i tak obsługiwało się go przez naciskanie lub przytrzymywanie jednego z tylko dwóch przycisków, co generowało błędy i nie było zbyt wygodne. Z czasem jednak pojawiła się w repo wersja Tetris Multi Button, w której rozwiązano ten problem wykorzystując odpowiednią kombinację rezystorów i przycisków, dzięki czemu podłączono trzy przyciski pod jeden pin mikrokontrolera. Nie posiadam czystego ATtiny85, ale miałem na stanie Digisparka z tym chipem i postanowiłem złożyć to urządzenie. To chyba pierwsze nagranie wideo takiego zestawu - w sieci brak w sieci brak filmów i opisów wykonania pełnej, pięcio-przyciskowej wersji. Żeby się nie pogubić podczas montażu 🙂 , korzystając z rozpiski pinów Digisparka i ATtiny85 przeniosłem dostarczony ze sketchem poniższy schemat: na lekko chaotyczną wersję graficzną dla stykowej płytki prototypowej i Digisparka zasilanego baterią 9V: a przy okazji też dla czystego ATtiny85 zasilanego baterią 3V: Poszczególne przyciski odpowiadają za: start nowej gry lub restart aktualnie rozgrywanej, włączenie/wyłączenie układu, obrót, przyspieszenie opadania, przesunięcie w lewo, przesunięcie w prawo. Przytrzymanie przycisku opadania i włączenie restartu gry aktywuje tryb ducha, a przytrzymanie przycisku opadania razem z przyciskiem obrotu podczas restartu ustawia trudny poziom gry, wypełniając dodatkowo błędnie klockami część planszy. Po aktywacji tych opcji grę należy uruchomić przyciskiem przyspieszonego opadania. Wersja na czystym ATtiny85 potrzebuje tylko 3V, bo sketch jest pisany dla obniżonego do 8MHz taktowania zegara. Wersję dla Digisparka zasilam poprzez pin VIN 9V baterią i przy takim poborze wystarczy jej na baaardzo długo. Wymagane do działania 5V przenoszę dodatkowym, pustym goldpinem na lewą stronę płytki prototypowej za pomocą dziesięciocentymetrowego przewodu połączeniowego żeńsko-męskiego. Po zlutowaniu powyższego układu w połączeniu z gołym ATtiny85 i po ubraniu go w miniaturowy brelok do kluczy, koszt (bez baterii) zamknąłby się poniżej 15 złotych. Na Aliexpress ATtiny85 kosztuje 0,80$, ekran Oled 128×64 1.80$, mały piezo buzzer 0,17$, a reszta części to już koszty groszowe. Tym sposobem otrzymujemy pełną grę sterowaną w identyczny sposób, jak w wielkich automatach Arcade lata temu, a do tego zasilaną malutką baterią 3V. Do zestawu zamiast białego ekranu można wybrać ekran niebieski lub żółto-niebieski, ale ponieważ ten ostatni jest dzielony na dwie różne części z odstępem, to nie prezentuje się zbyt atrakcyjnie (co widać na powyższym filmie) i polecam jednokolorowy odpowiednik. Potencjalny brelok mógłby wyglądać tak, jak na obrazku poniżej: Kod Zmiany w kodzie, których dokonałem, nie są duże, ale znaczące w odbiorze gry. Poniżej różnice między moją wersją, dostępną na GitHubie, a oryginałem gry dla ATtiny85: Dodałem losowość doboru klocków – domyślnie ATtiny Tetris generuje tę samą sekwencję klocków w każdej nowej grze, bo użyta funkcja random korzysta w kółko z tej samej tablicy liczb losowych. Programiści nie aktywowali randomSeed, ponieważ wszystkie piny w układzie są już podłączone. Moja wersja przesuwa tablicę liczb pseudo-losowych po każdym rozpoczęciu gry inkrementując seed do EEPROMu i aktywując w ten sposób pełną pseudolosowość doboru klocków (powtarzają się tylko pierwsze dwa). Grając w oryginalną wersję, do piątej linii miałem już ustalony optymalny schemat położenia klocków, przez co gra stawała się nudna. Teraz jest już poprawnie i mam świadomość, że wykorzystanie EEPROMu w randomSeedowaniu ograniczy liczbę rozgrywek do zaledwie 99000 😉 (podana liczba uwzględnia już obecny w kodzie zapis najlepszych wyników również do EEPROMu). Na starcie gry dodałem fragment tematu muzycznego z oryginalnej gry Tetris z 1986 roku – domyślnie brak jakiejkolwiek muzyki w grze. Po wybraniu trybu ducha temat muzyczny jest nieco dłuższy. Dodałem dźwięk opadającego klocka (tylko podczas swobodnego spadania) oraz inny dźwięk dla klocka, który kończy opadanie. Domyślnie dźwięki w tej grze pojawiają się sporadycznie – tylko podczas usunięcia pełnej linii klocków oraz na zakończenie gry. Naprawiłem część błędnie wyświetlanych fontów na ekranie startowym Domyślnie wyłączyłem tryb ducha, który wyświetla podpowiedź o docelowej pozycji klocka. Tryb ducha można aktywować sposobem opisanym powyżej pod konfiguracją przycisków. Ze swoimi zmianami musiałem się zmieścić w sześciu procentach wolnej pamięci. Obecnie pozostaje już tylko 1% wolnego 🙂 Projekcik jest dość interesujący i co najważniejsze, w pełni użyteczny, więc możliwe, że znajdą się osoby chcące zbudować taki układzik. Jako dodatek zamieszczam więc proces instalacji. Instalacja Kod zabiera blisko 100% pamięci ATtiny85, więc na Digisparku nie mieści się bootloader i nie można uploadować sketcha poprzez USB – należy więc skompilować hexa i wgrać go za pomocą programatora ISP. Jako programatora użyłem Arduino wg poniższego schematu: Na Arduino trzeba wgrać dostępny w przykładach Arduino IDE sketch ArduinoISP. Aby poprawnie skompilować tę wersję dla Digisparka należy w Arduino IDE zastąpić dodatkowy adres URL dla menadżera płytek od Digistump JSONem dla czystego ATtiny85, a następnie wybrać płytkę ATtiny25/45/85, procesor ATtiny85, zegar Internal 8MHz. Następnie z menu szkic eksportować skompilowany program, który zostanie wtedy umieszczony w katalogu źródła sketcha. Na koniec zostaje już tylko wgranie hexa na Digisparka poprzez Arduino programem avrdude. Poniżej przykładowe polecenie kopiujące (z fusami) wykonane na macOS, ale w innych systemach wygląda podobnie: /Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avrdude -C/Applications/Arduino.app/Contents/Java/hardware/tools/avr/etc/avrdude.conf -v -pattiny85 -cstk500v1 -P/dev/cu.usbmodem14101 -b19200 -Uflash:w:/Users/username/Documents/ATtiny-Tetris-Gold/ATtiny-Tetris-Gold.ino.tiny8.hex -U lfuse:w:0xF1:m -U hfuse:w:0xD5:m -U efuse:w:0xFE:m Jak przebiega taki proces można obejrzeć na końcu powyższego filmiku. Poniżej też zdjęcie "programatora" i układu obok siebie. W filmie i na zdjęciach do włączania układu korzystam z przełącznika suwakowego SS22T25, ale lepiej sprawdził się przełącznik prosty ESP1010. Napięcia na przyciskach są istotne i podczas gry trzeba uważać aby nie dotykać rezystorów 🙂 Ten problem wyeliminuje się po zlutowaniu układu. Najlepszy wynik gry jest również zapisywany w EEPROMie. Ponieważ głośniczek jest podłączony do pierwszego pinu Digisparka, to dodatkowo otrzymujemy LEDowe sygnały świetlne w momencie odtwarzania dźwięków. Po zakończeniu gry Tetris natychmiast przechodzi w tryb uśpienia z minimalnym poborem prądu, jednak do Digisparka dodałem przełącznik włączający/wyłączający zasilanie dla układu – wersja opierająca się wyłącznie na ATtiny85 nie wymaga wykonania tego kroku. Gra chodzi bardzo płynnie, co widać na powyższym filmie, gdy przytrzymuję przycisk przyspieszonego opadania dla kilku klocków pod rząd. Miniaturowy ATtiny Tetris Gold jest bardzo regrywalny (nie nudzi się) i to niesamowite, że udało się go upchnąć na tanim ATtiny85 przy zachowaniu tak dużej funkcjonalności. Serdecznie polecam montaż tego prostego układu. Powiększenia układów i trochę więcej informacji można znaleźć na moim blogu: http://jm.iq.pl/tetris
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.