Ta strona używa ciasteczek (plików cookies), dzięki którym może działać lepiej. Dowiedz się więcejRozumiem i akceptuję

Kurs Arduino II – #9 – wielozadaniowość, opóźnienia z millis()

Programowanie 31.12.2017 Damian (Treker)

Funkcja delay (do wprowadzania opóźnień) to jedna z pierwszych rzeczy, której uczymy się podczas poznawania Arduino. Jej działanie może jednak generować wiele kłopotów.

Na szczęście z pomocą przychodzi nam bardziej rozbudowane rozwiązanie bazujące na funkcji millis. Dzięki niej Arduino może wykonywać kilka zadań „jednocześnie”.

Nawigacja serii artykułów:
« poprzednia częśćnastępna część »

Kup zestaw elementów i zacznij naukę w praktyce! Przejdź do strony dystrybutora »

Problem wielozadaniowości na Arduino

Miganie diodą, to najpopularniejszy przykład, od którego zaczyna się naukę Arduino. Najczęściej wygląda on następująco:

Krótka zawartość pętli loop() wystarcza do tego, aby migać diodą. Uwaga: Przejście od ustawienia odpowiedniego stanu na wyjściu do delay() jest błyskawiczne, na poniższej animacji zajmuje ono „drobną chwilę”, aby było widać, co się dzieje:

Zawartość pętli loop() – miganie jedną diodą.

Wiele osób od razu wpada na pomysł migania dwiema diodami (i to z różną częstotliwością). Tym samym dochodzą do następującego programu:

Działanie tego kodu odbiega od tego, czego spodziewa się zdecydowana większość osób. Trzeba pamiętać, że Arduino wykonuje wszystkie instrukcje z pętli loop() linijka, po linijce. W danym momencie mikroprocesor może zająć się tylko jedną operacją.

Dodatkowo trzeba pamiętać, że funkcja opóźniająca delay() zupełnie zatrzymuje działanie programu!

Zamiast oczekiwanego, równoległego migania diod otrzymujemy coś dziwnego:

Działanie programu, który pozornie miał migać dwiema diodami (równolegle).

Początkowo może to budzić zdziwienie. W końcu komputery, którymi się posługujemy wykonują masę różnych operacji jednocześnie (nawet te, które mają procesor z jednym rdzeniem). Tak naprawdę procesor z PC nie jest w tym przypadku lepszy od mikrokontrolera z Arduino. On też wykonuje w danej chwili tylko jedną operację. Wielozadaniowość komputerów polega na tym, że przełączają się bardzo szybko między różnymi zadaniami (tysiące razy na sekundę). Z punktu widzenia człowieka jest to niezauważalne.

Efekt jest analogiczny do multipleksowania wyświetlaczy 7-seg. Tam też świeci się jednocześnie tylko 1 cyfra, całą resztę zawdzięczamy bezwładności naszych oczu.

Kiedy można korzystać z delay()?

Funkcja opóźniająca delay() jest niezwykle prosta i wygodna. Niestety każde jej wystąpienie blokuje cały program. W przypadku prostych przykładów nie stanowi to dużego problemu. Przy dużych projektach jest to problematyczne. Korzystanie z tej funkcji może nieść za sobą trudne do zidentyfikowania błędy w działaniu programów (np. chwilowe problemy z pewnymi bibliotekami).

Z funkcji delay() można korzystać, tylko jeśli cały czas mamy świadomość, że jej działanie polega na „zamrożeniu” wykonywania całego programu!

Co zamiast delay()?

Istnieje wiele bibliotek, które pozwalają na wprowadzanie „magicznych”, nieblokujących opóźnień. Oczywiście życie należy sobie ułatwiać, więc można sięgać po takie gotowce. Jednak wiele osób zupełnie nie wie jak działają te opóźnienia, co w efekcie prowadzi do kolejnych, jeszcze dziwniejszych błędów.

Dlatego na początek warto zacząć od przestawienia sposobu myślenia i rozpoczęcia pisania programów w innych sposób. Stąd najlepiej zacząć od zera, bez gotowych bibliotek…

Stoper wbudowany w Arduino – millis()!

W Arduino znajdziemy funkcję millis(). Jej działanie najlepiej przyrównać do stopera, który rusza w momencie uruchomienia Arduino. Funkcja ta zwraca liczbę milisekund, która upłynęła od podłączenia płytki do prądu. Nie musimy uruchamiać tego „stopera”, Arduino zrobi to za nas. Działa on zawsze i w każdym programie. Liczenie zrealizowane jest sprzętowo, czyli z użyciem liczników (timerów). Wskazań millis() nie da się przypadkiem zafałszować.

Arduino posiada wbudowany stoper!

Nawet funkcja delay() nie da rady zatrzymać wewnętrznego stopera, którego wynik możemy odczytać z użyciem millis()!

Łączenie obu wspomnianych funkcji (delay i millis) mija się z celem i może generować pewne problemy, jednak warto wiedzieć, że jest to możliwe – jeszcze do tego wrócimy.

Jak wykorzystać millis()?

Funkcja ta nie przyjmuje żadnego argumentu. Po prostu zwraca liczbę milisekund, która upłynęła od momentu uruchomienia Arduino. Każda sekunda, to 1000 ms, więc zwracana wartość będzie rosła bardzo szybko. Trzeba zadbać o to, aby zmienna, do której zapisujemy informację była odpowiednio pojemna. W tym celu korzystamy z typu danych unsigned long, np.:

Sprawdźmy teraz, jak ten stoper działa w praktyce. Możemy zacząć od wysyłania aktualnego czasu do komputera przez UART. Oczywiście nie możemy wysyłać go bez żadnych przerw, bo zapchamy bufor danych i wszystko się zawiesi.

Nie umiemy jeszcze zatrzymywać programu w „sprytny sposób”, więc skorzystajmy ostatni raz z „nieszczęsnego” delay():

Po uruchomieniu programu w monitorze portu szeregowego, co około 1 sekundę, pojawi się liczba milisekund, która upłynęła od startu Arduino:

Efekt działania programu.

Nie bez powodu napisałem „co około 1 sekundę”, jak widać pojawiają się drobne nieregularności o +/- 1 milisekundę. Eksperyment ten pokazał przy okazji kolejną wadę delay() liczenie czasu tą metodą nie jest dokładne. Oczywiście tutaj 1 milisekunda wiele nie zmienia. Jednak, jeśli układ miałby działać cały czas (np. jako zegar), to z czasem rozbieżności byłyby znaczne.

Zestaw elementów do przeprowadzenia wszystkich ćwiczeń

Gwarancja pomocy na forum dla osób, które kupią poniższy zestaw!

Części pozwalające wykonać ćwiczenia z kursu Arduino (poziom 2) dostępne są w formie gotowych zestawów! W komplecie m.in. diody programowalne, termometry analogowe i cyfrowe, czujnik ruchu (PIR), wyświetlacze 7-segmentowe oraz znacznie więcej!


Kup w Botland.com.pl »

Jakie jest ograniczenie funkcji millis()?

Jak było widać na wcześniejszej animacji zwracana wartość rośnie szybko. Kiedyś licznik ten się przepełni (przekroczy swoją maksymalną wartość i wróci do zera). Na szczęście w tym przypadku przepełnienie nastąpi dopiero po 50 dniach pracy.

Oczywiście nie oznacza to, że należy resetować Arduino co 50 dni. Licznik się przepełni i zacznie pracować od początku. Jeśli program będzie napisany poprawnie, to wyzerowanie licznika nie wpłynie na jego działanie.

Dodatkowo warto pamiętać, że operacje na tak dużych liczbach (unsigned long) mogą prowadzić do pewnych błędów i przekłamań logicznych. Szczególnie, gdy spróbujemy wykonywać działania matematyczne z mniejszymi zmiennymi (np. typu int)!

Lepsza wersja odmierzania czasu

Wiemy już, że w Arduino wbudowany jest dokładny stoper. Pora wykorzystać go do generowania opóźnień. Stopera nie możemy zerować, nie będziemy też na niego w żaden sposób wpływać. Wystarczy, że będziemy „znać aktualny czas” względem startu Arduino. 

Jak powinno wyglądać odmierzenie sekundy?

  1. Sprawdzamy aktualny czas i go zapamiętujemy.
  2. W każdym obiegu pętli sprawdzamy aktualny czas:
    1. Jeśli różnica między czasem aktualny, a poprzednio zapamiętanym jest mniejsza od 1 sekundy, to znaczy, że pożądany czas jeszcze nie upłynął.
    2. Jeśli różnica między czasem zapamiętanym i aktualnym wynosi 1 sekundę, to… właśnie tyle czasu minęło!

Kluczowe w takim podejściu jest to, że program cały czas „biega w kółko” i nigdzie się nie zatrzymuje. Jego działanie polega na ciągłym sprawdzaniu ile czasu minęło, a równocześnie może robić jeszcze inne rzeczy.

Pora przelać powyższy algorytm na kod:

Zmienne przechowujące czas są typu unsigned long, więc aby uniknąć problemów z porównywaniem wartości, liczbę milisekund wpisano z dopiskiem UL (1000UL).
Dzięki temu kompilator będzie traktował 1000 jak wartość typu unsigned long.

Program sprawdza aktualny czas, liczy różnicę i jeśli jest ona większa lub równa 1000, to wiemy, że na pewno minęła sekunda. Dla bezpieczeństwa w warunku umieszczona jest nierówność, zamiast sztywnego „== 1000”. Czasami może się zdarzyć, że przy bardzo rozbudowanych programach z jakiegoś powodu nie wstrzelimy się idealnie w 1000. Taka nierówność zapewnia nas, że program się „nie zatrzaśnie” i wejdziemy do warunku przy najbliższej okazji (np. 1001ms).

Wiemy, że minęła sekunda, gdy warunek zostanie spełniony. Dlatego zaraz na jego początku zapamiętujemy aktualny czas jako poprzedni (w którym był spełniony warunek). Kolejną sekundę będziemy liczyć od nowej wartości. W tym wypadku warunek będzie spełniony, gdy licznik millis wskaże kolejne 1000, 2000, 3000, 4000 itd.

Działanie programu w praktyce widoczne jest poniżej:

Od razu widać przewagę względem funkcji delay() tutaj wypisanie czasu wywoływane jest idealnie co sekundę!

Działanie drugiej wersji programu w praktyce.

Oczywiście zmienna roznicaCzasu jest zbędna (tutaj użyta dla większej czytelności). Równie dobrze można różnicę czasu policzyć bezpośrednio w warunku:

Miganie diody bez delay()

Teraz pora wykorzystać zdobyte informacje do migania diodą. Potrafimy odmierzyć dokładnie czas, więc pozostaje zmiana stanu diody w odpowiednim momencie. Podłączamy diodę do pinu numer 3 i działamy!

W przykładach korzystam z dwóch diod, aby były lepiej widoczne na zdjęciach. Jeśli nie masz diod z zestawu do kursu Arduino (poziom I) możesz wykorzystać diodę RGB z zestawu do poziomu II i sterować niezależnie każdym kolorem. 

Aby układ działał poprawnie musimy zapamiętać aktualny stan diody (świeci/nie świeci). Wtedy w warunku, który będzie prawdziwy co sekundę będziemy mogli zmienić stan diody na przeciwny. Informację na ten temat przechowamy w zmiennej  int stanLED1 = LOW;. Negację stanu diody możemy wykonać później następującą operacją:  stanLED1 = !stanLED1;  

LOW to stała, która oznacza 0, więc możemy ją przypisać do do zmiennej typu int.

Kod realizujący to zadanie widoczny jest poniżej:

Po jego uruchomieniu dioda będzie migała zgodnie z pierwszym przykładem:

Miganie LED dzięki millis().

Negacja stanu w formie  stanLED1 = !stanLED1; jest krótka i wygodna, ale nie musi być dla wszystkich intuicyjna (działa tylko dzięki odpowiedniej deklaracji stałych LOW i HIGH). Dla większego bezpieczeństwa można ten fragment kodu przerobić na taką postać:

Wielozadaniowość na Arduino – miganie diodami

Pora rozbudować powyższy przykład, aby migać niezależnie dwiema diodami. Dopiero wtedy widoczna będzie przewaga tego rozwiązania. Tym razem chcemy, aby jedna dioda zmieniała swój stan częściej od drugiej:

Pętla loop() nadal będzie tylko jedna, a wszystkie zadania będą wykonywane linijka po linijce. Tym razem program będzie mógł niezależnie migać dwiema diodami.

Dwa „równoległe” zadania w jednej pętli loop.

Potrzebujemy dwie dodatkowe zmienne. W pierwszej przechowamy informację „kiedy” ostatni raz zmieniliśmy stan drugiej diody. Kolejna będzie trzymała informacje o jej stanie (świeci/nie świeci).

Cała reszta programu będzie analogiczna. Podłączamy drugą diodę do pinu nr 4 i ruszamy:

Od teraz diody będą migały niezależnie! Jedna zmienia swój stan co sekundę, a druga co pół:

Dla łatwiejszego zauważenia różnicy można sobie
przysłonić na chwilę jedną diodę, a później drugą.

Niezależne miganie dwóch diod.

Zmiany będą widoczne najlepiej, jeśli wprowadzimy większe różnice. Niech pierwsza dioda zmienia stan co sekundę, a druga co 200 ms. Najlepiej dodać w tym celu dwie zmienne np.: miganieLED1 oraz miganieLED2.

Nowe zmienne deklarujemy jako unsigned long, dzięki czemu unikniemy ewentualnych problemów podczas porównywania dużych wartości. Oczywiście w tym przypadku nie trzeba dodawać już do liczby dopisku UL.

Teraz efekt jest widoczny znacznie lepiej:

Większa różnica w częstotliwości migania.

Migające diody i przycisk na Arduino

Jeszcze lepszy efekt uzyskamy, jeśli dodamy do programu przycisk. Standardowo, gdybyśmy korzystali z delay() to funkcja ta blokowałaby możliwość natychmiastowego sprawdzania wejść. Należałoby trzymać przycisk do czas, aż program dotrze do linijki sprawdzającej. Działoby się to rzadko, bo delay() zamrażałby program na kilka sekund.

Tutaj nie będzie takiego problemu, możemy zwyczajnie dodać warunek, który będzie działał natychmiast. Przykładowo niech wciśnięcie przycisku (pin 2) powoduje, że dioda LED1 będzie zmieniała swój stan znacznie szybciej (co 100 ms).

No i gotowe – prosta wielozadaniowość w praktyce! Migamy niezależnie diodami i błyskawicznie reagujemy na wciśnięcie przycisku. Oczywiście to tylko przykład. Zamiast zmiany stanu LEDów może się tam pojawić coś zupełnie innego.

Niezależne miganie diod + reakcja na wejście.

Inteligentne oświetlenie

Pierwotnie artykuł ten miał dotyczyć zupełnie czegoś innego (automatyki domowej), na koniec nawiąże więc do tego tematu w formie „bardzo luźnego” przykładu.

Coraz więcej osób interesuje się tematem inteligentnych budynków. Automatyczne podnoszące się rolety, zdalne sterowanie urządzeniami i oświetleniem, zdalny podgląd temperatury. Na rynku znaleźć można wiele gotowych rozwiązań dla automatyki domowej. Niestety większość z tych systemów łączy jedna, niepożądana cecha – wysoka cena.

