Przeszukaj forum
Pokazywanie wyników dla tagów 'Arduino'.
Znaleziono 544 wyników
-
Miałem opisać zupełnie inne urządzenie - cóż, w ostatniej chwili postanowiłem coś poprawić i wyskoczyło parę niedogodności. Ponieważ w międzyczasie udało mi się dokończyć mój odtwarzacz do książek - więc pozwolę sobie go przedstawić: oto Lekton. Dla tych którzy pamiętają poprzedną wersję: zrezygnowałem z określenia "czytak" z uwagi na istnienie komercyjnego produktu o tej nazwie, z którym nie mam zamiaru konkurować. Niech więc będzie (jak przystało na maniaka SF) Lekton... Zacząłem od przeanalizowania zalet i wad poprzedniego urządzenia (które zresztą uległo awarii, co prawda dość szybko usuniętej ale jednak). Na pierwszy rzut poszła konstrukcja. Niewątpliwie układ klawiatury był trafieniem w dziesiątkę: tylko dziewięć klawiszy, łatwych do odróżnienia bezwzrokowo, obsługujących najpotrzebniejsze funkcje... No i z zalet to tak praktycznie wszystko. Matrycowe podłączenie klawiatury praktycznie eliminowało możliwość odczytania więcej niż jednego klawisza jednocześnie. W dodatku stanowiąca całość z resztą obudowy klawiatura do jakiegokolwiek serwisowania wymagała rozebrania praktycznie całego urządzenia (a to mi się zdarzyło, bo wskutek upadku urwał mi się jeden z przewodów). Dalej: co prawda udało mi się zmieścić urządzenie w zakładanym rozmiarze paczki papierosów, ale okazało się to nieco bez sensu. Wewnątrz panował straszliwy ścisk, złożenie całości wymagało jakiejś potwornej ekwilibrystyki, rozmiar implikował niewygodne rozmieszczenie poszczególnych modułów których zresztą było za dużo. W dodatku użyty moduł WROVER co prawda miał wystarczającą ilość pamięci (16 MB flash, 8 MB PSRAM z czego praktycznie 4 MB do bezpośredniego użytku) ale konwerter UART/USB już się nie zmieścił i trzeba było użyć dedykowanego zewnętrznego konwertera. Poza tym stwierdziłem, że głośnik jest tu absolutnie niepotrzebny. Mogłem zamienić moduł MAX98357 na PCM5702A, w efekcie otrzymując możliwość zarówno odtwarzania w stereo (o tym potem), jak i podłączenia przycisku w słuchawkach (czego mi w poprzedniej wersji brakowało). Zahaczając nieco o program: Przede wszystkim założenie o trzymaniu wszystkiego w wewnętrznej pamięci flash modułu ESP32 wydawało się dobrym pomysłem - okazało się jednak, że miejsca nawet po kompresji jest trochę za mało. Co prawda teoretycznie obsługa przez interfejs WWW nie wymagała jakichś specjalnych dedykowanych aplikacji - jednak samo przygotowanie książki jej wymaga, co czyni ów pomysł niespecjalnie trafnym. Dlatego postanowiłem wrócić do pierwotnej koncepcji karty microSD. Dzięki temu mogłem ograniczyć wymagania co do pojemności pamięci flash do 8 MB (przy czym należało pamiętać, że ponad 4MB zajmuje partycja z głosem Mbroli), a tym samym użycie miniaturowej płytki XIAO S3. Tak więc postanowiłem nie opierać się na poprzednim projekcie. Ponieważ największym elementem był akumulator 5000 mAh - wewnętrzna szerokość wyszła mi 75mm. Pozostałe wymiary dopasowywałem na bieżąco w trakcie projektowania. Zacząłem od klawiatury. Postanowiłem zrobić z niej oddzielny moduł (zawierający również gniazdko słuchawkowe) po prostu wsuwany w odpowiednie szczeliny w obudowie. Jako że długość gniazda wyznaczała rozmiar modułu, nie musiałem bawić się w miniaturyzację. Na kawałku taniej płytki uniwersalnej umieściłem klawisze i wyłącznik, na drugiej (takiej porządniejszej) ekspander MCP23017 Wyszło to tak: Dzięki ekspanderowi mogłem podłączyć wszystkie klawisze oddzielnie oraz przycisk na słuchawkach. Oprócz scalaka są tak tylko dwa rezystory - jeden 10k jako pullup do resetu, drugi 3.3k do przysisku słuchawek. Całość łączy się pięcioma przewodami z główną płytką. Jako że z założenia konstrukcja miała być raczej prowizorycznym prototypem - nie bawiłem się w jakieś wymyślne płytki i miniaturyzacje. Znów kawałek uniwersalnej, na nim XIAO wetknięty w dwa żeńskie goldpiny (żeby można było później wyjąć) i dekoder I2S (co prawda wlutowany, ale pięć pinów wystarczy dmuchnąć hotairem). Dodatkowo rezystory dzielnika i kondensatorek do pomiaru stanu akumulatora - i prowizorka wyszła tak: Oddzielnie umieściłem moduł DS3231 (ten z EEPROM-em) i gniazdo karty microSD (miałem takie do Wemosa D1 Mini, wystarczyło dociągnąć jeden drucik żeby mieć wszystkie piny po jednej stronie): Docelowo miało to być umieszczone nad akumulatorem, czyli miejsca miałem dużo... I tu uwaga: mimo że starałem się zrobić to z gotowych modułów, jednego nie mogłem przeskoczyć: pamięci FRAM. Postanowiłem po prostu przylutować ją "na plecach" EEPROM-u moduły zegarka, zwierając linie adresowe do masy. W ten sposób miałem tam dwie pamięci: EEPROM na adresie 0x57 i FRAM na 0x50. Obudowa po umieszczeniu w niej akumulatora (widać, skąd takie a nie inne wymiary wygląda tak): A zmontowana całość razem z płytką główną i kłębowiskiem kabli: Teraz wystarczyło wydrukować górną część obudowy i urządzenie było gotowe do testów (przy okazji w zdjęciu zawarta jest informacja że nie jestem wielbłądem): W międzyczasie oczywiście powstawał program. Nie będę wnikał w szczegóły (kod w załączniku, każdy może obejrzeć), powiem tylko jakie są możliwości: Odtwarzanie audiobooków z możliwością przyspieszenia (pliki sonicFiler.cpp i sonicFilter.h w katalogu src są przystosowane do współpracy z popularną biblioteką ESP8266Audio) Odtwarzanie ebooków za pomocą syntezatora Mbrola Głosowe komunikaty (również generowane Mbrolą) Możliwość zrobienia zakładki (jednej na książkę) Autozakładka czyli możliwość zrobienia "redo" jeśli coś się pogubiłem w przewijaniu Możliwość przechowywania 511 książek (audio i ebook razem) z zapamiętywaniem ostatnio słuchanej książki oraz pozycji w każdej książce Zapis pozycji w pamięci FRAM co kilka sekund (audiobook) lub co zdanie (ebook) umożliwia powrót do ostatnio słuchanej pozycji nawet w przypadku nieoczekiwanego wyłączenia zasilania Ebooki podzielone na zestawy po max. 16 pozycji, ilość zestawów nieograniczona Przewijanie w trakcie odtwarzania jest możliwe (czasowe przy audio, zdanie/akapit przy ebookach) ale bez przeskoczenia granic pliku mp3 (audio) czy rozdziału (ebook), jast to możliwe tylko w trybie pauzy Podanie ogólnych lub szczegółowych informacji o bieżącej pozycji czytania Zegarek (godzina i minuta) oraz stan akumulatora (w procentach) Do wgrania audiobooka trzeba wyjąć kartę i przełożyć do czytnika. Za pomocą programu mkaudiobook mogę wgrać audio na kartę, z możliwą korektą (mono 65 kbps dla czytanych książek lub kopia dla słuchowisk stereo). Program przy okazji tworzy pliki informacyjne dla każdego pliku mp3 - nie jest to konieczne, Lekton potrafi sobie sam te pliki utworzyć, ale wtedy pierwsza próba odtworzenia rozpoczyna się od stworzenia owych plików, i ma się czas na zrobienie kawy. Co prawda nie przewiduję takiej sytuacji - ale pozostawiam sobie możliwość przegrania audiobooka z komputera na którym nie mogę uruchomić programu. Ebook (w odpowiednim formacie tekstowym, można go zrobić dowolnym edytorem tekstu w ciągu paru chwil z pliku txt ebooka) też może być przegrany bezpośrednio, ale wygodniejsze jest przegranie za pomocą dedykowaniej aplikacji. Aplikacja (Python3 i Gtk+ 3) umożliwia wygodne wgrywanie/usuwanie książek i robienie porządków na karcie poprzez połączenie do USB. Ogólnie jest tam tego trochę więcej, ale nie będę Was zanudzać szczegółami. Oczywiście jak najszybciej wziąłem się do testowania. Już następnego dnia wyskoczył problem: czy ja to coś wyłączyłem czy może leży sobie i podgryza akumulator? Mógłbym oczywiście dodać jakieś ledy ale w porę się opamiętałem: przecież na dekoderze I2S świeci sobie na czerwono śliczna dioda, wystarzy dodać okienko przez które będzie ją widać. Drugi problem był raczej natury usability: identyczne przyciski utrudniały szybkie znalesienie właściwego. Tak więc przycisk START zrobiłem nieco większy i wyczuwalny pod palcem, przy okazji poprawiłem suwak wyłącznika (na zdjęciu wydrukowane żółtym filamentem), dorobiłem okienka nad diodą I2S i przy okazji nad kontrolką ładowania XIAO - i wyszło to tak: Teraz mogłem wziąć się do dłuższych testów. Jak zwykle wyskoczyły jakeś drobne poprawki w programie, ale to było do przewidzenia i poprawiałem na bieżąco. I pewnie bym to w takiej postaci pozostawił, gdyby nie dwie sprawy: po pierwsze urządzenie było jednak nieco za duże, po drugie nie wziąłem pod uwagę drobiazgu - mianowicie naładowanie do pełna akumulatora za pomocą rachitycznej ładowareczki XIAO trwałoby jakiś tydzień... Ponieważ nie miałem jakiegoś porządnego modułu z gniazdem microSD, a nie bardzo miałem ochotę na lutowanie jakiegoś milimetrowego rastra (cóż, oczy już nie te co kiedyś) poszukałem trochę po sieci i znalazłem coś co wydawało mi się idealne do moich celów: produkcji Adafruit płytka z gniazdem, którą montuje się pod płytką XIAO. Trochę musiałem poczekać, bo botlandowe "kilka dni" zmieniło się w dwa tygodnie, ale wreszcie zamówiłem, przyszła... I po rozpakowaniu myślałem że mnie coś strzeli. Nauczka na przyszłość: zanim coś zamówisz obejrzyj sobie duże zdjęcie i nie chrzań, że się nie chce okularów szukać. Okazało się, że jakiś geniusz z Adafruit nie bardzo chyba wiedział jak się obchodzić z chińskimi wynalazkami. Gniazdo jest na płytce umieszczone tak, że kartę trzeba włożyć centralnie na płytce (trochę by to było niewygodne ale ujdzie), to jeszcze od tyłu (nie od strony gniazda USB na XIAO). Po prostu nie zauważyłem, że gniazdo na płytce nie jest umieszczone tak jak myślałem (czyli z przodu przy krawędzi) ale dokładnie odwrotnie! Czyli mój piękny projekt PCB mogłem wyrzucić do kosza i zacząłem się zastanawiać, co z tym fantem zrobić. W desperacji chwyciłem nożyce do laminatu, przyciąłem płytkę tak że mogłaby się zmieścić na PCB... i udało się! Teraz już mi sie nie chciało siadać do projektowania. Przed nosem leżała sobie porządna płytka uniwersalna 70x50 mm, akurat mieściły się obok siebie S3 i to nieszczęsne gniazdko. Rezystorki i kondensator w wersji SMD poszły na spód płytki (na szczęście 0805 bardzo ładnie pasują do rozstawu pól lutowniczych na płytce), parę połączeń kynarem - i gotowe! Mogłem zmniejszyć obudowę do jakichś rozsądnych rozmiarów! Wewnątrz wygląda to tak: Jak widać, cała elektronika zmieściła się nad akumulatorem, co pozwoliło mi na zmniejszenie długości obudowy. Dodałem jeszcze otwór przez który mogę się dostać do wtyczki akumulatora (zrobiłem sobie kiedyś taką uniwersalną ładowarkę do LiPo, przy 1A powinno się przez noc naładować), wydrukowane z polipropylenu zaślepki (wolałbym żeby jakieś paprochy z kieszeni nie dostały się do wnętrza), drugie okienko na drugą diodę XIAO (sygnalizuje stan urządzenia STOP/PAUZA/PLAY) - i w efekcie wygląda to tak: Tym razem kilkudniowe intensywne testy wykazały, że urządzenie w tej wersji jest wygodne i spełnia wszystkie moje oczekiwania. Na zakończenie kilka słów o załączonym pliku Lekton.zip. W kodzie pozostawiłem wyłączone fragmenty. MSC nie udało mi się uruchomić (zresztą w pewnym momencie zrezygnowałem z uwagi na dziwaczne problemy z komunikacją serial w trybier TinyUSB/CDC). Plik wifi pozostawiłem bo jest dość uniwersalny, krótki i być może ktoś będzie potrzebować czegoś podobnego. Plik OpenSCAD-a jest raczej do pooglądania sobie, nie jest stuprocentowo kompletny ale raczej chciałem pokazać w jaki sposób można w tym programie stworzyć projekt w miarę skomplikowanej obudowy. Gdyby ktoś koniecznie chciał uruchomić to u siebie, należy z GitHuba zgrać plik espola_pl1_full.blob i wgrać go do ESP od adresu 0x36E000 (zgodnie z adresem w mbro8nof.csv). W razie jakichkolwiek pytań jestem do dyspozycji.
- 6 odpowiedzi
-
- 13
-
-
Tym razem chciałbym pokazać wam projekt, w którym użyłem nie tylko elementy analogowe ale też i cyfrowe bo zaprojektowałem i wykonałem inteligentną podstawkę pod doniczkę sterowaną Arduino nano. Ten projekt był ciekawą przygodą bo zrobiłem go wraz z moimi kolegami z uczelni gdy już miałem odrobinę większe doświadczenie w elektronice, projektowaniu płytek PCB i druku 3d. Nasza inteligentna podstawka pod doniczkę miała być nie tylko projektem elektronicznym, ale także miała łączyć się z częścią mechaniczną i tak końcowo powstał projekt mechatroniczny. Tym razem projekt pomógł nam upiec dwie pieczenie na jednym ogniu, ponieważ zaliczyliśmy tym dwa osobne przedmioty, jeden który skupiał się na projektowaniu układów elektronicznych, a drugi zaś na druku 3d. Dostaliśmy odpowiednie wytyczne co do wykonania projektu i przeszliśmy do kwestii wymyślenia co tu by zrobić, no i tak po dość szybkiej naradzie padło na inteligentną doniczkę, ale okazało się, że nie byliśmy z tym pomysłem pierwsi i musieliśmy nieco zmodyfikować nasz pomysł. I tak oto stwierdziliśmy, że inteligentnych doniczek już było wiele ale inteligentna podstawka dalej była świeżym tematem. Projektowanie rozpoczęło się standardowo od projektu i symulacji części analogowej projektu w programie LTspice, a jako że ta miała na celu zasymulowanie wyświetlania na diodach LED temperatury powietrza zmierzonej przez czujnik LM35DZ/NOPB przy pomocy komparatorów napięcia, to poszło bardzo sprawnie bo mieliśmy już wiedzę jak taki układ zbudować i zasymulować. Poniżej zamieszczam schemat symulacji z LTspice. Gdy już część analogową mieliśmy z głowy to przeszliśmy do części cyfrowej, tutaj chcieliśmy zrobić pomiar wilgotności gleby przy pomocy czujnika pojemnościowego, pomiar, oraz pomiar temperatury powietrza i wyświetlenie tych informacji na wyświetlaczu LCD 2x16 i oprócz tego chcieliśmy sterować silnikiem krokowym nema17, który miał za zadanie wprawić w ruch podstawkę i obrócić doniczkę, żeby roślina była równomiernie nasłoneczniona z każdej strony. Schemat blokowy działania układu pokazano poniżej: Założenia projektowe: Układ inteligentnej doniczki działa na zasadzie integracji kilku modułów pomiarowych i wykonawczych, które wspólnie monitorują kluczowe parametry środowiskowe i sterują odpowiednimi funkcjami. Czujnik temperatury odczytuje bieżącą temperaturę otoczenia i przekazuje sygnał do komparatorów analogowych. Komparatory porównują odczyty z ustalonymi progami, które można regulować za pomocą potencjometrów. W wyniku tej analizy uruchamiane są odpowiednie kolory diod LED: niebieski sygnalizuje zbyt niską temperaturę, zielony wskazuje optymalny zakres, żółty ostrzega przed zbyt wysoką temperaturą, natomiast czerwony mówi o zawysokiej temperaturze. Dodatkowo, układ zawiera czujnik wilgotności gleby, który mierzy poziom wilgotności i przesyła dane do Arduino Nano. Mikroprocesor Arduino przetwarza te dane i wyświetla je na ekranie LCD. Na wyświetlaczu użytkownik może odczytać aktualny poziom wilgotności gleby (w procentach) oraz temperaturę otoczenia (w stopniach Celsjusza). Dzięki temu układ umożliwia stały nadzór nad warunkami, w jakich znajduje się roślina. Jednym z kluczowych elementów doniczki jest mechanizm obrotu, który jest sterowany przez Arduino Nano. Silnik krokowy odpowiedzialny za obrót aktywuje się w określonych odstępach czasu, co zapewnia równomierne nasłonecznienie rośliny, zwłaszcza w przypadku jednostronnego oświetlenia. Częstotliwość obrotu można dostosować w oprogramowaniu Arduino, co pozwala na elastyczne dostosowanie systemu do indywidualnych potrzeb użytkownika. Cały układ jest w pełni zautomatyzowany, co sprawia, że obsługa doniczki jest intuicyjna i nie wymaga stałego nadzoru. Możliwość regulacji progów temperatury za pomocą potencjometrów oraz programowalność Arduino Nano pozwalają na łatwą personalizację działania systemu. Dzięki integracji funkcji pomiarowych, sygnalizacyjnych, wizualizacyjnych i mechanicznych, doniczka zapewnia kompleksowe wsparcie w dbaniu o rośliny, niezależnie od warunków środowiskowych. Gdy już mieliśmy pomysł na obydwie części, analogową i cyfrową, oraz jak mają ze sobą współpracować to przeszliśmy do zaprojektowania schematu układu, który wyglądał następująco: Zasada działania: Układ sterowany jest za pomocą mikrokontrolera Arduino Nano, który pełni centralną rolę w zarządzaniu wszystkimi podłączonymi peryferiami. Mikrokontroler odbiera dane z czujników wilgotności oraz temperatury (LM35DZ), a także komunikuje się z wyświetlaczem LCD wykorzystującym interfejs I2C. Dodatkowo Arduino steruje ruchem silnika krokowego za pomocą sterownika A4988, co umożliwia realizację automatycznego podlewania rośliny. Stabilne zasilanie całego układu zapewnia regulator napięcia L7805CV, który konwertuje napięcie wejściowe na 5V. W celu poprawy stabilności pracy regulatora zastosowano kondensatory odsprzęgające na jego wejściu i wyjściu. W układzie zastosowano również czujniki temperatury LM35DZ, które dostarczają sygnały analogowe. Sygnały te są porównywane w komparatorach LM339N z napięciami referencyjnymi (REF1-REF8). W przypadku przekroczenia określonych progów temperatury zapalane są odpowiednie diody LED, sygnalizujące stan temperatury. Napięcia referencyjne zostały skonfigurowane za pomocą dzielników rezystorowych, co pozwala na precyzyjne ustawienie progów działania. W kolejnym kroku przeszliśmy do rozmieszczenia elementów na płytce PCB, wyszło to następująco: Rozmieszczenie elementów na płytce PCB zostało zaprojektowane z myślą o minimalizacji długości ścieżek oraz łatwości montażu. Arduino Nano zostało umieszczone centralnie po lewej stronie płytki, co ułatwia dostęp do jego pinów. Sterownik silnika krokowego (A4988) został ulokowany w pobliżu złącza silnika, co minimalizuje długość ścieżek sygnałowych. Regulator napięcia L7805CV z kondensatorami znalazł się w górnej części płytki, zapewniając stabilne zasilanie dla pozostałych elementów. Ścieżki na płytce zostały zaprojektowane w dwóch warstwach: połączenia na górnej warstwie oznaczono kolorem czerwonym, a na dolnej – niebieskim. Grubsze ścieżki odpowiadają za zasilanie, co zapewnia odpowiednią wydajność prądową. W prawej części płytki umieszczono złącza dla czujników wilgotności i temperatury oraz wyprowadzenia dla diod sygnalizacyjnych. Komparatory LM339N oraz diody LED z rezystorami 1 kΩ zostały rozmieszczone w górnej części płytki w sposób pozwalający na ich łatwe podłączenie do napięć referencyjnych i sygnałów sterujących. Projekt został zoptymalizowany tak, aby ułatwić zarówno montaż, jak i podłączanie zewnętrznych elementów, takich jak czujniki, silnik krokowy czy wyświetlacz LCD. Lista użytych elementów: Silnik krokowy nema17 Rezystor 1 kΩ [8szt] Rezystor 2 kΩ [1szt] Rezystor 10 kΩ [7szt] Potencjometr 10 kΩ [8szt] Regulator napięcia LM7805CV Czujnik temperatury lm35DZ Wyświetlacz LCD 2x16 Konwerter I2C dla wyświetlacza LCD HD44780 Pojemnościowy czujnik wilgotności gleby Sterownik silnika krokowego TMC2208 Gniazdo zasilania 2.5/5.5 kondensator 10 uF [1szt] kondensator 100 uF [2szt] komparator uniwersalny LM339N Arduino nano Dioda LED 5mm [8szt] W kolejnym kroku wytrawiliśmy płytkę PCB, następnie wywierciliśmy otwory i przylutowaliśmy elementy. A tak wyglądała płytka z osadzonymi komponentami: Jak już płytka była gotowa przeszliśmy do zaprojektowania obudowy w Fusion360 a następnie wydrukowaliśmy ją na drukarce Bambu P1S. Gdy już została wydrukowana obudowa do jednostki sterującej to trzeba był jeszcze wymyślić i zaprojektować część ruchomą podstawki z silnikiem i przekładnią, a to już nie było takie łatwe, gdyż jednak chcieliśmy żeby w miarę estetycznie to wyglądało, po dobrych kilku dniach spędzonych na szukaniu inspiracji powstała część ruchoma przedstawiona na kolejnych zdjęciach: Całość złożona wyglądała w ten sposób: Podsumowanie: Projekt inteligentnej podstawki pod doniczkę stanowi kompleksowe połączenie elektroniki analogowej i cyfrowej z elementami mechanicznymi oraz druku 3D, tworząc w efekcie w pełni funkcjonalny system mechatroniczny. Został on zrealizowany zespołowo w ramach zajęć akademickich i pozwolił jednocześnie zaliczyć dwa przedmioty: projektowanie układów elektronicznych oraz druk 3D. Dzięki zdobytemu wcześniej doświadczeniu możliwe było świadome podejście do projektowania PCB, programowania mikrokontrolera oraz konstruowania elementów mechanicznych. Układ oparto na Arduino Nano, które integruje dane z czujników temperatury i wilgotności gleby, prezentuje je na wyświetlaczu LCD oraz steruje silnikiem krokowym odpowiedzialnym za okresowy obrót doniczki. Część analogowa, zaprojektowana i zasymulowana w LTspice, realizuje wizualną sygnalizację temperatury za pomocą diod LED sterowanych przez komparatory napięcia. Część cyfrowa odpowiada za przetwarzanie danych, automatyzację pracy oraz obsługę interfejsu użytkownika. Całość została zaprojektowana jako system konfigurowalny, umożliwiający regulację progów temperatury oraz parametrów pracy silnika. Projekt obejmował pełny cykl realizacyjny: od koncepcji i symulacji, przez schemat i projekt PCB, wykonanie oraz montaż płytki, aż po zaprojektowanie i wydruk obudowy oraz mechanizmu obrotowego. Efektem końcowym jest estetyczne, funkcjonalne i w pełni zautomatyzowane urządzenie, które umożliwia stały nadzór nad warunkami wzrostu rośliny oraz zapewnia jej równomierne nasłonecznienie. Projekt ten był wartościowym doświadczeniem praktycznym, pozwalającym na integrację wiedzy z zakresu elektroniki, programowania, mechaniki i projektowania 3D w jednym, spójnym rozwiązaniu inżynierskim.
-
1. Cel i zakres Celem projektu jest ciągły nadzór nad parametrami środowiskowymi w serwerowni: temperaturą, wilgotnością względną oraz poziomem hałasu. Urządzenie ma wczesne wykrywać anomalie (np. awaria klimatyzacji, wzrost hałasu wentylatorów), rejestrować historię i raportować wartości do systemu Domoticz. 2. Architektura systemu System składa się z dwóch warstw: Warstwa akwizycji – Arduino Nano (8-bit MCU) zbiera szybkie próbki analogowe z mikrofonu MAX9814 oraz dane z czujnika SHT20 po magistrali I²C. Dane są wstępnie przetwarzane i przesyłane przez UART do Raspberry Pi. Warstwa bramki i zapisu – Raspberry Pi 4 (Raspbian/Linux) realizuje: odczyt dwóch sond DS18B20 po 1-Wire (wejście jądra: /sys/bus/w1/devices/28-00000053483a oraz drugi czujnik), harmonogram zadań cron (interwał 1 min), agregację i wysyłkę wszystkich wartości do Domoticz poprzez HTTP: http://{domoticz_host}:{domoticz_port}/json.htm?type=command¶m=udevice&idx={device_idx}&nvalue={nvalue}&svalue={svalue} 3. Sensory i interfejsy 3.1. Temperatura – DS18B20 (2 szt.) Wodoodporne sondy 1-Wire zasilane z 3,3 V lub 5 V (w zależności od trasy). Obie podłączone równolegle do jednego kanału 1-Wire na Raspberry Pi z rezystorem podciągającym 4,7 kΩ do linii danych. Oprogramowanie identyfikuje unikalne adresy czujników; skrypt czyta pliki w1_slave, filtruje wartości CRC i przekazuje wynik w °C do Domoticz. Interwał próbkowania: 60 s. 3.2. Wilgotność i temperatura – SHT20 (I²C) Czujnik cyfrowy z sondą w stalowej obudowie, połączony przewodem ok. 5 m. Ze względu na długość magistrali zastosowano TCA4307 (Adafruit 5159) – bufor/Hot-Swap I²C stabilizujący zbocza i umożliwiający gorące dołączanie. Kolory przewodów: biały – GND, niebieski – 3,3 V, zielony – SDA → A5 Arduino, żółty – SCL → A4 Arduino. Częstotliwość I²C nominalnie 100 kHz (zalecane przy długich liniach). 3.3. Poziom dźwięku – MAX9814 (A0) Mikrofon elektretowy z automatycznym wzmocnieniem (AGC), zasilany 3,3–5 V, wyjście analogowe do A0 Arduino. Procedura pomiaru: przez 3 s wykonywany jest pomiar co 0,2 s (15 próbek), a następnie liczona jest amplituda (różnica między maksimum a minimum). Wynik odpowiada przybliżonej głośności/zmienności akustycznej w otoczeniu i służy do detekcji nietypowych zdarzeń (np. hałas łożysk, alarmy). 4. Komunikacja i format danych Arduino Nano komunikuje się z Raspberry Pi przez UART (np. 115200 8N1). Ramka danych może mieć postać JSON/CSV, np.: TEMP1=23.56;TEMP2=23.42;HUM=45.1;SHTT=23.7;SND=128 Raspberry Pi łączy dane z DS18B20 z ramką z Nano, waliduje zakresy (np. –40…85 °C dla DS18B20, 0…100% RH dla SHT20) i wysyła do Domoticz odpowiednimi idx. Wysyłka realizowana przez skrypt uruchamiany z cron co minutę; w przypadku błędu HTTP przewidziany jest retry oraz zapis do lokalnego logu. 5. Zasilanie i okablowanie Urządzenia zasilane z jednej szyny 5 V z zabezpieczeniem (bezpiecznik/ogranicznik prądu). Zastosowano: Radiator i wentylację Raspberry Pi 4 (zespół odprowadzania ciepła), Obudowę plastikową z przepływem powietrza, Shield Proto z listwą ARK dla solidnych przyłączy, Przewody 3- i 4-żyłowe (ok. 30 m) prowadzone z dala od kabli zasilania 230 V; zalecane skrętki dla linii sygnałowych. Przewidziano gniazdo RJ45 jako przepust/organizację okablowania sygnałowego. 6. Montaż i bezpieczeństwo Wszystkie połączenia sygnałowe wykonane jako niskonapięciowe SELV. Linie 1-Wire i I²C prowadzone możliwie krótko; dla odcinków dłuższych – ekran lub bufor (jak TCA4307). Obudowa zamknięta, dostęp serwisowy przez pokrywę. Brak bezpośrednich połączeń z siecią 230 V w urządzeniu. 7. Oprogramowanie i utrzymanie System: Raspbian z włączonymi modułami w1-gpio, i2c, serial. Usługi: skrypt akwizycji uruchamiany przez cron co 1 min; logi rotowane (logrotate). Zabezpieczenia: ograniczenie dostępu HTTP do Domoticz (token/hasło), firewall sieciowy, separacja VLAN gdzie możliwe. Kalibracja: wstępna weryfikacja sond temperatury w znanym punkcie (np. 0 °C z lodem); sanity-check wilgotności na referencyjnych warunkach; próg alarmu akustycznego ustalany empirycznie w godzinach normalnej pracy serwerowni. 8. Integracja z Domoticz Dla każdego parametru utworzono urządzenie w Domoticz (oddzielne idx dla: Temp1, Temp2, Wilgotność, Temp SHT, Hałas). Dane przekazywane poprzez żądanie HTTP GET zgodnie z API Domoticz. W systemie konfiguruje się sceny/zdarzenia: alarm wysokiej temperatury, długotrwały wzrost hałasu, trend wilgotności (np. wykrycie zalania/awarii nawilżania). 9. Testy i kryteria akceptacji Test komunikacji: poprawny odczyt wszystkich sensorów przez 24 h bez utraty ramek. Test odporności: symulacja wzrostu temperatury (np. odłączenie jednego klimatyzatora) – rejestracja i alarm. Test akustyczny: sztuczne źródło hałasu – wzrost amplitudy o ustalony próg, wygenerowanie zdarzenia. Pora na zdjęcia poglądowe, najpierw po zmontowaniu: Uruchomienie systemu bez niespodzianki: A tak po uporządkowaniu połączeń i skręceniu: Na koniec kody źródłowe do czujników. Zaczynam od dwóch czujników temperatury w serwerowni. Posiadają tylko trzy wyprowadzenia. #!/usr/bin/env python3 import os import time import requests # Ładowanie modułów (możesz dodać te polecenia do /etc/rc.local lub skonfigurować je w systemd) os.system('modprobe w1-gpio') os.system('modprobe w1-therm') # Ustawienie ścieżki do czujnika DS18B20 device_folder = '/sys/bus/w1/devices/28-00000053483a' device_file = os.path.join(device_folder, 'w1_slave') def read_temp_raw(): with open(device_file, 'r') as f: lines = f.readlines() return lines def read_temp(): lines = read_temp_raw() # Czekamy, aż pierwszy wiersz potwierdzi poprawny odczyt ('YES') while lines[0].strip()[-3:] != 'YES': time.sleep(0.2) lines = read_temp_raw() equals_pos = lines[1].find('t=') if equals_pos != -1: temp_string = lines[1][equals_pos+2:] temp_c = float(temp_string) / 1000.0 return temp_c raise RuntimeError("Błąd odczytu temperatury!") # Odczyt temperatury temperature = read_temp() print("Temperatura: {:.2f} °C".format(temperature)) # Konfiguracja Domoticz domoticz_host = "localhost" domoticz_port = "8080" device_idx = "1" # <--- zmień na właściwy numer urządzenia w Domoticz nvalue = 0 svalue = "{:.2f}".format(temperature) # Ustaw dane autoryzacyjne (login/hasło) username = "update" # <--- wpisz swój login password = "password" # <--- wpisz swoje hasło # Przygotowanie adresu URL do aktualizacji urządzenia w Domoticz url = f"http://{domoticz_host}:{domoticz_port}/json.htm?type=command¶m=udevice&idx={device_idx}&nvalue={nvalue}&svalue={svalue}" print("Wysyłanie danych do Domoticz:", url) try: response = requests.get(url, timeout=10, auth=(username, password)) if response.status_code == 200: print("Pomyślnie wysłano dane do Domoticz.") else: print("Błąd wysyłania danych, kod HTTP:", response.status_code) except Exception as e: print("Błąd przy wysyłaniu danych do Domoticz:", e) Powinny być dwa takie skrypty umieszczone w podkatalogu aplikacji Domoticz, u mnie są temp1.py i temp2.py, różnią się tylko w jednej linii kodu: device_idx = "1" # <--- zmień na właściwy numer urządzenia w Domoticz - dla temp1.py device_idx = "2" # <--- zmień na właściwy numer urządzenia w Domoticz - dla temp2.py Pora teraz na czujnik wilgotności i temperatury w serwerowni: #!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Odczyt SHT30 + wysyłka temperatury i wilgotności do Domoticz # import time import socket import requests from smbus2 import SMBus # ---------- 1. Funkcje pomocnicze ---------- # a) Bieżący adres IP (lub wpisz "localhost") def get_local_ip(): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: s.connect(("8.8.8.8", 80)) return s.getsockname()[0] finally: s.close() # b) CRC-8 z datasheetu SHT3x (polinom 0x31, init 0xFF) def crc8(data): crc = 0xFF for byte in data: crc ^= byte for _ in range(8): crc = (crc << 1) ^ 0x31 if (crc & 0x80) else (crc << 1) crc &= 0xFF return crc # c) Odczyt jednorazowy temperatury [°C] i RH [%] def read_sht30(bus, addr=0x44): CMD_SINGLE_HIGHREP = [0x2C, 0x06] # single-shot, high repeatability, no CS bus.write_i2c_block_data(addr, CMD_SINGLE_HIGHREP[0], CMD_SINGLE_HIGHREP[1:]) time.sleep(0.015) # 15 ms zgodnie z arkuszem raw = bus.read_i2c_block_data(addr, 0x00, 6) # weryfikacja CRC if crc8(raw[0:2]) != raw[2] or crc8(raw[3:5]) != raw[5]: raise RuntimeError("Błędna suma CRC (SHT30)") raw_temp = raw[0] << 8 | raw[1] raw_humid = raw[3] << 8 | raw[4] temp_c = -45 + 175 * (raw_temp / 65535.0) rh = 100 * (raw_humid / 65535.0) return round(temp_c, 2), round(rh, 1) # d) Podpowiedz status wilgotności dla Domoticz def humidity_status(rh): if 40 <= rh <= 60: return 1 # Comfortable if rh < 30: return 2 # Dry if rh > 70: return 3 # Wet return 0 # Normal # ---------- 2. Konfiguracja ---------- DOMO_HOST = get_local_ip() # albo "localhost" DOMO_PORT = 8080 DEVICE_IDX = 3 # <-- wstaw IDX czujnika Temp+Hum USERNAME = "update" PASSWORD = "password" # ---------- 3. Główna logika ---------- with SMBus(1) as bus: try: temp, rh = read_sht30(bus) h_stat = humidity_status(rh) # Format dla czujnika Temp+Hum: "T;RH;HumStat" svalue = f"{temp:.2f};{rh:.1f};{h_stat}" url = (f"http://{DOMO_HOST}:{DOMO_PORT}/json.htm?" f"type=command¶m=udevice&idx={DEVICE_IDX}" f"&nvalue=0&svalue={svalue}") print(f"Odczyt SHT30 → {temp:.2f} °C {rh:.1f}% RH") r = requests.get(url, timeout=10, auth=(USERNAME, PASSWORD)) r.raise_for_status() #print(f"Wysłano do Domoticz ({DOMO_HOST}), HTTP {r.status_code} OK") except Exception as e: print("Błąd:", e) Teraz pora na kod, który został wgrany do Arduino Nano (bardzo lubię z niego korzystać). Skrypt ma za zadanie wysyłać dane z drugiego czujnika temperatury i wilgotności oraz poziom dźwięku w serwerowni. Te wszystkie dane są przekazywane do Raspberry Pi 4 w zadanych interwałach czasowych. #include <Wire.h> #include <Adafruit_SHT31.h> Adafruit_SHT31 sht31; /* ---------- CZASY ---------- */ const unsigned long PERIOD_SHT_MS = 30000UL; // 30 s const unsigned long SOUND_STEP_MS = 200UL; // 0,2 s const uint8_t SOUND_BUF_LEN = 18; // 18 próbek → 3,6 s /* ---------- PINY ---------- */ const uint8_t PIN_SOUND = A0; /* ---------- ZMIENNE ---------- */ unsigned long lastSht = 0; unsigned long lastSound = 0; uint16_t soundBuf[SOUND_BUF_LEN]; uint8_t soundIx = 0; bool bufFilled = false; void setup() { Serial.begin(9600); Wire.begin(); if (!sht31.begin(0x44)) { Serial.println(F("Nie znaleziono czujnika SHT-30!")); while (true) delay(1000); } } void loop() { unsigned long now = millis(); /* --- 1. SHT-30 co 30 s --- */ if (now - lastSht >= PERIOD_SHT_MS) { lastSht = now; float t = sht31.readTemperature(); float rh = sht31.readHumidity(); if (!isnan(t) && !isnan(rh)) { Serial.print(F("SHT:")); Serial.print(t, 1); Serial.print(','); Serial.println(rh, 1); } else { Serial.println(F("SHT_ERR")); } } /* --- 2. próbkowanie dźwięku co 0,2 s --- */ if (now - lastSound >= SOUND_STEP_MS) { lastSound = now; uint16_t raw = analogRead(PIN_SOUND); // 0-1023 soundBuf[soundIx++] = raw; if (soundIx >= SOUND_BUF_LEN) { // bufor pełny soundIx = 0; bufFilled = true; } if (bufFilled && soundIx == 0) { // co 18 próbek (3,6 s) uint16_t vMin = soundBuf[0]; uint16_t vMax = soundBuf[0]; for (uint8_t i = 1; i < SOUND_BUF_LEN; ++i) { if (soundBuf[i] < vMin) vMin = soundBuf[i]; if (soundBuf[i] > vMax) vMax = soundBuf[i]; } uint16_t amp = vMax - vMin; // amplituda Serial.print(F("WPSE:")); Serial.println(amp); // np. WPSE:187 } } } Teraz muszę jeszcze odebrać dane z portu USB Raspberry Pi 4 i wysłać to do Domoticza, program działa w pętli nieskończonej i musi być uruchomiony na starcie tylko raz. #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Odczyt danych z Arduino (SHT30 + czujnik dźwięku) po UART i wysyłka do Domoticz (idx: temperatura+wilgotność oraz sensor dźwięku). """ import serial import time import requests # ---------- KONFIGURACJA ---------- SERIAL_PORT = "/dev/ttyUSB0" BAUDRATE = 9600 DOMO_HOST = "localhost" DOMO_PORT = 8080 IDX_SHT = 4 # Temp + Hum (dummy) IDX_SOUND = 5 # Custom Sensor (General) USERNAME = "update" PASSWORD = "password" NVALUE = 0 # nvalue = 0 dla pomiarów liczbowych # ---------- FUNKCJE POMOCNICZE ---------- def humidity_status(rh: float) -> int: """Zwraca kod statusu RH zgodnie z Domoticz.""" if 40 <= rh <= 60: return 1 # Comfortable if rh < 30: return 2 # Dry if rh > 70: return 3 # Wet return 0 # Normal def domo_update(idx: int, svalue: str) -> None: """Wysyła pojedynczy odczyt do Domoticz.""" url = (f"http://{DOMO_HOST}:{DOMO_PORT}/json.htm" f"?type=command¶m=udevice&idx={idx}" f"&nvalue={NVALUE}&svalue={svalue}") r = requests.get(url, timeout=10, auth=(USERNAME, PASSWORD)) r.raise_for_status() # ---------- INICJALIZACJA UART ---------- ser = serial.Serial(SERIAL_PORT, BAUDRATE, timeout=1) time.sleep(2) # Arduino resetuje się po ustawieniu DTR print("Start – oczekiwanie na dane z UART...") # ---------- GŁÓWNA PĘTLA ---------- while True: try: line = ser.readline().decode("utf-8", errors="ignore").strip() except serial.SerialException as e: print("Błąd portu szeregowego:", e) time.sleep(5) continue if not line: continue print("UART >", line) try: # ----- Pakiet SHT30 ------------------------------------------------- if line.startswith("SHT:"): # Oczekiwany format z Arduino: "SHT:23.4,46.7" try: temp_str, rh_str = line[4:].split(",") temp = float(temp_str) rh = float(rh_str) except ValueError: print("Błędny format SHT:", line) continue h_stat = humidity_status(rh) # Domoticz wymaga: "temp;humidity;humidity_status" svalue = f"{temp:.2f};{rh:.1f};{h_stat}" domo_update(IDX_SHT, svalue) # print("↑ Domoticz SHT", svalue) # ----- Pakiet natężenia dźwięku ------------------------------------ elif line.startswith("WPSE:"): # Oczekiwany format: "WPSE:512" try: raw = int(line[5:]) except ValueError: print("Błędny format WPSE:", line) continue domo_update(IDX_SOUND, str(raw)) # print("↑ Domoticz SOUND", raw) # ----- Nierozpoznany prefiks --------------------------------------- else: print("Nieznany format:", line) except requests.RequestException as e: print("Błąd HTTP:", e) # Niewielka pauza odciążająca CPU time.sleep(0.05) Ostatnią rzeczą jest konfiguracja crona, aby regularnie przesyłać dane co minutę: * * * * * /home/norbert/domoticz/myscrypts/temp1.py * * * * * /home/norbert/domoticz/myscrypts/temp2.py * * * * * /usr/bin/python3 /home/norbert/domoticz/myscrypts/sht30.py Dane, które są przesyłane z Arduino do Rasperry Pi 4 są realizowane jako zwyczajna usługa linuksa, poniżej jej konfiguracja: [Unit] Description=Arduino ↔ Domoticz bridge After=network-online.target Wants=network-online.target [Service] Type=simple User=norbert # Użytkownik musi być w grupie „dialout”, żeby otworzyć /dev/ttyUSB0 Group=norbert ExecStart=/usr/bin/env python3 /home/norbert/domoticz/myscrypts/arduino2domoticz2.py WorkingDirectory=/home/norbert/domoticz/myscrypts Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target
-
- 2
-
-
- Raspberry Pi
- Arduino
-
(i 3 więcej)
Tagi:
-
... Czyli zbudowałem po prostu termostat ukończony termostat Remontując moje mieszkanie stwierdziłem, że dopóki mam dostęp do rur, to wyposażę je w dodatkowy zawór. Ale nie byle jaki - elektrozawór. Pomyślałem, że spróbuję swoich sił i stworzę prosty system kontroli nad temperaturą w mieszkaniu w czasie sezonu grzewczego. Ale zaraz - przy grzejnikach są głowice termostatyczne, więc jaki jest tego sens? Otóż pod względem praktycznym może niewielki, ale zawsze to jakaś okazja, żeby pobawić się w elektronika Poza tym, można w przyszłości rozbudować system o podłączenie do internetu. Wtedy wyjeżdżając z domu na dłuższy czas głowice zostawiamy na maksymalnym poziomie, a temperaturą domu sterujemy z aplikacji. Ale tutaj koniec dygresji, wracamy do właściwego projektu Jak miałoby to wyglądać? położenie elektrozaworu w biegu rury zasilającej C.O. W obudowie urządzenia zostanie zamknięty mikrokontroler oraz czujnik temperatury powietrza. Do tego wyświetlacz i kilka przycisków jako interfejs komunikacji z użytkownikiem. W obiegu rur wody centralnego ogrzewania, a dokładniej na rurze zasilającej, zamontowany zostanie elektrozawór przystosowany do pracy z ciśnieniem i temperaturą w takich instalacjach. teoretyczny przebieg temperatury w czasie Użytkownik w dowolnym momencie używając przycisków na obudowie wybierze zadaną temperaturę powietrza. Mikrokontroler cały czas porównuje ją z temperaturą zmierzoną wbudowanym czujnikiem. Możliwe są tylko 3 opcje: obie będą równe, zadana będzie większa, lub zadana będzie mniejsza. Przypadek, kiedy są one sobie równe uznałem, że zaliczę do jednej z pozostałych, ponieważ nie ma to zbyt dużego wpływu na działanie urządzenia. A więc pozostają 2 przypadki: wybrano temperaturę większą od obecnej (lub mieszkanie się wystudziło), więc zawór trzeba otworzyć; wybrano temperaturę mniejszą (lub obecna się na tyle zwiększyła) i wtedy zawór należy zamknąć. Lista materiałów użytych do budowy tego projektu: Mikrokontroler - Proces prototypowania rozpocząłem jak to zwykle bywa na płytce Arduino UNO. Łatwo się na nią wgrywa program, więc proces poprawiania kodu i naprawiania błędów trwa możliwie szybko. Docelowo jednak do urządzenia trafił klon płytki Arduino Pro Mini z popularnego, azjatyckiego sklepu internetowego. Kosztowały mnie poniżej 7 zł / sztukę, więc koszt zbudowania projektu był o wiele przyjemniejszy. A Pro Mini posiada na tyle dużo wyprowadzeń, że ogarnie każdy nieduży projekt. Elektrozawór - Potrzebny był taki, który wytrzyma pracę przy ciśnieniu i temperaturze wody C.O. Kiedyś mierzyłem ciśnienie zimnej wody w kranie, to wyszło mi 4 bary, natomiast z tego co widziałem, to większość urządzeń do pracy w domowej sieci wodociągowej ma określoną wytrzymałość do 10 barów (złączki, baterie umywalkowe). Temperatura w najgorszym wypadku będzie bliska wrzeniu, ale wiele rzeczy musiałoby pójść nie tak, żeby się tak nagrzała - instalacja jest przed tym zabezpieczona. Znalazłem więc w internecie taki elektrozawór: max. 16 bar, <95 st. C, bistabilny. Jest zasilany napięciem 230V. Nie jest to najbezpieczniejsze wyjście, początkującym elektronikom odradzam - zastąpcie go innym, na przykład na 24V. Ja wybrałem taki, ponieważ nie znalazłem wtedy żadnych ciekawych na 5V ani 12V, a zasilacza 24V nie chciałem kupować - budżet był mocno ograniczony. nakręcana głowica elektrozaworu instrukcja podłączenia mojego elektrozaworu Uwaga! Osoby nieuprawnione do pracy z napięciem sieciowym 230V nie powinny samodzielnie dokonywać przy nim jakichkolwiek prac! Nieumiejętna praca z napięciem sieciowym grozi porażeniem, pożarem, a nawet śmiercią. Najlepiej wybierać więc komponenty sterowane napięciami bezpiecznymi, albo jeśli to niemożliwe: tę część prac, która jest związana z napięciem sieciowym oddać do wykonania elektrykowi. Obudowa - W chwili powstawania tego projektu nie miałem jeszcze przywileju posiadania własnej drukarki 3D. Kolega miał, ale zgodził się tylko na wydruk małych przycisków, obudowa była za duża na jego sprzęt ze względu na uszkodzenie poziomowania stołu i odklejanie się wydruków. Wtedy jeszcze się na tym nie znałem, więc korzystałem z tego co jest. Po partyzancku kupiłem więc odpowiedniej wielkości zamykaną puszkę elektryczną w niebieskim markecie budowlanym z nadzieją, że wszystko się do niej zmieści i nie będzie to wyglądało jak rupieć z wysypiska (to akurat się niezbyt udało). Czujnik temperatury - wybór padł na DHT11 - czujnik temperatury powietrza i wilgotności. Bez większego powodu. Bez porównywania do innych rodzajów czujnika. Jest po prostu mały, tani i oferuje wystarczający zakres mierzonych wartości - 0°C do +50°C, wilgotność od 20% do 90% RH. Wilgotności w tym projekcie w ogóle nie badam, a temperatura pomieszczenia mieści się z dużym zapasem w podanych widełkach. Ja miałem już ten czujnik w wersji nagiej - sam czujnik, jednak polecam rozważyć gotowy moduł na płytce, ponieważ wtedy problem rezystora podciągowego nie będzie Was dotyczyć (o tym niżej). Wyświetlacz - LCD 2x16 znaków z niebieskim podświetleniem. Duży, czytelny, łatwo się nim steruje. Przyciski - do sterowania najlepiej nadają się monostabilne / chwilowe. Wystarczą nawet najprostsze, tzw. tactile switche. Zasilacz 5V - wystarczy jakikolwiek stałonapięciowy. Układ nie pobiera dużo mocy, więc nawet niewielka moc zasilacza sobie poradzi. Dobrze mieć kompatybilną wtyczkę, żeby nie trzeba było ciąć przewodu. Miałem akurat gniazdo montażowe jack 5,5/2,1, więc dobrałem zasilacz z odpowiednią wtyczką. Przekaźniki - w mojej wersji projektu są potrzebne, ponieważ wybrany elektrozawór jest sterowany napięciem 230V. Wybrałem gotowe moduły, które są wyposażone w odpowiednie zabezpieczenie, o czym też niżej. Sposób podłączenia: Całość układu najlepiej zobrazuje schemat. Jest on prosty, wykonany na szybko w programie graficznym. Nie jest to najlepsza metoda, ale na czas budowy tego projektu jeszcze nie umiałem korzystać z niczego bardziej profesjonalnego. Uproszczony schemat układu Na schemacie wyraźnie widać rozdzielenie części układu, która pracuje na napięciu 230V, i pozostałą, zasilaną napięciem 5V. Tych dwóch światów lepiej nie łączyć, a już na pewno nie w niekontrolowany sposób. Przekaźniki zapewniają wystarczającą izolację galwaniczną, która zabezpieczy pozostałą część układu przed śmiercionośnym napięciem sieciowym. Użyłem dwóch przekaźników: pierwszy umożliwia załączenie fazy w ogóle, a drugi przekierowuje ją na jeden z dwóch przewodów sterujących elektrozaworu (FO - faza otwieranie, FZ - faza zamykanie) zgodnie ze schematem przedstawionym w jego instrukcji obsługi. tutaj proces montażu układu w obudowie, dzisiaj wiele rzeczy zrobiłbym inaczej... jedyne elementy, które udało mi się wydrukować - przedłużki do przycisków zaprojektowane w TinkerCAD Arduino, serce układu jest oczywiście podłączone do zasilania: VCC do 5V, GND do masy. Czujnik temperatury oprócz pinów zasilania ma trzeci pin - wyjście danych. Podłączony jest bezpośrednio do cyfrowego wejścia Arduino, ale w posiadanej przeze mnie wersji dodatkowo potrzebny jest rezystor podciągowy do 5V (na przykład 4,7 kilooma), ponieważ to wyprowadzenie wewnątrz samego czujnika jest typu otwarty kolektor. Przesyłając dane zwiera on wyjście danych do masy, jednak trzeba ustalić jego stan w momencie, kiedy nie zwiera. 3 przyciski zwierają po wciśnięciu cyfrowe wejście Arduino do masy, ale podobnie jak wyżej opisany czujnik, należy ustalić ich stan bez wciśnięcia, dlatego znowu dajemy rezystory podciągowe (na przykład 4,7 kilooma). Jeśli chodzi o podłączenie wyświetlacza LCD to, szczerze mówiąc, Ameryki nie odkryłem. Zrobiłem to zgodnie z poradnikiem, jakich wiele w sieci. Ciekawe jest to, że nie trzeba podłączać wszystkich pinów danych (D0-D7), wystarczy podłączyć 4 z nich (D4-D7) i wyświetlacz działa jak należy, a piny mikrokontrolera można wykorzystać na co innego. Pamiętajcie o potencjometrze do ustalania kontrastu - ja użyłem zwyczajny liniowy 10 kiloomów. Przekaźniki na schemacie przedstawione są w uproszczony sposób. Te użyte przeze mnie są gotowymi modułami na płytce, która zawiera ważny element - diodę gaszącą. Jest ona podłączona równolegle do cewki przekaźnika w kierunku zaporowym. Podczas załączenia zasilania na cewkę nic szczególnego się nie dzieje - elektromagnes przełącza styki. Ale przy nagłym zaniku zasilania na niej - powstaje przepięcie przez zapadające się pole magnetyczne. To zjawisko potrafi niszczyć czułe komponenty. Popularnym rozwiązaniem tego problemu jest właśnie dioda gasząca, która rozładowuje cały impuls przepięcia chroniąc resztę układu. tym skutkuje brak zabezpieczenia przekaźnika - błędy i krzaki na wyświetlaczu tak faktycznie wygląda moduł przekaźnika - widać diodę gaszącą (1N4007) Sterowanie: Całością urządzenia zarządzał prosty kod napisany w C++ w starym IDE Arduino. Zawierał gotowe biblioteki do pracy z czujnikiem temperatury DHT11 oraz z wyświetlaczem LCD. Niestety kodu już nie posiadam przez śmierć ówczesnego dysku, opiszę zatem słowami co mniej-więcej się działo, ale służę pomocą, jeśli ktoś będzie chciał odtworzyć coś podobnego i będzie miał jakieś pytania. Układ nie posiada pamięci podtrzymywanej po zaniku zasilania, więc po uruchomieniu ustawiona zostaje temperatura zadana domyślna, wybrana przeze mnie w sposób subiektywny - 22 stopnie C. Użytkownik zmieni ją w miarę potrzeby. Wyświetlacz pokazuje w pierwszym wierszu frazę "T. zmierz. 22.3" pokazując jaką wartość temperatury aktualnie zmierzono. W drugim wierszu wyświetla obecną pozycję zaworu: "zawór zamknięty", "zawór otwarty", "otwieranie zaworu", "zamykanie zaworu". Środkowy przycisk "OK" powoduje przełączenie urządzenia z trybu wyświetlania wartości na tryb nastaw. Symbolizuje to zmiana tekstu pierwszego wiersza na "T.zadana >22.0<". Objęcie wartości temperatury nawiasami trójkątnymi sugeruje w intuicyjny sposób, że tę wartość można zmieniać. Służą do tego oczywiście przyciski "góra" i "dół". Ponowne przyciśnięcie "OK" wraca do trybu wyświetlania jak na początku. Pętla programu w regularnych odstępach czasu pobiera wartość temperatury z czujnika i porównuje ją z wartością zadaną. Jeśli wymagana jest zmiana położenia zaworu (zawór jest otwarty i jednocześnie t.zmierzona > t.zadana, albo zawór jest zamknięty i jednocześnie t.zmierzona < t.zadana) to wystawiane są odpowiednie sygnały na przekaźniki na czas ok. 20 sekund - tyle potrzeba żeby zawór się całkowicie obrócił w drugą skrajną pozycję, z lekkim zapasem. Podsumowanie: Urządzenie działa dokładnie tak, jak zaplanowałem. To znaczy zawór obraca się w docelową pozycję, wtedy kiedy wykryje konieczność nagrzania / schłodzenia mieszkania. Niestety ze względu na dużą inercję temperatury powietrza, pewną odległość urządzenia od grzejników, odczyt temperatury wewnątrz obudowy oraz kilka innych czynników mój termostat przestrzeliwuje nieco w obie strony. Temperatura powietrza w mieszkaniu nie jest utrzymywana na stałym poziomie, ale oscyluje wokół wartości zadanej. Po zamknięciu zaworu ciepła woda niby nie płynie w grzejnikach, ale wciąż w nich stoi nieruchomo. Nadal przez jakiś czas oddaje więc ciepło do pomieszczenia. Przez to temperatura rośnie o około 1,5 stopnia C powyżej zadanej, zanim zacznie faktycznie spadać. Podobnie w drugą stronę: zawór się otwiera, ciepła woda płynie w kierunku grzejników, ale są one już wystudzone i potrzebują chwili żeby się rozgrzać. Wobec tego temperatura spada o około 1,5 stopnia C poniżej zadanej i tak w kółko w cyklu trwającym około 1,5 - 2 godzin. Nie jest to może idealne rozwiązanie, ale ja i tak jestem z tego projektu zadowolony. Ja się dużo przy nim nauczyłem, a może też kogoś z czytających zainspiruję. Całość powstała 4 lata temu, moja wiedza poszła dużo naprzód, w końcu dorobiłem się też drukarki 3D, więc następne projekty będą wyglądały na pewno dużo lepiej. Życzę powodzenia i czekam na pytania. zmierzona faktycznie temperatura pokazuje niedoskonałość mojego termostatu
-
Pomysł na wykonanie automatycznego szlabanu zrodził się z potrzeby stworzenia modelu parkingowej bramki, który w prosty sposób demonstruje współpracę kilku elementów elektronicznych – czujnika odległości, serwomechanizmu oraz sygnalizacji świetlnej LED. Chciałem, aby projekt był jednocześnie ciekawy wizualnie i edukacyjny, tak aby mógł zainspirować inne osoby do nauki podstaw programowania Arduino i budowania prostych systemów automatyki. Budowę rozpocząłem od zaprojektowania schematu połączeń. Sercem układu jest mikrokontroler Arduino UNO, który kontroluje serwomechanizm odpowiedzialny za podnoszenie i opuszczanie szlabanu. Do wykrywania samochodu zastosowałem czujnik ultradźwiękowy HC-SR04. Kiedy czujnik wykryje obiekt w odległości mniejszej niż 10 cm, szlaban automatycznie unosi się do góry, a dioda zielona zapala się, sygnalizując możliwość przejazdu. Po oddaleniu obiektu szlaban po upływie kilku sekund powraca do pozycji poziomej, a zapala się dioda czerwona. W trakcie budowy napotkałem kilka problemów. Najwięcej trudności sprawiło mi stabilne zamocowanie serwomechanizmu tak, aby ramię szlabanu poruszało się płynnie i nie blokowało się przy końcowych pozycjach. Musiałem kilka razy poprawiać uchwyt oraz zmieniać kąty obrotu, aby uniknąć przeciążenia serwa. Kolejnym wyzwaniem było odpowiednie ekranowanie przewodów zasilających i sygnałowych od zakłóceń, które powodowały nieprawidłowe odczyty czujnika odległości – szczególnie przy dłuższych przewodach. Ostatecznie udało mi się ograniczyć błędne pomiary poprzez skrócenie kabli i lepsze rozmieszczenie komponentów na płytce. Efekt końcowy przerósł moje oczekiwania – szlaban działa stabilnie i niezawodnie. Projekt jest świetnym przykładem zastosowania podstawowych elementów elektronicznych i programowania w praktyce. Może być inspiracją dla osób zaczynających przygodę z Arduino, bo pokazuje, że nawet prosty układ daje satysfakcję i przydatne doświadczenie. Projekt można łatwo rozbudować – np. o pilot bezprzewodowy, moduł RFID lub sygnalizację dźwiękową. Poniżej zamieszczam zdjęcia przedstawiające gotowy projekt w działaniu. Linki do produktów ze sklepu Botland: Arduino UNO R3 – główny sterownik projektu. Ultradźwiękowy czujnik odległości HC-SR04 2-200cm - justPi – wykrywa obecność pojazdu. Serwo SG-90 - micro - 180 – podnosi i opuszcza szlaban. Taśma piankowa, samoprzylepna dwustronna 10mm x 10m Zestaw diod LED 5mm - justPi - 16szt. Zestaw przewodów połączeniowych justPi - męsko-męskie 20cm 40szt. Rezystor justPi THT CF węglowy 1/4W 330Ω - 30szt. - Rezystor 330Ω w tym projekcie jest niezbędnym elementem zabezpieczającym LED i mikrokontroler. Bez niego diody mogłyby ulec uszkodzeniu, a całość działałaby nieprawidłowo. Poniżej zamieszczam również wizualny projekt układu wykonany w Tinkercad, aby łatwiej było zobaczyć, jak poszczególne elementy zostały rozmieszczone i połączone. Taki schemat może być pomocny dla osób, które chcą odtworzyć mój projekt krok po kroku. Dla pełnej przejrzystości zamieszczam także kod programu Arduino, który steruje szlabanem i sygnalizacją LED: #include <Servo.h> Servo mojeSerwo; const int trigPin = 9; const int echoPin = 10; const int zielonyLED = 7; const int czerwonyLED = 8; long czas; int odleglosc; unsigned long czasBrakuWykrycia = 0; const unsigned long czasNaPowrot = 3000; void setup() { mojeSerwo.attach(6); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); pinMode(zielonyLED, OUTPUT); pinMode(czerwonyLED, OUTPUT); Serial.begin(9600); } void loop() { // Pomiar odległości digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); czas = pulseIn(echoPin, HIGH); odleglosc = czas * 0.034 / 2; Serial.print("Odległość: "); Serial.print(odleglosc); Serial.println(" cm"); if (odleglosc < 10) { mojeSerwo.write(180); czasBrakuWykrycia = millis(); digitalWrite(zielonyLED, HIGH); digitalWrite(czerwonyLED, LOW); } else { if (millis() - czasBrakuWykrycia >= czasNaPowrot) { mojeSerwo.write(90); digitalWrite(zielonyLED, LOW); digitalWrite(czerwonyLED, HIGH); } } delay(50); }
-
Mam problem z robotem 2WD. NRF działa, jak dam zamiast v wartość bezpośrednio 255 to też jedzie do przodu i do tyłu. Nie działa w momencie gdy wstawiam v, czytane z NRFa. Na razie chcę żeby chociaż to działało, potem będę się głowił nad skrętami. Jest tam trochę śmieci, gdyż usuwałem kod związany ze skręcaniem (chciałbym zmieniać prędkość każdego z kół w zależności od kierunku skrętu) Prosiłbym o pomoc. Zakres joysticka wynosi ok. 0-675. Środek w pkt ok. X:336, Y:335 Robot na arduino nano, nadajnik na uno. #include <SPI.h> #include <RF24.h> RF24 radio(9,8); const byte adres[6]="00001"; int v; //predkosc do przodu/tyłu int a; //predkosc obrotu struct Data{ int x; int y; }; void setup() { Serial.begin(9600); radio.begin(); radio.setPALevel(RF24_PA_HIGH); radio.setDataRate(RF24_250KBPS); radio.openReadingPipe(0, adres); radio.startListening(); pinMode(10, OUTPUT); //IN 1 pinMode(6, OUTPUT); //IN 2 pinMode(7, OUTPUT); //IN 3 pinMode(4, OUTPUT); //IN 4 pinMode(3, OUTPUT); //EN 1 2 pinMode(5, OUTPUT); //EN 3 4 } void loop() { bool kierunek=true; //true dla przodu, false dla tyłu bool zwrot=false; //true że zmienia kierunek, false że nie float v2, a2; int pwm1=0, pwm2=0; Data data; bool zwrot2=false; //true dla prawo, false dla lewo if(radio.available()) { radio.read(&data, sizeof(data)); v=data.x-338; if(v<0) { kierunek=false; v=-1*v; } a=data.y-338; if(a<-10) //skręt w lewo { zwrot=true; zwrot2=false; a=-1*a; } else if(a>10) //skręt w prawo { zwrot=true; zwrot2=true; } else { a=0; } int a2=map(a, 0, 350, 0, 250); int v2=map(v, 0, 350, 0, 255); pwm1=min(255, a); if(v<10) { analogWrite(3, 0); analogWrite(5, 0); digitalWrite(10, LOW); digitalWrite(6, LOW); digitalWrite(7, LOW); digitalWrite(4, LOW); } else if(kierunek==true) { analogWrite(3, v2); analogWrite(5, v2); digitalWrite(10, HIGH); digitalWrite(6, LOW); digitalWrite(7, HIGH); digitalWrite(4, LOW); } else if(kierunek==false) { analogWrite(3, v2); analogWrite(5, v2); digitalWrite(10, LOW); digitalWrite(6, HIGH); digitalWrite(7, LOW); digitalWrite(4, HIGH); } } else if(radio.available()==false) { pwm1=0; pwm2=0; analogWrite(3, pwm2); analogWrite(5, pwm1); digitalWrite(10, LOW); digitalWrite(6, LOW); digitalWrite(7, LOW); digitalWrite(4, LOW); Serial.print("Nie połączono"); } }
-
Mechanik TEAM - Szkolne Koło Robotyczne przy ZSM w Rzeszowie
MechanikTEAM opublikował temat w Koła Naukowe
Cześć! Szkolne Koło Robotyczne „Mechanik TEAM” powstało na początku roku szkolnego 2024/2025 w Zespole Szkół Mechanicznych w Rzeszowie. Choć oficjalnie działa dopiero od niedawna, jego korzenie sięgają wcześniejszych działań uczniów szkoły. Zakładając konto na Forbocie chcemy podzielić się z Wami naszymi pomysłami, dokonaniami . Postaramy się przedstawić i zaprosić do śledzenia naszych poczynań. Logo Szkolnego Koła Robotycznego Mechanik TEAM Logo bynajmniej nie jest przypadkowe - to bardzo uproszczony szkic naszego pierwszego robota. O nas Jesteśmy grupą uczniów (aktualnie ok. 10 osób) uczęszczających do różnych klas Zespołu Szkół Mechanicznych w Rzeszowie. Łączy nas pasja do budowania robotów, elektroniki, mechaniki, projektowania 3D oraz dobrej zabawy. Działalność Uczestniczymy w konkursach, w których startują nasze roboty (lub w ramach nich są budowane) - np. X-Challenge 2024, "Naukolatek - nastoletni naukowiec" czy też I miejsce w IX edycji ogólnopolskiego Konkursu Młodych Inżynierów. Oczywiście bierzemy również udział w wydarzeniach promujących edukację i naukę - np. Moc Odkrywców 2025 czy w dniach otwartych naszej szkoły. Roboty Fredzio Nasza debiutancka konstrukcja — mobilny robot zdalnie sterowany zbudowany na bazie lekkich, ale wytrzymałych profili aluminiowych i napędzany czterema niezależnymi silnikami — miała swoją premierę podczas ogólnopolskich zawodów X-Challenge 2024 w kategorii Task Hunters. Choć nasz robot powstawał w zawrotnym tempie, przy bardzo ograniczonym budżecie i w warunkach dalekich od idealnych, postanowiliśmy podjąć wyzwanie. Budowa była pełna niespodzianek – od drobnych zwarć w układzie elektrycznym, przez poważną awarię w dniu zawodów, aż po spektakularną wywrotkę podczas próbnego przejazdu i złamane ramię w trakcie jednej z konkurencji. Mimo tych przeciwności nasza konstrukcja nie tylko wystartowała, ale również zajęła 10. miejsce w stawce kilkudziesięciu zespołów z całej Polski, co uważamy za ogromny sukces. Już teraz pracujemy nad nową, poprawioną wersją na kolejne zawody - powstaje Fredzio 2.0. Ragnar Pod względem budowy, robot bazuje na solidnym i lekkim szkielecie wykonanym z profili aluminiowych, co zapewnia zarówno wytrzymałość, jak i elastyczność w dalszej rozbudowie. Napędzany jest przez cztery niezależne silniki, z których każdy steruje osobnym kołem typu omniwheel – to właśnie one czynią tę konstrukcję wyjątkową. Zastosowanie omniwheels umożliwia robotowi pełną swobodę ruchu: może poruszać się nie tylko klasycznie – do przodu i do tyłu – ale także w bok, po skosie, a nawet obracać się wokół własnej osi. Co więcej, dzięki odpowiedniemu algorytmowi sterowania możliwe jest przesunięcie osi obrotu robota poza jego fizyczną podstawę – nawet o 30 cm. To oznacza, że maszyna może np. precyzyjnie okrążać obiekt znajdujący się przed nią bez potrzeby zmiany orientacji całej konstrukcji. Podobnie jak nasz wcześniejszy projekt – Fredzio – robot ten może być sterowany zdalnie, jednak tutaj poszliśmy o krok dalej. Dzięki zastosowaniu dodatkowych sensorów, możliwa jest również częściowa autonomia. Wyposażyliśmy go w ultradźwiękowy czujnik odległości oraz laserowy dalmierz, który pozwala wykrywać przeszkody nawet w promieniu do 12 metrów. W połączeniu z prostym, ale skutecznym algorytmem omijania przeszkód, robot potrafi samodzielnie analizować otoczenie i podejmować decyzje, np. o zmianie kierunku jazdy. Robot Autonomiczny Choć na pierwszy rzut oka ten niewielki robot może przypominać dziecięcą zabawkę, jego wnętrze skrywa zaskakująco zaawansowaną technologię. To efekt naszej pracy w ramach konkursu „Naukolatek” oraz projektu badawczego „Zastosowanie sztucznej inteligencji w nawigacji robotów”, którego celem było stworzenie robota zdolnego do samodzielnego poruszania się w nieznanym środowisku przy wykorzystaniu nowoczesnych algorytmów lokalizacji i planowania trasy. Pod tą niepozorną obudową znajduje się zestaw przemyślanych rozwiązań sprzętowych i programistycznych. Wyposażyliśmy robota m.in. w enkodery, czujnik położenia oraz LIDAR – system umożliwiający precyzyjne skanowanie otoczenia przy pomocy światła laserowego. Po stronie oprogramowania zdecydowaliśmy się na wykorzystanie ROS2 (Robot Operating System 2) – otwartoźródłowego frameworka powszechnie stosowanego w badaniach nad robotyką mobilną. Dzięki połączeniu danych z LIDAR-u i enkoderów możliwe było wdrożenie technologii SLAM (Simultaneous Localization and Mapping), która pozwala robotowi jednocześnie mapować otoczenie i określać własną pozycję w przestrzeni. W efekcie robot jest w stanie autonomicznie poruszać się po nieznanym terenie, wykrywać i omijać przeszkody, a także planować trasę do zadanego punktu z użyciem algorytmu A*. Pracownia W tym roku udało nam się pozyskać i wyremontować dwa pomieszczenia, w których ma teraz siedzibę nasze Koło. Są już meble, stoły, oświetlenie, ale wciąż urządzamy naszą pracownię. Kontakt Wszystkich zainteresowanych naszą działalnością zapraszamy do kontaktu - napisz wiadomość prywatną. WWW: mechanik.team Email: [email protected]-
- Koło Naukowe
- Szkoła
- (i 3 więcej)
-
Trudne początki Tak naprawdę to jest chyba mój pierwszy projekt w świecie Arduino! Zamarzyłem sobie zbudowanie własnego, terenowego pojazdu zdalnie sterowanego - takiego, na którym można potem zamontować jakiś chwytak, ramię albo kamerkę z przekaźnikiem FPV. Kontroler Tu akurat nie miałem większego wyboru, bo wtedy pod ręką miałem akurat Arduino Leonardo. Zaletą tej płytki jest kompatybilność z popularnymi shieldami do Uno a także złącze microUSB typu B (zamiast mało popularnego złącza "drukarkowego" w Uno). Na potrzeby tego niezbyt skomplikowanego projektu moc obliczeniowa tej płytki jest również całkowicie wystarczająca. Podwozie Moim planem było zbudowanie definitywnego i niepokonanego łazika marsjańskiego, więc zwykłe podwozie nie wchodziło w grę - koniecznie musiało być terenowe. Przegrzebałem naprawdę połowę Internetu w poszukiwaniu tego idealnego podwozia (ale - nie ukrywajmy - mieszczącego się również w moim budżecie) i w końcu mój wybór padł na podwozie Dagu DG012-ATV z napędem na cztery koła. Nieco podwyższony prześwit w stosunku do innych podwozi (dający nadzieję na pokonywanie niewielkich przeszkód), napęd na cztery koła - wszystko to brzmiało bardzo zachęcająco. Czterema silnikami coś oczywiście musi obracać, razem z podwoziem nabyłem więc również czterokanałowy sterownik silników DFRobota (w postaci shieldu dla Arduino). Serwo i czujnik Żeby urozmaicić nieco projekt, dodałem do niego serwo modelarskie, na którym zamontowałem ultradźwiękowy czujnik odległości z założeniem, że spróbuję kiedyś napisać program do autonomicznego poruszania się. RC Od długiego czasu używam do zdalnego sterowania aparatury FrSky Taranis, więc oczywiście musiałem skorzystać z kompatybilnego odbiornika - w tym przypadku X8R. Zasilanie Projekt oczywiście musiał być mobilny, więc zdecydowałem się na zasilenie go dwucelowym akumulatorem Lipo; konieczne okazało się też zastosowanie układu BEC, który obniżył napięcie akumulatora do 5V. Teraz pozostało już tylko zmontować wszystko w całość. Montaż podwozia Tu obyło się bez niespodzianek i problemów, po prostu skręciłem wszystko zgodnie z instrukcją i wyprowadziłem na zewnątrz przewody, którymi zasilane miały być silniki. Potem sprawy nieco się skomplikowały. Wszystko rozbiło się generalnie o to, że jak bym nie ułożył elementów na podwoziu, po prostu nie było takiego ułożenia, żeby wszystko się zmieściło. Sprawy utrudniał również fakt zastosowania Leonardo, które - umówmy się - jest raczej kobylaste i znacznie lepiej sprawdziłoby się tu kompatybilne z tą płytką Arduino Micro. Do tego dochodził BEC, odbiornik, serwo (którego nota bene nie miałem jak zamocować, bo w wersji 4WD podwozia miejsce na serwo zajmowane było przez dwa dodatkowe silniki) no i oczywiście akumulator. Dlatego zdecydowałem się na umieszczenie wszystkiego ponad podwoziem, pozostawiając na dole sam akumulator. Przykleiłem więc na dolnym pokładzie rzep, na którym mocowany jest akumulator - przeciążenia podczas poruszania robota są tak znikome, że jest to naprawdę pewny i sprawdzony sposób montażu (pozwalający też szybko zamontować albo zdemontować akumulator w razie potrzeby). Oprócz tego przykręciłem w narożnikach podwozia długie dystanse (kupione kiedyś w Chinach na potrzeby projektu quadrokoptera) i zabrałem się za przygotowywanie górnej części pojazdu. Górne podwozie wykonałem z fragmentu płytki aluminiowej, którą dociąłem tak, by znalazła się dokładnie ponad górną częścią podwozia - i jednocześnie dzięki temu przykryła koła, z których pył mógł się dostawać do elektroniki. W płytce wyciąłem otwór na serwo; ponieważ nie dysponuję żadnym sprzętem CNC, który pomógłby mi wyciąć równy, parametryczny otwór, rad nierad wziąłem do ręki wiertarkę, najpierw nawierciłem otwory, potem zamontowałem w niej frez i zacząłem ręcznie wycinać aluminium, a na końcu doszlifowałem wszystko pilnikami o zmniejszającej się ziarnistości. Cały proces poniżej: Teraz można było powoli przystąpić do montażu. Na pierwszy ogień poszło serwo, które na szczęście wpasowało się w wycięty przeze mnie otwór po prostu idealnie. Następnym krokiem było zamontowanie BECa, którego umieściłem pod górnym pokładem. Przewód zasilający (zakończony złączem T-Dean) musiałem rozgałęzić, ponieważ jedna para przewodów musiała zostać połączona z BECem, który obniży napięcie do 5V dla części elektroniki, zaś druga para - do sterownika silników, który będzie potem je zasilał. Szczęśliwie silniki akceptują napięcie dwucelowego akumulatora LiPo - trzeba to koniecznie sprawdzić przed zakupem/montażem! Arduino Leonardo można zasilić bezpośrednio z akumulatora, natomiast konieczne było przylutowanie odpowiedniej wtyczki (na zdjęciu po prawej stronie). Na koniec pozostało podłączenie wszystkich komponentów i otrzymujemy następujący efekt: Programowanie Pierwszym programikiem, który napisałem, był tester silników, który uruchamiał każdy z nich na pół sekundy. Kod wygląda następująco: const int E1 = 3; // Motor1 Speed const int E2 = 11; // Motor2 Speed const int E3 = 5; // Motor3 Speed const int E4 = 6; // Motor4 Speed const int M1 = 4; // Motor1 Direction const int M2 = 12; // Motor2 Direction const int M3 = 8; // Motor3 Direction const int M4 = 7; // Motor4 Direction void M1_advance(char Speed) { digitalWrite(M1, HIGH); analogWrite(E1, Speed); } void M2_advance(char Speed) { digitalWrite(M2, LOW); analogWrite(E2, Speed); } void M3_advance(char Speed) { digitalWrite(M3, LOW); analogWrite(E3, Speed); } void M4_advance(char Speed) { digitalWrite(M4, HIGH); analogWrite(E4, Speed); } void M1_back(char Speed) { digitalWrite(M1, LOW); analogWrite(E1, Speed); } void M2_back(char Speed) { digitalWrite(M2, HIGH); analogWrite(E2, Speed); } void M3_back(char Speed) { digitalWrite(M3, HIGH); analogWrite(E3, Speed); } void M4_back(char Speed) { digitalWrite(M4, LOW); analogWrite(E4, Speed); } void setup() { for (int i = 3; i < 9; i++) pinMode(i, OUTPUT); for (int i = 11; i < 13; i++) pinMode(i, OUTPUT); pinMode(LED_BUILTIN, OUTPUT); } void loop() { digitalWrite(LED_BUILTIN, HIGH); M1_advance(100); delay(500); M1_advance(0); M2_advance(100); delay(500); M2_advance(0); M3_advance(100); delay(500); M3_advance(0); M4_advance(100); delay(500); M4_advance(0); digitalWrite(LED_BUILTIN, LOW); delay(2000); // Delay 2S } Jak widać, sterowanie silnikami odbywa się poprzez podawanie kierunku poprzez piny cyfrowe i prędkości poprzez piny analogowe - proste, jak konstrukcja cepa. Teraz przyszła kolej na odbiornik RC i tu zaczęły się schody. Odbiorniki klasycznie podają stan każdego kanału poprzez sygnał PWM. W praktyce jest to seria naprzemiennych zer i jedynek, z których każda para 1+0 trwa 1/55 sekundy. Wartość możemy odczytać mierząc czas trwania sygnału 1: jeżeli jest to 1 milisekunda, przyjmujemy wartość minimalną ("-1"), jeżeli 1.5 milisekundy, to wartość środkową ("0"), zaś jeśli 2 milisekundy, to wartość maksymalną ("1"). Niektóre odbiorniki pozwalają na nieco szerszy zakres - od 0.5ms do 2.5ms, X8R domyślnie podaje wartości z tego pierwszego zakresu. Ponieważ musiałem mierzyć wartości na dwóch różnych kanałach, zdecydowałem się skorzystać z mechanizmu przerwań. Działa on mniej więcej następująco: gdy zajdzie wybrane zdarzenie (na przykład zmiana stanu danego pinu z niskiego na wysoki), wykonanie programu jest przerywane, zostaje wykonana funkcja oznaczona jako tzw. handler przerwania, a po jej zakończeniu program wznawia wykonanie w miejscu, w którym został przerwany. Na Arduino można znaleźć bardzo wygodną bibliotekę EnableInterrupt, która uogólnia sposób dodawania i usuwania handlerów przerwań pomiędzy różnymi wersjami Arduino, a korzysta się z niej w następujący sposób: #include "EnableInterrupt.h" volatile int microsStart = 0; void pin0Rising() { microsStart = micros(); disableInterrupt(0); enableInterrupt(0, pin0Falling, FALLING); } void pin0Falling() { int microsEnd = micros(); int diff = microsEnd - microsStart; if (diff > 1500) { digitalWrite(LED_BUILTIN, HIGH); } else { digitalWrite(LED_BUILTIN, LOW); } disableInterrupt(0); enableInterrupt(0, pin0Rising, RISING); } void setup() { enableInterrupt(0, pin0Rising, RISING); pinMode(LED_BUILTIN, OUTPUT); } void loop() { } Po wpięciu przewodu sygnałowego do pinu 0 i uruchomieniu programiku na kontrolerze, wbudowana dioda powinna się zapalić w momencie, gdy ustawimy drążek w położeniu większym niż połowa. Zwrócę jeszcze uwagę na magiczne słówko "volatile" przy deklaracji zmiennej - informuje ono kompilator, że zmienna ta nie może zostać zoptymalizowana (kompilator, a dokładniej optymalizator w niektórych przypadkach może w locie usunąć zmienną, na przykład jeżeli nie jest ona używana lub przez cały czas trwania programu ma zawsze tę samą wartość). Dodam tu jeszcze jedną ważną informację: program obsługi przerwania powinien być tak krótki, jak to tylko możliwe! Ma to sporo sensu jeżeli się nad tym nieco dłużej zastanowić, ale ja na to nie wpadłem i w pierwotnej wersji programu cała obsługa silników umieszczona była właśnie w programie obsługi przerwania. I ku mojemu zdziwieniu, po uruchomieniu programu, pomimo tego, że drążki były w położeniu zerowym, dwa silniki zaczęły się obracać. Okazało się, że obsługa przerwania obliczającego czas trwania PWM na pierwszym kanale trwała tak długo, że sztucznie opóźniała wywołanie drugiego przerwania wykonującego te same obliczenia dla drugiego kanału, przez co podawało ono zawyżone wartości. Wystarczyło przebudować program w taki sposób, by obsługa silników znalazła się poza programami obsługi przerwań i wszystko wróciło do normy. Pamiętajmy - to może oczywiste, ale mimo wszystko warto to powiedzieć - że mikrokontrolery nie są wielowątkowe, a przerwania nie są wątkami: wykonują się tylko jedno na raz. Drugie przerwanie musi czekać, aż pierwsze zostanie do końca obsłużone. Przed napisaniem końcowego programu pozostało mi już tylko przetestować obsługę czujnika ruchu, programik wygląda następująco: #include <NewPing.h> #define ULTRASONIC_TRIGGER_PIN 9 #define ULTRASONIC_ECHO_PIN 10 #define MAX_DISTANCE 400 NewPing sonar(ULTRASONIC_TRIGGER_PIN, ULTRASONIC_ECHO_PIN, MAX_DISTANCE); void setup() { pinMode(LED_BUILTIN, OUTPUT); } void loop() { digitalWrite(LED_BUILTIN, HIGH); delay(50); digitalWrite(LED_BUILTIN, LOW); delay(50); int ping = sonar.ping_cm(); if (ping < 30) digitalWrite(LED_BUILTIN, HIGH); else digitalWrite(LED_BUILTIN, LOW); delay(200); } Również i tu korzystam z wbudowanej diody, która powinna zapalić się, gdy zmierzona przez czujnik odległość będzie mniejsza niż 30 cm. Wreszcie program obsługujący całego robota: #include <NewPing.h> #include "EnableInterrupt.h" #include "NewPing.h" // Motor constants // Motor1 Speed #define MOTOR_1_SPEED_PIN 3 // Motor2 Speed #define MOTOR_2_SPEED_PIN 11 // Motor3 Speed #define MOTOR_3_SPEED_PIN 5 // Motor4 Speed #define MOTOR_4_SPEED_PIN 6 // Motor1 Direction #define MOTOR_1_DIR_PIN 4 // Motor2 Direction #define MOTOR_2_DIR_PIN 12 // Motor3 Direction #define MOTOR_3_DIR_PIN 8 // Motor4 Direction #define MOTOR_4_DIR_PIN 7 // Ultrasonic constants #define ULTRASONIC_TRIGGER_PIN 9 #define ULTRASONIC_ECHO_PIN 10 #define MAX_DISTANCE 400 // Servo constants #define SERVO_PIN 13 // AI constants #define DISTANCE_THRESHOLD_2 80 #define DISTANCE_THRESHOLD_1 40 // Ultrasonic control NewPing sonar(ULTRASONIC_TRIGGER_PIN, ULTRASONIC_ECHO_PIN, MAX_DISTANCE); // Motor control functions void M1_advance(byte Speed) ///<Motor1 Advance { digitalWrite(MOTOR_1_DIR_PIN, HIGH); analogWrite(MOTOR_1_SPEED_PIN, Speed); } void M2_advance(byte Speed) ///<Motor2 Advance { digitalWrite(MOTOR_2_DIR_PIN, LOW); analogWrite(MOTOR_2_SPEED_PIN, Speed); } void M3_advance(byte Speed) ///<Motor3 Advance { digitalWrite(MOTOR_3_DIR_PIN, LOW); analogWrite(MOTOR_3_SPEED_PIN, Speed); } void M4_advance(byte Speed) ///<Motor4 Advance { digitalWrite(MOTOR_4_DIR_PIN, HIGH); analogWrite(MOTOR_4_SPEED_PIN, Speed); } void M1_back(byte Speed) ///<Motor1 Back off { digitalWrite(MOTOR_1_DIR_PIN, LOW); analogWrite(MOTOR_1_SPEED_PIN, Speed); } void M2_back(byte Speed) ///<Motor2 Back off { digitalWrite(MOTOR_2_DIR_PIN, HIGH); analogWrite(MOTOR_2_SPEED_PIN, Speed); } void M3_back(byte Speed) ///<Motor3 Back off { digitalWrite(MOTOR_3_DIR_PIN, HIGH); analogWrite(MOTOR_3_SPEED_PIN, Speed); } void M4_back(byte Speed) ///<Motor4 Back off { digitalWrite(MOTOR_4_DIR_PIN, LOW); analogWrite(MOTOR_4_SPEED_PIN, Speed); } // PWM control volatile int microsYStart = 0; volatile int microsXStart = 0; volatile int microsZStart = 0; volatile int yMicros = 0; volatile int xMicros = 0; volatile int zMicros = 0; int UScounter = 0; int USlock = 0; // Pin 0 interrupt handling void pin0Rising() { microsYStart = micros(); disableInterrupt(0); enableInterrupt(0, pin0Falling, FALLING); } void pin0Falling() { yMicros = micros() - microsYStart; disableInterrupt(0); enableInterrupt(0, pin0Rising, RISING); } // Pin 1 interrupt handling void pin1Rising() { microsXStart = micros(); disableInterrupt(1); enableInterrupt(1, pin1Falling, FALLING); } void pin1Falling() { xMicros = micros() - microsXStart; disableInterrupt(1); enableInterrupt(1, pin1Rising, RISING); } // Pin2 interrupt handling void pin2Rising() { microsZStart = micros(); disableInterrupt(2); enableInterrupt(2, pin2Falling, FALLING); } void pin2Falling() { zMicros = micros() - microsZStart; disableInterrupt(2); enableInterrupt(2, pin2Rising, RISING); } void setup() { for (int i = 3; i < 9; i++) pinMode(i, OUTPUT); for (int i = 11; i < 13; i++) pinMode(i, OUTPUT); pinMode(ULTRASONIC_TRIGGER_PIN, OUTPUT); pinMode(ULTRASONIC_ECHO_PIN, INPUT); pinMode(SERVO_PIN, OUTPUT); enableInterrupt(0, pin0Rising, RISING); enableInterrupt(1, pin1Rising, RISING); enableInterrupt(2, pin2Rising, RISING); } void loop() { // Eval motor signals int yValue = (yMicros - 1500) / 2; int xValue = (xMicros - 1500) / 2; if (yValue > 260 || yValue < -260) yValue = 0; if (xValue > 260 || xValue < -260) xValue = 0; int leftEngines = constrain(xValue + yValue, -250, 250); int rightEngines = constrain(yValue - xValue, -250, 250); // Check for obstacles every 10th iteration UScounter++; if (UScounter >= 10) { UScounter = 0; int frontDistance = sonar.convert_cm(sonar.ping_median(5)); if (frontDistance != 0 && frontDistance < DISTANCE_THRESHOLD_2) { if (frontDistance > DISTANCE_THRESHOLD_1) USlock = 1; else USlock = 2; } else USlock = 0; } if (USlock == 1) { leftEngines = constrain(leftEngines, -250, 128); rightEngines = constrain(rightEngines, -250, 128); } if (USlock == 2) { leftEngines = constrain(leftEngines, -250, 0); rightEngines = constrain(rightEngines, -250, 0); } if (abs(leftEngines) < 20) { M3_advance(0); M4_advance(0); } else if (leftEngines > 0) { M3_advance(leftEngines); M4_advance(leftEngines); } else { M3_back(-leftEngines); M4_back(-leftEngines); } if (abs(rightEngines) < 20) { M1_advance(0); M2_advance(0); } else if (rightEngines > 0) { M1_advance(rightEngines); M2_advance(rightEngines); } else { M1_back(-rightEngines); M2_back(-rightEngines); } } Pozwala on na kontrolowanie robota przy pomocy jednego drążka aparatury (dwóch kanałów). Oprócz tego program cyklicznie sprawdza odległość przed robotem i zabezpiecza przed wjechaniem w ścianę: w przypadku przeszkody znajdującej się bliżej niż 80 cm od robota, zostanie ograniczona jego maksymalna prędkość, natomiast jeżeli odległość ta będzie mniejsza niż 40cm, robot zatrzyma się całkowicie i niemożliwe będzie ruszenie nim do przodu. Wnioski To była prawdziwa frajda zobaczyć, jak robot rusza i jeździ zgodnie z instrukcjami z aparatury! Pierwszy skończony projekt. Nauczyłem się na nim dużo, bo okazało się, że w trakcie pracy podjąłem bardzo dużo nietrafnych decyzji. Robot tak naprawdę nigdy nie wyjechał z domu, ma bardzo otwartą konstrukcję, która sprzyja dostawaniu się do obudowy pyłu i piachu. W domowych warunkach wystarczyłby natomiast napęd na dwa koła - w ten sposób miałbym też trochę miejsca wewnątrz obudowy. Arduino Leonardo jest świetne, ale wielkie. Znacznie lepiej sprawdziłoby się Arduino Micro albo Teensy. To drugie nawet bardziej, bo shield do sterowania silnikami pożera dużo pinów i niewiele zostaje na odbiór sygnału z odbiornika RC. Udało mi się jeszcze podłączyć czujnik odległości, ale serwo wpiąłem już bezpośrednio w odbiornik, bo po prostu zabrakło mi pinów. Nie ma większego sensu robić żadnych projektów (innych niż wstępne prototypy) na przewodzikach połączeniowych. Lepiej kupić sobie płytkę prototypową, złącza goldpinowe, przewodziki kydexowe i polutować wszystko na płytce - układ zajmuje znacznie mniej miejsce i jest znacznie mniej podatny np. na przypadkowe wyjęcie przewodu. Chodzi mi po głowie wskrzeszenie projektu - właśnie przy pomocy Teensy oraz drukarki 3D, przy pomocy której mogę wydrukować całe nadwozie szyte na miarę. Ale podejrzewam, że zajmę się tym dopiero za jakiś czas...
-
Cześć, Jakiś rok temu zacząłem przygodę z elektroniką i programowaniem. Po zakupie i zrobieniu kursów na Forbocie zacząłem wykonywać nowe projekciki. Moją uwagę przyciągnął manipulator Braccio. Stwierdziłem że jest to świetna baza do nauki programowania pod jakieś bardziej złożone projekty . Tak więc kupiłem manipulator. Po złożeniu można powiedzieć że delikatnie się rozczarowałem gdyż żeby wykonać jakiś złożony program z ruchami jest bardzo ciężko. W pętli wpisujemy komendę ruchu wraz z pozycjami wszystkich silników i prędkości (BraccioRobot.moveToPosition(pos.set(180, 165, 0, 0, 180, 10), 50); ) Jest to na tyle uciążliwe że wpisujemy pozycję z ręki i patrzymy po uruchomieniu co się wydarzy. Stwarza to duże ryzyko kolizji i zniszczenia zabawki. Zapewne myślałem że kupując taki zestaw będzie się go programowało jak na zajęciach z robotyki ustawiamy na pozycje i zapisujemy. Dlatego też postanowiłem stworzyć taki sterownik od zera. W założeniu w trybie uczenia będziemy mogli sterować dowolnie każdym silnikiem i zapisywać pozycję w jakiej jest robot. A w trybie automatycznym wykonać wcześniej zapisane ruchy. W projekcie wykorzystałem 7 potencjometrów wieloobrotowych (6 na każdy silnik i jeden do ustawiania prędkości), 5 przycisków (obecnie program wykorzystuje 4 ;) , moduł karty pamięci, wyświetlacz LCD 4x20, diodę sygnalizacyjną, a całe sterownie jest na ARDUINO MEGA z powodu możliwości późniejszej rozbudowy projektu. Obecnie po uruchomieniu sterownik zaczytuje program z karty pamięci , jeżeli takiego nie ma to wyświetla się komunikat. Po wciśnięciu jednego z przycisków wchodzimy w tryb uczenia. W tym trybie kręcimy potencjometrami ustawiając całego robota w potrzebnej pozycji oraz ustawiając prędkość dla poszczególnego przejazdu. Pozycje poszczególnych serw pokazywane są na wyświetlaczu LCD. Wciskamy przycisk i zapisujemy pozycję na kartę SD. Po zapisaniu wszystkich pozycji możemy wejść w tryb automatyczny i po wciśnięciu przycisku robot będzie wykonywał zapisane ruchy aż do wciśnięcia innego przycisku. Program jest jeszcze archaiczny i dużo rzeczy trzeba poprawić czy dorobić ( jak np zapis wielu programów i ich wybór, sterowanie poprzez wpisanie pozycji XYZ chwytaka i wiele wiele innych). Jednak w tym momencie zabawa z programowaniem przestała być wygodna. Prowizoryczne podłączenie na płytce stykowej, małe potencjometry itp powodowały przerwania przy ustawianiu itp. Dlatego stwierdziłem że kolejny krok to wykonanie kompatybilnej obudowy. Porysowałem cały projekt w 3d i zleciłem wykonanie obudowy do wykonania. Obecnie jestem na etapie montażu i lutowania pierwszej wersji sterownika Gdy to skończę wrzucę jakieś filmy jak to pracuje wszystko i biorę się na rozbudowę programu sterującego. Tak czy inaczej na chwilę obecną jestem w stanie zaprogramować i wykonywać ruchy robota bez użycia komputera. Gdyby kogoś zainteresował temat dorzucę jeszcze jakieś zdjęcia i efekt finalny Pozdrawiam
-
Kolejny sterownik akwarystyczny, a może coś więcej :)
jacqob569 opublikował temat w Artykuły użytkowników
Witam, Chcę przedstawić wam moje podejście do tematu sterowników akwarystycznych. Sterownik ten zbudowałem dla swojego dziadka, który chciał załączać automatycznie pompkę, napowietrzacz i światło do oświetlenia akwarium. Zacząłem więc planować, stwierdziłem, że sterownik musi posiadać minimum 2 wyjścia 230V, jedno wyjście 12V z możliwością sterowania PWM, jakieś bajery (odczyt temperatury wody, automatyczne wyłącznie przekaźników, automatyczny karmnik dla ryb itp). Kilka lat temu zbudowałem swój pierwszy sterownik akwarystyczny (nazwałem go V.1), wykorzystałem do tego celu esp8266-12e, moduł podwójnego przekaźnika elektromagnetycznego oraz czujnik DS18B20. Nie mam niestety zdjęć pierwszego modelu. Działał on u mnie przez ponad rok, w tym czasie wynotowałem kilka wad mojego rozwiązania. Największe wady: -Podczas braku dostępu do internetu nie mogłem korzystać z sterownika, -Przekaźnik nie zawsze wyłączał się, -esp zawieszało się podczas załączania przekaźnika (podczas rozbiórki sterownika okazało się, że dobrałem złe wartości rezystorów do LM317 i zamiast 3,3V na esp podawałem 2,95V ) Sterownik V.2 (dla dziadka) Postanowienia: Urządzenie musiało być proste w użytkowaniu, nie mogło tracić swoich podstawowych funkcjonalności podczas braku dostępu do internetu, sterownik powinien pamiętać stany wyjść podczas braku zasilania sterownik powinien automatycznie sterować wyjściami i modułami do niego podłączonymi sterownik powinien sterować wyjściem 12V w sposób płynny, imitować zachód lub wschód słońca na pasku LED musiał posiadać funkcję zdalnego zarządzania swoimi funkcjami Działanie: Sterownik składa się z dwóch części, głównego sterownika oraz karty sieciowej. Sterownik główny odpowiada za włączanie/wyłączanie gniazd 230V, podawaniem sygnału PWM na wyjście, odczyt temperatury/wilgotności z czujników ds18b20 i DHT11, wyświetlanie godziny i innych danych na wyświetlaczu siedmio segmentowym, sprawdzaniu i wykonywaniu "alarmów", komunikowanie się z modułem karty sieciowej poprzez UART. Karta sieciowa - czyli moduł esp8266 (Wemos D1 mini) odpowiada za komunikację z aplikacją Blynk, zbieranie informacji (logów) z działania całego urządzenia i zapisywaniu ich na karcie SD, pobieraniu z internetu godziny do synchronizacji czasu, wysyłaniu wiadomości email z błędami w działaniu urządzenia lub plikami txt z logami, wyświetlaniu statusu połączenia z siecią i aplikacją za pomocą diod LED, komunikacją ze sterownikiem głównym, obsłudze komend wysyłanych przez terminal w aplikacji Blynk, itp. Budowa: W sterowniku głównym znajduje się: Arduino Nano Moduł podwójnego przekaźnika półprzewodnikowego SSR, zdecydowałem się na zmianę rodzaju przekaźnika z powodu tego, że taki przekaźnik jest bezgłośny, zawsze działa (nie zawiesza się tak jak przekaźniki elektromagnetyczne) Moduł zegara czasu rzeczywistego DS1307, odpowiada za dostarczanie dla wyświetlacza aktualnej godziny nawet po wyłączeniu zasilania oraz jako czas potrzebny do działania alarmów. Czujniki DS18B20 (wersja wodoodporna na metrowym przewodzie, metalowa końcówka czujnika znajduje się w wodzie) oraz DHT11 (wyprowadzony jest na zewnątrz obudowy sterownika) 4 przyciski z podświetleniem led w 4 kolorach(czerwony, zielony, niebieski, żółty). Odpowiadają one za włączanie i wyłączanie przekaźników, zmianę jasności paska LED, włączenie karmnika dla ryb oraz wyświetleniu na wyświetlaczu aktualnych temperatur Wyświetlacz siedmio segmentowy TM1637, wyświetla on aktualną godzinę, temperaturę, kody błędów Tranzystor IRF540 wraz z transoptorem PC817 i kilkoma rezystorami Przetwornica step-down (obniża napięcie 12V z zasilacza, na 5V potrzebnych do zasilenia Arduino i reszty modułów) Bezpieczniki 1A na gniazdka 230V oraz 1,2A na zasilaczu 12V Włącznik dźwigniowy (przerywa napięcie zasilania 12V) Zasilacz 12V 1,5A W karcie sieciowej znajduje się: Wemos D1 Mini, z dolutowaną anteną od starego routera tp-link (link do instrukcji takiej operacji instrukcja ) Adapter do karty microSD - SD 3 diody plus rezystory 220Ω micro switch, służy do resetowania płytki przełącznik 3 pozycyjny, służy do wyboru sieci WiFi, np. 1 - sieć1, 2 - sieć2, 3 - sieć serwisowa przełącznik 2 pozycyjny, służy do wyłączenia 3 diod sygnalizacyjnych LED, kiedy nie są potrzebne konwerter stanów logicznych, służy do konwersji napięć między Arduino a Wemosem na magistrali UART moduł pcf8574, steruje diodami oraz automatycznym karmnikiem, w przyszłości może także sterować kolejnymi przekaźnikami Działanie Sterownika głównego void setup: wczytanie ostatnich stanów wyjść z pamięci EEPROM ustawienie przerwania na 1ms (odpowiada za zmianę wartości na pinie PWM oraz dekrementuje zmienną potrzebną do wyświetlania informacji na wyświetlaczu ustawiam wejścia i wyjścia ustawiam stany pinów przy pomocy danych odczytanych z pamięci EEPROM (funkcja przywracania ostatnich stanów pinów) ustawiam jasność paska led (podczas włączenia sterownika, światło powoli rozjaśnia się, do ostatniego zapisanego stanu) Inicjuje zegar, czujniki temperatur, przyciski itp. void loop: W pętli loop sprawdzam: Czy jakiś przycisk został wciśnięty Ustawiam stany pinów, odświeżam informacje na wyświetlaczu Sprawdzam czy jakiś alarm może zostać wykonany Sprawdzam czy karta sieciowa wysłała jakieś dane Liczę ilość przejść pętli w czasie jednej sekundy, co sekundę wyświetlam tę ilość na serial monitorze Sprawdzam, czy minęło 5 sekund, po tym czasie raportuje do karty sieciowej o stanach wyjść ------------------------------------------------------------------------------------------------------------------------ 1.Sprawdzenie przycisków - Arduino obsługuje 4 przyciski, służą one do: Pojedyncze (krótkie wciśnięcie) - włączenie/wyłączenie przekaźnika 1 i 2, zmiany jasności paska LED podłączonego do pinu PWM, załączenie karmnika, Długie wciśnięcie - wyświetlenie na wyświetlaczu siedmio segmentowym temperatury wody/powietrza, szybkie wyłączenie paska LED przycisk niebieski - krótkie wciśnięcie (zmiana stanu przekaźnika 1) / długie wciśnięcie (wyświetlenie na wyświetlaczu temperatury wody) przycisk czerwony - krótkie wciśnięcie (zmiana stanu przekaźnika 2) / długie wciśnięcie (wyświetlenie na wyświetlaczu temperatury powietrza) przycisk zielony - krótkie wciśnięcie (zmiana stanu na pinie PWM ), każde wciśnięcie zwiększa wartość na pinie PWM [0,20,40,60,80,100%] / długie wciśnięcie szybko wygasza pasek LED przycisk żółty - krótkie wciśnięcie. Powoduje mruganie podświetleniem przycisku czerwonego i zielonego przez 10 sekund, wciśnięcie przycisku zielonego spowoduje podjęcie przez sterownik próby załączenia automatycznego karmnika \ wciśnięcie przycisku czerwonego anuluje ten proces, tak samo jak nie wciśnięcie żadnego z przycisków przez 10 sekund void checkbutton() { if (checkbutton_debuce == 1) { Serial.print(F("R_SW_State: ")); Serial.print(R_SW_state); Serial.print(F(" | B_SW_State: ")); Serial.print(B_SW_state); Serial.print(F(" | G_SW_State: ")); Serial.print(G_SW_state); Serial.print(F(" | Y_SW_State: ")); Serial.println(Y_SW_state); } G_SW.read(); B_SW.read(); Y_SW.read(); R_SW.read(); if (B_SW.changed()) { display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(1, false, 1, 1); last_millis2 = 0; B_SW_state = B_SW.toggleState(); if (B_SW_state == 1) { display.setSegments(display_symbol_off, 2, 2); send_command(s1on, 8); } else { display.setSegments(display_symbol_on, 2, 2); send_command(s1off, 8); } screen_timeout = 5000; } if (R_SW.changed()) { display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(2, false, 1, 1); last_millis2 = 0; R_SW_state = R_SW.toggleState(); if (R_SW_state == 1) { send_command(s2on, 8); display.setSegments(display_symbol_off, 2, 2); } else { send_command(s2off, 8); display.setSegments(display_symbol_on, 2, 2); } screen_timeout = 5000; } if (G_SW.pressedFor(LONG_PRESS)) { digitalWrite(GREEN_SW_LIGHT, 0); delay(50); digitalWrite(GREEN_SW_LIGHT, 1); delay(50); digitalWrite(GREEN_SW_LIGHT, 0); delay(50); digitalWrite(GREEN_SW_LIGHT, 1); delay(50); last_millis2 = 0; G_SW_button_cycle = 0; G_SW_state = 0; set_pwm_led_1(G_SW_state); led_step = 2; Serial.println("Green Button LONG PRESS DETECTED !"); display.clear(); display.showNumberDec(map(G_SW_state, 0, 255, 0, 100), false, 3, 1); display.setSegments(display_symbol_s, 1, 0); while (true) { G_SW.read(); if (G_SW.wasReleased()) { break; } } screen_timeout = 5000; return; } if (G_SW.changed()) { digitalWrite(GREEN_SW_LIGHT, 1); last_millis2 = 0; G_SW_button_cycle++; if (G_SW_button_cycle >= 6) { G_SW_button_cycle = 0; } switch (G_SW_button_cycle) { case 0: G_SW_state = 0; break; case 1: G_SW_state = 51; break; case 2: G_SW_state = 102; break; case 3: G_SW_state = 153; break; case 4: G_SW_state = 204; break; case 5: G_SW_state = 255; break; } display.clear(); display.showNumberDec(map(G_SW_state, 0, 255, 0, 100), false, 3, 1); display.setSegments(display_symbol_s, 1, 0); vpwm1[4] = (byte)G_SW_button_cycle * 2; send_command(vpwm1, 8); delay(40); if (G_SW_state != 255) { digitalWrite(GREEN_SW_LIGHT, 0); } led_step = led_step_button; screen_timeout = 5000; } if (Y_SW.pressedFor(LONG_PRESS)) { //locking feeding option Serial.println("Yellow Button LONG PRESS DETECTED !"); while (true) { Y_SW.read(); if (Y_SW.wasReleased()) { //send_command(auto_feed, 9); delay(25); break; } } return; } if (Y_SW.changed()) { bool wait2pressB = 0; bool w2p_ledstate = 1; unsigned long y_long_press_ms = millis(); unsigned long y_long_press_ms_led = millis(); while (wait2pressB == 0) { green_led_switch_on = 0; R_SW.read(); G_SW.read(); if (millis() - y_long_press_ms_led > 500) { digitalWrite(GREEN_SW_LIGHT, w2p_ledstate); digitalWrite(RED_SW_LIGHT, w2p_ledstate); w2p_ledstate = !w2p_ledstate; y_long_press_ms_led = millis(); } if (G_SW.changed()) { digitalWrite(GREEN_SW_LIGHT, G_SW_state); digitalWrite(RED_SW_LIGHT, R_SW_state); send_command(auto_feed, 9); delay(25); green_led_switch_on = 1; return; } else if (R_SW.changed()) { digitalWrite(GREEN_SW_LIGHT, G_SW_state); digitalWrite(RED_SW_LIGHT, R_SW_state); green_led_switch_on = 1; return; } else { } if (millis() - y_long_press_ms > 10000) { digitalWrite(GREEN_SW_LIGHT, G_SW_state); digitalWrite(RED_SW_LIGHT, R_SW_state); green_led_switch_on = 1; return; } } } } 2.Ustawienie odpowiednich stanów pinów: Ta funkcja odpowiada za zmianę stanów wyjść do których podłączony jest moduł przekaźników oraz za zmianę informacji na wyświetlaczu (godziny). void set_pin_states() { set_power_230V_1(B_SW_state); set_power_230V_2(R_SW_state); display_value_on_7s_display(Y_SW_state); } void set_power_230V_1(bool s) { digitalWrite(power_230V_1, !s); digitalWrite(BLUE_SW_LIGHT, s); } void set_power_230V_2(bool s) { digitalWrite(power_230V_2, !s); digitalWrite(RED_SW_LIGHT, s); } void display_value_on_7s_display(byte dis) { if (screen_timeout > 0) { return; } int current_t = 0; current_t = H * 100 + MIN; display.showNumberDecEx(current_t, 64, true); } 3."Alarmy": Poprzednia wersja sterownika posiadała ogromną wadę, chodzi o automatyczne załączanie przekaźników lub innych modułów. W poprzedniej wersji za załączanie automatyczne odpowiadała aplikacja Blynk z widżetem Timer, było to dość wygodne wyjście, ponieważ całą konfigurację (co ma się włączyć, kiedy, itp.) robiło się w przejrzystym menu aplikacji. To rozwiązanie miało jednak dużą wadę, podczas braku dostępu do internetu, sterownik nie wykonywał żadnych akcji. W drugiej wersji postanowiłem całkowicie oddzielić od siebie warstwę internetową (komunikacji z internetem) od warstwy podstawowych funkcjonalności, takich jak automatyczne włączanie/wyłączanie przekaźników, automatyczna zmiana jasności, włączenie karmnika itp. Co to jest "alarm"? - alarmem nazywam jedną funkcję, która wykona się o danej godzinie, minucie, dniu. Przechowywany jest w formie 5 jedno bajtowych komórek, ułożonych obok siebie w pamięci EEPROM. Alarm składa się z: A (Action- Pierwsza komórka, przechowuje numer funkcji, która ma zostać wykonana.) D (Day- Dzień tygodnia w który musi wykonać się alarm. 1 - poniedziałek / ... / 7 - niedziela / 8 - każdy dzień tygodnia) H (Hour - godzina. 0 - 23) M (Minute - minuta, 0 - 59) V (Value - parametr funkcji, np. funkcja 1 przyjmuje parametr 1 lub 0 [ włączenie / wyłączenie przekaźnika 1]) Ograniczenia - sterownik może przechować maksymalnie 80 alarmów, dwa lub więcej alarmów nie może zostać wykonanych w tej samej minucie Funkcja sprawdzająca, czy jakiś alarm może zostać wykonany: void check_alarm() { if (alarm_onoff == 1) { int p = 0; int n = 0; while (n <= ile_alarmow) { if (EEPROM.read(n * 5) != 127) { p = (byte)(n * 5); byte alarm_d, alarm_h, alarm_min, alarm_action, alarm_val; alarm_action = (byte)EEPROM.read(p); alarm_d = (byte)EEPROM.read(p + 1); alarm_h = (byte)EEPROM.read(p + 2); alarm_min = (byte)EEPROM.read(p + 3); alarm_val = (byte)EEPROM.read(p + 4); get_current_time(); if ((alarm_d == 8 || alarm_d == weekd) && alarm_onoff == 1) { if (alarm_h == H && alarm_min == MIN) { Serial.print(F("Wykonam akcje nr. ")); Serial.println(alarm_action); Serial.print(F("Alarm is trigered on ")); Serial.print(alarm_h); Serial.print(F(":")); Serial.print(alarm_min); Serial.print(F(":")); Serial.println(S); execute_alarm((byte)alarm_action, (byte)alarm_val); alarm_onoff = 0; alarm_timer.reset(); //1 minuta przerwy między alarmami } } else { //Serial.print(F("Nie wykonam akcji nr. ")); //Serial.println(i); } } n++; } } } void execute_alarm(byte execute_action_number, byte execute_value) { Serial.print(F("I'm executing function number ")); Serial.print(execute_action_number); Serial.print(F(" with value: ")); Serial.println(execute_value); if (execute_action_number == 1 || execute_action_number == '1') { bool action_flag; if (execute_value == 1) { action_flag = 1; } else if (execute_value == 0) { action_flag = 0; } else { action_flag = 0; } B_SW_state = action_flag; display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(1, false, 1, 1); if (B_SW_state == 1) { display.setSegments(display_symbol_off, 2, 2); } else { display.setSegments(display_symbol_on, 2, 2); } screen_timeout = 5000; //set_power_230V_1(action_flag); } else if (execute_action_number == 2 || execute_action_number == '2') { bool action_flag2; if (execute_value == 1) { action_flag2 = 1; } else if (execute_value == 0) { action_flag2 = 0; } else { action_flag2 = 0; } R_SW_state = action_flag2; display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(2, false, 1, 1); last_millis2 = 0; R_SW_state = R_SW.toggleState(); if (R_SW_state == 1) { send_command(s2on, 8); display.setSegments(display_symbol_off, 2, 2); } else { send_command(s2off, 8); display.setSegments(display_symbol_on, 2, 2); } screen_timeout = 5000; //set_power_230V_2(action_flag2); } else if (execute_action_number == 3 || execute_action_number == '3') { if (execute_value < 11 && execute_value > -1) { G_SW_state = map(execute_value, 0, 10, 0, 255); Serial.print(F("Setting led pwm value by alarm to ")); Serial.println(G_SW_state); if (execute_value == 0) { G_SW_button_cycle = 0; } else if (execute_value > 0 && execute_value <= 2) { G_SW_button_cycle = 1; } else if (execute_value > 2 && execute_value <= 4) { G_SW_button_cycle = 2; } else if (G_SW_state > 4 && G_SW_state <= 6) { G_SW_button_cycle = 3; } else if (G_SW_state > 6 && G_SW_state <= 8) { G_SW_button_cycle = 4; } else if (G_SW_state > 8 && G_SW_state <= 10) { G_SW_button_cycle = 5; } else { G_SW_button_cycle = 0; } led_step = led_step_alarm; //set_pwm_led_1(execute_value); display.clear(); display.showNumberDec(map(G_SW_state, 0, 255, 0, 100), false, 3, 1); display.setSegments(display_symbol_s, 1, 0); screen_timeout = 5000; } else { Serial.println(F("Wrong value !")); } } else if (execute_action_number == 4 || execute_action_number == '4') { Serial.println(F("Automatic feeding !!!")); //byte auto_feed[9] = {0x7E, 'A', 'F', ';', 'e', 'E', 'G', 0, 0x7F} send_command(auto_feed, 9); } else if (execute_action_number == 5 || execute_action_number == '5') { //re_send_time[9] = {0x7E, 'R', 'E', ':', 'S', '8', 'M', 0, 0x7F}; Serial.println(F("Alarm Automatic time syncing !!!")); send_command(re_send_time, 9); } else if (execute_action_number == 6 || execute_action_number == '6') { set_brightness(execute_value); } else if (execute_action_number == 7 || execute_action_number == '7') { if (EEPROM.read(1010) == 0) { //EEPROM.write(1010, 1); eeprom_optimalization(1010, 1); if (execute_value == 0) { //reset arduino Serial.println(F("Resseting arduino...")); resetFunc(); } else if (execute_value == 1) { //reset esp Serial.println(F("Resseting esp...")); send_command(reset_esp_command, 9); } else if (execute_value == 2) { //reset arduino and esp Serial.println(F("Resseting esp...")); send_command(reset_esp_command, 9); Serial.println(F("Resseting arduino...")); resetFunc(); } else { Serial.println(F("Wrong reset value!range:0-2")); //EEPROM.write(1010, 0); eeprom_optimalization(1010, 0); } } } else if (execute_action_number == 8 || execute_action_number == '8') { //send_all_mail } else if (execute_action_number == 9 || execute_action_number == '9') { //deleta_all_sd_card_files } else { Serial.println(F("Wrong alarm !!!")); } } 4.Sprawdzenie czy karta sieciowa wysłała jakieś dane: Esp8266 i Arduino Nano komunikują się ze sobą poprzez UART. Wszystkie informacje przesyłane są w postaci komend rozpoczynających się od znaku 0x7E, dalej reszta komendy wraz z parametrami, suma kontrolna, znak 0x7F, 3 znaki nowej lini '\n'. Przykładowa komenda wysyłana przez Arduino do Esp: 0x7E, 'n', '1', 'o', 'n', '+', (wyliczona suma kontrolna), 0x7F, '\n', '\n', '\n' Funkcja sprawdzająca odebrane dane w karcie sieciowej oraz Arduino jest prawie taka sama, polega na nasłuchiwaniu czy jest coś nadawane. Jeżeli tak to funkcja zapisuje do buffera bajt po bajcie aż do otrzymania trzech znaków nowej lini '\n' pod rząd. Jeżeli otrzymała te 3 znaki, rozpoczyna liczenie sumy kontrolnej od początku komendy do ostatniego bajtu danych w komendzie. Następnie porównuje wyliczoną sumę kontrolną z odebraną sumą, jeżeli sumy zgadzają się (są takie same), to Arduino lub Esp wykonuje daną komendę, np. komenda (0x7E, 'n', '1', 'o', 'n', '+', (wyliczona suma kontrolna), 0x7F, '\n', '\n', '\n') odpowiada za ustawienie w aplikacji Blynk stanu przycisku nr. 1 na włączony (podczas włączenia przekaźnika 1 przy pomocy przycisku na obudowie, Arduino musi wysłać tą informacje do karty sieciowej, aby użytkownik miał podgląd na aktualny stan przekaźnika). void recive_data() { bool control_val = 1; espSerial.flush(); if (espSerial.available() && control_val == 1) { espSerial.flush(); //digitalWrite(13, 1); delay(3); r_array_counter = 0; r_array[r_array_counter] = '\n'; timestart = millis(); //byte character_counter = 0; bool detect_end = 0; byte add_val = 2; while (espSerial.available() && r_array_counter < 25 && (millis() - timestart) < 400 && control_val == 1) { delay(1); byte character = espSerial.read(); r_array[r_array_counter] = character; r_array_counter++; r_array[r_array_counter] = '\n'; if (character != '\n' && control_val == 1) { if (serial_reciv_debug == 1) { Serial.print(F("{")); Serial.print(character); Serial.print(F("}")); } } if (character == '\n' && r_array[r_array_counter - 3] == '\n' && r_array[r_array_counter - 2] == '\n' && r_array[r_array_counter - 4] == 0x7F) { detect_end = 1; } if (detect_end == 1 && control_val == 1) { detect_end = 0; r_array_counter -= 1; if (serial_reciv_debug == 1) { Serial.print(F(" [r_array_counter]-> ")); Serial.println(r_array_counter); Serial.println(F(" - new line symbol detect!")); } byte chk = 0; uint8_t i = 0; for (i = 0; i < r_array_counter - (2 + add_val); i++) { chk ^= r_array[i]; } Serial.print("Control sum = "); Serial.println(chk); if (r_array[0] == 0x7E && r_array[r_array_counter - (1 + add_val)] == 0x7F && (byte)r_array[r_array_counter - (2 + add_val)] == (byte)chk && control_val == 1) { if (serial_reciv_debug == 1) { Serial.println(F("Good control sum !")); } last_millis2 = 0; if (r_array[1] == 's' && (r_array[3] == '$' || r_array[3] == '#')) { switch (r_array[2]) { case '1': if (r_array[4] == 'o' && r_array[5] == 'n') { B_SW_state = 1; set_power_230V_1(1); B_SW.m_toggleState = 1; display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(1, false, 1, 1); display.setSegments(display_symbol_off, 2, 2); screen_timeout = 5000; } else if (r_array[4] == 'o' && r_array[5] == 'f') { B_SW_state = 0; set_power_230V_1(0); B_SW.m_toggleState = 0; display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(1, false, 1, 1); display.setSegments(display_symbol_on, 2, 2); screen_timeout = 5000; } else { } break; case '2': if (r_array[4] == 'o' && r_array[5] == 'n') { R_SW_state = 1; set_power_230V_2(1); R_SW.m_toggleState = 1; display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(2, false, 1, 1); display.setSegments(display_symbol_off, 2, 2); screen_timeout = 5000; } else if (r_array[4] == 'o' && r_array[5] == 'f') { R_SW_state = 0; set_power_230V_2(0); R_SW.m_toggleState = 0; display.clear(); display.setSegments(display_symbol_G, 1, 0); display.showNumberDec(2, false, 1, 1); display.setSegments(display_symbol_on, 2, 2); screen_timeout = 5000; } else { } break; } control_val = 0; } ... } } } 5.Sprawdzanie wydajności programu: Podczas pisania programu dla tego sterownika, musiałem sprawdzać wydajność danego rozwiązania, np. ile czasu zajmuje włączenie/wyłączenie jednej funkcji. Napisałem więc prostą funkcję, która sprawdza ile raz wykonują się wszystkie funkcje w funkcji main. Co sekundę Arduino wysyła na serial monitor informację ile razy wykonała się funkcja main. void framerate() { fr++; if (S != fr_sec) { Serial.print("##> FRAMERATE = "); Serial.print(fr); Serial.println(" fps <##"); fr = 0; fr_sec = S; } } 6.Funkcja wysyłające dane do karty sieciowej: Sterownik co 5 sekund wysyła do karty sieciowej informację o aktualnych temperaturach wody i powietrza oraz wilgotność. Wysyła je w formie komendy: 0x7E, '%', (wilgotność powietrza), 'T', (temp. powietrza liczba całkowita), (temp. powietrza liczba po przecinku), '@', (temp. wody liczba całkowita), (temp. wody liczba po przecinku), (suma kontrolna), 0x7F, '\n', '\n', '\n' Co 10 sekund wysyła dane oraz zapisuje stan wyjść w pamięci EEPROM, jeżeli są one inne od ostatnio zapisanych ( jest to potrzebne do działania funkcji przywracania stanów podczas wyłączenia sterownika). Wysyłane dane składają się z informacji o stanach wyjść sterownika (stan przekaźnika 1 i 2, wartość na pinie PWM). Komenda wygląda następująco: 0x7E, (stan przekaźnika 1), '&', (stan przekaźnika 2), '^', (wartość na pinie PWM przeskalowana od 0 do 10), '%', (suma kontrolna) , 0x7F, '\n', '\n', '\n' void execute_millis_functions() { if ((millis() - last_millis1) > 5000) { send_sensors_data(); last_millis1 = millis(); } if ((millis() - last_millis2) > 10000) { sync_data(); Serial.print(F("****> power_loos_counter = ")); Serial.println(power_loos_counter); if (B_SW_state != EEPROM.read(1001)) { //EEPROM.write(1001, B_SW_state); eeprom_optimalization(1001, B_SW_state); power_loos_counter++; } if (R_SW_state != EEPROM.read(1002)) { //EEPROM.write(1002, R_SW_state); eeprom_optimalization(1002, R_SW_state); power_loos_counter++; } if (G_SW_state != EEPROM.read(1003)) { //EEPROM.write(1003, G_SW_state); eeprom_optimalization(1003, G_SW_state); power_loos_counter++; } last_millis2 = millis(); } if (alarm_onoff == 0 && alarm_timer.isReady()) { alarm_onoff = 1; } if (timer1.isReady()) { timer_clock(); } } void sync_data() { //sync[9] = {0x7E, 0, '&', 0, '^', 0, '%', 0, 0x7F}; if (B_SW_state == 1) { data_2_sync[1] = 1; } else if (B_SW_state == 0) { data_2_sync[1] = 0; } else { } if (R_SW_state == 1) { data_2_sync[3] = 1; } else { data_2_sync[3] = 0; } if (G_SW_button_cycle == 0) { data_2_sync[5] = 0; } else if (G_SW_button_cycle == 1) { data_2_sync[5] = 2; } else if (G_SW_button_cycle == 2) { data_2_sync[5] = 4; } else if (G_SW_button_cycle == 3) { data_2_sync[5] = 6; } else if (G_SW_button_cycle == 4) { data_2_sync[5] = 8; } else if (G_SW_button_cycle == 5) { data_2_sync[5] = 10; } else { data_2_sync[5] = 0; } send_command(data_2_sync, 9); } KOD Sterownika (Zalecam wykorzystanie bibliotek dołączonych do tego artykułu, ponieważ zmieniałem w nich kilka rzeczy): aquarium_driver_v2.zip Działanie Karty Sieciowej (esp8266) void setup(): void setup() { Serial.begin(9600); EEPROM.begin(1000); //0-405 - alarms eeprom //507-998 - epprom variable space //1000-4096 - log eeprom space delay(50); pcf8574.pinMode(Wifi_status_led, OUTPUT); pcf8574.pinMode(Blynk_status_led, OUTPUT); pcf8574.pinMode(Connection_status_led, OUTPUT); pcf8574.pinMode(feeding_pin, OUTPUT); pcf8574.pinMode(retraction_pin, OUTPUT); pcf8574.pinMode(wifi_network_sw_a, INPUT_PULLUP); pcf8574.pinMode(wifi_network_sw_b, INPUT_PULLUP); pcf8574.begin(); pcf8574.digitalWrite(Wifi_status_led, OFF); pcf8574.digitalWrite(Blynk_status_led, OFF); pcf8574.digitalWrite(Connection_status_led, OFF); pcf8574.digitalWrite(feeding_pin, OFF); pcf8574.digitalWrite(retraction_pin, OFF); for (int i = 0; i < 880; i++) { message_aray[i] = 127; } feeding_time = EEPROM.read(510); //510 retraction_time = EEPROM.read(511); //511 feeding_today = EEPROM.read(509); //509 delay(100); set_network(); byte datalog_d, datalog_m, datalog_y; datalog_d = EEPROM.read(523); datalog_m = EEPROM.read(524); datalog_y = EEPROM.read(525); String dd_str = ""; if (datalog_d < 10) { dd_str = "0"; } dd_str += String(datalog_d); String dm_str = ""; if (datalog_m < 10) { dm_str = "0"; } dm_str += String(datalog_m); String dy_str = ""; if (datalog_y < 10) { dy_str = "0"; } dy_str += String(datalog_y); current_log_filename = "L" + dd_str + dm_str + dy_str + ".txt"; cmd.print("Log filename is "); cmd.println(current_log_filename); cmd.flush(); if (EEPROM.read(522) == 1) { EEPROM.write(522, 0); EEPROM.commit(); send_mail(0); } else if (EEPROM.read(522) == 2) { EEPROM.write(522, 0); EEPROM.commit(); send_mail(1); } else { } if (WiFi.status() == 6) { pcf8574.digitalWrite(Wifi_status_led, OFF); } unsigned long startWifi = millis(); WiFi.mode(WIFI_STA); WiFi.begin(E_ssid.c_str(), E_pass.c_str()); bool wifi_flag = false; while (WiFi.status() != WL_CONNECTED) { delay(100); pcf8574.digitalWrite(Wifi_status_led, wifi_flag); wifi_flag = !wifi_flag; if (millis() > startWifi + myWifiTimeout) { Serial.println("Time out"); break; } } Blynk.config(auth, server, port); checkBlynk(); timer.setInterval(functionInterval, myfunction); // run some function at intervals per functionInterval timer.setInterval(blynkInterval, checkBlynk); // check connection to server per blynkInterval timer.setInterval(500L, period_function); timer.setInterval(5000L, sync_alarms); // sync alarm && check_logs(); timer.setInterval(15000L, check_day_change); cmd.clear(); blynk_led.off(); setSyncInterval(1000); dp_network(); if (debug_log_string.length() > 0) { cmd.println(debug_log_string); cmd.flush(); } add_log(48, datalog_d, datalog_m, datalog_y); add_log(30, 1, 999, 999); check_day_change(); } Zainicjowanie UARTA oraz pamięci "EEPROM" esp8266 Ustawienie pinów modułu ekspandera pcf8574 (wejścia dla przełącznika do wyboru sieci WiFi, wyjścia do sterowania karmnikiem oraz dla diod LED wyświetlających stan połączenia z siecią WiFi, połączenia z serwerem aplikacji Blynk oraz potwierdzenie o otrzymaniu komendy od Arduino na magistrali UART) Odczytanie parametrów z pamięci "EEPROM" (karta sieciowa odczytuje: informacje potrzebne do działania karmnika) Odczytanie z przełącznika wybranej sieci wifi oraz wczytanie nazwy Sieci (SSID) oraz hasła do sieci Ustawienie nazwy pliku z logami sterownika Próba połączenia się z siecią Konfiguracja ustawień aplikacji Blynk i sprawdzenie połączenia z serwerem aplikacji Ustawienie Timerów Dodanie do logów informacji o starcie karty sieciowej ------------------------------------------------------------------------------------------------------------------------ 4.Sterownik może obsłużyć maksymalnie 3 sieci WiFi, 2 sieci użytkownika oraz jedną sieć serwisową. Na karcie sieciowej znajduje się przełącznik 3 pozycyjny, pierwsze dwie pozycje to sieci użytkownika, ostatnia, to sieć serwisowa. Po sprawdzeniu przez kartę sieciową stanu przełącznika, odczytuje z pamięci "EEPROM" nazwę sieci (SSID) oraz hasło, jeżeli wybrano 3 pozycję przełącznika, SSID ustawiane jest na SIEC_SERWISOWA oraz hasło SERWISANT123. Ma to na celu zapobieganie blokadzie karty sieciowej, przez źle podane hasło. Hasło do sieci WiFi można zmienić przy pomocy telefonu, służy do tego komenda add_wifi=(numer pod którym ma zostać zapisana nowa sieć, 1 lub 2)-(SSID maks 20 znaków),(Hasło maks 20 znaków) 5.Podczas załączania się karty sieciowej tworzona jest nazwa pliku, do którego będą zapisywane dane. Nazwa zawiera aktualną datę, np. L010321.txt, urządzenie zapisuję do tego pliku oraz pliku DATALOG.txt informację o działaniu sterownika oraz karty sieciowej w postaci logów. void loop(): void loop() { recive_data(); check_feeding(); if (millis() - repair_data_timer >= 10000 && EEPROM.read(526) == 1) { repair_data_timer = millis(); byte repair_number = check_is_data_ok(); if (repair_number != 0) { repair_data(repair_number); } } if (millis() >= pwm_timeout_time) { pwm_timeout = 0; } if (Blynk.connected()) { Blynk.run(); } timer.run(); if (millis() - one_hour_timer >= 60000 && one_hour_timer != 0) { //rechecking values one_hour_timer = 0; E_value_eeprom_Succes_write_counter_esp = 0; E_value_eeprom_Fail_write_counter_esp = 0; E_value_eeprom_Succes_write_counter_arduino = 0; E_value_eeprom_Fail_write_counter_arduino = 0; E_arduino_alarm_syncing_counter = 0; } } 1.Sprawdzenie czy otrzymano jakieś dane 2.Włączenie/wyłączenie karmnika 3.Sprawdzenie poprawności danych 4.Callback Blynk'a 5.Resetowanie kodów błędu po upłynięciu 1h ------------------------------------------------------------------------------------------------------------------------ 1. Funkcja ta działa tak samo jak funkcja sprawdzająca czy otrzymano dane z sterownika 2. Karta sieciowa odpowiada za sterowanie modułem karmnika, sterowany jest dwoma pinami z modułu pcf8574 podłączonego do Wemosa. Stan wysoki na pierwszym pinie załącza karmienie, stan wysoki na drugim pinie zaczyna cofać pokarm do zbiornika. 3. Ten element programu służy do sprawdzania, czy wszystko z danymi (np. stany wyjść, zmienne zapisane w "EEPROMIE" itp.) jest ok. Np. komórka 510 w pamięci "EEPROM" przechowuje czas podawania pokarmu, zakres to 0-29 sekund. Cały sterownik umożliwia zmianę wartości w pamięci EEPROM zdalnie przy pomocy komend, np. write_esp_eeprom=(komórka pamięci "EEPROM" w zakresie 0-1000),(wartość od 0 do 255) lub write_ard_eeprom=(komórka pamięci EEPROM w zakresie 0-1023),(wartość od 0 do 255). Przez przypadek mogę ustawić wartość w komórce 510 na 255, oznacza to, że karmnik będzie podawał pokarm przez ponad 4 minuty ! Jest to dość niebezpieczne dla ryb. Funkcja ta sprawdza więc czy wszystkie wpisane wartości w odpowiednich komórkach są poprawne, jeżeli nie, to sterownik informuje o tym użytkownika oraz stara się naprawić dane, np. ustawia wartość w komórce 510 na 2 sekundy. Sterownik może wysłać maila jeżeli usterka jest bardziej złożona, np. sterownik oraz karta sieciowa korzystają z pamięci EEPROM, pozwala ona na zachowanie wartości nawet po zaniku zasilania, niestety korzystanie z tej pamięci jest ograniczone ilością zapisów (średnio około 100 000), po tej wartości z czasem mogą pojawiać się problemy z pamięcią, może ona ulec uszkodzeniu. Napisałem funkcję która ma na celu kontrolowanie zużycia tej pamięci, void eeprom_optimalization(int E_case, int E_val2w, bool add2counter, bool safe_mode) { if (E_val2w > 255 || E_val2w < 0) { cmd.println("Value to write is out of range!"); add_log(31, E_case, E_val2w, 0); return; } if (safe_mode == 1) { //int eeprom_case_not2write[7] = {507, 508, 509, 510, 511, 512, 513}; for (int i = 0; i < eeprom_case_not2write_length; i++) { if (E_case == eeprom_case_not2write[i]) { cmd.println("This eeprom case cannot be written!"); cmd.flush(); add_log(31, E_case, E_val2w, 1); return; } } } else { for (int i = 0; i < eeprom_case_not2write_length; i++) { if (E_case == eeprom_case_not2write[i]) { cmd.println("This eeprom case is dangerous to written!"); cmd.flush(); break; } } } byte val2write = (byte)E_val2w; byte E_val_read = EEPROM.read(E_case); if (E_val_read != E_val2w) { EEPROM.write(E_case, val2write); if (add2counter == 1) { int r_e = ((EEPROM.read(514) << 8) + EEPROM.read(515)); r_e += 1; byte r_e_b = highByte(r_e); EEPROM.write(514, r_e_b); r_e_b = lowByte(r_e); EEPROM.write(515, r_e_b); cmd.print("Writting eeprom succesful counter = "); cmd.println(r_e); } add_log(31, E_case, E_val2w, 2); } else { if (add2counter == 1) { int r_e = ((EEPROM.read(516) << 8) + EEPROM.read(517)); r_e += 1; byte r_e_b = highByte(r_e); EEPROM.write(516, r_e_b); r_e_b = lowByte(r_e); EEPROM.write(517, r_e_b); cmd.print("Writting eeprom fail counter = "); cmd.println(r_e); } add_log(31, E_case, E_val2w, 3); } EEPROM.commit(); } Ma ona kilka ważnych według mnie funkcji: sprawdzenie czy podana wartość mieści się w zakresie 0-255 sprawdzenie czy podany numer komórki nie jest zapisany na liście z niebezpiecznymi komórkami do zapisu, jeżeli tak to albo odmówi zapisu w danym miejscu albo poinformuje użytkownika, że trzeba uważać na tą komórką pamięci sprawdzenie czy aktualna wartość w podanej komórce jest inna od podanej, jeżeli tak to następuje zapis oraz zwiększenie się licznika udanych zapisów Sterownik sprawdza czy ilość zapisów nie przekracza wartości maksymalnej, np. 1500 zapisów, jeżeli przekracza, to informuje o tym użytkownika wysyłając mu wiadomość email. 5. Jeżeli coś z danymi jest nie tak, to użytkownik może dostać wiadomość, nie chcę jej jednak otrzymywać często, wystarczy przypomnienie np. co godzinę. Dlatego karta sieciowa ustawia jedno godzinny time out na wysyłanie wiadomości, jeżeli w czasie jednej godziny użytkownik nie naprawi usterki (nie zresetuje przy pomocy komendy licznika zapisów) to wyśle on kolejną wiadomość. Obsługa komend przez kartę sieciową: Podczas budowy i programowania tego urządzenia miałem na celu stworzenie czegoś w rodzaju zdalnego zarządzania całym urządzeniem, obecnie sterownik pracuje u dziadka dlatego muszę mieć możliwość kontrolowania jego działania zdalnie. Dla przykładu, jedyną opcją na dodanie alarmu jest wpisanie go w formie komendy na telefonie. Plusem tego rozwiązania jest to, że nie muszę jechać do dziadka aby zmienić np. godzinę włączenia światła itp., mam także podgląd na to czy wszystko działa. Komendy wprowadzane są w widżecie Terminal w aplikacji Blynk, kod obsługujący komendy: BLYNK_WRITE(V6) { String cmd_request = " "; cmd_request = param.asStr(); if (String("cls") == cmd_request) { cmd.clear(); add_log(51, 1, 999, 999); } else if (cmd_request.startsWith("show_alarms=") || cmd_request.startsWith("sha=")) { uint8_t index_val_1 = cmd_request.indexOf('='); uint8_t cmd_val = cmd_request.substring(index_val_1 + 1).toInt(); show_all_alarms[3] = cmd_val + '0'; send_command(show_all_alarms, 8); add_log(52, cmd_val, 999, 999); } else if (String("alarms_counter") == cmd_request || String("ac") == cmd_request) { int e_count = 0, n = 0; while (n <= ile_alarmow) { if (EEPROM.read((n * 5) + 1) != 127) { e_count++; } n++; } cmd.print("Esp alarms: "); cmd.println(e_count); send_command(show_alarms_counter, 8); add_log(53, e_count, 999, 999); } else if (cmd_request.startsWith("display_alarm=") || cmd_request.startsWith("da=")) { uint8_t index_val_1 = cmd_request.indexOf('='); uint8_t ter_val = cmd_request.substring(index_val_1 + 1).toInt(); if (ter_val <= ile_alarmow) { cmd.print("Displayed alarm "); cmd.print(ter_val); cmd.println(" :"); cmd.print("-Esp: "); cmd.flush(); int p = ter_val * 5; cmd.print("f(x): "); cmd.print(EEPROM.read(p + 4)); cmd.print("|D: "); cmd.print(EEPROM.read(p + 1)); cmd.print("|H: "); cmd.print(EEPROM.read(p + 2)); cmd.print("|MIN: "); cmd.print(EEPROM.read(p + 3)); cmd.print("|X: "); cmd.println(EEPROM.read(p + 5)); cmd.flush(); show_alarm[4] = ter_val + '0'; send_command(show_alarm, 8); } else { cmd.print("Wrong alarm case (0-"); cmd.print(ile_alarmow); cmd.println(")"); cmd.flush(); } add_log(54, ter_val, 999, 999); } else if (cmd_request.startsWith("set_alarm=") || cmd_request.startsWith("sa=")) { int eep_szuf = 1; int eep_buff = 1; uint8_t eep_a, eep_b, eep_c, eep_d, eep_f; uint8_t index_val_1, index_val_2, index_val_3, index_val_4, index_val_5, index_val_6; int cmd_val_1, cmd_val_2, cmd_val_3, cmd_val_4, cmd_val_5, cmd_val_6; index_val_1 = cmd_request.indexOf('='); index_val_2 = cmd_request.indexOf(','); index_val_3 = cmd_request.indexOf('-'); index_val_4 = cmd_request.indexOf(':'); index_val_5 = cmd_request.indexOf('&'); index_val_6 = cmd_request.indexOf('*'); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); cmd_val_2 = cmd_request.substring(index_val_2 + 1).toInt(); cmd_val_3 = cmd_request.substring(index_val_3 + 1).toInt(); cmd_val_4 = cmd_request.substring(index_val_4 + 1).toInt(); cmd_val_5 = cmd_request.substring(index_val_5 + 1).toInt(); cmd_val_6 = cmd_request.substring(index_val_6 + 1).toInt(); if (cmd_request[index_val_1] == '=' && cmd_request[index_val_2] == ',' && cmd_request[index_val_3] == '-' && cmd_request[index_val_4] == ':' && cmd_request[index_val_5] == '&' && cmd_request[index_val_6] == '*') { if ((cmd_val_2 >= 0 && cmd_val_2 < 9) && (cmd_val_3 > -1 && cmd_val_3 < 24) && (cmd_val_4 > -1 && cmd_val_4 <= 60)) { if (cmd_val_1 == 127) { cmd.print("[auto-case]-"); for (int w = 0; w <= ile_alarmow; w++) { if (EEPROM.read((w * 5) + 1) == 127) { eeprom_optimalization(0, w, 1, 0); byte x = EEPROM.read(0); cmd.print("Setting auto alarm value to "); cmd.println(x); cmd.flush(); //EEPROM.commit(); eep_szuf = x * 5; eep_buff = eep_szuf / 5; break; } } } else { if (cmd_val_1 >= 0 && cmd_val_1 <= ile_alarmow) { cmd.print("Setting manual alarm value to "); cmd.println(cmd_val_1); eep_szuf = cmd_val_1 * 5; eep_buff = eep_szuf / 5; } else { cmd.print("Wrong manual case value!"); return; } } cmd.println(); cmd.flush(); cmd.print("Setting alarm "); cmd.print(eep_buff); cmd.print(" on the day "); cmd.print(cmd_val_2); cmd.print(" ("); cmd.print(cmd_val_3); cmd.print(":"); cmd.print(cmd_val_4); cmd.print(") - function: "); cmd.print(cmd_val_5); cmd.print(" with value: "); cmd.println(cmd_val_6); cmd.flush(); eeprom_optimalization(eep_szuf + 1, cmd_val_2, 1, 0); eeprom_optimalization(eep_szuf + 2, cmd_val_3, 1, 0); eeprom_optimalization(eep_szuf + 3, cmd_val_4, 1, 0); eeprom_optimalization(eep_szuf + 4, cmd_val_5, 1, 0); eeprom_optimalization(eep_szuf + 5, cmd_val_6, 1, 0); //EEPROM.commit(); set_alarm[2] = cmd_val_2 + '0'; set_alarm[3] = cmd_val_3 + '0'; set_alarm[4] = cmd_val_4 + '0'; set_alarm[6] = cmd_val_5 + '0'; set_alarm[7] = cmd_val_6 + '0'; set_alarm[9] = eep_buff + '0'; send_command(set_alarm, 13); } else { cmd.println("Wrong time !"); cmd.flush(); } } else { cmd.println("Wrong command structure!"); cmd.flush(); } add_log(26, eep_buff, cmd_val_5, cmd_val_6); } else if (String("alarm_help") == cmd_request || String("ah") == cmd_request) { cmd.clear(); cmd.println("write: set_alarm=y,D-H:M&F*X$A"); String y_nr_str = "y - nr. szufladki (127 = auto, 0-" + String(ile_alarmow) + " counter)"; cmd.println(y_nr_str); cmd.println("D - dzien (1-7) 8=all"); cmd.println("H - godzina (0-23)"); cmd.println("M - minuta (0-59)"); cmd.println("F - funkcja (1-6)[alarm_function=help]"); cmd.println("x - wartosc funkcji"); cmd.println(""); cmd.flush(); cmd.println("______________"); cmd.println("F: 1 - gniazdo 1 [1 or 0] (pompa)"); cmd.println(" 2 - gniazdo 2 [1 or 0]"); cmd.println(" 3 - pasek led [0 - 100]"); cmd.println(" 4 - karmnik [none]"); cmd.flush(); cmd.println(" 5 - auto time syncing [none]"); cmd.println(" 6 - display brightness [0 - 3]"); cmd.println(" 7 - reset device [0-2:arduino/esp/all]"); cmd.flush(); add_log(55, 1, 999, 999); } else if (cmd_request.startsWith("rm_alarm=") || cmd_request.startsWith("rma=")) { uint8_t index_val_1; int cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); if (cmd_val_1 < 0 || cmd_val_1 >= ile_alarmow) { cmd.print("Wrong alarm number - range(0-") ; cmd.print(ile_alarmow); cmd.println(")"); add_log(56, cmd_val_1, 0, 999); } else { cmd.print("Removing alarm nr. "); cmd.println(cmd_val_1); rm_single_alarm[3] = cmd_val_1 + '0'; int n = cmd_val_1; n = n * 5; eeprom_optimalization(n + 1, 127, 1, 0); eeprom_optimalization(n + 2, 127, 1, 0); eeprom_optimalization(n + 3, 127, 1, 0); eeprom_optimalization(n + 4, 127, 1, 0); eeprom_optimalization(n + 5, 127, 1, 0); //EEPROM.commit(); send_command(rm_single_alarm, 8); add_log(56, cmd_val_1, 1, 999); } } else if (String("rm_all_alarm") == cmd_request) { send_command(rm_all_alarms, 8); for (int n = 1; n <= ((ile_alarmow + 1) * 5) - 1; n++) { eeprom_optimalization(n, 127, 0, 0); //EEPROM.commit(); if (n % 10 == 0) { cmd.print("Removed case from "); cmd.print(n - 10); cmd.print(" to "); cmd.println(n); } } eeprom_optimalization(0, 0, 1, 0); //EEPROM.commit(); add_log(57, 1, 999, 999); } else if (String("millis_usage") == cmd_request || String("mu") == cmd_request) { float x, z; cmd.println(); cmd.print("ESP8266: "); x = millis() / (1000 * 60); int millis_esp = millis() / (1000 * 60); cmd.print(millis() / (1000 * 60)); cmd.print(" min. / 71,580 min. - "); z = x / 71580; cmd.print(z, 3); cmd.println(" %"); send_command(disp_millis, 6); add_log(58, millis_esp, 999, 999); } else if (cmd_request.startsWith("read_ard_eeprom") || cmd_request.startsWith("rae")) { //eeprom_action[11] = {0x7E, 'E', 0, 'P', '.', 0, 0, 'm', 0, 'A', 0, 0x7F}; uint8_t index_val_1; int cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); if (cmd_val_1 > 1023 || cmd_val_1 < 0) { cmd.println("Wrong value (0-1023)"); add_log(59, cmd_val_1, 0, 999); } else { byte Bdata_a = 0, Bdata_b = 0; Bdata_a = highByte(cmd_val_1); Bdata_b = lowByte(cmd_val_1); cmd.print("highByte = "); cmd.print(Bdata_a); cmd.print(" - lowByte = "); cmd.println(Bdata_b); //eeprom_action[12] = {0x7E, 'E', 0, 'P', '.', 0, 0, 'm', 0, 'A', 0, 0x7F}; eeprom_action[2] = 1 + '0'; eeprom_action[5] = Bdata_a; eeprom_action[6] = Bdata_b; eeprom_action[8] = 0 + '0'; send_command(eeprom_action, 12); add_log(59, cmd_val_1, 1, 999); } } else if (cmd_request.startsWith("write_ard_eeprom=") || cmd_request.startsWith("wae=")) { uint8_t index_val_1, index_val_2; int cmd_val_1, cmd_val_2; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); index_val_2 = cmd_request.indexOf(','); cmd_val_2 = cmd_request.substring(index_val_2 + 1).toInt(); if (cmd_val_1 > 1023 || cmd_val_1 < 0 || cmd_val_2 > 255 || cmd_val_2 < 0) { cmd.println("Wrong value (val1: 0-1023 val2: 0-255)"); add_log(60, cmd_val_1, cmd_val_2, 0); } else { byte Bdata_a = 0, Bdata_b = 0; Bdata_a = highByte(cmd_val_1); Bdata_b = lowByte(cmd_val_1); cmd.print("highByte = "); cmd.print(Bdata_a); cmd.print(" - lowByte = "); cmd.println(Bdata_b); //eeprom_action[12] = {0x7E, 'E', 0, 'P', '.', 0, 0, 'm', 0, 'A', 0, 0x7F}; cmd.print("Writening ard eeprom case "); cmd.print(cmd_val_1); cmd.print(" with value of "); cmd.println(cmd_val_2); eeprom_action[2] = 2 + '0'; eeprom_action[5] = Bdata_a; eeprom_action[6] = Bdata_b; eeprom_action[8] = cmd_val_2; send_command(eeprom_action, 12); add_log(60, cmd_val_1, cmd_val_2, 1); } } else if (cmd_request.startsWith("sync_time=")) { //set_time[12] = {0x7E, 'T', 0, 0, 0, '/', 0, 0, 0, 's' 0, 0x7F}; //H:Min-D/M.Y uint8_t i_val_1, i_val_2, i_val_3, i_val_4, i_val_5; int val_1, val_2, val_3, val_4, val_5; i_val_1 = cmd_request.indexOf('='); val_1 = cmd_request.substring(i_val_1 + 1).toInt(); i_val_2 = cmd_request.indexOf(':'); val_2 = cmd_request.substring(i_val_2 + 1).toInt(); i_val_3 = cmd_request.indexOf('-'); val_3 = cmd_request.substring(i_val_3 + 1).toInt(); i_val_4 = cmd_request.indexOf('/'); val_4 = cmd_request.substring(i_val_4 + 1).toInt(); i_val_5 = cmd_request.indexOf('.'); val_5 = cmd_request.substring(i_val_5 + 1).toInt(); String currentTime = String(val_1) + ":" + String(val_2) + ":" + String(second()); String currentDate = String(val_3) + " " + String(val_4) + " " + String(val_5 + 2000); cmd.print(currentTime); cmd.print(" - "); cmd.println(currentDate); cmd.flush(); set_time[2] = val_1 + '0'; set_time[3] = val_2 + '0'; set_time[4] = second() + '0'; set_time[6] = val_3 + '0'; set_time[7] = val_4 + '0'; set_time[8] = val_5 + '0'; send_command(set_time, 12); add_log(61, val_1, val_2, val_3); } else if (String("auto_sync_time") == cmd_request || String("ast") == cmd_request) { String currentTime = String(hour()) + ":" + minute() + ":" + second(); String currentDate = String(day()) + " " + month() + " " + year(); cmd.print(currentTime); cmd.print(" - "); cmd.println(currentDate); set_time[2] = hour() + '0'; set_time[3] = minute() + '0'; set_time[4] = second() + '0'; set_time[6] = day() + '0'; set_time[7] = month() + '0'; set_time[8] = year() - 2000 + '0'; send_command(set_time, 12); add_log(62, hour(), minute(), day()); } else if (cmd_request.startsWith("Arduino_info=") || cmd_request.startsWith("ai=")) { uint8_t index_val_1; int cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); ard_info[3] = (byte)cmd_val_1 + '0'; send_command(ard_info, 8); add_log(63, cmd_val_1, 999, 999); } else if (String("arduino_info_help") == cmd_request || String("aih") == cmd_request) { cmd.println("1 - show arduino alarm syncing counter"); cmd.println("2 - print arduino eeprom in serial monitor"); cmd.println("3 - reset alarm syncing counter"); cmd.println("4 - show arduino eeprom succesful writing counter"); cmd.flush(); cmd.println("5 - show arduino eeprom fail writing counter"); cmd.println("6 - reset arduino eeprom succesful writing counter"); cmd.println("7 - reset arduino eeprom fail writing counter"); cmd.flush(); cmd.println("8 - reset arduino eeprom succesful and fail writing counter"); cmd.flush(); add_log(64, 1, 999, 999); } else if (cmd_request.startsWith("read_esp_alarm") || cmd_request.startsWith("rea")) { uint8_t index_val_1; int cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); int n = 0; int p = 0; byte ac_2_log = 0; byte log_score = (byte)cmd_val_1; if (cmd_val_1 == 0 || cmd_val_1 == 1) { }else{ cmd.println("Wrong argument (0 or 1)"); cmd.flush(); add_log(65, log_score, ac_2_log, 999); return; } while (n <= ile_alarmow) { if (cmd_val_1 == 0) { if (EEPROM.read((n * 5) + 1) != 127 && n <= ile_alarmow) { p = n * 5; cmd.print("Szuf."); cmd.print(n); cmd.print(" ->f(x): "); cmd.print(EEPROM.read(p + 4)); //1 cmd.print(" |D: "); cmd.print(EEPROM.read(p + 1)); //2 cmd.print("|H: "); cmd.print(EEPROM.read(p + 2)); //3 cmd.print("|MIN: "); cmd.print(EEPROM.read(p + 3)); //4 cmd.print("|X: "); cmd.println(EEPROM.read(p + 5)); //5 cmd.flush(); ac_2_log += 1; } } else { p = n * 5; cmd.print("Szuf."); cmd.print(n); cmd.print(" ->f(x): "); cmd.print(EEPROM.read(p + 1)); cmd.print(" |D: "); cmd.print(EEPROM.read(p + 2)); cmd.print("|H: "); cmd.print(EEPROM.read(p + 3)); cmd.print("|MIN: "); cmd.print(EEPROM.read(p + 4)); cmd.print("|X: "); cmd.println(EEPROM.read(p + 5)); cmd.flush(); ac_2_log += 1; } n++; } add_log(65, log_score, ac_2_log, 999); } else if (String("rm_esp_all_alarm") == cmd_request) { int n = 0; for (n = 1; n <= ((ile_alarmow + 1) * 5) - 1; n++) { eeprom_optimalization(n, 127, 0, 0); //EEPROM.commit(); if (n % 10 == 0) { cmd.print("Removed case from "); cmd.print(n - 10); cmd.print(" to "); cmd.println(n); } } //EEPROM.commit(); add_log(66, 1, 999, 999); } else if (cmd_request.startsWith("write_esp_eeprom=") || cmd_request.startsWith("wee=")) { uint8_t index_val_1; int cmd_val_1; uint8_t index_val_2; int cmd_val_2; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); index_val_2 = cmd_request.indexOf(','); cmd_val_2 = cmd_request.substring(index_val_2 + 1).toInt(); if (cmd_val_1 > -1 && cmd_val_1 < 1000 && cmd_val_2 > -1 && cmd_val_2 < 256) { eeprom_optimalization(cmd_val_1, cmd_val_2, 0, 0); //EEPROM.commit(); } else { cmd.print("Error value! Val1 = Range (0-1000) / Val2 = Range(0-255)"); cmd.flush(); add_log(67, cmd_val_1, cmd_val_2, 0); return; } cmd.print("Writening to Esp eeprom case "); cmd.print(cmd_val_1); cmd.print(" value "); cmd.println(cmd_val_2); cmd.flush(); add_log(67, cmd_val_1, cmd_val_2, 1); } else if (cmd_request.startsWith("read_esp_eeprom=") || cmd_request.startsWith("ree=")) { uint8_t index_val_1; int cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); if (cmd_val_1 > -1 && cmd_val_1 < 1000) { cmd.print("Reading Esp eeprom case "); cmd.print(cmd_val_1); cmd.print(" = "); cmd.println(EEPROM.read(cmd_val_1)); //EEPROM.commit(); cmd.flush(); add_log(68, cmd_val_1, EEPROM.read(cmd_val_1), 1); } else { cmd.println("Wrong value range(0-1000)!"); cmd.flush(); add_log(68, cmd_val_1, 0, 0); } } else if (String("clear_arduino_eeprom") == cmd_request) { cmd.println("Clearing Arduino eeprom with value 127"); cmd.flush(); send_command(clear_ard_eeprom, 8); add_log(69, 1, 999, 999); } else if (String("clear_esp_eeprom") == cmd_request) { for (int n = 0; n < 4096; n++) { eeprom_optimalization(n, 127, 0, 1); } //EEPROM.commit(); add_log(70, 1, 999, 999); } else if (String("what's_time") == cmd_request || String("wt") == cmd_request) { //what_is_time[9] = {0x7E, 'W', 'H', 'a', '|', 'S', 0, 0x7F}; send_command(what_is_time, 8); add_log(71, 1, 999, 999); } else if (String("debug_help") == cmd_request || String("dh") == cmd_request) { cmd.clear(); cmd.flush(); cmd.println("Debuging option: "); cmd.println("write: debug_mode=x"); cmd.println(); cmd.println("______________"); cmd.println(" [0]-disable all debuging info"); cmd.flush(); cmd.println(" [1]-enable recive data debug"); cmd.println(" [2]-enable check control sum debug"); cmd.println(" [3]-enable send command debug"); cmd.println(" [4]-enable sync alarms debug"); cmd.println(" [5]-enable thingspeak data debug"); cmd.flush(); cmd.println(" [6]-enable feeding info debug"); cmd.println(" [7]-enable data about syncing pwm value"); cmd.println(" [8]-enable show free ram space"); cmd.println(" [9]-enable show add log result"); cmd.flush(); cmd.println(" [10]-enable check data is ok debug"); cmd.println(" [11]-enable send check is new day command debug"); cmd.flush(); /* bool D_recive_data_single_char = 0; bool D_check_control_sum = 0; bool D_sync_alarms = 0; bool D_send_command = 0; */ add_log(72, 1, 999, 999); } else if (cmd_request.startsWith("debug_mode") || cmd_request.startsWith("dm")) { uint8_t index_val_1; int cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); byte D_ok = 1; if (cmd_val_1 == 0) { D_recive_data_single_char = 0; D_check_control_sum = 0; D_sync_alarms = 0; D_send_command = 0; D_thingspeak_info = 0; D_feeding_info = 0; D_pwm_syncing_value = 0; D_show_ram = 0; D_add_log = 0; D_check_is_data_ok = 0; D_check_is_data_error = 0; D_checkday_change = 0; } else if (cmd_val_1 == 1) { D_recive_data_single_char = 1; } else if (cmd_val_1 == 2) { D_check_control_sum = 1; } else if (cmd_val_1 == 3) { D_send_command = 1; } else if (cmd_val_1 == 4) { D_sync_alarms = 1; } else if (cmd_val_1 == 5) { D_thingspeak_info = 1; } else if (cmd_val_1 == 6) { D_feeding_info = 1; } else if (cmd_val_1 == 7) { D_pwm_syncing_value = 1; } else if (cmd_val_1 == 8) { D_show_ram = 1; } else if (cmd_val_1 == 9) { D_add_log = 1; } else if (cmd_val_1 == 10) { D_check_is_data_ok = 1; D_check_is_data_error = 1; } else if(cmd_val_1 == 11) { D_checkday_change = 1; } else { cmd.println("Wrong argument!"); cmd.flush(); D_ok = 0; } add_log(73, cmd_val_1, D_ok, 999); } else if (String("feed_info") == cmd_request || String("fi") == cmd_request) { feeding_time = EEPROM.read(510); //510 retraction_time = EEPROM.read(511); //511 feeding_today = EEPROM.read(509); //509 F_pump_pin = EEPROM.read(508); cmd.print("1. Today feeding "); cmd.print(feeding_today); cmd.println(" times"); cmd.println(); cmd.flush(); cmd.println("2. Feeding settings: "); cmd.print(" a) Feeding time = "); cmd.print(feeding_time); cmd.println(" s"); cmd.print(" b) Retraction time = "); cmd.print(retraction_time); cmd.println(" s"); cmd.flush(); cmd.print(" c) Max fedding per day = "); cmd.println(max_daily_feeding_counter); cmd.flush(); cmd.print(" d) OFF this socket, when feeding (0-none/1-blue/2-red/3-all)= "); cmd.println(F_pump_pin); cmd.flush(); cmd.print(" e) time to turn on socket (min)= "); cmd.println(time_to_return); cmd.flush(); cmd.print(" f) feeding status: "); if (EEPROM.read(513) == 0) { cmd.println("Ok"); } else if (EEPROM.read(513) == 1) { cmd.println("No feeding today"); } else if (EEPROM.read(513) == 2) { cmd.println("Feeding is permanently Off"); } else { cmd.println("Error with value! out of range(0-2)"); add_log(74, 3, 999, 999); return; } cmd.flush(); add_log(74, EEPROM.read(513), 999, 999); } else if (cmd_request.startsWith("feed_sett=") || cmd_request.startsWith("fs=")) { uint8_t index_val_1; uint8_t cmd_val_1; uint8_t index_val_2; uint8_t cmd_val_2; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); index_val_2 = cmd_request.indexOf(','); cmd_val_2 = cmd_request.substring(index_val_2 + 1).toInt(); if (cmd_val_1 == 1) { if (cmd_val_2 > 0 && cmd_val_2 < 30) { eeprom_optimalization(510, cmd_val_2, 1, 0); //EEPROM.commit(); feeding_time = EEPROM.read(510); cmd.print("Setting feeding time to "); cmd.print(feeding_time); cmd.println(" s"); cmd.flush(); add_log(75, 1, cmd_val_2, 1); } else { cmd.print("Wrong argument 1s - 29s"); cmd.flush(); add_log(75, 1, cmd_val_2, 0); } } else if (cmd_val_1 == 2) { if (cmd_val_2 > 0 && cmd_val_2 < 30) { eeprom_optimalization(511, cmd_val_2, 1, 0); //EEPROM.commit(); retraction_time = EEPROM.read(511); cmd.print("Setting retraction time to "); cmd.print(retraction_time); cmd.println(" s"); cmd.flush(); add_log(75, 2, cmd_val_2, 1); } else { cmd.print("Wrong argument 1s - 29s"); cmd.flush(); add_log(75, 2, cmd_val_2, 0); } } else if (cmd_val_1 == 3) { bool add_log75 = 1; if (cmd_val_2 == 1) { cmd.println("Setting socket number to blue, number 1"); eeprom_optimalization(508, 1, 1, 0); //EEPROM.commit(); F_pump_pin = EEPROM.read(508); } else if (cmd_val_2 == 2) { cmd.println("Setting socket number to red, number 2"); eeprom_optimalization(508, 2, 1, 0); //EEPROM.commit(); F_pump_pin = EEPROM.read(508); } else if (cmd_val_2 == 3) { cmd.println("Setting socket number to red and blue, number 3"); eeprom_optimalization(508, 3, 1, 0); //EEPROM.commit(); F_pump_pin = EEPROM.read(508); } else if (cmd_val_2 == 0) { cmd.println("Setting socket number to none, number 0"); eeprom_optimalization(508, 0, 1, 0); //EEPROM.commit(); F_pump_pin = EEPROM.read(508); } else { cmd.println("Error value! range(1-blue / 2-red / 3-red and blue / 0-none)"); add_log75 = 0; } add_log(75, 3, cmd_val_2, add_log75); } else if (cmd_val_1 == 4) { bool add_log75 = 1; if (cmd_val_2 > 0 && cmd_val_2 <= 60) { eeprom_optimalization(507, cmd_val_2, 1, 0); //EEPROM.commit(); time_to_return = EEPROM.read(507); cmd.print("Setting time_to_return value to "); cmd.print(time_to_return); cmd.println(" min"); } else { cmd.println("Wrong value. range(1-60)"); add_log75 = 0; } cmd.flush(); add_log(75, 4, cmd_val_2, add_log75); } else { cmd.println("Wrong function number: from 1 to 4"); cmd.flush(); add_log(75, 5, 999, 999); } } else if (String("feed_sett_info") == cmd_request || String("fsi") == cmd_request) { cmd.clear(); cmd.println(" feed_sett=x,y"); cmd.println(""); cmd.println("---------------------------------"); cmd.flush(); cmd.println(""); cmd.println("--> 1 - setting feeding time in seconds (y = 1-60)"); cmd.println(""); cmd.println("--> 2 - setting retraction time in seconds (y = 1-60)"); cmd.println(""); cmd.println("--> 3 - setting socket color to off when feeding (y: 0= none | 1= blue | 2= red | 3= all)"); cmd.flush(); add_log(76, 1, 999, 999); } else if (cmd_request.startsWith("set_display_brightnes=") || cmd_request.startsWith("sdb=")) { uint8_t index_val_1; uint8_t cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); if (cmd_val_1 > -1 && cmd_val_1 <= 3) { cmd.print("Setting display brightness to "); cmd.println(cmd_val_1); cmd.flush(); //set_disp_brightnes[8] = {0x7E, 'S', 'd', 0, 'B', '?', 0, 0x7F}; set_disp_brightnes[3] = cmd_val_1 + '0'; send_command(set_disp_brightnes, 8); add_log(77, cmd_val_1, 1, 999); } else { cmd.println("Wrong value - range(0-3)"); cmd.flush(); add_log(77, 4, 0, 999); } } else if (cmd_request.startsWith("reset_device=")) { uint8_t index_val_1; uint8_t cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); switch (cmd_val_1) { case 0: cmd.println("Resetting arduino..."); send_command(reset_ard, 9); add_log(78, cmd_val_1, 1, 999); break; case 1: cmd.println("Resetting esp..."); save_data(1); //save_data(0); delay(100); add_log(78, cmd_val_1, 1, 999); ESP.restart(); break; case 2: cmd.println("Resetting arduino and esp..."); send_command(reset_ard, 9); save_data(1); //save_data(0); delay(100); add_log(78, cmd_val_1, 1, 999); ESP.restart(); break; default: cmd.println("Wrong value! Range(0-2)(arduino/esp/all)"); add_log(78, 3, 0, 999); return; } } else if (cmd_request.startsWith("add_wifi=")) { uint8_t index_val_1 = cmd_request.indexOf('='); uint8_t cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); uint8_t index_val_2 = cmd_request.indexOf('-'); uint8_t index_val_3 = cmd_request.indexOf(','); String cmd_val_2 = cmd_request.substring(index_val_2 + 1, index_val_3); String cmd_val_3 = cmd_request.substring(index_val_3 + 1); if (cmd_val_1 == 1 || cmd_val_1 == 2) { int offset = 600 + (25 * (cmd_val_1 - 1)) + (cmd_val_1 - 1); for (int i = 0; i <= 25; i++) { eeprom_optimalization(offset + i, 0, 0, 1); } //EEPROM.commit(); byte len = cmd_val_2.length(); for (int i = 0; i < len; i++) { eeprom_optimalization(offset + i, cmd_val_2[i], 0, 0); } if (EEPROM.commit() == 1) { cmd.println("Writting ssid - OK!"); } else { cmd.println("Writting ssid - ERROR!"); add_log(79, cmd_val_1, 2, 999); return; } offset = 600 + (25 * (cmd_val_1 + 1)) + (cmd_val_1 + 1); for (int i = 0; i <= 25; i++) { eeprom_optimalization(offset + i, 0, 0, 0); } //EEPROM.commit(); len = cmd_val_3.length(); for (int i = 0; i < len; i++) { eeprom_optimalization(offset + i, cmd_val_3[i], 0, 0); } if (EEPROM.commit() == 1) { cmd.println("Writting pass - OK!"); } else { cmd.println("Writting pass - ERROR!"); add_log(79, cmd_val_1, 2, 999); return; } add_log(79, cmd_val_1, 1, 999); } else { cmd.println("Wrong value! Range(1-2)"); add_log(79, cmd_val_1, 0, 999); } } else if (cmd_request.startsWith("display_wifi=") || cmd_request.startsWith("dw=")) { uint8_t index_val_1 = cmd_request.indexOf('='); int cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); if (cmd_val_1 == 1 || cmd_val_1 == 2) { cmd.print("E_ssid->"); cmd.print(read_eeprom_wifi(cmd_val_1 - 1)); cmd.print("<- \ E_pass->"); cmd.print(read_eeprom_wifi(cmd_val_1 + 1)); cmd.println("<-"); cmd.flush(); } else if (cmd_val_1 == 0) { cmd.print("E_ssid->"); cmd.print(network0_E_ssid); cmd.print("<- \ E_pass->"); cmd.print(network0_E_pass); cmd.println("<-"); cmd.flush(); } else { cmd.println("Wrong value! Range(0-2)"); add_log(80, cmd_val_1, 0, 999); return; } add_log(80, cmd_val_1, 1, 999); } else if (String("Clear_all_wifi=AquaDzz@iadek19") == cmd_request) { for (int i = 600; i <= 703; i++) { eeprom_optimalization(i, 0, 0, 1); } //EEPROM.commit(); add_log(81, 1, 999, 999); } else if (String("daw") == cmd_request || String("display_all_wifi") == cmd_request) { for (int i = 0; i < 3; i++) { cmd.print(i); cmd.print("["); for (int j = 0; j <= 25; j++) { cmd.print((char)(EEPROM.read(600 + (25 * i) + i + j))); if (j != 25) { cmd.print(","); } } cmd.println("]"); cmd.flush(); } add_log(82, 1, 999, 999); } else if (cmd_request.startsWith("auto_feeding_off=") || cmd_request.startsWith("afo=")) { uint8_t index_val_1 = cmd_request.indexOf('='); byte cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); if (cmd_val_1 >= 0 && cmd_val_1 <= 2) { eeprom_optimalization(513, cmd_val_1, 1, 0); //EEPROM.commit(); if (cmd_val_1 == 0) { cmd.println("This function is off, feeding every day"); } else if (cmd_val_1 == 1) { cmd.println("feeding today is off, next feeding tomorrow"); } else if (cmd_val_1 == 2) { cmd.println("feeding every day is off, next feeding when this function will be off"); } else { cmd.println("Error"); add_log(83, cmd_val_1, 0, 999); return; } cmd.flush(); add_log(83, cmd_val_1, 1, 999); } else { cmd.println("Wrong value! range(0-2: no/one_day/every_day)"); add_log(83, 3, 0, 999); } } else if (cmd_request.startsWith("set_light_speed=") || cmd_request.startsWith("sls=")) { uint8_t index_val_1; uint8_t cmd_val_1; uint8_t index_val_2; uint8_t cmd_val_2; uint8_t sval_1 = 127; uint8_t sval_2 = 127; uint8_t sval_3 = 127; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1).toInt(); index_val_2 = cmd_request.indexOf(','); cmd_val_2 = cmd_request.substring(index_val_2 + 1).toInt(); if (cmd_val_1 >= 1 && cmd_val_1 <= 4) { if (cmd_val_2 >= 0 && cmd_val_2 <= 255 && cmd_val_2 != 127) { //-------------------------------0----1----2---3---4---5---6----7---8---9---10--11-- //send_light_time_value[12] = {0x7E, 'L', 'I', 0, 'Q', 0, 'H', '|', 0, '@', 0, 0x7F}; if (cmd_val_1 == 1) { sval_1 = cmd_val_2; cmd.print("Setting led_step_button value to "); cmd.println(sval_1); } else if (cmd_val_1 == 2) { sval_2 = cmd_val_2; cmd.print("Setting led_step_app value to "); cmd.println(sval_2); } else if (cmd_val_1 == 3) { sval_3 = cmd_val_2; cmd.print("Setting led_step_alarm value to "); cmd.println(sval_3); } else if (cmd_val_1 == 4) { sval_1 = cmd_val_2; sval_2 = cmd_val_2; sval_3 = cmd_val_2; cmd.print("Setting led_step_button value to "); cmd.println(sval_1); cmd.print("Setting led_step_app value to "); cmd.println(sval_2); cmd.print("Setting led_step_alarm value to "); cmd.println(sval_3); } else { cmd.println("Error!"); } send_light_time_value[3] = sval_1; send_light_time_value[5] = sval_2; send_light_time_value[8] = sval_3; send_command(send_light_time_value, 12); } else { cmd.println("Wrong second value! Range(0-255) without 127"); } } else { cmd.println("Wrong value! Range (1-4)"); } cmd.flush(); add_log(84, cmd_val_1, cmd_val_2, 999); } else if (String("wifi_info") == cmd_request || String("wi") == cmd_request) { cmd.clear(); cmd.print("* Selected wifi id = "); cmd.println(network_id); if (network_id != 0) { cmd.print("* wifi eeprom SSID = "); cmd.println(read_eeprom_wifi(network_id - 1)); cmd.print("* wifi eeprom PASS = "); cmd.println(read_eeprom_wifi(network_id + 1)); } else { cmd.print("* wifi default SSID = "); cmd.println(network0_E_ssid); cmd.print("* wifi default PASS = "); cmd.println(network0_E_pass); } cmd.flush(); cmd.print("* wifi ssid value = "); cmd.println(E_ssid); cmd.print("* wifi pass value = "); cmd.println(E_pass); cmd.flush(); cmd.println("*******************************"); cmd.print("digitalRead P6 = "); cmd.println(pcf8574.digitalRead(wifi_network_sw_a)); cmd.print("digitalRead P7 = "); cmd.println(pcf8574.digitalRead(wifi_network_sw_b)); cmd.flush(); add_log(85, network_id, 999, 999); } else if (String("esp_info") == cmd_request || String("ei") == cmd_request) { cmd.clear(); cmd.print("* Firmware version: "); cmd.println(F_version); cmd.print("* EEPROM eeprom_optimalization writing succesful counter = "); cmd.println((EEPROM.read(514) << 8) + EEPROM.read(515)); cmd.print("* EEPROM eeprom_optimalization writing fail counter = "); cmd.println((EEPROM.read(516) << 8) + EEPROM.read(517)); cmd.println(ESP.getFlashChipId()); cmd.flush(); cmd.print("* Free heap: "); cmd.println(ESP.getFreeHeap()); cmd.print("* save log counter: "); cmd.println(save_log_counter()); cmd.print("* buffor log counter: "); cmd.println(log_buffor_counter()); cmd.print("* file name = "); cmd.println(read_file_name()); cmd.flush(); add_log(86, 1, 999, 999); } else if (String("display_log") == cmd_request || String("dl") == cmd_request) { //send_data_log = true; int dl_counter = display_log(); add_log(87, dl_counter, 999, 999); } else if (String("log_info") == cmd_request || String("li") == cmd_request) { //send_data_log = true; cmd.print("* LOGS counter = "); int li_counter = log_buffor_counter(); cmd.println(li_counter); cmd.print("* kolejka size = "); cmd.println(kolejka_size()); cmd.flush(); cmd.print("* kolejka empty = "); cmd.println(kolejka_empty()); cmd.print("* current_log_filename = "); cmd.println(current_log_filename); cmd.flush(); add_log(88, li_counter, kolejka_size(), (byte)kolejka_empty()); } else if (String("send_all_logs") == cmd_request) { cmd.println("ESP8266 will restart!"); EEPROM.write(522, 1); EEPROM.commit(); save_data(1); //save_data(0); delay(100); add_log(89, 1, 999, 999); ESP.restart(); } else if (cmd_request.startsWith("send_log=")) { uint8_t index_val_1; String cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1); if (!SD.begin(D8)) { send_mail_serial("initialization failed!", 1); add_log(32, 1, 999, 999); add_log(92, 0, 999, 999); return; } if (SD.exists(cmd_val_1)) { cmd.println("ESP8266 will restart!"); EEPROM.write(522, 2); EEPROM.commit(); save_file_name(cmd_val_1); cmd.println(read_file_name()); cmd.flush(); save_data(1); //save_data(0); delay(1000); ESP.restart(); } else { cmd.println("This file doesn't exist!"); cmd.flush(); } } else if (cmd_request.startsWith("change_filename=")) { uint8_t index_val_1; String cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1); save_file_name(cmd_val_1); delay(50); read_file_name(); } else if (String("sd_card_info") == cmd_request || String("sci") == cmd_request) { if (!SD.begin(D8)) { cmd.println("initialization failed!"); add_log(32, 1, 999, 999); delay(50); add_log(90, 0, 999, 999); return; } cmd.println("initialization done."); cmd.flush(); File root = SD.open("/"); printDirectory(root, 0); cmd.println("done!"); cmd.flush(); add_log(90, 1, 999, 999); } else if (cmd_request.startsWith("remove_file=")) { uint8_t index_val_1; String cmd_val_1; index_val_1 = cmd_request.indexOf('='); cmd_val_1 = cmd_request.substring(index_val_1 + 1); if (!SD.begin(D8)) { cmd.println("initialization failed!"); add_log(32, 1, 999, 999); return; } cmd.println("initialization done."); cmd.flush(); File root = SD.open("/"); if (!SD.exists(cmd_val_1)) { cmd.println("This file doesn't exist!"); cmd.flush(); add_log(91, 0, 999, 999); return; } SD.remove(cmd_val_1); if (!SD.exists(cmd_val_1)) { cmd.println("The file was deleted successfully !"); cmd.flush(); } else { cmd.println("The file could not be deleted !"); cmd.flush(); } add_log(91, 1, 999, 999); } else if (String("Remove_All_Files") == cmd_request) { if (!SD.begin(D8)) { cmd.println("initialization failed!"); add_log(32, 1, 999, 999); return; } cmd.println("initialization done."); cmd.flush(); File root = SD.open("/"); delay(100); rm(root, rootpath); if (!DeletedCount && !FailCount && !FolderDeleteCount) { add_log(17, DeletedCount, FailCount, 0); } else { cmd.print("Deleted "); cmd.print(DeletedCount); cmd.print(" file"); if (DeletedCount != 1) { cmd.print("s"); } cmd.print(" and "); cmd.print(FolderDeleteCount); cmd.print(" folder"); if (FolderDeleteCount != 1) { cmd.print("s"); } cmd.println(" from SD card."); if (FailCount > 0) { cmd.print("Failed to delete "); cmd.print(FailCount); cmd.print(" item"); if (FailCount != 1) { cmd.print("s"); } } add_log(17, DeletedCount, FailCount, 0); FailCount = 0; FolderDeleteCount = 0; DeletedCount = 0; } } else if (String("help") == cmd_request) { cmd.clear(); cmd.println("You can use this command: "); cmd.println("-------------------------------------------"); cmd.println("* cls"); cmd.println("* show_alarms or sha"); cmd.println("* alarms_counter or ac"); cmd.println("* display_alarm=[nr.szuf.] or da"); cmd.println("* set_alarm=[check in alarm_help or ah] or sa"); cmd.flush(); cmd.println("* rm_alarm=[remove alarm nr.] or rma"); cmd.println("* rm_all_alarm"); cmd.println("* millis_usage or mu"); cmd.println("* read_ard_eeprom=[nr.szuf.] or rae"); cmd.println("* write_ard_eeprom=x,y[szuf.0-1023/val.0-255] or wae"); cmd.println("* sync_time=[H:Min-D/M.Y]H=0-23|Min=0-59|Y=1-100"); cmd.flush(); cmd.println("* auto_sync_time or ast"); cmd.println("* Arduino_info=[check in arduino_info_help or aih] or ai"); cmd.println("* read_esp_alarm=[0-used/1-all] or rea"); cmd.println("* rm_esp_all_alarm"); cmd.println("* write_esp_eeprom=x,y[E.case 0-1023/val.0-255 or wee"); cmd.flush(); cmd.println("* read_esp_eeprom=x[nr.szuf.] or ree"); cmd.println("* clear_arduino_eeprom"); cmd.println("* clear_esp_eeprom"); cmd.println("* what's_time[arduino rtc time] or wt"); cmd.flush(); cmd.println("* debug_help or dh"); cmd.println("* debug_mode=[0/10] or dm"); cmd.println("* feed_info or fi"); cmd.println("* feed_sett=[check in feed_sett_info or fsi]"); cmd.println("* set_display_brightnes= x[x= 0-3] or sdb=x"); cmd.flush(); cmd.println("* add_wifi=x-y,z[nr.0-3 | ssid | pass]"); cmd.println("* display_wifi=x[nr.0-3]"); cmd.println("* reset_device=[0-arduino/1-esp/2-all]"); cmd.println("* display_all_wifi or daw"); cmd.println("* auto_feeding_off=[0-2:no/one_day/every_day]"); cmd.flush(); cmd.println("* set_light_speed=[x=1-4:button/app/alarm/all | y=0-100] or sls"); cmd.println("* wifi_info or wi"); cmd.println("* esp_info or ei"); cmd.println("* display_log or dl"); cmd.println("* log_info or li"); cmd.flush(); cmd.println("* clear_all_logs"); cmd.println("* sd_card_info or sci"); cmd.println("* send_all_logs"); cmd.println("* send_log=(log name, log0001.txt)"); cmd.flush(); cmd.println("* change_filename=(log name, log0001.txt)"); cmd.println("* remove_file=(filename - L0001.txt) or Remove_All_Files"); cmd.println("-------------------------------------------"); cmd.flush(); add_log(100, 1, 999, 999); } else { cmd.print("Wrong command: "); cmd.write(param.getBuffer(), param.getLength()); cmd.println(" <- Try command: help"); add_log(100, 0, 999, 999); } cmd.flush(); } Jak to działa? Istnieją dwie opcje, użytkownik wpisał poprawną komendę lub wpisał nie znaną komendę. W pierwszym przypadku wykonuje się fragment kodu przypisany do danej komendy, np. komenda cls odpowiada za wyczyszczenie terminala. Jeżeli podano nieznaną komendę, to na terminalu wyświetli się napis Wrong command: (podana komenda) <- Try command: help. Zaprogramowałem także coś w stylu spisu treści, po wpisaniu komendy help, na terminalu pojawią się wszystkie dostępne komendy oraz ich parametry. Spis komend: * cls * show_alarms or sha * alarms_counter or ac * display_alarm=[nr.szuf.] or da * set_alarm=[check in alarm_help or ah] or sa * rm_alarm=[remove alarm nr.] or rma * rm_all_alarm * millis_usage or mu * read_ard_eeprom=[nr.szuf.] or rae * write_ard_eeprom=x,y[szuf.0-1023/val.0-255] or wae * sync_time=[H:Min-D/M.Y]H=0-23|Min=0-59|Y=1-100 * auto_sync_time or ast * Arduino_info=[check in arduino_info_help or aih] or ai * read_esp_alarm=[0-used/1-all] or rea * rm_esp_all_alarm * write_esp_eeprom=x,y[E.case 0-1023/val.0-255 or wee * read_esp_eeprom=x[nr.szuf.] or ree * clear_arduino_eeprom * clear_esp_eeprom * what's_time[arduino rtc time] or wt * debug_help or dh * debug_mode=[0/10] or dm * feed_info or fi * feed_sett=[check in feed_sett_info or fsi] * set_display_brightnes= x[x= 0-3] or sdb=x * add_wifi=x-y,z[nr.0-3 | ssid | pass] * display_wifi=x[nr.0-3] * reset_device=[0-arduino/1-esp/2-all] * display_all_wifi or daw * auto_feeding_off=[0-2:no/one_day/every_day] * set_light_speed=[x=1-4:button/app/alarm/all | y=0-100] or sls * wifi_info or wi * esp_info or ei * display_log or dl * log_info or li * clear_all_logs * sd_card_info or sci * send_all_logs * send_log=(log name, log0001.txt) * change_filename=(log name, log0001.txt) * remove_file=(filename - L0001.txt) or Remove_All_Files Logi: Dodałem tę funkcję jedynie w celach edukacyjnych (chciałem przetestować, czy tworzenie logów ma jakikolwiek sens oraz czy jest to trudne). Okazało się jednak, że logi mogą być pomocne, dzięki nim mogłem testować, czy alarmy wykonują się poprawnie o zadanej godzinie, dlaczego i kiedy resetuje się samoczynnie sterownik itp. Tworzenie logów polega na zapisywaniu w buforze w pamięci RAM informacji o wykonaniu się jakiegoś zdarzenia, o czasie wykonania się tego zdarzenia oraz o jego parametrach. Przykładowo, zauważyłem, że karta sieciowa resetuje się samoczynnie co jakiś czas. Dzięki zapisanym logą mogę dowiedzieć się kiedy dokładnie sterownik zresetował się, (znam moment zakończenia się funkcji setup) oraz przeanalizować co mogło taki reset wykonać. void setup() { ... add_log(48, datalog_d, datalog_m, datalog_y); add_log(30, 1, 999, 999); //Karta sieciowa wystartowała check_day_change(); } Jestem w stanie sprawdzić dzień, godzinę i minutę wykonania się praktycznie każdego elementu programu sterownika lub karty sieciowej, co jest bardzo pomocne. Każdy log składa się z 8 bajtów danych: D- dzień | H- godzina | M- minuta | A- co to jest za log | Val1_lB / Val1_hB- zapisany na maksymalnie dwóch bajtach parametr | Val2- drugi parametr | Val3- trzeci, ostatni parametr przykładowy log: 100->D19H20M14A1&1#127, 127 Taki log mówi mi, że 19-stego dnia miesiąco o godzinię 20:14 ktoś wcisnął na obudowie sterownika przycisk niebieski i teraz stan na przekaźniku nr.1 jest wysoki. Wiem także, że nie podano 2 i 3 parametru ponieważ mają one wartość 127, nie wiem dokładnie dlaczego wybrałem tą liczbę a nie na przykład 255, ale stosuję tą zasadę w mechanizmie logów oraz podczas odczytywania alarmów. 100-> oznacza, że jest to setny zapisany log od czasu wyzerowania licznika logów, ten licznik zapisywany jest także w pamięci "EEPROM". Spis wszystkich logów: LOG ACTION: -> ARDUINO <- * 1 - blue switch was press in arduino (val1 = 1[on] / 2[off]) * 2 - red switch was press in arduino (val1 = 1[on] / 2[off]) * 3 - green switch was press in arduino (val1 = 0-10) * 4 - feeding button was press in arduino (val1 = None) * 5 - arduino was lock feeding option * 6 - arduino execute alarm (val1 = alarm number | val2 = alarm value) * 7 - arduino was reset * 8 - The arduino eeprom has been cleared -> ESP <- * 17 - delete all sd card files (val1= rm_file counter \ val2 = fail_counter \ val3 = status[1-ok/0-error]) * 18 - repair data function is running (val1=val2repair|val2=1-repair_automaticly/2-repair_manual/0-error value) * 19 - sending error data value log (val1 = 1) * 20 - V0 button has been pressed (val1 = 1[on] / 2[off]) * 21 - V1 button has been pressed (val1 = 1[on] / 2[off]) * 22 - v2 led pwm slider (val1 = 0-10) * 23 - feeding button has been pressed * 24 - feeding has been ended * 25 - feeding has been cancled (val1 = 1[to much feeding] | 2[feeding is off today] | 3[feeding is off] | 4[feeding is now running]) * 26 - setting alarm (val1 = number of allarm | val2 = function) * 27 - executing terminal command (val1 = command number | val2 = ?) * 28 - clear all logs * 29 - log_counter (val1 = counter) * 30 - esp8266 boot up succesful val1 =1 * 31 - write to esp eeprom (val1 = eeprom case | val2 = value | val3 = 0-error value/1-safe_mode_trigered/2-eeprom write/3-eeprom don't write) * 32 - sd card initialization failed * 33 - email was send * 34 - save log on sd card (val1= 0 Can't create file!/ 1-save ok /) val2 = send_counter * 35 - recived button blue change state command (val1 = 1-on/0-off) * 36 - recived button red change state command (val1 = 1-on/0-off) * 37 - recived button green change state command (val1 = 0-10) * 38 - recived button yellow press - feeding (val1 = 1-feeding_ok/0-feeding error/2-feeding_is_now_run | val2= feeding_today_counter) * 39 - It's new day (val1 = yesterday | val2 = today) * 40 - resetting esp trigered by alarm (val1=1) * 41 - syncing time trigered by alarm (val1 = 1) * 42 - recived alarm synchronized counter from arduino (val1 = counter) * 43 - recived millis from arduino (val1 = ard_millis) * 44 - recived arduino eeprom case value (val1 = arduino eeprom case / val2 = eeprom value) * 45 - recived send to esp eeprom succesful writing counter (1-eeprom succesful writing counter|2-eeprom fail writing counter|?-wrong value) * 46 - recived and printing in cmd (val1 = funkcja | val2 = day | val3 = value) * 47 - recived arduino rtc time (val1 = hour | val2 = day | val3 = week day) * 48 - changed log filename (val1 = day | val2 = month | val3 = year) * 49 - save_file_name (val1 = 1-ok/0-error) 51 - Action = cls | val1 = 1 | val2 = None | val3 = None 52 - Action = show_alarms | val1 = cmd_val | val2 = None | val3 = None 53 - Action = alarms_co unter | val1 = e_count | val2 = None | val3 = None 54 - Action = display_alarm | val1 = ter_val | val2 = None | val3 = None 55 - Action = alarm_help | val1 = 1 | val2 = None | val3 = None 56 - Action = rm_alarm | val1 = 1(done)/0(error) | val2 = None | val3 = None 57 - Action = rm_all_alarm | val1 = 1 | val2 = None | val3 = None 58 - Action = millis_usage | val1 = millis_esp | val2 = None | val3 = None 59 - Action = read_ard_eeprom | val1 = arduino_eeprom_case | val2 = 1(done)/0(error) | val3 = None 60 - Action = write_ard_eeprom | val1 = arduino_eeprom_case | val2 = value_to_write | val3 = 1(done)/0(error) 61 - Action = sync_time | val1 = godzina | val2 = minuta | val3 = dzien 62 - Action = auto_sync_time | val1 = godzina | val2 = minuta | val3 = dzien 63 - Action = Arduino_info | val1 = cmd_val_1 | val2 = None | val3 = None 64 - Action = arduino_info_help | val1 = 1 | val2 = None | val3 = None 65 - Action = read_esp_alarm | val1 = 0(only alarm)/1(all eeprom cases)/?(error, wrong value 0 or 1) | val2 = ile_alarmow_odczytano | val3 = None 66 - Action = rm_esp_all_alarm | val1 = 1 | val2 = None | val3 = None 67 - Action = write_esp_eeprom | val1 = EEPROM case | val2 = value to write | val3 = 0(error!Wrong value 1 or 2)/1(Done) 68 - Action = read_esp_eeprom | val1 = EEPROM case | val2 = read value | val3 = 0(error!Wrong eeprom case)/1(Done) 69 - Action = clear_arduino_eeprom | val1 = 1 | val2 = None | val3 = None 70 - Action = clear_esp_eeprom | val1 = 1 | val2 = None | val3 = None 71 - Action = what's_time | val1 = 1 | val2 = None | val3 = None 72 - Action = debug_help | val1 = 1 | val2 = None | val3 = None 73 - Action = debug_mode | val1 = debug_option | val2 = (0-error value/1-ok) | val3 = None 74 - Action = feed_info | val1 = (0-feeding\1-not today\2-off\3-wrong value) | val2 = None | val3 = None 75 - Action = feed_sett | val1 = 1-5 | val2 = cmd_val_2 | val3 = 1(Done)/0(Error) 76 - Action = feed_sett_info | val1 = 1 | val2 = None | val3 = None 77 - Action = set_display_brightnes | val1 = 0-3 | val2 = 1(Done)/0(Error) | val3 = None 78 - Action = reset_device | val1 = 0-2 | val2 = 1(Done)/0(Error) | val3 = None 79 - Action = add_wifi | val1 = wifi_number | val2 = 1(Done)/0(Error)/2(commit error) | val3 = None 80 - Action = display_wifi | val1 = number of wifi to displayed | val2 = 1(Done)/0(Error) | val3 = None 81 - Action = Clear_all_wifi=AquaDzz@iadek19 | val1 = 1 | val2 = None | val3 = None 82 - Action = display_all_wifi | val1 = 1 | val2 = None | val3 = None 83 - Action = auto_feeding_off | val1 = 1 | val2 = None | val3 = None 84 - Action = set_light_speed | val1 = cmd_val_1 | val2 = cmd_val_2 | val3 = None 85 - Action = wifi_info | val1 = network_id | val2 = None | val3 = None 86 - Action = esp_info | val1 = 1 | val2 = None | val3 = None 87 - Action = display_log | val1 = display_log_counter | val2 = None | val3 = None 88 - Action = log_info | val1 = log_counter | val2 = kolejka_size | val3 = kolejka_empty 89 - Action = send_all_logs | val1 = 1 | val2 = None | val3 = None 90 - Action = sd_card_info | val1 = (1-sd card read ok\ 0-sd card read error) | val2 = None | val3 = None 91 - Action = remove_file | val1 = 1-delete ok/0-error | val2 = None | val3 = None 92 - Action = send_log | val1 = 1-ok/0-error | val2 = None | val3 = None 93 - Action = | val1 = 1 | val2 = None | val3 = None 94 - Action = | val1 = 1 | val2 = None | val3 = None 95 - Action = | val1 = 1 | val2 = None | val3 = None 96 - Action = | val1 = 1 | val2 = None | val3 = None 97 - Action = | val1 = 1 | val2 = None | val3 = None 98 - Action = | val1 = 1 | val2 = None | val3 = None 99 - Action = | val1 = 1 | val2 = None | val3 = None 100 - Action = help or kommand unknown | val1 = (1-help ok/0-command unknown) | val2 = None | val3 = None Funkcja dodająca logi: void add_log(byte action, int val1, int val2, int val3) { /* if (kolejka_size() >= 400) { cmd.println("Buffor is full! Saving data on sd card!"); cmd.flush(); save_data(1); //save_data(0); } */ if (val1 < 0 || val1 > 65000) { cmd.print("Error! Log_add function - val1 out of range(0-65000) ->"); cmd.println(val1); cmd.flush(); return; } byte val1_hb = 0, val1_lb = 0; val1_hb = highByte(val1); val1_lb = lowByte(val1); byte log_D = day(); byte log_H = hour(); byte log_M = minute(); if (log_D < 0 || log_D > 31) { cmd.print("Error! add_log function - problem with day() value! Out of range(0-31) ->"); cmd.println(log_D); return; } if (log_H < 0 || log_H > 23) { cmd.print("Error! add_log function - problem with hour() value! Out of range(0-23) ->"); cmd.println(log_H); return; } if (log_M < 0 || log_M > 59) { cmd.print("Error! add_log function - problem with minute() value! Out of range(0-59) ->"); cmd.println(log_M); return; } int push_case = kolejka_push(log_D); kolejka_push(log_H); kolejka_push(log_M); kolejka_push(action); kolejka_push(val1_hb); kolejka_push(val1_lb); if (val2 > 255 || val2 < 0) { kolejka_push(127); } else { kolejka_push(val2); } if (val3 > 255 || val3 < 0) { kolejka_push(127); } else { kolejka_push(val3); } if (D_add_log == 1) { cmd.print("Setting log case "); cmd.print(push_case / 8); cmd.print("-"); cmd.print(kolejka_size()); cmd.print(" at "); cmd.print(message_aray[push_case + 1]); cmd.print(":"); cmd.flush(); cmd.print(message_aray[push_case + 2]); cmd.print(", day "); cmd.print(message_aray[push_case]); cmd.print(" -> Action "); cmd.print(message_aray[push_case + 3]); cmd.print(" with val1: "); cmd.flush(); cmd.print((message_aray[push_case + 4] << 8) + message_aray[push_case + 5]); cmd.print(" with val2: "); cmd.print(message_aray[push_case + 6]); cmd.print(" with val3: "); cmd.println(message_aray[push_case + 7]); cmd.flush(); } } Zapisywanie danych na karcie SD: Tworzenie logów już działa, ale co z ich przechowywaniem w pamięci RAM? Jak wiadomo pamięć RAM kiedyś się skończy, nie mogę więc zapisywać w niej setek czy tysięcy logów, kolejnym problemem jest to, że po utracie zasilania wszystkie moje logi przepadną. W pierwszym momencie planowałem zapisywać logi w pamięci EEPROM, szybko zrezygnowałem jednak z tego pomysłu, ponieważ ograniczał mnie rozmiar tej pamięci. Zdecydowałem się na użycie karty pamięci jako magazynu na dane. Nie zrezygnowałem jednak z buffera na dane, powodem tej decyzji jest to, że dla karty SD nie jest zdrowe ciągłe otwieranie i zamykanie plików, np. 30 razy na minutę, 24 godziny na dobę przez cały rok. Lepszym pomysłem jest hurtowe zapisywanie kilku logów podczas jednego otwarcia i zamknięcia pliku. Aktualnie zapisuję na karcie pamięci 75 logów na jedno otwarcie pliku, buffer może pomieścić maksymalnie 110 logów, mam więc zapas w razie problemu z zapisem w danej chwili. wywołanie funkcji zapisującej na karcie SD: if (kolejka_size() >= 600) { save_data(1); } Co 5 sekund urządzenie sprawdza czy w bufferze znajduje się więcej niż 74 logi (ponieważ 600 komórek / 8 komórek na log = 75 logów). Jeżeli tak to następuje próba zapisu. Próba zapisania danych występuje także podczas resetowania się karty sieciowej przy użyciu alarmu lub komendy (sterownik główny lub karta sieciowa mogą resetować się auto magicznie, jeżeli ustawie taki alarm), ma to na celu zapobieganie przed utraceniem danych. funkcja zapisująca dane: void save_data(bool datafile_all) { if (!SD.begin(D8)) { cmd.println("initialization failed!"); cmd.flush(); add_log(32, 1, 999, 999); return; } else { sd_card_work = true; } long log_counter = readLongFromEEPROM(518); cmd.println("Saving data from buffer to sd card!"); cmd.print("Saving "); cmd.print(kolejka_size()); cmd.println(" logs !"); cmd.print("Log name = "); cmd.println(current_log_filename); cmd.print("Log counter = "); cmd.println(log_counter); cmd.flush(); File dataFile; String full_datalog_name = "DATALOG.txt"; if (datafile_all == 1) { full_datalog_name = current_log_filename; } if (!SD.exists(full_datalog_name)) { cmd.println(full_datalog_name + " doesn't exist! Creating file in progress... "); dataFile = SD.open(full_datalog_name, FILE_WRITE); dataFile.close(); if (!SD.exists(full_datalog_name)) { cmd.println("Can't create file!"); cmd.flush(); add_log(34, 0, 999, 999); return; } else { cmd.println("File was created!"); cmd.flush(); } } else { cmd.println("File " + full_datalog_name + " existing!"); cmd.flush(); } log_counter = readLongFromEEPROM(518); byte wd = 0; String dataString = ""; int send_counter = 0; byte k_size = kolejka_size(); while (kolejka_empty() == false) { if (wd == 0) { dataString = ""; dataFile = SD.open(full_datalog_name, FILE_WRITE); if (!dataFile) { cmd.println("Error When opening file " + full_datalog_name); dataFile.close(); return; } } dataString += String(log_counter); dataString += "->D"; dataString += String(kolejka_pop()); dataString += 'H'; dataString += String(kolejka_pop()); dataString += 'M'; dataString += String(kolejka_pop()); dataString += 'A'; dataString += String(kolejka_pop()); dataString += '&'; byte war1, war2; war1 = kolejka_pop(); war2 = kolejka_pop(); int wartosc = (war1 << 8) + war2; dataString += String(wartosc); dataString += '#'; dataString += String(kolejka_pop()); dataString += '$'; dataString += String(kolejka_pop()); dataString += '\n'; send_counter++; if (wd == 25 || kolejka_empty() == true) { dataFile.println(dataString); cmd.println("Writing " + full_datalog_name + " OK!"); dataFile.close(); dataFile = SD.open("DATALOG.txt", FILE_WRITE); if (!dataFile) { cmd.println("Error When opening file DATALOG.txt"); dataFile.close(); return; } dataFile.println(dataString); cmd.println("Writing DATALOG.txt OK!"); dataFile.close(); wd = -1; } wd++; log_counter++; } writeLongIntoEEPROM(518, log_counter); cmd.print("Log counter -> "); cmd.println(readLongFromEEPROM(518)); cmd.print("Succesful save "); cmd.print(send_counter); cmd.println(" logs"); cmd.flush(); if (dataString.length() > 0) { cmd.println("String is no empty!"); cmd.flush(); dataFile.close(); dataFile = SD.open(full_datalog_name, FILE_WRITE); if (!dataFile) { cmd.println("Error When opening file " + full_datalog_name); dataFile.close(); return; } dataFile.println(dataString); cmd.println("Writing " + full_datalog_name + " OK!"); dataFile.close(); dataFile = SD.open("DATALOG.txt", FILE_WRITE); if (!dataFile) { cmd.println("Error When opening file DATALOG.txt"); dataFile.close(); return; } dataFile.println(dataString); cmd.println("Writing DATALOG.txt OK!"); dataFile.close(); } SD.end(); add_log(34, 1, send_counter, 999); } Na początku sprawdzam, czy mogę odczytać / zapisać coś na karcie pamięci, jeżeli tak to wypisuje na terminalu w aplikacji Blynk informację o nazwie pliku do którego zostaną zapisane dane, ilość zapisywanych logów, licznik wszystkich zapisanych logów. Następnie sprawdzane jest czy plik o podanej nazwie istnieje, ta nazwa powstaje podczas uruchamiania się karty sieciowej na podstawie aktualnej daty (przykładowa nazwa L010321.txt). Jeżeli nie istnieje, to jest tworzony. Kolejnym krokiem jest odczytanie wszystkich komórek z buffera, zapisując je jednocześnie w postaci stringów z dodanymi przerywaczami w postaci litery i znaków na karcie SD. Na koniec na terminalu wypisuje status zapisu, zamykam plik, dodaje log z informacją o zapisie oraz liczbie zapisanych logów. Wysyłanie danych w wiadomości email: Podczas programowania tego urządzenia chciałem w jakiś sposób przesyłać dane z karty pamięci na zewnętrzny serwer, pocztę, dysk google lub jako dane w tabelkach w google spreedsheet online. Testowałem właśnie to ostatnie rozwiązanie, korzystałem z tego poradnika. Działało to dość słabo, bez problemu mogłem wysyłać dane do 7 komórek na raz (jeden alarm), ale wysyłanie trwało około 5 sekund. Wysłanie 75 alarmów zajęło by około 7 minut, niestety nie był to jedyny problem. Podczas hurtowego wysyłania alarmów, esp często się zawieszało lub niektóre alarmy były pomijane. Zrezygnowałem więc z tego pomysłu. Kolejnym pomysłem było wysyłanie logów jako stringi w wiadomości email, postanowiłem użyć widżetu email z aplikacji Blynk. Pozwala on na proste wysyłanie wiadomości na podane adres email (nadal korzystam z tego rozwiązania podczas wysyłania kodów z błędami). Jedną z wad tego rozwiązania było ograniczenie do maks. 1200 znaków na wiadomość, jeden alarm może składać się maksymalnie z 33 znaków (np. 34432->D28H13M44A45&12345#127$127), czyli teoretycznie w jednej wiadomości mogę wysłać 36 logów, w praktyce mniej ponieważ tytuł wiadomość też zabiera znaki. Pomyślałem, że mogę wysyłać właśnie tak pocięte wiadomości odczytywane z karty SD, jednak jest to trochę karkołomne rozwiązanie. W jeden dzień jestem w stanie wyprodukować nawet 600 logów, wysyłanie ich wszystkich na pocztę jest trochę mało wygodne. Szukałem rozwiązania pozwalającego na wysłanie pliku txt na pocztę lub serwer, i tak tworze taki plik na karcie SD, więc dlaczego go nie wykorzystać. Znalazłem projekt EMailSender, jest moim zdaniem świetny. Pozwala on na przesyłanie różnych plików, np. .txt, .jpg na pocztę gmail. Działa on na esp8266, esp32, czy też arduino z podłączoną kartą sieciową. Pliki na poczcie otrzymuję jako załącznik. Jedyną wadą jest długi czas wysyłani się plików. Podczas wysyłania plików na pocztę, karta sieciowa resetuje się i wchodzi w tryb wysyłania wiadomości. Podczas tego trybu aplikacja Blynk ani inne funkcje karty sieciowej nie działają, działa jednak wysyłanie komend do sterownika głównego. Zmodyfikowałem trochę bibliotekę EMailSender, dzięki temu mogę obliczyć ile procent wiadomości zostało wysłane, te informację wysyłam do sterownika głównego, a on pokazuje je na wyświetlaczu. Podczas wysyłania wiadomości na serial monitorze mogą pojawić się dane, np. o błędzie podczas wysyłania, błędzie podczas logowania się lub informacja o pomyślnym wysłaniu wiadomości, wszystkie te dane zapisuję w bufferze, a po połączeniu się esp z aplikacją Blynk, wyświetlam je na terminalu. void send_mail(bool send_own_file) { String datafile_name = "DATALOG.txt"; send_mail_serial("Starting!", 1); send_mail_serial("Initializing SD card...", 1); if (!SD.begin(D8)) { send_mail_serial("initialization failed!", 1); add_log(32, 1, 999, 999); return; } if (send_own_file == 1) { datafile_name = read_file_name(); } send_mail_serial("initialization done.", 1); File root = SD.open("/"); delay(100); if (WiFi.status() != WL_CONNECTED) { static uint16_t attempt = 0; send_mail_serial("Connecting to ", 0); WiFi.begin(E_ssid.c_str(), E_pass.c_str()); send_mail_serial(String(E_ssid.c_str()), 1); uint8_t i = 0; while (WiFi.status() != WL_CONNECTED && i++ < 50) { delay(200); send_mail_serial(".", 0); } ++attempt; send_mail_serial("", 1); if (i == 51) { send_mail_serial("Connection: TIMEOUT on attempt: ", 0); send_mail_serial(String(attempt), 1); if (attempt % 2 == 0) send_mail_serial("Check if access point available or SSID and Password", 1); return; } send_mail_serial("Connection: ESTABLISHED", 1); //send_mail_serial("Got IP address: ", 0); //send_mail_serial(String(WiFi.localIP()), 1); } byte datalog_d, datalog_m, datalog_y; datalog_d = EEPROM.read(523); datalog_m = EEPROM.read(524); datalog_y = EEPROM.read(525); String dd_str = ""; if (datalog_d < 10) { dd_str = "0"; } dd_str += String(datalog_d); String dm_str = ""; if (datalog_m < 10) { dm_str = "0"; } dm_str += String(datalog_m); String dy_str = ""; if (datalog_y < 10) { dy_str = "0"; } dy_str += String(datalog_y); current_log_filename = "L" + dd_str + dm_str + dy_str + ".txt"; EMailSender::EMailMessage message; message.subject = "Logi z sterownika"; message.message = "Pobierz plik z logami :)<br>"; message.mime = MIME_TEXT_PLAIN; byte message_size = 0; if (send_own_file == 0) { message_size = 2; } else { message_size = 1; } EMailSender::FileDescriptior fileDescriptor[message_size]; fileDescriptor[0].filename = (datafile_name); fileDescriptor[0].url = ("/" + datafile_name); fileDescriptor[0].storageType = EMailSender::EMAIL_STORAGE_TYPE_SD; if (message_size > 1) { fileDescriptor[1].filename = (current_log_filename); fileDescriptor[1].url = ("/" + current_log_filename); fileDescriptor[1].storageType = EMailSender::EMAIL_STORAGE_TYPE_SD; } EMailSender::Attachments attachs = {message_size, fileDescriptor}; EMailSender::Response resp = emailSend.send("[email protected]", message, attachs); send_mail_serial("Sending status: ", 1); send_mail_serial(String(resp.status), 1); send_mail_serial(String(resp.code), 1); send_mail_serial(String(resp.desc), 1); SD.end(); EEPROM.write(522, 3); EEPROM.commit(); add_log(33, 1, 999, 999); } kod karty sieciowej (Zalecam użyć dołączonych prze zemnie bibliotek): kod_karty_sieciowej.zip Automatyczny karmnik: Do budowy automatycznego karmnika wykorzystałem projekt ze strony thingiverse. Dodałem tylko własną obudowę na silnik oraz zrezygnowałem z górnego mieszalnika. W środku obudowy znajduje się: mostek h (LM293D), odpowiada on za sterowanie silniczkiem (4,5V) z przekładnią z zabawki 3 diody led, pierwsza odpowiada za informowanie o zasilaniu karmnika (odłączyłem ją ponieważ niepotrzebnie świeciła cały dzień), 2 i 3 dioda świeci odpowiednio podczas procesu podawania pokarmu lub powrotu do pojemnika Do działania karmnika potrzebny jest także stabilizator napięcia 12v na 5v, znajduję się on jednak poza obudową karmnika. Postanowiłem umieścić go w małej prostokątnej obudowie wraz z bezpiecznikiem i gniazdem LAN. Tak karmnik jest połączony ze sterownikiem głównym poprzez moduł z gniazdem LAN na skrętce komputerowej Wykorzystuje tam 6 żył na zasilanie oraz po jednej żyle na sterowanie mostkiem h. Aktualnie moduł karmnika oraz moduł z gniazdem LAN dla jego zasilania, odłączone są od sterownika głównego (mam zamiar wykorzystać je w swoim sterowniku). Obudowa: Obudowa sterownika głównego to puszka natynkowa hermetyczna z wyciętym otworem wentylacyjnym na wieczku oraz przyklejonej w tym miejscu kratki wydrukowanej na drukarce 3D, otworem na bezpiecznik z prawej strony, otworem do podwójnego gniazdka 230v, otworem i ramką dla wyświetlacza oraz otworami dla przycisków w wieczku obudowy. Obudowa dla karty sieciowe została w pełni wydrukowana na drukarce 3D, jest ona dość spora, ale i tak ledwo zmieściłem się ze wszystkimi potrzebnymi elementami. W pierwotnej wersji z sterownika głównego wystawał przymocowany na krótkim przewodzie prostokątny moduł z elektroniką sterującą automatycznym karmnikiem, znajdował się w niej bezpiecznik, stabilizator napięcia oraz złącze do podłączenia karmnika na długim przewodzie. Usunąłem go jednak, ponieważ dziadek nie korzystał z karmnika Nie jestem zadowolony z finalnego efektu, następnym razem wykorzystam dużo większą obudowę w której zmieszczę całą elektronikę sterownika głównego oraz karty sieciowej. Podsumowanie Jestem zadowolony z działania tego urządzenia, podczas testowania go w swoim akwarium z rybami nie sprawiał mi problemów. Co nie wyszło? moim zdaniem większość kodu mogła by być napisana lepiej, aktualnie widzę kilka błędów np. możliwość wykonania się tylko jednego alarmu na minutę, brak odpowiednich zabezpieczeń podczas wpisywania komend w terminalu, lekki bałagan w kodzie Moduł karmnika powinien być podłączony do sterownika a nie do karty sieciowej, takie podłączenie czasami uniemożliwia karmienie podczas braku dostępu do internetu lub podczas startu karty sieciowej Wysyłanie kilku dniowych plików z logami trwa nawet kilka godzin, nie jest to jednak do końca wada, ponieważ mogę wysyłać taki plik w nocy Automatyczny karmnik nie jest wykorzystywany przez dziadka, trochę mu nie ufa wygląd sterownika jest moim zdaniem ok, ale wygląd karty sieciowej jest dość śmieszny, tym bardziej doklejonej na gorący klej anteny od routera podczas testów nigdy nie miałem problemów z działaniem karty sieciowej nawet gdy była oddalona od routera w lini prostej na 15 metrów przez 3 ściany i podłogę, jednak podczas próby uruchomienia całego urządzenia u dziadka okazało się, że nowy router z światłowodem oddalony o 6 metrów w linii prostej przez 3 ściany nie jest w stanie połączyć się z kartą sieciową, problemem mogły być także inne sieci WiFi ponieważ mieszkanie znajduje się w bloku. Dlatego na szybko musiałem dodać zewnętrzną antenę ze starego routera TP-linka, po tej modyfikacji wszystko działa bez problemu Co wyszło? Urządzenie jest stabilne, nie zdarzają się mu już samo resety lub zawieszanie się Sterownik główny jest autonomiczny, działa nawet kiedy karta sieciowa jest wyłączona Alarmy działają bez problemu Pasek LED podłączony do sterownika działa bardzo dobrze, powoli rozjaśniające i ściemniające się światło nie straszy ryb Logi tworzone przez sterownik pozwalają na sprawdzenie co działa nie tak jak powinno, jest to moim zdaniem fajne narzędzie które będę używać w przyszłych projektach Najważniejsza rzecz, mój dziadek jest zadowolony z działania tego urządzenia, nie musi już codziennie kilka razy sięgać do listwy i wkładać/wyciągać wtyczkę od pompki, napowietrzacza oraz zasilacza do oświetlenia LED Plany na przyszłość Podczas budowy i programowania tego urządzenia nauczyłem się sporo rzeczy, aktualnie pracuję nad czymś w stylu uniwersalnego sterownika do oświetlenia zewnętrznego / systemu nawadniania / sterownika do mojego akwarium. Ma on mieć możliwość dodawania alarmów poprzez wyświetlacz lcd 20x4 i menu z 4 przyciskami, sterowania wyjściami poprzez telefon, odczytywanie danych z czujników itp. Chcę wykorzystać i ulepszyć kilka pomysłów z tego sterownik w moim nowym urządzeniu. Dziękuje za przeczytanie tego artykułu, mam nadzieję że ktoś z mojego rozwiązania coś wykorzysta. Powodzenia we wszystkich waszych przyszłych projektach -
Zawodne połączenia przewodów do MAX7219 w Arduino UNO
Karendale opublikował temat w Zupełnie zieloni
Dzień dobry, Wrzucam post w tę kategorie,bo to podstawy. Mam model Arduino UNO wraz z dwoma płytkami MAX7219 32x8, które mają niezależne dostarczanie energii od Arduino(splitter na powerbanku). Połączenia mam za pomocą łączników ze zdjęcia i nie są one ani stabilne, ani solidne - wystarczy lekko inaczej ustawić kabelek, żeby cała łączność przestała działać. Do połączenia tymczasowego z Arduino używam tzw. jumper wires i tu na razie nie mam zastrzeżeń. Ciężko mi sprawdzić sprawność układu i kodu, gdy całość się 'glitchuje' lub kod działa tylko na jednej płytce,a dalsza jest cała zaświecona(niezależnie od użytej płytki,mam kilka w razie przypadkowego uszkodzenia) Czy to wina słabej jakości kabli?(te akurat mam no-name, które były dorzucone do płytek, bo mam same przewody męsko-męskie) - wydaje mi się,że to może być jeden czynnik,ale kątownik nie jest stabilnie zamocowany i zwyczajnie porusza się w łączeniach MAX7219 i wpływa negatywnie na prace układu. Czy są solidniejsze metody łączenia niż to co na zdjęciu, czy tylko lutowanie mi zostaje? Chciałam solidne połączenia, żeby nie musieć tego trwale mocować, ale żeby układ był gotowy do włożenia do obudowy(w środku pusta, więc może być mały ruch kabli podczas noszenia obudowy). dodatkowo,czy mogę wygiąć 'nóżki' płytki MAX7219,żeby mieć niewidoczne połącznie płytek(chciałam je ustawić jeden obok drugiego poziomo,bez przerwy)? Z góry dziękuję -
Witam, 2 tygodnie temu przerabiałem kurs z arduino. Ostatnio nie miałem zbyt wiele czasu i dzisiaj mogłem wrócić do nauki z powrotem. Najpierw chciałem powtórzyć to czego nauczyłem się wcześniej, wróciłem więc do drugiej części kursu w której zapalaliśmy czerwoną diodę. Jednak tym razem nie chce się zapalić, zamieniałem już wszystkie części (kable, rezystory, diodę, a nawet kanał z 8 na 7) ale nadal nie działa. Czy mogę w jakiś sposób sprawdzić który element powoduje błąd w wykonywaniu się programu? void setup() { pinMode(8, OUTPUT); digitalWrite(8, HIGH); } void loop() { }
-
Ploter XY (90x60 cm) do rysowania szablonów elementów samolotów RC
aimeiz opublikował temat w Projekty - DIY
Do moich konstrukcji modelarskich potrzebowałem plotter do wyrysowania szablonów sporych elementów do samolotów RC. Drukowanie na drukarce i sklejanie z arkuszy A4 jest niewygodne i prowadzi do błędów, więc postanowiłem wykonać plotter o sporym formacie. W Internecie znalazłem niedrogie tablice ścienne z ramą aluminiową i magnetycznym pokryciem i uznałem, że będzie to dobra podstawa do mojego plotera. Największe dostępne są 120x90cm, ale wybrałem mniejszą 90x60cm. Urządzenie takiej wielkości zajmuje dużo miejsca, więc wykonałem je w formie do zawieszenia na ścianie. Bazą doświadczeń do wykonania projektu był przeznaczony do wykonanie przez młodzież z klubu radiowego mini ploterek "złomek" wykonany z elementów uzyskanych z uszkodzonych nagrywarek DVD, uzyskiwanych bezpłatnie w serwisach komputerów i z kontrolerem na Arduino UNO lub mini i shieldu CNC. Jako oprogramowanie - firmware użyłem tam dostępny na GitHub firmware grbl i początkowo duży ploter też działał na takim sterowniku. W międzyczasie znalazłem projekt grbl_Esp32, umożliwiający bezprzewodową łączność z urządzeniem, poprzez stronę www, telnet, BT i też tradycyjnie po kablu USB. Dzięki zastosowaniu procesore ESP32, urządzenie może łączyć się z lokalną siecią, lub działać przez bezpośrednie połączenie w trybie AP. Webowe GUI można zmodyfikować do własnych potrzeb. Napisane jest z użyciem popularnego frameworku bootstrap. Podobno istnieje lepsza wersja, która pokazuje na bieżąco postęp wydruku i położenie karetki. Też zauważyłem że jest napisany od nowa grbl-Esp, ale nie testowałem. Najpopularniejsze podobne konstrukcje bazują na tradycyjnym rozwiązaniu XYZ, gdzie silnik osi Y jeździ wraz z karetką. To rozwiązanie nie podobało mi się, gdyż czyni ruchomą część ciężką i wymaga dodatkowych 4 przewodów do silnika krokowego. Spodobało mi się rozwiązanie tzw.. CoreXY, gdzie silniki obydwu osi są nieruchome a karetka zawiera jedynie serwomechanizm osi Z. To bardzo ciekawe rozwiązanie. Gdy obydwa zębate paski poruszają się w tym samym kierunku, karetka porusza się po osi X, a jak w przeciwnych - po osi Y, tak że potrzebny jest jedynie 3 żyłowy kabelek do serwomechanizmu. Wykonanie plotera. Po zaopatrzeniu się w podzespoły należy zacząć od wydrukowania plastikowych elementów. Następnie należy wywiercić otwory na śrub mocujące uchwyty silników i rolek w aluminiowej ramie tablicy. Elementy ustawia się według zewnętrznych krawędzi tablicy. Początkowo nie stosowałem dodatkowego usztywnienia tablicy, co sprawdzało się przez kilka lat, ale plotter wisi na ścianie nad kaloryferem i pod okienkiem piwnicznym, które w lecie jest otwierane i niestety pilśniowa płyta nieco się spaczyła, więc zamówiłem aluminiowe ceowniki, które przykręcę od spodu tablicy - 3 sztuki wzdłuż osi Y, aby zapobiec wypaczaniu. Zastanawiam się jak umocować do nich blat tablicy najlepiej bez przewiercania, aby powierzchnia robocza nadal pozostała idealnie gładka. Najlepiej usztywnić tablicę od razu podczas budowy. Ważną rzeczą jest zapewnienie właściwego zasilania serwomechanizmu. Dostępne kable spiralne niestety mają niepomijalną oporność i przy gwałtownych ruchach serwomechanizmu spada napięcia. Dlatego zastosowałem regulowalny moduł step-down z ustawionym napięciem 6V akceptowanym przez serwomechanizm, aby na końcu dostawał co najmniej pełne 5V. W przyszłości zamierzam umieścić ten moduł w karetce, co zapewni dobre zasilanie serwomechanizmu. Leży u mnie w szpargałach laserowy moduł do cięcia i być może ten element użyty będzie zarówno do rysowania, jak i cięcia, a posiadanie wycinarki laserowej o takim formacie to super sprawa. Szczegóły widoczne na zdjęciach: Oprogramowanie firmware należy ściągnąć ze strony projektu projekt GRB:_Esp32 na Github. Należy zmodyfikować pliki konfiguracyjne: config.h - tu można wpisać SSID i hasło punktu dostępowego naszej sieci, ale nie jest to niezbędne. Machine.h - tu wpisujemy jedną linię, aby kompilator użył pliku odpowiedniego dla naszej maszyny. #include "Machines/corexy_servo_Z_R32_UNO_CNC.h" //This is for Core XY Plotter with servo. stworzyć na bazie pliku corexy_pen_laser.h, plik dla własnej maszyny. Sa tam zdefiniowane porty dla silników, serwomechanizmu, krańcówek i parametry czasowe, które nie zostały zmieniane. W moim przypadku stworzyłem plik corexy_servo_Z_R32_UNO_CNC.h, poniżej najistotniejsze modyfikacje. #define X_STEP_PIN GPIO_NUM_26 //GPIO_NUM_12 #define X_DIRECTION_PIN GPIO_NUM_16 //GPIO_NUM_26 #define Y_STEP_PIN GPIO_NUM_25 //GPIO_NUM_14 #define Y_DIRECTION_PIN GPIO_NUM_27 //GPIO_NUM_25 #define STEPPERS_DISABLE_PIN GPIO_NUM_12 //GPIO_NUM_13 #ifdef PEN_LASER_V1 #define X_LIMIT_PIN GPIO_NUM_2 #endif #ifdef PEN_LASER_V2 #define X_LIMIT_PIN GPIO_NUM_13 //GPIO_NUM_15 #endif #define Y_LIMIT_PIN GPIO_NUM_5 //GPIO_NUM_4 #define USING_SERVO // uncomment to use this feature #ifdef USING_SERVO #define Z_SERVO_PIN GPIO_NUM_17 //GPIO_NUM_23 //GPIO_NUM_27 #define DEFAULT_Z_MAX_TRAVEL 5.0 // or change it live with $Z/MaxTravel=5.0 #define DEFAULT_Z_HOMING_MPOS 0.0 // $Z/Homing/MPos=5.0 #endif Widać które porty procesora sterują silnikami, serwem i które użyte są do krańcówek, zresztą początkowo ploter nie posiadał krańcówek. Nie są one niezbędne, choć wygodne, bo w powtarzalny sposób sprowadzamy karetkę do położenia x0, Y0.Było trochę eksperymentowania i poprzednie wartości zostawiłem w formie zakomentowanej. Po skompilowaniu i załadowaniu firmware, można do tego celu użyć Arduino IDE lub VScode / PlatformIO, najwygodniej połączyć się po kablu USB za pomocą dowolnego programu do obsługi urządzeń grbl, może być bezpłatny laserGrbl do ściągnięcia z sieci. To właściwie jest podstawowy program do obsługi urządzenie i przygotowywania projektów, ale są też inne. Trzeba w konfiguracji plotera wpisać ssid i hasło do sieci lokalnej, choć można to wstępnie wpisać w pliku config.h, wtedy ploter odrazu połaczy się z siecią i można się komunikować przez przeglądarkę. Początkowo interfejs webowy jest mizerny i pozwala tylko na wgranie właściwej strony www index.html.gz z katalogu data. Interfejs GUI www umożliwia ręczne sterowanie ploterem, też wgrywanie projektów na kartę SD i modyfikacje ustawień grbl. W konfiguracji grbl należy dobrać ustawienia, tak, aby karetka poruszała się we właściwych kierunkach i o właściwe przesunięcia w mm. są tam też maksymalne prędkości, przyspieszenia, należy to dobrać do wykonanej konstrukcji. Trzeba też wyregulować natężenie prądu silników w stepstikach, tak aby radiatorki za bardzo nie parzyły i silniki nie grzały się zbyt mocno, a jednocześnie zapewniały szybkości i przyspieszenia. Co do konstrukcji mechanicznej, są dwa istotne aspekty: Prowadnice liniowe trzeba przyciąć do takiej długości, aby opierały się o silniki i mocowania rolek. Mimo że elementy plastikowe zaprojektowane są tak, aby wchodziły "na wcisk", jednak warto zabezpieczyć się wszelkimi dostępnymi sposobami, aby prowadnice nie miały możliwości też wzdłużnego przemieszczania się. Są dodatkowym, a właściwie głównym elementem zapewniającym sztywność konstrukcji, mimo że aluminiowa rama tablicy jest dość solidna. Element mocowania pióra przesuwany jest po metalowych prowadnicach. Prowadnice początkowo wchodzą dość ciasno i trzeba mechanizm dotrzeć a potem nasmarować smarem do drukarek 3D, aby poruszał się lekko, ale bez luzów. Pierwotna wersja zawierała łożyska liniowe, ale niestety te małe łożyska są źle wykonane i mają duże luzy, a nie chciałem stosować łożysk 8mm, więc zrezygnowałem z łożysk i karetka sama jest łożyskiem. Generalnie sprawdza się, choć przyznam że karetka i mechanizm osi Z to element, który warto by przebudować i ulepszyć. Tak samo pióro kulkowe z jednej strony odporne jest na zbyt duży docisk, ale wymaga rozpisania przed wydrukiem, Piórka typu flamaster z twardym końcem drą papier, a te miękkie rozpłaszczają się. To wychodzi po miesiącach / latach, kiedy płyta paczy się i traci swą równość, dlatego warto zastosować dodatkowe usztywnienia. Wszelkie długopisy, mazaki, zwłaszcza kulkowe, ze względu na grawitacyjny spływ barwnika, nie piszą "pod górę" i źle piszą w poziomie. Dlatego dolne nóżki plotera są długie, a górne krótsze, dzięki czemu pióro skierowane jest nieco do dołu, co zapewnia ciągłość pisania. Jeśli kreślimy na cienkim papierze, to warto podłożyć kilka kartek, aby zapewnić pewną miękkość i zabezpieczyć się przed pisaniem bezpośredni na tablicy, w przypadku przecięcia papieru przez pióro. Ja mam założonych kilka kartek pełnego formatu tak na stałe zamocowanych magnesami neodymowymi i dopiero na tym podkładzie jest mocowane właściwe medium. To co mogę powiedzieć po kilku latach użytkowania. Urządzenie działa, jest wygodne i przydatne. Wisi na ścianie i nie zajmuje miejsca. To co bym ulepszył, to usztywnienie tablicy i ulepszenie mechanizmu osi Z, przy zachowaniu niewielkich rozmiarów i elastyczności w stosowanych pisakach. Są takie wielokolorowe długopisy. Taki pisak pozwalałby na ręczną zmianę koloru bez wyjmowania pióra, ale są zbyt grube na tę wersję karetki. Być może przebuduję karetką, aby można było mocować moduł lasera. Umożliwiało by to zarówno rysowanie jak i cięcie przy dobraniu odpowiednich parametrów szybkości i mocy lasera. Niestety nie mogę nigdzie znaleźć filmów z pracy plotera, choć na pewno gdzieś je mam. Jak tylko wykonam usztywnienia tablicy, to sfilmuję pracę urządzenia i dodam link do filmu. Spis elementów. Elementy dostępne w Botland mają linki do sklepu. Niestety głównych elementów nie znalazłem w Botland, więc trzeba ich szukać gdzie indziej. Łożyska liniowe LM8UU x8 Karta microSD >=8GB np. moduł czytnik kart MicroSD Zasilacz 12V/3A Moduł step down 12 - 6V >= 2A do zasilania serwomechanizmu. Wałek liniowy 8mm 80cm x2, 64cmx2 wałki liniowe 4mm do karetki. Płytka ESP32 Wemos D1 UNO R32 na ESP32 ewentualnie ardi32 - trzeba by dostosować obudowę i konfigurację oprogramowania do tej płytki. CNC Shield V3 do Arduino UNO Moduł sterownika 4 silników krokowych Sterownik silnika krokowego A4988 RepRap lub lepszy x2 Serwomechanizm MG90 1x Miękka i długa ściskana sprężynka 5x50mm i stalowy drucik 1mm do mechanizmu podnoszenia i opuszczania pióra (oś Z), zapewniającego miękki docisk pióra do papieru. silnik krokowy MEMA17 np. 12V/0.4A, Ja użyłem demobilowych z Allegro z zębatkami do pasków. x2 Kabelki płytka - silnik 70cm x1. 20cm x1 Rolki do pasków zębatych 10mm x8 bierne, 2 szt. do silników krokowych, jeśli nie mają zintegrowanych. Śruby, nakrętki, podkładki do mocowania uchwytów i na ośki rolek. Do dobrania, Wkręty.... Kable spiralne o małej rezystancji do karetki. Tablica magnetyczna 90x60. https://allegro.pl/oferta/tablica-magnetyczna-suchoscierna-90x60cm-suchoscieralna-magnesty-markery-17140553162 Magnesy neodymowe silne np. 12x10x4mm do mocowania papieru. Ceowniki do usztywnienia tablicy 15x20mm 60cm x3 Oprogramowanie grbl_esp32: https://github.com/bdring/Grbl_Esp32 Długopis / pisak kulkowy lub inny 1x Wydrukowane 3D elementy plastikowe. Mogą być z PLA. Komplet plików stl do wydruku w załączony pliku zip. stl.zip -
Dzień dobry. Ostatnio kupiłem silnik 0802SE 19500KV BLDC i ESC 1S - link do ESC: https://pl.aliexpress.com/item/1005006869914363.html?spm=a2g0o.order_detail.order_detail_item.3.539943ceoi18oW&gatewayAdapt=glo2pol. Jednak po kupieniu okazało się że piny do podłączenia które autor zamieścił na stronie sprzedaży okazały się nieprawidłowe i musiałem próbować po kolei każdy pin i w ten sposób udało mi się ustalić gdzie jest pin od zasilania i masy. Problem jest w tym, że nie udało mi się odnaleźć pinu od PWM ponieważ gdziekolwiek go podłączę to silnik stoi w miejscu. Załączam zdjęcia podłączenia ESC do arduino. Dodatkowo zaznaczam że nie jestem totalnym amatorem z Arduino ale o silnikach BLDC i ESC nie wiem prawie nic. Liczę na waszą pomoc. Jak by coś było nie jasne to napisze
-
Witam, na początku zaznaczę ze jeśli chodzi o inteligentny dom to jestem zielony w temacie, ale z racji tego że podwórek mam rozkopany przez małe remonty to pomyślałem że to dobry czas na ułożenie kabli pod przyszłe inwestycje żeby za rok nie kopać od nowa. Ja w skrócie opiszę co posiadam a potem czego oczekuje i jaki mam pomysł, może ktoś z Was mi pomoże:) Więc tak, posiadam na ten moment: -Synology NAS(nagrywanie kamer, możliwe że w przyszłości będą na nim uruchamiane skrypty w Pythonie do sterowania swiatlami/nawadnianiem) -2x kamera Reolinka Duo 2 PoE -Switch PoE max 35W( Aktualnie podpięte 2 kamery które z włączonymi diodami LED biorą nie więcej niż 20-25W, więc zostaje zapas mocy na Acces Point PoE do grodu). -30 lamp LED NOSTALIT 12X12 które według specyfikacji na stronie biorą 6W maksymalnie(chce je podzielić na 4 osobno sterowane sekcje, sterowanie arduino???) -arduino z poprzednich projektów -Radio łazienkowe 20W mocy RMS na kanał( 2 kanały) Chciałbym go użyć do zasilania kolumn na zewnątrz Czego potrzebuje: -Napewno asystent głosowy do sterowania nawadnianiem ogrodu, włączania podglądu z kamer na rzutniku, przełączania muzyki na dworze, sterowania oświetleniem -Mikrofony zewnętrzne do nasłuchu moich poleceń na podwórzu poza domem które będę mógł w jakiś sposób podpiąć do mojego asystenta głosowego -Kilka gniazdek WiFi które będą włączane/wyłączne asystentem głosowym -Acces Point PoE który zasięgiem obejmie otwarty obszar (10mx10m)bez przeszkód fizycznych, będzie wisiał 3.5m nad ziemią -2 kolumny zewnętrzne odporne na warunki atmosferyczne o mocy przynajmniej 25-30W RMS - System nawadniania ogrodu(3 osobno sterowane sekcje, węże, złączki i końcówki nawadniające ogarnę sobie sam, chodzi mi bardziej o pomoc w doborze sterownika(arduino obsłuży ledy i nawadnianie jednoczesnie??), elektrozaworów, czujników wilgotności i deszczu -stacja pogody dla bieżącego pokazania wilgotności temperatury itd -smartwatch który obsłuży mi asystenta głosowego w domu(na zewnątrz planuje rozmieścić mikrofony) -mnóstwo kabli które będę musiał pociągnąć pod ziemią w peszlach( Czy można użyć np 5x1mm do sterowania LEDami poprzez arduino i podzielić te 5 żył na 3 osobne sekcje?Odległość od ostatniej lampy do arduino to jakieś 50m) Chciałbym aby to wszystko dało się spiąć Pythonem( w miarę go ogarniam), aby wszystko było ze sobą kompatybilne i nie było sytuacji w której gniazdko Wifi nie przyjmuje poleceń od czujnika temperatury czy coś w tym stylu Wiecie napewno o co mi chodzi... Czy to jest bardzo złożony projekt według Was? Dziękuję za wszelkie porady i wskazówki!
- 21 odpowiedzi
-
- Arduino
- przekaźnik
-
(i 1 więcej)
Tagi:
-
Projekt zegarka opartego na GPS zamiast DS3231/DS1307
pawel26021995 opublikował temat w Projekty - DIY
Projekt zegarka opartego na GPS jako module czasu rzeczywistego. Po kilku testach układów DS1307 oraz DS3231 stwierdziłem, że nie są one wystarczająco precyzyjne; każdy z nich po jakimś czasie gubi parę minut. Testowałem układy zakupione w Polsce i w Chinach – każdy zachowywał tak samo, uje stąd pomysł na wykorzystanie modułu GPS jako źródła czasu rzeczywistego. Cena modułu GPS w Chinach to około 8 zł, więc porównywalnie do ceny układów DS. Zastosowanie LED-ów WS2812B 5V pozwala na tworzenie różnych animacji i wybór kolorów spośród 16 777 216 możliwości oraz wybór jasności 0–255. W ostatecznej wersji programu pozostałem przy jednym kolorze, który najbardziej mi odpowiada. Program umożliwia łatwą zmianę koloru i ilości użytych diod. Zastosowano 28 modułów LED WS2812B 8-bitowych tworzących wyświetlacze 7-segmentowe: LED-y 0–7 – segment a, 8–15 – segment b itd. Każde 8 LED-ów tworzy osobny wyświetlacz i jest podłączone do innego wyjścia Arduino. Podzielone są na dziesiątki godzin, godziny, dziesiątki minut i minuty oraz dwukropek, który jest zrobiony z dwóch diod LED WS2812B z taśmy LED. Ponieważ jasność diod LED w nocy może być oślepiająca, zastosowałem warunek, który zmienia jasność diod LED na 10 w godzinach 20–6. Można też całkowicie wyłączyć zegarek w tych godzinach. Minimalna jasność dobrze się sprawdza w nocy, w szczególności po przebudzeniu. Strefa czasowa jest ustawiona na stałe na +1 względem czasu UTC. Strefę czasową można zmienić za pomocą komunikacji szeregowej, wysyłając żądaną wartość. Program korzysta ze standardowych bibliotek NeoPixel oraz TinyGPS++. Całość przyklejona jest do plexy z podświetlania z zepsutego monitora. Podgrzana hotair aby była plastyczna do formowania w kształt V. Podłączenie jest tak proste, że nie potrzeba schematu. Całkowity koszt budowy zegarka około 50zł kupując części w chinach. Zachęca do wspierania Polskich sklepów takich jak botland. Opis programu: //wykorzystane biblioteki do obsługi GPS oraz ledów ws2812. #include <Adafruit_NeoPixel.h> #include <TinyGPS++.h> // Definicja segmentów ósemki (po 8 LED na segment) const uint8_t segments[8][8] = { {0, 1, 2, 3, 4, 5, 6, 7}, // Segment A {8, 9, 10, 11, 12, 13, 14, 15}, // Segment B {16, 17, 18, 19, 20, 21, 22, 23}, // Segment C {24, 25, 26, 27, 28, 29, 30, 31}, // Segment D {32, 33, 34, 35, 36, 37, 38, 39}, // Segment E {40, 41, 42, 43, 44, 45, 46, 47}, // Segment F {48, 49, 50, 51, 52, 53, 54, 55} // Segment G }; W tym miejscu można dowolnie zmieniać ilość zastosowanych ledów podzielonych na segmenty. // Mapowanie cyfr na segmenty (0 = wyłączony, 1 = włączony) const uint8_t digitMap[10][7] = { {1, 1, 1, 1, 1, 1, 0}, // 0 {0, 1, 1, 0, 0, 0, 0}, // 1 {1, 1, 0, 1, 1, 0, 1}, // 2 {1, 1, 1, 1, 0, 0, 1}, // 3 {0, 1, 1, 0, 0, 1, 1}, // 4 {1, 0, 1, 1, 0, 1, 1}, // 5 {1, 0, 1, 1, 1, 1, 1}, // 6 {1, 1, 1, 0, 0, 0, 0}, // 7 {1, 1, 1, 1, 1, 1, 1}, // 8 {1, 1, 1, 1, 0, 1, 1} // 9 }; Tworzenie mapy ledów tak, aby cały segment 8 LED był wykorzystywany jako jeden LED. uint8_t newBrightness = (hour >= 20 || hour < 6) ? 2 : 10;//Operator trójargumentowy działa w postaci: warunek ? wartość_jeśli_prawda : wartość_jeśli_fałsz; Sprawdzanie aktualnego czasu i dostosowywanie jasności zegara. // Funkcja wyświetlająca czas na wszystkich ósemkach void displayTime(int hour, int minute, int second) { displayDigit(strip1, hour / 10, strip1.Color(100, 100, 0)); // Dziesiątki godzin displayDigit(strip2, hour % 10, strip2.Color(100, 100, 0)); // Jedności godzin displayDigit(strip3, minute / 10, strip3.Color( 100, 100, 0)); // Dziesiątki minut displayDigit(strip4, minute % 10, strip4.Color(100, 100, 0)); // Jedności minut } Podział czasu na dziesiątki i jedności oraz wybór koloru. Zdjęcia projektu: Dla porównania zegarek oparty o DS1307 ustawiony 01.02.2025 równo z GPS, spieszy o około 1,5 minuty. Cały program można pobrać z Google dysku 7egment_ws2812b_gps_nano.zip Arduino nano LED WS2812B GPS -
Niskobudżetowy zegar Nixie Każdy elektronik chyba kiedyś widział urządzenie oparte o lampy Nixie. Z racji ich uroku, niepowtarzalnego wyglądu i chęci zrobienia czegoś "wow", i ja taki zbudowałem. Działanie lamp Nixie: Dla tych, którzy nie wiedzą co lampy Nixie, już służę pomocą: lampy Nixie zostały wynalezione w latach 60. ubiegłego wieku. Pierwsza firma która je produkowała tak je nazwała i się ta nazwa przyjęła. Były też to pierwsze wyświetlacze cyfrowe. Ich działanie polega na jonizowaniu się gazu (neonu z domieszkami) wokół katody z przyłożonym napięciem ok. 180V. Zjonizowany gaz powoduje świecenie się, i układa się wokół katody (w tym przypadku cyfry). Na żywo wygląda to bezcennie, lecz należy pamiętać że to wysokie napięcie. Budowa: Ale może najpierw coś o mnie: nazywam się Leon, mam 14 lat, chodzę do 8 klasy podstawówki i interesuję się elektroniką, informatyką, itp. Mam też drukarkę 3D - nie wykorzystałem jej w konstrukcji z racji jej awarii (czekam jeszcze na nowego rampsa ). Przechodząc już do zegara: z racji mojego stosunkowo młodego wieku, nie mam zbyt dużo pieniędzy na projekty więc chciałem na całość przeznaczyć ok. 100 zł zebranych od dziadków. Dlatego miało wyjść tanio i dobrze. Założenia z góry były jasne: multiplexowanie 1 sterownikiem, użycie 4 lamp, oraz materiałów z odzysku. Zacząłem od zrobienia przetwornicy step-up na 200V prądu stałego. Skorzystałem z tego schematu, który się sprawdził dość dobrze. Potem przyszedł mi sterownik 74141, oraz neonówka - mogłem już sprawdzić czy wszystko działa, i działało za pierwszym razem (możecie zacząć budować bunkier na apokalipsę). Następnie przeszedłem do zrobienia płytki głównej - goła atmega 328 z kwarcem 16mhz, ze sterownikiem na jednej płytce. Od razu zamontowałem moduł czasu RTC DS1302 (najtańszy) który lekko zmodyfikowałem - piny dałem z drugiej strony, a na górze zamontowałem koszyczek na dużą baterię od biosa. Do tego doszedł stabilizator 7805 i sterownik katod lamp. Całość wyszła całkiem schludnie - jestem z tego zadowolony. Na końcu doszły mi tranzystory do sterowania anodami lamp. Zastosowałem tu klucz z NPN MPSA42 oraz PNP MPSA92. I tutaj, podczas testów zrobiłem błąd - z racji małego protoboarda zrobiło się zwarcie, przez które zjarałem mój pierwszy rezystor w życiu (!), a tranzystory jakoś działały dalej. Po naprawieniu usterki 1 lampa działała - mogłem wyświetlić wszystkie cyfry od 0 do 9. Mogłem też zmierzyć, że napięcie zapłonu wynosi 180V i obniża się do 140V napięcia pracy. Teraz zostało mi zrobić podstawki - model pod lampy IN-12 do druku mogę udostępnić, ale z racji uszkodzenia płyty musiałem je zrobić sam. Wziąłem więc starą pokrywkę od farby, wyciąłem prostokąty, markerem zaznaczyłem miejsca na piny wdg. datasheetu, mini wiertarką wywierciłem otwory. Musiałem przygotować też same piny do podstawek - użyłem tu rozwierconych pinów z podstawek precyzyjnych, a następnie młotkiem wbiłem we wcześniej przygotowaną podstawę. Elektronika była gotowa, więc zacząłem programować. Po chwili dodałem mikrofon elektretowy, aby po klaśnięciu zegar się sam wyłączył, i od razu przeświecił wszystkie cyfry w celu uniknięcia efektu zatrucia katod. Zauważyłem też, że cewka w przetwornicy się dość mocno grzeje - dałem więc kapkę pasty termoprzewodzącej z domieszkami złota i przykleiłem radiator. Została mi już najgorsza część - obudowa. Normalnie bym takową wydrukował, ale że nie mogłem, wyciąłem ze sklejki listewki które pomalowałem szprejem na czarny mat. Wywierciłem otwory, poskręcałem śrubami M2,5. Wyszło źle, krzywo, niedokładnie - po prostu do d.... , pewnie dlatego że to była moja pierwsza obudowa ze sklejki, i z pewnością wydrukuję później obudowę (post zaktualizuję). Z daleka, jak patrzymy na zegar, wygląda on ciekawie - czarna bryła, lampy rosyjskiej produkcji i to klaśnięcie - wszystko to sprawia, że zegar dodaje niepowtarzalny klimat do pokoju. Zegar robiłem cały tydzień szkolny. Działanie zegara: Zegar wyposażyłem w klawiaturę 3 przycisków - "+", "-", oraz "prog". Przytrzymując przycisk prog możemy nastawić zegar, klikając odpowiednio + i -, oraz kliknąć prog ponownie by nastawić kolejną cyfrę. Podczas zwykłego działania, kliknięcie + spowoduje wyświetlanie się minut oraz sekund, a - będzie wyświetlał godziny i minuty. Dodatkowo, jeżeli podczas uruchamiania zegara przytrzymamy przycisk +, zostanie wywołany efekt "slot machine". Całość programowałem w środowisku Arduino, za pomocą programatora USBASP. Lista zakupów: 4x lampy IN-12 - ok. 10zł/sztuka, 50zł całość (+przesyłka) konwerter step-up - jakieś 20zł za całość sterownik, neonówka i przesyłka - 20zł tranzystory z drobiazgami - 20zł ----------------------------------------------------------------------- Za całość zapłaciłem jakieś 110zł. Resztę elementów już miałem. Dość nieźle, kiedy najtańsze zegary były chyba za ok. 300zł. Cudem jest fakt, że przeżyłem - akurat teraz mnie nic nie kopnęło, ale wcześniej doświadczyłem mocy napięcia gniazdkowego (długa historia). Sam zegar przyniósł mi dużo pochwał, szacunek u kolegów, 6 z fizyki na semestr - to tak jak te cudowne aplikacje na androida Od siebie jeszcze powiem, że na pewno zegar rozbuduję i wzbogacę o nowe funkcje. Co dalej? Mam w planach kalkulator domowej roboty, z kolegą zbudowałem już działający prototyp urządzenia podlewającego rzeżuchę. Oczywiście zachęcam do budowy zegara, ale należy pamiętać o wysokim napięciu. Pozdrawiam, Leoneq :3
- 6 odpowiedzi
-
- 8
-
-
- elektronika
- arduino
-
(i 3 więcej)
Tagi:
-
Chciałbym przedstawić mój projekt, który traktuję jako hobbystyczny, będący w rozwoju od kilku dobrych lat, w zależności od tego na co pozwala mi tzw. "normalne życie", i wszelakie ograniczenia z nim związane. Jestem pasjonatem motoryzacji wszelakiej. Odkąd zrobiłem prawo jazdy, i zderzyłem się z przykrą rzeczywistością polskich warsztatów, i tego jak one traktują klienta, i swoją pracę, zacząłem się interesować kwestiami serwisu, budowy silników, i wszystkiego co z tym związane. Zaczynałem od etapu "otwieram maskę, i nie do końca wiem na co patrzę". Obecnie własnoręcznie buduję silniki, wraz z elektroniką sterującą. Moim pierwszym autem był Ford Escort z silnikiem 1.8D. Stara, wolnossąca jednostka, która nie miała żadnej elektroniki sterującej. Czysta mechanika. Przejechałem tym autem dziesiątki tysięcy kilometrów. Mam zarówno do tego auta jak i tego konkretnego silnika sentyment, pomimo że był wolny, stosunkowo głośny, i problematyczny w zimie. Kilkanaście lat temu wpadłem na pomysł, by "dla sportu" rozebrać ten silnik (kiedy miałem już inne auto oczywiście), i zobaczyć jak to wszystko jest zbudowane od wewnątrz. Miałem wtedy już jakieś doświadczenie z mechaniką silnikową - nie sprawiało mi problemu np. by wymienić rozrząd, itp. Ale jak to ja, stwierdziłem że to za mało - jak już rozbiorę ten silnik, to spróbuję go złożyć na nowo... ulepszając. I tak narodził się mój projekt - zbudowałem sobie hybrydę 1.8D/TD/TDDI (dla fordziarzy dieslowców te skróty coś faktycznie oznaczają! ), a wchodząc głębiej w szczegóły - był to silnik który zachowując zelety pancerności starego 1.8D, wprowadzał jedną, podstawową modyfikację - zmianę sposobu wtrysku paliwa z tzw. "pośredniego" na "bezpośredni". Z tym wiązało się mnóstwo innych rzeczy które trzeba było ogarnąć - np. pompa wtryskowa, wtryskiwacze, turbo, ustawienie tego wszystkiego... No ale zaraz zaraz. Co to ma wszystko wspólnego z tym portalem (bynajmniej nie motoryzacyjnym)? Bo w międzyczasie narodził się pomysł, by do tego silnika (który finalnie wylądował w starej, ale odnowionej blacharsko Fieście), zbudować coś na kształt ECU. Na początku plany były skromne - ot, chciałem mieć ładną wizualizację parametrów silnika. Temperatura, ciśnienie doładowania... Zamiast kilku wskaźników montowanych nie wiadomo gdzie, nie pasujących do auta (miłośnicy tunningu wiedzą o czym piszę), fajnie by było mieć to wszystko na jednym wyświetlaczu. I tak narodziło się to: Na początku był to stosunkowo prosty układ, który głównie odpowiedzialny był za monitorowanie temperatury (chłodziwa/oleju), dolotu, ciśnienia doładowania, temperatury spalin, obrotów silnika, i obciążenia, które w późniejszym etapie wykorzystałem do sterowania turbiną o zmiennej geometrii. Projekt powoli się rozrastał. Pozwolił mi na zestrojenie auta tak, że uzyskałem ok. 170KM. Niestety, z czasem zaczęły mi przeszkadzać ograniczenia - mały rozmiar wyświetlacza, dodatkowo zacząłem myśleć o rezygnacji z mechanicznej pompy wtryskowej, która jest ograniczona w temacie regulacji dawki paliwa, czy kąta wtrysku (przy takiej mocy niestety nieuniknione było dymienie w pewnych sytuacjach). I o ile implementacja nowego wyświetlacza to szczegół praktycznie nie warty wspominania, tak migracja w stronę pompy sterowanej elektronicznie to już wyższy poziom. Pierwsza wersja mojego "ECU" pracowała w oparciu o płytkę ItsyBitsy M0 (z mikrokontrolerem ATSAMD21). To fajna płytka do małych projektów. Jednakże docelowo użyłem tutaj Raspberry pi pico - względem ItsyBitsy jest to spory upgrade - wiecej CPU (dwa rdzenie), więcej pamięci, więcej wszystkiego. Całość pracuje niby pod kontrolą Arduino - piszę niby, bo korzystam raptem z kilku bibliotek (CAN, wyświetlacz), ale powoli tworzę swoistego HALa który umożliwi mi uruchomienie tego kodu pod dowolną platformą. Wyświetlacz był do niedawna częścią sterownika, jednak w momencie rozpoczęcia implementacji pompy VP37, kod odpowiedzialny za wyświetlanie zmigrowałem do osobnego moduły, który czyta wartości wystawiane przez ECU za pomocą magistrali CAN. Być może opowiem o tym module za jakiś czas, bo po niewielkiej modyfikacji można by go zaadoptować w zasadzie do każdego auta z magistralą CAN które wystawia na niej odpowiednie ramki. Co do samego tematu sterowania pompą VP37, to jest to coś, co może zainteresować tutaj więcej osób niż tylko te związane z motoryzacją. Oto ogólny widok na pompę, tak by było wiadomo o czym w ogóle mowa (normalnie są brudne, w kolorze aluminiowym. ) Pod tym czerwonym kapturkiem znajduje się część pompy, która nas najbardziej interesuje. Jest to tzw. elektromagnetyczny nastawnik (ilości paliwa). W praktyce jest to masywna cewka, którą steruje ECU za pomocą PWM. Ogólny koncept wydaje się prosty, ale diabeł, jak to mówią, tkwi w szczegółach. W internecie dostępnych jest sporo filmów które pokazują jak sterować takim nastawnikiem, ja osobiście również coś tam nagrałem: W teorii wydaje się to być proste - ot, generujemy falę prostokątną o określonych parametrach, lub dobranych doświadczalnie, i tyle. Jednak jest to znacznie bardziej skomplikowane w praktyce. Po pierwsze, nastawnik jest elementem bardzo nieliniowym, skłonnym do generowania oscylacji, z racji posiadania sprężyn, których podstawową funkcją jest sprowadzenie nastawnika do pozycji spoczynkowej w momencie gdy np. zostanie odłączone napięcie zasilania (zgaszenie auta). Po drugie - jest to element pracujący w dosyć skrajnym środowisku - jest wypełniony paliwem, pracujący ze zmieniającą się temperaturą w sporych zakresach, narażony na wibracje wszelkiego rodzaju. Do tego dochodzą jeszcze fluktuacje napięcia zasilającego cewkę. No i po trzecie - nie wystarczy go sobie ot tak ustawić "na pałę" i liczyć na to że będzie dobrze. ECU musi wiedzieć w jakim położeniu znajduje się nastawnik, bo na tej podstawie regulowana jest dawka paliwa, co związane jest z przyśpieszeniem, mocą, obrotami jałowymi, itp. Już nie brzmi aż tak prosto. Co przychodzi na myśl w kwestii sterowania? Samą cewką nastawnika steruje się prosto - wystarczy driver na jakimś porządnym mosfecie, z driverem na bramce, którym steruje mikrokontroler. Elementem niezbędnym jest tu mocna dioda Schottky’ego, która służy tutaj do tłumienia przepięć indukowanych w uzwojeniu cewki podczas pracy układu. W przypadku tej cewki ma ona naprawdę sporo do roboty. A od strony programowej? Wiadomo, pierwsze co przychodzi do głowy to nieśmiertelny PID. Oczywiście nie jest to taka "najprostsza" implementacja, u siebie mam np. implementacje różniczkowania z filtrem dolnoprzepustowym o stałej czasowej Tf, metody wykrywające oscylacje, ustawianie parametrów P I D w czasie rzeczywistym, itp. Ale jak wiadomo, regulator PID działa w oparciu o sprzężenie zwrotne, dążąc do minimalizacji wartości błędu między sygnałem zadanym a rzeczywistym. Tylko skąd wziąć w tym przypadku informację o położeniu nastawnika, niezbędną do wyliczenia wartości błędu dla PID? Z jakiegoś powodu Bosch w tych pompach nie zastosował potencjometru, który można by wykorzystać do tego celu (może kwestia trwałości), tylko zastosował zupełnie inny mechanizm. Wygląda on tak: Jest to pierścień z dwoma cewkami. Jak to działa? Wbrew pozorom bardzo prosto: ruch ramienia nastawnika powoduje zmianę indukcyjności cewek. Taki mechanizm jest genialny pod kątem trwałości, coś co w dzisiejszych czasach nie ma zastosowania... Jak to jednak wykorzystać w pratyce, razem z mikrokontrolerem? Z laborek ze szkoły (wow, szkoła się przydała...) przypomniałem sobie że istnieje coś takiego jak generator Hartleya: Układ ten to generator sinusoidy. W połączeniu z cewkami z pompy, generuje on sinusoidę o różnej częstotliwości, oraz amplitudzie. Czyli po wyprostowaniu i odfiltrowaniu przebiegu, mamy już coś co może użyć mikrokontroler (ADC) jako sygnał zwrotny. Oczywiście jest to schemat przykładowy, w praktyce wartości elementów są inne, oraz sam układ jest trochę bardziej skomplikowany. Ogólnie zmodyfikowany lekko PID radzi sobie całkiem nieźle z dryftem napięciowym, temperaturowym, i jest w stanie utrzymać stabilnie nastawnik w zadanym położeniu. Jeszcze odnośnie samego sterownika - bo napisałem o nim parę słów, a w sumie nawet go nie pokazałem Zanim zacznie się krytyka co do wyglądu - jest to cały czas układ rozwojowy, docelowo zaprojektuję porządne PCB i zlecę wykonanie jakiejś firmie. Układ jest wbrew pozorom bardzo stabilny, zarówno od strony mechanicznej, jak i software. W chwili obecnej, by zwiększyć jego stabilność mechaniczną, jest zalany żywicą. Wygląda on w tak: Zbudowałem 2 takie jednostki, jedną do developmentu w domu (części softu które nie wymagają silnika do pracy), i drugą do testów w garażu, gdzie stoi silnik testowy: ...i teraz przystopuję trochę, bo nie wiem czy ten temat jest w ogóle dla was jakkolwiek interesujący, i czy jest sens by o nim pisać dalej, być może bardziej szczegółowo. Ostatecznie jest to również sterowanie silnikiem za pomocą Arduino, tylko takim trochę większym i mocniejszym. Dajcie znać co sądzicie o temacie, jestem otwarty na krytykę, pomysły, pytania, cokolwiek.
- 8 odpowiedzi
-
- 13
-
-
- Arduino
- raspberry pi
-
(i 2 więcej)
Tagi:
-
Nie wiem czy można to nazwać pełnoprawnym DIY ale dzisiaj zaprezentuję drugie życie robota z kursu budowy robotów. Komentarz dla czepialskich: tak, mogę tego robota nazwać robotem ponieważ jest wyposażony w akcelerometr który jest pewnego rodzaju czujnikiem. Wykorzystałem tą konstrukcję, ponieważ chciałem zrobić coś małego co będzie mogło jeździć po całym domu z kamerką. Najpierw omówimy sobie co było gotowe a później co dodałem albo zmieniłem. Cała konstrukcja, Arduino i shield forbota przez cały czas były na swoim miejscu nic w tej kwestii nie ruszałem (oprócz przylutowania paru kabelków do shielda, i wkręcenia wyższych dystansów pod Arduino). Natomiast ze zmian jest jedna ale za to ważna sprawa jaką jest zasilanie. W oryginalnym robocie całość była zasilana z 6 paluszków AA co daje razem 9 woltów (1,5V * 6 = 9V) . Wady takiego rozwiązania były dwie - brak możliwości ponownego naładowania i zajmowały sporo miejsca. Pozbyłem się problemu i zamieniłem na 2 akumulatorki 18650 co daje 8.4 woltów (4.2V * 2 = 8,4V). Ale ktoś może powiedzieć zaraz zaraz, ale 2 takie akumulatorki 18650 wcale nie zajmują jakoś mniej miejsca i w ogóle to 8.4 V to nie za mało ? I ma rację bo przy 8.4V mogłem jeździć niecałe dwie minuty (to wszystko wina malinki) dlatego dodałem przetwornicę step- up która podnosi napięcie do 10V. Wracając do kwestii zajmowanego miejsca przez baterie to może faktycznie 18650 nie zajmuje mniej miejsca objętościowo ale za to kształtem idealnie wpasowały się pod przestrzeń między podwoziem a Arduino. Swoją drogą, gdybym nie odkrył tego miejsca to ten projekt by nie powstał. To tyle ze zmian teraz czas na dodatki. Najważniejszym dodatkiem jest mój samodzielnie zlutowany shield na shielda, może śmiesznie to brzmi ale tak właśnie jest. Zawiera on: moduł radiowy nRF24l01 który odpowiada za sterowanie wszystkim, akcelerometr MPU6050 który daje możliwość cieszyć się w miarę prostą jazdą do przodu, wyprowadzenia na 2 serwa które pozwalają poruszać kamerą aby uzyskać większy kąt widzenia, tranzystor do którego podpięte są 2 ledy 1w każda. Drugim dodatkiem jest Raspberry Pi 4B razem z kamerką . Tak zdaję sobie sprawę że rpi 4 to overkill do takiego projektu, zajmuje połowę platformy i potrafi zużywać więcej prądu niż cała reszta robota. Ale po prostu nie mam nic innego pod ręką. Teraz powiem kilka słów o poszczególnych funkcjach i podzespołach (jeśli można tak to nazwać). Zasilanie - nie będę się już powtarzał ale dla tych co jeszcze się nie domyślili dopowiem tylko że malinka jest zasilana przez przetwornicę step-down ( 8.4V przetwarza na 5V) Sterowanie - jak już mówiłem za sterowanie odpowiada nRF24l01, jako transmitera używam własnoręcznie zlutowanego pilota którego używałem już w moich poprzednich projektach i o dziwo cały czas jest sprawny. Na razie pilot ma 10 kanałów z czego 8 używam w tym robocie. Krótka rozpiska co obsługuje poszczególny kanał: lewy joystick góra dół - jazda przód tył, lewy joystick lewo prawo - obrót lewo prawo, prawy joystick góra dół - ruch kamerą góra dół, prawy joystick lewo prawo - ruch kamerą lewo prawo, potencjometr - trymer (albo jakoś tak, nie wiem jak to się pisze) do kamery lewo prawo, chodzi o taki minimalny ruch serwem który pomaga ustawić kamerę w miarę prosto, środkowy(pod nrf'em) toggle switch - włącza funkcję w miarę prostej jazdy do przodu, prawy toggle switch - światła on/off, przycisk w lewym joysticku - włącza buzzer (ten wbudowany w forbotowego shielda). Akcelerometr - sprawił mi najwięcej problemów, a właściwie to program którego nie umiałem poprawnie napisać. Okej przyznam się że funkcji przetwarzającej dane z akcelerometru nie napisałem w pełni samodzielnie. Działa to tak że program odczytuje przyspieszenie w osi Z i w zależności od tej danej steruje prędkością poszczególnych silników. Kamera - podłączona do RPI 4B przekazuje obraz przez Wi-Fi w sieci WLAN . Ja siedząc przy komputerze widzę ten obraz z programu VNC. Oczywiście najpierw muszę wywołać jakąś komendę którą się z wami podzielę, może ktoś skorzysta: raspivid -o -t 0 -w 800 -h 400 -rot 180 -fps 30 Ta część też zajęła mi trochę czasu. Chciałem żeby opóźnienie obrazu nie przekraczało 200ms więc przekopywałem internet żeby znaleźć jak streamować obraz za pomocą netcata. Niestety nie udało się znaleźć żadnego właściwego poradnika. Po dłuższym czasie poszukiwań znalazłem tą komendę która pozwoliła mi streamować obraz z kamery na naprawdę sensownym opóźnieniu (postaram się później podesłać filmik w komentarzu). Wady - oczywiście, tak jak wszystko na tym świecie robot ma wady i to nie jedną, a kilka: środek ciężkości - można głównym ciężarem są akumulatorki które są położone z tyłu co znacznie przesuwa środek ciężkości (czerwony - gdzie jest, zielony - gdzie powinien być), Raspberry Pi 4B - zajmuje za dużo miejsca i pobiera za dużo prądu, Zasilanie - pomimo że je zmieniłem to nadal 18650 nie są najlepszym rozwiązaniem. Na końcu zamieszczę jeszcze filmik pokazujący moc akcelerometru, ruch kamery i jazdę po dywanie.
- 7 odpowiedzi
-
- 16
-
-
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.
-
Zdalnie sterowany samochodzik pilotem IR
BeeKeyPro opublikował temat w Projekty - DIY (początkujący)
Witam. Budowę auta na pilota planowałem już od dawna, ale nie wiedziałem jak się do tego zabrać. Dopiero niedawno gdy miałem okazje wykonać projekt z tego filmu, dowiedziałem się jak to zrobić. I tak oto powstało takie auto na pilota: Auto na pilota Użyte komponenty i moduły: Arduino UNO Moduł sterownika silników DC L293D Akumulatorki Li-Ion 18650 + koszyk Silnik DC z przekładnią + koła (x4) Odbiornik IR Rezystor 68Ω Kondensator 100µF Przełącznik I/O Pilot IR (ja użyłem ten pilot) Na samym początku musiałem zbadać kody które odbiornik odbiera po wciśnięciu danego przycisku na pilocie, więc napisałem i wgrałem taki kod: #include <IRremote.h> const int RECV_PIN = A0; void setup() { Serial.begin(9600); IrReceiver.begin(RECV_PIN, ENABLE_LED_FEEDBACK); Serial.println("Odbiornik IR gotowy."); } void loop() { if (IrReceiver.decode()) { Serial.print("Kod: "); Serial.println(IrReceiver.decodedIRData.decodedRawData, HEX); IrReceiver.resume(); } Okazało się, że odbiornik odbiera losowe sygnały gdy klikam ten sam przycisk. Problem rozwiązał ,,filtr na zasilaniu odbiornika". (problem szczegółowo opisywany i rozwiązywany jest w tym wątku): Ustaliłem sposób w jaki będą działać przyciski i zająłem się budową auta: Instrukcja obsługi pilota Auto budowałem w podobny sposób jak na filmie do którego linkowałem na początku tego opisu. Dlatego nie będę też opisywał tutaj tego jakie kroki wykonałem, żeby je zbudować. Lepiej zajrzyjmy do kodu. #include <IRremote.hpp> #include <AFMotor.h> AF_DCMotor motorM1(4); AF_DCMotor motorM2(3); AF_DCMotor motorM3(2); AF_DCMotor motorM4(1); const int RECV_PIN = A0; int speed=200; void setup() { IrReceiver.begin(RECV_PIN); motorM1.setSpeed(speed); motorM2.setSpeed(speed); motorM3.setSpeed(speed); motorM4.setSpeed(speed); } void loop() { if (IrReceiver.decode()) { if(IrReceiver.decodedIRData.decodedRawData == 0xBF40FF00){ // JAZDA W PRZÓD motorM1.run(FORWARD); motorM2.run(FORWARD); motorM3.run(FORWARD); motorM4.run(FORWARD); } if(IrReceiver.decodedIRData.decodedRawData == 0xE619FF00){ // JAZDA W TYŁ motorM1.run(BACKWARD); motorM2.run(BACKWARD); motorM3.run(BACKWARD); motorM4.run(BACKWARD); } if(IrReceiver.decodedIRData.decodedRawData == 0xF807FF00){ // SKRĘT W LEWO motorM1.run(FORWARD); motorM2.run(FORWARD); motorM3.run(BACKWARD); motorM4.run(BACKWARD); } if(IrReceiver.decodedIRData.decodedRawData == 0xF609FF00){ // SKRĘT W PRAWO motorM3.run(FORWARD); motorM4.run(FORWARD); motorM2.run(BACKWARD); motorM1.run(BACKWARD); } if(IrReceiver.decodedIRData.decodedRawData == 0xEA15FF00){ // STOP motorM3.run(RELEASE); motorM4.run(RELEASE); motorM2.run(RELEASE); motorM1.run(RELEASE); } IrReceiver.resume(); } } I tak jak widać po kodzie, sterowanie tym autem jest uciążliwe. W klasycznych autach na pilota, jak klika się przycisk (jazda w przód) to auto jedzie póki nie puścisz przycisku. Tutaj jak klikam przycisk, to auto jedzie do momentu w którym nie nacisnę innego przycisku, lub przycisku stop. Jak wie ktoś jak wykonać taki kod, który pozwoli sterować autem tak jak w klasycznych autach, to był bym wdzięczny jakby podpowiedział pod tym opisem. Podczas pisania kodu, napotkałem jeszcze jeden problem. Biblioteka AFMotor gryzła się z IRremote, przez co nie mogłem sterować silnikami M1 i M2, więc w kodzie mogłem używać tylko jednej z tych bibliotek. Rozwiązanie problemu też znajduje się w tym wątku: Na koniec najlepsza część, czyli filmik: Choć kod i schemat połączenia wszystkiego były wykonane przeze mnie to dużo inspirowałem się tym filmem. Dziękuje za przeczytanie całego DIY i z góry przepraszam, jeśli się je dziwnie czytało, ale zupełnie nie wiedziałem jak się do niego zabrać. Czekam z niecierpliwością na wasze uwagi. Pozdrawiam. -
Dzień dobry mam pytanie czy za pomocą bibliotki arduino ble i arduino uno r4 wifi można emulować urządzenie HID (Human Interface Device, jeśli czegoś nie pomyliłem) jeżeli tak, to czy ktoś chciał by mi z tym pomóc?
-
Miałem pewne obiekcje z umieszczeniem wpisu we właściwym dziale. Bo z jednej strony konkurencja w DIY jest spora i niektóre projekty są bardzo zaawansowane - z drugiej jednak nie jest to znowu takie "mini". Tak więc umieszczam tu, najwyżej Administracja zdecyduje inaczej Dzisiaj więc coś prostszego - czyli projekt który zajął mi niewiele ponad weekend, co jednak nie znaczy że nie trzeba było trochę pokombinować... ale zacznę od początku. Otóż grupa uczestników zajęć teatralnych w WTZ stwierdziła, że muszą koniecznie i bezwzględnie zagrać "A niech to gęś kopnie" Guśniowskiej. Tekst faktycznie ciekawy, został dostosowany (nie przeze mnie na szczęście) dla potrzeb uczestników o dość specyficznym sposobie pojmowania świata, przyjęte zostały założenia scenografii... no i teraz się zaczęło "ethanak pomóż" W jednej z ostatnich scen bardzo ważną rolę odgrywa kuchenka, na której zwierzątka przygotowują sobie obiadek. No i tak: jako że scenografia to tekturowe pudełka, cała obudowa musi wyglądać jak takie pudełko; musi być widoczny płomień, najchętniej regulowany; na kuchence można postawić naczynie (nie wiem, pewnie też tekturowe, ale to na szczęście nie moja działka); ponieważ w ferworze gotowania zwierzątka urywają gałkę, trzeba to przewidzieć w konstrukcji; trzeba róœnież przewidzieć możliwość łatwego "zdmuchnięcia" płomienia przez osobę o ograniczonych możliwościach ruchowych; i najważniejsze - urządzenie musi być tanie a prohekt umożliwiać szybką realizację. Na początek poszły poszukiwania pudełka. Na szczęście okazało się, że mogę dostać pudełko - koszyczek po owocach. Jest na tyle wytrzymałe że powinno przeżyć obchodzenie się z nim na scenie, a postawione dnem do góry ma coś w rodzaju wzmocnionych nóżek (to powinno wyjaśnić dlaczego obudowa urządzenia jest tekturowa). Teraz przyszła kolej na elektronikę. Jak zwykle trzeba było sięgnąć do szuflady i zdecydować, czego można użyć. A więc: Arduino Pro Mini (klon) - używany poprzednio w jakimś projekcie, sprawny całkowicie; LEDy WS2812 luzem (o tym później); Enkoder obrotowy z przyciskiem; Duży przycisk z podświetleniem (niepodłączonym - podświetlenie mogłoby przeszkadzać); różnej maści diody, wyłączniki, śrubki, przewody, jednostronna płytka uniwersalna i (najważniejsze) - moje ulubione złącza; przetwornica step-up MT 3608; buzzer bez generatora a przede wszystkim różnokolorowe filamenty. Postanowiłem, że urządzenie będzie zasilane z dwóch baterii AA. Również ne bardzo chciało mi się lutować pojedyncze ledy, tak więc doszedł zakup dwóch elementów: Pierścień LED RGB koszyk na 2 baterie AA Mając to wszystko mogłem zabrać się do pracy. Ponieważ podłączenie pierścienia do Arduino było oczywiste, w czasie gdy drukowały się elementy mocujące mogłem zająć się programem. Zrobiłem następujące założenia: po włączeniu (lub na żądanie, po wciśnięciu przycisku enkodera w trybie oczekiwania) urządzenie mierzy napięcie baterii i w zależności od niego zapala na trzy sekundy trzy diody - kolory diod oznaczają "nowe", "sprawne", "do wymiany"; po przekręceniu gałki w prawo (co najmniej o dwa skoki enkodera) uruchomiona zostaje symulacja zapalarki (błyska na biało jedna z diod, a buzzer imituje iskrę) a po chwili zapalają się na niebiesko diody pierścienia na średnim poziomie jasności; przekręcanie gałki w prawo to zwiększenie jasności niebieskich diod oraz wprowadzenie "iskierek" (im wyższy poziom tym więcej), w lewo to zmniejszenie jasności do zera i (jeśli przez dwie sekundy nikt nie kręci gałką) "fuknięcie" wygaszanego płomienia i przejście do stanu oczekiwania na włączenie; wyrwanie gałki powoduje zapalenie diod symulujące niekontrolowany płomień; wciśnięcie przycisku "stop" (ten duży) przy działającym płomieniu to również "fuknięcie" i wygaszenie. Kod w załączniku: Geskuchenka.zip Trochę bardziej skomplikowane było opracowanie dającej się wyrwać gałki. W końcu stanęło na tym, że na oś enkodera został wciśnięty wydrukowany z polipropylenu sześciokąt ze stożkowatym zakończeniem ułatwiającym trafienie przy wkładaniu, a płytka z zamocowaną gałką (mocowana na wcisk) ma występ przyciskający ramię krańcówki z rolką. Na zdjęciu co prawda słabo to widać, ale można się zorientować. I jeszcze ciekawostka: sześciokąt tak się dopasował do ośki enkodera, że wciśnięcie go (a przede wszystkim wyciągnięcie) było dość trudne ze względu na ciśnienie powietrza... pomogło przewiercenie go według osi Po zmontowaniu całości okazało się (zgodnie z przewidywaniami) że patrząc z boku nie widać płomienia. Stąd ta dziwna konstrukcja nad ledami (powinna być wydrukowana z naturalnego TPU, ale akurat mi się skończył, użyłem PLA). Tak wygląda wnętrze kuchenki (przed wklejeniem szufladki na luźne kabelki od gałki i zamocowaniem taśm): A oto kuchenka w całej okazałości: I jeszcze którki filmik z działania: No i... trzeba będzie zaprosić do teatru
-
Firma Arduino głównie jest znana ze swoich płytek deweloperskich o tej samej nazwie. W sprzedaży jest wiele rodzajów Arduino, ale czy wiedziałeś, że Arduino ma w swojej ofercie sterowniki PLC? Arduino Opta jest to sterownik micro PLC wyprodukowany we współpracy z firmą Finder, która specjalizuje się w projektowaniu i produkcji komponentów elektromechanicznych, takich jak przekaźniki. Chociaż projekt miał swoją premierę już dość dawno, to do tej pory nie był pokazywany szerzej na Forbocie. Mam nadzieję, że ten artykuł opisujący moje pierwsze uruchomienie tego sterownika będzie pomocny dla osób, które chcą rozpocząć przygodę z Arduino Opta. W tym artykule przedstawię Ci: Czym właściwie jest Arduino Opta? Twoje pierwsze kroki z tym sterownikiem. Jakie funkcje i możliwości oferuje? Projekt: implementacja sterowania temperaturą w terrarium. Arduino Opta PLC. Źródło zdjęcia. Czym właściwie jest Arduino Opta? Sterownik charakteryzuje się swoją małą skalą. Mikro PLC oznacza, że jest to urządzenie wyprodukowane w standardzie zwykłego sterownika, ale w mniejszej skali - np. mniej wejść i/lub wyjść, mniej funkcjonalności. Takie rozwiązania sprawdzą się w mniej skomplikowanych aplikacji. A jakie parametry cechują sterownik? Generalna specyfikacja PLC. Źródło zdjęcia. Sterownik jest certyfikowany, co oznacza, że można go stosować w przemyśle - ograniczeniem są parametry techniczne, takie jak pamięć, klasa ochrony IP, itp. Sterownik na pewno nie ma problemu z szybkością przez swój dwurdzeniowy procesor STM32. Parametry wejść analogowych. Źródło zdjęcia. Parametry wejść cyfrowych i wyjść przekaźnikowych. Źródło zdjęcia. Maksymalny prąd wyjściowy na pojedyncze wyjście sterownika wynosi 10 A. W sterowniku zastosowano wyjścia przekaźnikowe, które są o wiele wolniejsze od tranzystorowych. Przez to czas przełączania stanu wyjść jest rzędu milisekund, ale w zamian możliwe jest przełączanie stosunkowo wysokich prądów, przy jednoczesnej izolacji sygnałów. Po więcej informacji nt. danych technicznych odsyłam do dokumentacji sterownika. Pierwsze kroki z Arduino Opta: micro PLC Producent oferuje bezpłatny kurs, który wprowadzi Cię w podstawy sterowników logicznych, a także pokaże jak możesz stworzyć swoje pierwsze programy. Są to świetne materiały dla początkujących. Dowiecie się czym są sterowniki PLC, jakie są języki programowania lub jak odczytywać wartości analogowe z czujnika temperatury. Dodatkowo kurs wprowadzi Cię w techniki łączenia ze sobą kilku sterowników za pomocą standardu Modbus TCP/IP. Dla zainteresowanych polecam mój bliźniaczy artykuł, w którym omówiłem dokładniej czym są sterowniki PLC. Pomoże Ci przy pierwszych uruchomieniach programów na Arduino Opta. A teraz dość gadania i przejdźmy do praktyki! W poniższym artykule testuję Arduino PLC Starter Kit, który możecie zakupić na Botlandzie. Na zestaw składają się: sterownik Arduino Opta WiFi. moduł DIN Simul8 (przełączniki). moduł DIN Celcius (płytka grzewcza wraz z czujnikiem temperatury). Co oferuje Arduino Opta? Sterownik z dedykowanym środowiskiem programistycznym Arduino PLC IDE to naprawdę potężne narzędzie. Nie sposób w jednym artykule zmieścić wszystkich dostępnych możliwości, dlatego poniżej znajdziesz skrót tych najważniejszych, a potem przejdziemy do realnego wykorzystania Arduino Opta. Do testów oscyloskopu i trybów debugowania wykorzystałem środowisko Arduino PLC IDE. WiFi i Bluetooth zostały przetestowane w Arduino IDE. Wszystkie adresy ukryłem - lepiej dmuchać na zimne . a) Proste programowanie i diody Sterownik zaprogramujemy za pomocą USB-C w bardzo szybki i przyjemny sposób. Do dyspozycji na sterowniku mamy kilka diod, których zachowanie możemy zaprogramować w taki sposób, jaki chcemy. b) Cyfrowy oscyloskop W Arduino PLC IDE możemy śledzić konkretne wartości włączając oscyloskop i dodając do niego interesującą nas zmienną. Wygenerowany wykres możemy analizować i pobrać. Dodanie do oscyloskopu zmiennej sens_temp_cels, która symuluje temperaturę. Widok oscyloskopu. c) WiFi Wykorzystuję Arduino Opta WiFi, więc głupio byłoby nie przetestować jego tytułowej funkcjonalności. Na początku połączyłem się z moim domowym WiFi za pomocą przykładu Scan Network Advanced. Wykrycie dostępnych sieci WiFi i wypisanie ich na Serial Monitor. Udane połączenie z moją siecią WiFi. Do połączenia wykorzystałem ten przykład. Czas na rozmowę! Wykorzystuję przykład WiFiAdvancedChatServer, dzięki któremu mogę stworzyć chat server i wysyłać wiadomości do sterownika. Za pomocą laptopa połączę się bezprzewodowo z serwerem i wyślę wiadomość. Sterownik będzie połączony z moim komputerem stacjonarnym za pomocą przewodu, na którym wyświetlę odebraną wiadomość. Przykład musiałem lekko zmodyfikować, aby wyświetlał znaki, a nie pełne wiadomości. Wtedy lepiej działało . Połączenie do WiFi i utworzenie chat server. Strona Arduino Opta. Połączenie do serwera za pomocą komendy telnet <IP> i wpisanie wiadomości. Strona laptopa. Odebrana wiadomość i rozłączenie klienta. Strona Arduino OPTA. d) Ethernet i Modbus Niestety nie byłem w stanie przetestować tych funkcjonalności, dlatego odsyłam do materiałów od Finder . e) Bluetooth Low Energy Oprócz WiFi jest także możliwość połączenia się ze sterownikiem za pomocą Bluetooth! Skan pobliskich urządzeń z włączonym Bluetooth. Opta wykrył mój telefon. Wykorzystałem przykład Arduino BLE Scan. f) Live debug mode Środowisko Arduino PLC IDE oferuje podglądanie stanu zmiennych na żywo. Było to bardzo przydatne, gdy tworzyłem algorytm do sterowania terrarium, który przedstawię w dalszej części tego artykułu. Podgląd zmiennych na żywo. Aktywowane zmienne są podświetlone. Wyświetlany jest także odliczony czas timera obok jego wyjścia ET. g) Debugowanie Wykonywany kod można debugować, zaznaczając odpowiednie punkty stopu. Wynik debugowania. W prawym dolnym rogu możemy zauważyć status sterownika HALTED, co oznacza, że przez debugowanie został zatrzymany. Twój pierwszy projekt wykorzystując Opta! W imię zasady Learning-by-doing i w celu przedstawienia możliwości sterownika wymyślmy problem, na podstawie którego krok po kroku przeprowadzę Cię przez podstawy programowania tej jednostki. Zaprojektujmy program sterujący ogrzewaniem terrarium. Nie jestem ekspertem zoologii. Użycie algorytmu na żywych jednostkach na własną odpowiedzialność! System ma działać zgodnie z następującymi zasadami: Standardowe ogrzewanie: Terrarium jest ogrzewane co minutę, aby utrzymać temperaturę 27°C, gdy drzwi są zamknięte. Zamknięcie zrealizujemy za pomocą symulacji krańcówki - wykorzystamy przełącznik. Zakładamy, że jeżeli drzwi są otwarte, to zmienna drzwi = 1. Jeżeli są zamknięte, to drzwi = 0. Reakcja na otwarcie drzwiczek: W stanie otworzonego terrarium niemożliwe jest załączenie grzałki. Jeśli drzwiczki zostaną otwarte i zamknięte, system natychmiast podnosi temperaturę do 30°C. Tryb manualny: Użytkownik może włączyć ogrzewanie ręcznie za pomocą przełącznika. Grzałka działa w tym trybie, dopóki przełącznik nie zostanie zwolniony. Informacja dla programisty: Po podłączeniu sterownika do komputera i włączeniu terminala powinny co 5 sekund pojawiać się informacje o: Temperaturze. Stanie drzwi (otwarte/zamknięte). Informacje mogą pojawić się także po wciśnięciu przycisku na PLC (USER). Zaprojektowany algorytm powinien uwzględniać bezpieczeństwo i zapewniać płynne przełączanie między trybami pracy. Włączenie grzałki powinno być zasygnalizowane włączeniem LED 1 na PLC. Połączenie układu Zanim zabierzemy się do programowania musimy wykonać odpowiednie połączenia między sterownikiem a modułami. Starter Kit oferuje przewody, za pomocą których możemy wszystko połączyć. Poniżej znajduje się schemat, w jaki sposób należy połączyć układ: Schemat połączeniowy dla naszego ćwiczenia. Powyższy układ połączono następująco: +24 V połączono razem między modułami. Osobne połączenia wykonano dla GND. Wyjście przełączników w DIN SIMUL8: nr 1 → I1. nr 8 → I2. Do wyjścia przekaźnikowego nr 1 podłączono na wejście +24 V. Wyjście z niego podłączono do INPUT HEAT 1. Gdy wyjście będzie aktywne, to rozpocznie się grzanie płytki. OUTPUT VOLTAGE w Din Celcius połączono do I7. Tym wejściem będziemy odczytywali temperaturę. Kolory przewodów: Czerwony - +24 V. Czarny - GND. Niebieski - sygnały logiczne/informacyjne. Znaczenie wejść i wyjść, i zmienne odpowiadające za nie: I1 - grzanie manualne (grzanie_manual). I2 - otworzenie drzwiczek (drzwi). I7 - temperatura terrarium (temperatura_terrarium). Przycisk USER na PLC - wysłanie informacji o stanie układu (info). Wyjście nr 1 - załączenie grzania terrarium (grzalka). LED STATUS 1 - informacja o załączeniu grzania (led_grzanie). Jeżeli uruchamiasz sterownik po raz pierwszy, to skorzystaj z lekcji Getting Started na stronie Arduino. Lekcja nauczy Cię jak skonfigurować sterownik i upewnić się, że wszystko działa. Oprogramowanie 1. Deklaracja zmiennych Zacznijmy od zadeklarowania wejść i wyjść. Robi się to w tabeli, co gwarantuje przejrzystość i czytelność. Konfiguracja wejść programowalnych. I7 ustawiamy jako wartość analogową o rozdzielczości 12 bitów. Konfiguracja wyjść programowalnych. Konfiguracja LED. Konfiguracja przycisku USER umieszczonego na PLC. Arduino OPTA umożliwia dodanie kilku programów, które będą równolegle wykonywane. Przyda nam się to, bo nasz projekt będzie się składał z: Pętli głównej odpowiedzialnej za: Pracę manualną. Pracę automatyczną. Przełączanie między pracą automatyczną a manualną. Wykrycie otworzenia terrarium. Załączanie grzałki. Skryptu, który będzie przeliczał wartość wyjściową czujnika temperatury na temperaturę wyrażoną w stopniach Celsjusza. Skryptu wyświetlającego informację o stanie układu. 2. Tryb manualny Sprawa jest prosta - załączając I1 włączamy grzałkę. Dodajmy nowy program: Dodawanie nowego programu. Po wybraniu New program wyskoczy okno: Wybieramy język LD, czyli najpopularniejszy język programowania PLC. Program nazwiemy main. Istnieją 4 rodzaje programów: Opisy typów programów. Źródło zdjęcia. Wybierzemy Fast, bo będzie to nasz główny program, w którym będą odbywały się najważniejsze rzeczy. Wyskoczy nam okno programu: Poświęćmy chwilę na wyjaśnieniu jak działa język LD. Język drabinkowy korzysta ze styków i cewek. Styki symbolizują wejścia, a cewki wyjścia. Rodzaje styków i cewek: -| |- - styk normalnie otwarty. Aktywuje się, gdy przypisana zmienna wynosi 1. -| / |- - styk normalnie zamknięty. Aktywuje się, gdy przypisana zmienna wynosi 0. -| S |- - styk set. Po aktywacji zmienna pozostaje aktywna do momentu resetu. -| R |- - resetuje zasetowany styk. -| P |- - styk wykrywający zbocze narastające zmiennej. -| N |- - styk wykrywający zbocze opadające zmiennej. -( )- - cewka normalnie otwarta. Aktywacja poprzez 1 na wejściu. -( / )- cewka normalnie zamknięta. Aktywacja poprzez 0 na wejściu. Po dwukrotnym naciśnięciu na styk pojawi się okno, w którym możemy skonfigurować go, np. przypisać do niego zmienną: Konfiguracja styku. Nie zapominajmy o diodzie, która ma nas informować o grzaniu. Cewkę dodamy poprzez wciśnięcie ikony coil: Ikona Coil. Bądź poprzez wciśnięcie prawego przycisku myszki i wybranie odpowiedniej opcji: Opcja dodania Coil. A oto nasz tryb manualny: Dobrym nawykiem jest stosowanie markerów. Są to zmienne w pamięci, w których przechowuje się wynik operacji logicznej. Wynik załączenia grzania przypiszmy do markera, a następnie za pomocą markera ustawmy odpowiednie cewki. Utwórzmy zmienną lokalną: Dodanie zmiennej lokalnej. Zmienna lokalna. I przenieśmy przypisanie wyjść do następnej linijki za pomocą markera. Dodanie kolejnej linijki kodu. Tryb manualny W taki sposób zbudowaliśmy nasz pierwszy funkcjonalny program! Aby go przetestować, należy przesłać go do sterownika. Opcja wysłania programu do sterownika. Po przesłaniu programu możemy włączyć podgląd (watch) i zobaczyć jak się zachowują nasze zmienne: Watch. Należy uniemożliwić grzanie, gdy drzwiczki są otwarte: Styk NC blokuje przepływ sygnału do cewek. Widzimy, że przy otwartych drzwiach grzałka nie działa. 3. Pobranie informacji o temperaturze Aby pobrać informację o temperaturze wewnątrz terrarium, posłużymy się poradnikiem. W skrócie: Poniżej przedstawiam program w języku ST do przeliczania wartości z czujnika na temperaturę: Dodajemy nowy program i nowe zmienne globalne. 4. Zamknięcie drzwiczek Teraz zajmijmy się grzaniem do 30 stopni, gdy drzwiczki zostaną zamknięte. Dodajmy następujące instrukcje w main: Jeżeli zamkną się drzwi (zbocze opadające na zmiennej drzwi), to ustawi się flaga drzwi_N_flaga (potrzebna do dalszej części kodu). Jeżeli osiągniemy 30 stopni, to resetujemy flagę. Dodatkowo tworzymy nowy tryb grzania (tryb_drzwi_N). Napiszmy skrypt w języku ST, który będzie obsługiwał zmienną tryb_drzwi_N: Jeżeli flaga zamknięcia drzwi jest aktywna, to włączamy tryb grzania. Jeżeli osiągnięto temperaturę 30 stopni, to włączamy odpowiednią flagę, a w main wyłączamy drzwi_N_flaga. 5. Standardowe ogrzewanie Stwórzmy tryb standardowego grzania. W tym celu stwórzmy zmienną odliczanie, dzięki której będziemy sygnalizować, czy mamy odliczać minutę do grzania. Dodatkowo utworzymy program w trybie Init, który wykona się tylko raz przy włączeniu sterownika. Wystartujemy w nim odliczanie. Zawartość programu Init. Zmienna jest typu bool. W zastosowaniach przemysłowych praca maszyny musi się odbyć po jawnym sygnale operatora, więc nasze rozwiązanie nie jest zgodne ze sztuką. Jednak na cele edukacyjne i hobbystyczne to nam ułatwia sprawę. Do mierzenia czasu służą timery. Aby go dodać klikamy opcję New Block: Dodanie bloku. W Object browser wyszukujemy timer TON. TON działa w następujący sposób: Opis timera TON. Źródło zdjęcia: Arduino PLC IDE. Obsługa trybu standardowego. Powyższy kod działaja w następujący sposób: Jeżeli odliczanie jest załączone i nie osiągnięto temperatury 27 stopni, to odliczamy 60 sekund. Po odliczeniu czasu wyłączamy odliczanie (reset) i załączamy tryb standardowy. Jeżeli osiągniemy 27 stopni, to załączamy znowu odliczanie i ściągamy flagę trybu standardowego. Dodajemy program, który obsłuży flagi i wykryje przekroczenie 27 stopni. Zauważ, że jest on bardzo podobny do trybu drzwi. Dodajemy włączenie grzałki pod wpływem trybu standardowego. Dodatkowo widoczne jest zabezpieczenie, w którym przy otworzonych drzwiach nie ma możliwości grzania - styk NC drzwi. Jeżeli dotrwałeś do tego momentu, to chciałbym Ci serdecznie pogratulować. Właśnie stworzyliśmy system ogrzewania do terrarium! Działanie programu. Przedstawiłem tryb manualny i reakcję na zamknięcie drzwi. Czerwona dioda na DIN CELSIUS sygnalizuje grzanie. 6. Informacja dla programisty Stwórzmy ostatnią część projektu, jakim jest wyświetlanie co pewien czas, bądź na żądanie, pewnych parametrów związanych z algorytmem. Wykorzystamy bardzo ciekawą funkcjonalność narzędzia Arduino PLC IDE - połączenie Sketcha z Arduino IDE i algorytmu sterowania z Arduino PLC IDE. Shared variables to zmienne, które będą wymieniane między algorytmem sterowania, a Sketchem. W Shared Variables w Outputs wpisujemy zmienne, które chcielibyśmy śledzić: Tworzymy nowy skrypt o nazwie do_zmiennych_OUT, w którym będziemy przypisywali wartości do zmiennych w Sketchu: Następnie tworzymy skrypt, który co wciśnięcie przycisku lub co 5 sekund wyświetli informacje o statusie algorytmu: Program do przesyłu informacji. Funkcja, która umożliwia wysłanie informacji do odbiornika. Przetestujmy wyświetlanie informacji w Serial monitor w Arduino IDE: Nasz program jest skończony! Podsumowanie To była bardzo długa i intensywna bitwa… Ale udało się! Poznaliśmy razem dużo możliwości sterownika. WiFi, Bluetooth, równoległość wykonywania operacji, sterowanie temperaturą, wyświetlanie informacji to tylko niektóre z możliwości tego PLC. Trzeba przyznać, że to potężna jednostka, która ma ogrom potencjalnych zastosowań. To naprawdę wszechstronne urządzenie, które może ułatwić wiele zadań i sprawdzić się w różnych projektach. Jest łatwe w obsłudze i daje dużo możliwości, co czyni je świetnym wyborem do nowoczesnych rozwiązań. ________ Informacja: zestaw z Arduino Opta na potrzeby niniejszego artykułu dostarczyła firma Botland - oficjalny dystrybutor Arduino.
- 3 odpowiedzi
-
- 6
-
-
- Elektronika
- Programownie
-
(i 1 więcej)
Tagi:
