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 serdecznie, Jest to mój pierwszy post pewnie i błachy ale szukajką nie znalazłem. Czy do portów 0 oraz 1 na Arduino Uno. Mogę wpiąć wyjścia echo oraz Trig z HC SR04 ? , lub wpiąć to do wyjść analogowych A0-A5?. Buduje maly zamek do drzwi, po naciśnięciu przycisku obraca się serwo, zapala czerwona diodę oraz wyświetla napis zamknięte. I to działa. Jednocześnie gdy drzwi będą zamknięte, dalmierz zmierzy odległość od drzwi i jeżeli będzie ona mniejsza niż ustawione 2cm to również akcja z serwo i wyświetlaczem się wykona. Sęk w tym że brakuje mi dwóch wejść. Został mi wolny jeden port cyfrowy(8) oraz 0 i 1. Jeżeli temat w złym miejscu to z góry przepraszam.
  2. Mam problem z podłączeniem do arduino mega modułu HC-06, w moim układzie mam 2 silniki i jeszcze będę musiał dodać 6 serwomechanizmów co zajmie mi wszystkie piny PWM a mój moduł bluetooth z jakiegoś powodu działa tylko na pinach 2-13. Jak to naprawić? #include <SoftwareSerial.h> #define BT_TX 0 #define BT_RX 1 SoftwareSerial bt(BT_TX, BT_RX); char x; void setup() { dht.begin(); pinMode(2,OUTPUT); pinMode(4,OUTPUT); pinMode(5,OUTPUT); pinMode(7,OUTPUT); bt.begin(9600); } void loop() { digitalWrite(3,255); digitalWrite(6,255); if(bt.available()){ x = bt.read(); bt.println(x); } if(x == 'B'){ digitalWrite(4,HIGH); digitalWrite(5,HIGH); } else if(x == 'F'){ digitalWrite(2,HIGH); digitalWrite(7,HIGH); } else if(x == 'L'){ digitalWrite(2,HIGH); digitalWrite(5,HIGH); } else if(x == 'R'){ digitalWrite(7,HIGH); digitalWrite(4,HIGH); } else if(x == 'S'){ digitalWrite(2,LOW); digitalWrite(4,LOW); digitalWrite(5,LOW); digitalWrite(7,LOW); } delay(100); }
  3. Cześć, na wstępie zaznaczę, że dopiero zaczynam i jestem całkowicie zielony jeśli chodzi o elektronikę. Szukając pomysłu na prezent dla drugiej połówki znalazłem coś takiego [LINK] i postanowiłem, że zrobię swoją własną wersje tylko przy wykorzystaniu Arduino Nano i programowalnych ledów. Chciałbym żeby całość była zasilana bateriami - i tutaj jest główny powód pisania tego postu, bo nie do końca wiem jak to wszystko połączyć. Zaznaczę, że do zasilenia będzie ok. 7-8 ledów. Lepiej użyć baterii 1.5V i przetwornicy step-up, czy baterii 9V i podpiąć się po prostu do pinu VIN? Jeśli istnieją inne opcje to proszę dajcie znać. Zależy mi żeby układ był jak najbardziej oszczędny i optymalny żeby nie trzeba było często wymieniać baterii 😄 Pozdrawiam serdecznie.
  4. 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.
  5. 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ć?
  6. 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 😄
  7. 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:
  8. 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
  9. 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).
  10. 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ć
  11. 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.
  12. 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; } }
  13. 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.
  14. Cześć, może komuś by się chciało napisać coś na temat pisania bibliotek do Arduino? Temat nie poruszany a potrzebny. Baaaaaardzo potrzebny 😁
  15. 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.
  16. 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! 😊
  17. 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?
  18. 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
  19. 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); }
  20. 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ę) 😀
  21. 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); } }
  22. 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!
  23. 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.
  24. 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.😉
  25. 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
×
×
  • 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.