Tym razem zajmiemy się tematem „inteligentnego oświetlenia”, które będzie oświetlało schody. Wśród czytelników Forbota mamy również młodych adeptów elektroniki. W związku z tym nie będę poruszał tematów sterowanie normalnym oświetleniem (230V) – zajmiemy się bezpiecznym przykładem. Osoby zainteresowane implementacją bardziej rozbudowanych rozwiązań na pewno będą już samodzielnie potrafiły dostosować układ (korzystając z przekaźników).

Przykład prostej automatyzacji z Arduino

Podczas schodzenia wieczorem po schodach zawsze włączamy światło. Podobnie, gdy idziemy przez ciemny korytarz. Jest to idealne zadanie do zautomatyzowania! Tym razem załóżmy, że sytuacja wygląda następująco: tuż obok drzwi wejściowych znajdują się schody, do tego na dole i u góry mamy małe korytarze.

Załóżmy, że chcemy uruchamiać światło w momencie wykrycia ruchu. Gdy czujnik PIR zauważy kogoś w okolicy schodów uruchomi światło na 180 sekund lub do czasu otworzenia drzwi.

Przykładowe pomieszczenie, które warto zautomatyzować.

Wybór odpowiedniego oświetlenia

Tak jak wspomniałem wcześniej, zabawę z 230V zostawmy dla bardziej doświadczonych osób. Sterowanie normalnym oświetleniem najczęściej realizowane jest za pomocą przekaźników.

Druga, znacznie bezpieczniejsza opcja, to popularne ostatnio paski LEDowe. Najpopularniejsze moduły tego typu można bezpiecznie zasilać z mniejszego napięcia (np. 12V). Do Arduino można je podłączyć za pomocą przekaźnika lub tranzystora (MOSFET). Informacje na temat sterowania peryferiów przez MOSFET zostały opisane podczas #3 części kursu.

Warto pamiętać, że przekaźnik pozbawia nas możliwości sterowania PWM (regulacja jasności paska LED). Co więcej przy każdym włączeniu/wyłączeniu oświetlenie będzie słyszalny dźwięk przełączania przekaźnika.

Trzecia opcja, to wykorzystanie diod programowalnych, np. WS2812, którymi zajmowaliśmy się podczas drugiej części kursu. Rozwiązanie to będzie droższe od zwykłych diod, ale pozwoli na ciekawsze efekty.

Przykładowy moduł diod RGB.

W ramach testów (ja wybrałem taką opcję) można też podłączyć zwykłą diodę świecącą, która będzie symulowała oświetlenie. Diodę podłączyłem do pinu numer 4.

Czujniki

Pora dobrać czujniki. Oczywiście ruch wykryjemy czujnikiem typu PIR (pin numer 5). Natomiast do monitorowania drzwi najlepszy będzie kontaktron (pin numer 3). Wszystkie te czujniki były opisane w artykule o tworzeniu prostej centralki alarmowej.

Czujnik PIR montujemy w korytarzu u góry, a kontaktron na drzwiach:

W praktyce moja platforma testowa wyglądała następująco:

Symulacja opisanego przypadku.

Program wykorzystujący millis()

Przed wykonaniem ćwiczeń z tego odcinka włączenie światłą na 180 sekund robilibyśmy za pomocą delay()Po wykryciu ruchu następowałoby włączenie oświetlenia i delay(180) w tym czasie układ byłby „zamrożony”. Nie reagowałby na kontaktron ani na żadne inne czujniki…

Oczywiście w opisywanym przypadku można byłoby podłączyć kontaktron pod przerwanie. Jednak przy większej liczbie czujników nie byłoby to możliwe!

Pora więc wykorzystać poznany dziś mechanizm. Program ma działać następująco: jeśli wykryty będzie ruch, to uruchamiamy oświetlenie na 180 sekund. Światło zgaśnie po upływie tego czasu (jeśli nie ma kolejnych ruchów) lub, gdy ktoś będzie wychodził, czyli otworzy drzwi.

Jedna z wielu możliwości realizacji tego przykładu wygląda następująco:

Oczywiście same założenia tego przykładu mają pewne wady i układ należałoby rozbudować, aby był przydatny w życiu codziennym. Warto byłoby chociażby dodać fotorezystor, dzięki któremu światło byłoby włączane jedynie w ciemności. Dalsze rozbudowanie układu zostawiam dla chętnych majsterkowiczów.

Podsumowanie

Ostatni przykład był bardzo prosty, ale pokazuje praktyczne zastosowanie dla funkcji millis, która była głównym bohaterem tego odcinka. Mam nadzieję, że od teraz nikt już nie będzie miał problemów z blokowaniem całego programu przez funkcję delay.

Zachęcam do własnych testów! Warto rozbudować opisane tutaj programy. Dodać więcej LEDów, przycisków i innych sensorów. Gdy wszystko będzie już jasne będzie można spokojnie przejść do bibliotek, które „same w magiczny sposób” wykonują takie opóźnienia.

Kup zestaw elementów i zacznij naukę w praktyce! Przejdź do strony dystrybutora »

Autor kursu: Damian Szymański
Ilustracje: Piotr Adamczyk

Powiadomienia o nowych, darmowych artykułach!

Komentarze

Treker
Autor wpisu
Administrator

11:59, 31.12.2017

#1

Jeszcze raz przepraszam wszystkich, którzy musieli czekać na kolejną część tak długo. Niestety plany związane z tym artykułem miałem zupełnie inne i wszystko przeciągało się w nieskończoność. W końcu wpadłem w pułapkę, bo chciałem „za dobrze” i ciągle przekładałem ten artykuł. Ostatecznie postanowiłem zmienić tematykę na millis(). Myślę, że będzie to korzystne dla wszystkich, bo warto znać tę funkcję. W kolejnym artykule krótkie podsumowanie kursu + plany na przyszłość :)

Elvis

12:35, 31.12.2017

#2

Mam takie dwie drobne uwagi. Przełączanie zadań odbywa się raczej tysiące razy na sekundę, niż miliardy - to dość czasochłonny proces wbrew pozorom. Druga sprawa to animacja przy miganiu dwóch diodek (jedna 0,5s, druga 1s) - może warto byłoby zmienić długość filmu, bo teraz dioda migająca co 0,5s ma taki dziwny przeskok. Dość długo próbowałem zrozumieć skąd w tym programie to dodatkowe, szybki mrugnięcie :)

SOYER

13:21, 31.12.2017

#3

Teraz o millis?!?!? Trzeba było 2 miechy temu to by zaoszczędziło Elvisovi żmudnego uczenia mojej skromnej osoby :-P .

Artykuł bardzo przydatny, choć dla mnie teraz już mniej(dzięki forum Forbota :-D ).

Dziękuję za ogrom włożonej pracy.

Mała uwaga, uważam, że lepiej pisać takie artykuły z nauką podstaw i uczenia kodowania, podawanie niuansów i sztuczek, niż skupianie się na praktyce. Wydaje się, że powyższy artykuł jest idealnie wyważony, podaje to co ważne w teorii i jakiś przykład dla zrozumienia, ew. podpowiedzi co do innego wykorzystania zdobytej wiedzy niż tylko jak w przykładzie.

Do artykułu jak bym jeszcze dopisał sposób

millis()-poprzedniZapisanyMillis[zwał jak zwał]>2000
korzystamy tylko z jednej zmiennej co przynajmniej, jak dla mnie, znacznie upraszcza całą robotę :-)

Pozdrawiam

Treker
Autor wpisu
Administrator

13:46, 31.12.2017

#4

Elvis, dzięki za uwagi! Już poprawione, faktycznie animacja była źle złożona.

SOYER, nie ukrywam, że Wasza dyskusja była też trochę inspiracją do tego artykułu. Dzięki za miłe słowa ;) Jeśli chodzi o zaproponowane przez Ciebie rozwiązanie:

millis()-poprzedniZapisanyMillis > 2000

To mam mieszane uczucia. Na pewno jest to poprawne, gdy mamy jedno opóźnienie w programie i potrzebujemy sprawdzić aktualny czas tylko raz. Jeśli takich wątków będzie 50, to w Twojej metodzie wywołasz 50 razy funkcję millis(), a w mojej tylko raz. W tym przypadku nie zrobi to wielkiej różnicy, ale obawiam się, że takie podejście wykształca nawyk, aby za każdym razem wywoływać funkcję (nawet jeśli wynik będzie taki sam). Jeśli zamiast millisa mielibyśmy bardziej rozbudowaną funkcję, to lepiej byłoby wykonać ją raz, zapisać wynik do zmiennej i później się do niej odwoływać - zamiast 50x wywoływać funkcję, która może być "zasobożerna", a zawsze zwróci ten sam wynik (w danym obiegu pętli). Jednak tak jak mówię, to tylko mój nawyk i nie jest on związany bezpośrednio z millis.

Elvis

14:01, 31.12.2017

#5

Ja widzę co najmniej dwie zalety wersji opisanej w kursie. Po pierwsze, każde wywołanie millis() może zwrócić inną wartość. W opisywanym przypadku to nie problem, ale lepiej sobie wyrabiać "odruch" używania tej samej wartości.

Druga sprawa to typ ze znakiem. Kod w kursie używał typu unsigned long. Natomiast jeśli użyjemy odejmowania jak w kodzie zaproponowanym przez SOYER-a, kompilator ma pełne prawo użyć typu long. Niestety wtedy pojawią się problemy w przypadku przepełnienia typu (czyli po 50 dniach). Wesja z kursu działa za to poprawnie nawet wtedy.

nse

14:38, 31.12.2017

#6

Elvis napisał/a:

Ja widzę co najmniej dwie zalety wersji opisanej w kursie. Po pierwsze, każde wywołanie millis() może zwrócić inną wartość. W opisywanym przypadku to nie problem, ale lepiej sobie wyrabiać "odruch" używania tej samej wartości.

Druga sprawa to typ ze znakiem. Kod w kursie używał typu unsigned long. Natomiast jeśli użyjemy odejmowania jak w kodzie zaproponowanym przez SOYER-a, kompilator ma pełne prawo użyć typu long. Niestety wtedy pojawią się problemy w przypadku przepełnienia typu (czyli po 50 dniach). Wesja z kursu działa za to poprawnie nawet wtedy.

Ja kiedyś wielowątkowość na AVR rozwiązałem za pośrednictwen zadeklarowanego rejestru flagowego sterowanego w przerwaniu z timera1, z flagami 1ms,10ms,100ms,1 ... , potem w pętli głównej podpinałem funkcje które mały być wykonywane w odpowiednich odstępach czasowych.

Pozdrawiam i Zdrowego Sylwestra ;)

SOYER

14:44, 31.12.2017

#7

Ok, czyli twierdzicie, że lepiej jak wrócę do currentMillis i previousMillis, w sumie macie rację, tylko, że wtedy trzeba pamiętać o aktualizowaniu w odpowiednim momencie obu zmiennych, co wiem, że początkującemu sprawia trochę problemu w momencie kiedy tych stoperów jest więcej niż dwa, trzy jednocześnie. Wtedy łatwiej jest stosować pojedynczą zmienną i traktować ją jako start odliczania.

Ale oczywiście zgadzam się, że nadpisana currentMillis jest stała i kropka. A millis() nawet wywołana jedną linijkę kodu niżej będzie się różnić od tej linijkę wyżej... bo chyba o to wam chodziło tak?

Belferek

14:58, 31.12.2017

#8

Wielu początkującym będzie sprawiać trudność zapanowanie nad zmiennymi określonymi przez millis() i dlatego w/g mnie szkoda, że nie wspomniano w artykule o bibliotekach ułatwiających "wielozadaniowość" w Arduino jak np. Timers czy leOS. Korzystając np. z leOS wystarczy raz zaplanować przedziały czasowe wykonywanych zadań (wątków) i tyle. W sumie artykuł ciekawy i powinien być obowiązkową lekturą, każdego rozpoczynającego przygodę z Arduino.

Pozdrawiam,

Elvis

15:05, 31.12.2017

#9

Nadmiar bibliotek bywa problematyczny, co już na tym forum się przewijało: biblioteka X działa pięknie ale nic mi nie działa jak dodam bibliotekę Y...

Jeśli Timers to ta biblioteka, którą znalazłem, to w niej właściwie nie ma kodu - taki cienki wrapper w C++ do wywołania millis(). Moim zdaniem lepiej zrozumieć jak millis() działa.

Natomiast IeOS to jakiś koszmarek. Wywoływanie funkcji z poziomu przerwań powinno być karane. Lepiej już uruchmić "prawdziwy" RTOS, chociażby FreeRTOS działa na Arduino.

ethanak

15:10, 31.12.2017

#10

Tak w kwestii formalnej:

Oba rozwiązania porównania - zarówno SOYERa:

millis()-poprzedniZapisanyMillis > 2000

jak i użyte w kursie:

roznicaCzasu > 1000

są co najmniej nieprawidłowe (a przynajmniej kompilator swoje zdanie na ten temat powinien wypluć - chyba że ktoś ma wyłączone wyświetlanie ostrzeżeń).

Prawidłowo w kursie powinno być:

roznicaCzasu > 1000UL

Belferek

15:14, 31.12.2017

#11

Elvis napisał/a:

Nadmiar bibliotek bywa problematyczny, co już na tym forum się przewijało: biblioteka X działa pięknie ale nic mi nie działa jak dodam bibliotekę Y...

Święta racja, sam się o tym przekonałem i super przydatnym byłoby zestawienie timerów Arduino wykorzystywanych przez popularne biblioteki. Szukałem i ... nie znalazłem, ale szukam takiego zestawienia dalej.

Elvis napisał/a:

Natomiast IeOS to jakiś koszmarek. Wywoływanie funkcji z poziomu przerwań powinno być karane.

Z pewnością masz rację. Należy pamiętać, że procedury obsługi przerwań nie mogą być czasochłonne, ale uważam, że dla prostych zastosowań leOS nie jest zły, a może wiele uprościć. No ale to moje zdanie - początkującego.

Pozdrawiam,

Treker
Autor wpisu
Administrator

16:05, 31.12.2017

#12

Belferek, specjalnie nie zajmowałem się tutaj bibliotekami, bo obawiam się, że część osób od razu ominęłaby "ręczne eksperymenty" z millis(). Bez zrozumienia tego tematu nie ma sensu brać się za jakieś gotowce. Może w przyszłości zrobię takie zestawienie - zobaczymy ;)

ethanak, słusznie, już dopisuję! Ciężko mi teraz stwierdzić jak dokładnie radzi sobie z tym tematem Arduino, bo wygląda na to, że wszystko działa bez dopisku UL poprawnie, ale dla formalności zaraz dodam taką informację. Dzięki!

SOYER

16:45, 31.12.2017

#13

O co chodzi z tym UL-em??

Belferek

16:53, 31.12.2017

#14

W reference znajdziemy:

Cytat:

Notes and Warnings

U & L formatters:

By default, an integer constant is treated as an int with the attendant limitations in values. To specify an integer constant with another data type, follow it with:

a 'u' or 'U' to force the constant into an unsigned data format. Example: 33u

a 'l' or 'L' to force the constant into a long data format. Example: 100000L

a 'ul' or 'UL' to force the constant into an unsigned long constant. Example: 32767ul

Więc pisząc np. 1000UL odwołujemy się do wartości unsigned long, a takiego typu wartość (unsigned long) zwraca millis()

Cytat:

Returns

Number of milliseconds since the program started (unsigned long)

Pozdrawiam,

Zobacz powyższe komentarze na forum

FORBOT Damian Szymański © 2006 - 2018 Zakaz kopiowania treści oraz grafik bez zgody autora. vPRsLH.