Skocz do zawartości

Działanie millis i delay - rozważania o odmierzaniu czasu


narbej

Pomocna odpowiedź

Witam!

Jestem bardzo młodym użytkownikiem ardunio ale bardzo starym wyjadaczem w programowaniu. Dlatego pozwolę sobie na maleńką krytykę, oczywiście konstruktywną "Lepszej wersji odmierzania czasu". Ale przedtem chylę czoła i dziękuję autorowi kursu. Żeby nie przedłużać. Dopisz np na początku, do  programu:

delay(10); lub delay(random(10)); i potestuj go. Zamiast 10 możesz użyć mniejszej wartości.

Czyli np:

void loop(){

delay(random(10));

.....

Przy sterowaniu czasem wyświetlania, takie wstrzeliwanie się [niedokładne], nie ma dużego znaczenia, ale jednak jest to drobny błąd.

Oczywiście bardzo łatwo go poprawić, ale najpierw spróbuj sam.

@Madaulpe Fajnie, że twój program działa tak jak chcesz, a jak działa, to prawo inżynierii mówi nie poprawiaj. Jednak jest tam dużo do poprawienia - ale skoro się uczysz, to pewnie za jakiś czas sam do tego dojdziesz - jeżeli nie, gdy będziesz chciał, spróbuję pomóc.

Pozdrawiam.

Edycja, posty zostały wydzielone z:

Link do komentarza
Share on other sites

@narbej, witam na forum 😉 Widzę, że to Twoje pierwsze kroki na Forbocie, oto najważniejsze informacje na start:

  • Chcesz przywitać się z innymi członkami naszej społeczności? Skorzystaj z tematu powitania użytkowników.
  • Opis najciekawszych funkcji, które ułatwiają korzystanie z forum znajdziesz w temacie instrukcja korzystania z forum - co warto wiedzieć?
  • Poszczególne posty możesz oceniać (pozytywnie i negatywnie) za pomocą reakcji - ikona serca w prawym dolnym rogu każdej wiadomości.

1 godzinę temu, narbej napisał:

Jestem bardzo młodym użytkownikiem ardunio ale bardzo starym wyjadaczem w programowaniu. Dlatego pozwolę sobie na maleńką krytykę, oczywiście konstruktyną "Lepszej wersji odmierzania czasu". Ale przedtem chylę czoła i dziękuję autorowi kursu. Żeby nie przedłużać. Dopisz np na początku, do  programu:

delay(10); lub delay(random(10)); i potestuj go. Zamiast 10 możesz użyć mniejszej wartości.

Dziękuję za miłe słowa na temat kursu. Mógłbyś wyjaśnić co dokładnie masz na myśli z dodawaniem tego opóźnienia za pomocą delay? Nie za bardzo rozumiem, co chcesz tym pokazać i jaki jest cel dodawania tego sztucznego opóźnienia w pętli - szczególnie, że raczej przy korzystaniu z millis warto już sobie odpuścić korzystanie z delay (szczególnie w taki sposób).

Link do komentarza
Share on other sites

Chodziło mi tylko o pokazanie efektu błędnego naliczania czasu. Jeżeli program obsługuje tylko miganie diodą [lub kilku diodami] to nic się nie dzieje. Jeżeli jednak w tej samej pętli, coś robimy, np sprawdzamy wciśnięcie klawisza itd, to mogą pojawić się problemy. Trudno byłoby jednak testować program wciskając kilkanaście [kilkadziesiąt] razy przycis, aby zobaczyć co się stanie. Dlatego wybrałem i wstawiłem funkcję delay(random(.......

Zamiast tego może w takim razie:

unsigned long czekaj11 = millis();

while(millis() < czekaj11 + 11) ; // też [jak w delay] nic nie rob [i blokuj działanie programu] ;-)

 

  • Lubię! 1
Link do komentarza
Share on other sites

Pisząc dla początkujących powinieneś bardzo jasno formułować swoje myśli. Nie każdy złapie w lot o co chodzi patrząc na króką wstawkę kodu a już na pewno będzie problem gdy pomininesz informację po co coś robić. Skoro widzisz jakieś potencjalne problemy, to opisuj je dokładniej.

9 godzin temu, narbej napisał:

Jeżeli jednak w tej samej pętli, coś robimy, np sprawdzamy wciśnięcie klawisza itd, to mogą pojawić się problemy

Jakie problemy? Przecież człowiek ledwo ogaraniający składnię języka i klecący coś na ekranie nie wie jak uruchamiać i/lub testować swój kod. W Arduino mamy dużo bibliotek i mnóstwo śmieciowego oprogramowania robionego przez totalnych amatorów (na którym wzorują się kolejni i lawina rusza) a bardzo mało artykułów o inżynierii oprogramowania więc gdzie ludzie mają się uczyć? Zwykle nawet nie wiedzą, co trzeba przeczytać i jak sobie pomóc. A i samo środowisko nie zachęca ani do pisania rozbudowanych programów (edytor wołą o pomstę do nieba) ani do jakiegoś sensownego podejścia do uruchamiania (brak debuggera czy choćby symulatora) czy testowania jako tako działającego programu. Jeśli ktoś nie przemyśli tego wcześniej (a przecież ledwo rozumie jeden ekran kodu) i nie wstawi mechanizmów wspomagających (choćby tylko printów lub mrugania LEDami), to może tylko wgrać bin i czekać na wyniki - to powszechny standard. Jeśli są niepoprawne, rozkłada ręce i pisze post "Pomóżcie, mój program nie działa jak powinien". Mamy tu tego setki a to oznacza poważny problem. Reasumując: w obszarze Arduino obowiązuje pomagających łopatologia, czyli co po czym i dlaczego. To nie jest środowisko programistów, ale wierzę, że dzięki tak doświadczonym specjalistom jak Ty to może się zmienić 🙂 To nie jest ironia. Powodzenia.

  • Lubię! 2
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

(edytowany)
2 godziny temu, marek1707 napisał:

... dzięki tak doświadczonym specjalistom jak Ty ...

Dzięki, ale bez przesady. Jestem tylko amatorem. Rzeczywiście, masz dużo racji, ale, możliwe że błędnie założyłem, że to ja jestem tu początkujący, a piszę do lepszych fachowców od arduino. Z drugiej strony, jeżeli ktoś nie potrafi wstawić jednej linijki kodu do kodu podanego w tutorialu ....?  Użyłem złego sformułowania. Dla początkującego użytkownika, chcącego sobie pomigać diodą/diodami, nie ma tu żadnego problemu. Dla profesjonalistów oczywiście też nie. Po prostu, jak wszystko, można napisać to samo po swojemu i inaczej. A czy lepiej? No i fakt, że problemem jest nie brak informacji [internet/książki] ale raczej ich nadmiar i jakość. 

Wersja [cała] kodu z kursu + wstawiona linijka: delay(3)

unsigned long aktualnyCzas = 0;
unsigned long zapamietanyCzas = 0;
unsigned long roznicaCzasu = 0;
 
void setup(){
  Serial.begin(9600);
}
 
void loop(){
  delay(3); // symulowanie dowolnej "malutkiej" 
             // innej od migania diodami dzialalnosci programu
  //Pobierz liczbe milisekund od startu
  aktualnyCzas = millis();
  roznicaCzasu = aktualnyCzas - zapamietanyCzas;
  
  //Jeśli różnica wynosi ponad sekundę
  if (roznicaCzasu >= 1000UL) {
    //Zapamietaj aktualny czas
    zapamietanyCzas = aktualnyCzas;
    //Wyslij do PC
    Serial.println(aktualnyCzas);
  }
}

Moja propozycja:

unsigned long zapamietanyCzas = 0;
unsigned long roznicaCzasu = 1000UL;
 
void setup(){
  Serial.begin(9600);
}
 
void loop(){
   delay(3);  
  //Jeśli różnica wynosi ponad sekundę
  if (zapamietanyCzas + roznicaCzasu <= millis()) {
    //Zapamietaj czas
    zapamietanyCzas += roznicaCzasu;
    //Wyslij do PC
    Serial.println(zapamietanyCzas);
  }
}

Pozostaje sprawa co się stanie po 50 dniach. Jak dla mnie nie jest to na razie żaden problem - mam arduino od tygodnia i paru dni i jak na razie nie "pracował dłużej niż jeden dzień 😉, więc do 50 dni jeszcze daleko.

Edytowano przez narbej
  • Lubię! 1
Link do komentarza
Share on other sites

@narbej wydaje mi się, że starasz się osiągnąć nieco inną funkcjonalność niż oferuje delay() i dlatego traktujesz rozwiązanie podane w artykule jako błąd.

Zacznijmy od samego "oryginalnego" delay. Funkcja ta blokuje wykonywanie programu na podany czas. Oczywiście to blokowanie nie jest idealne, czas jest zawsze obarczony pewnym błędem, ale przyjmujemy, że jest to dokładność akceptowalna do naszych potrzeb. Czyli pisząc delay(3) zakładamy, że program zatrzyma się na 3ms, nie wnikamy, czy jest to 2.95ms, czy może 5ms.

Co więcej delay() nie "widzi" całej reszty programu. Jeśli jest używana w pętli, to czas wykonania pętli będzie dłuższy niż samego wywołania delay().

Tak działa delay() i tak działa kod opisany w artykule.

Natomiast funkcjonalność, którą chcesz uzyskać to wykonywanie kodu co określony czas. Nie wiem jak coś takiego uzyskać w Arduino, ale opiszę różnicę na przykładzie FreeRTOS-a.

Tym co odpowiada delay() jest wywołanie funkcji vTaskDelay() - podobnie zatrzymuje ona wykonywanie zadania na podany czas, przy czym kwant czasu jest często dłuższy niż 1ms, więc i dokładność może być znacznie mniejsza. Chcąc uzyskać wykonanie kodu co wybrany czas, należy używać innej funkcji, w przypadku FreeRTOS-a jest to vTaskDelayUntil(): https://www.freertos.org/vtaskdelayuntil.html

Proponuję wydzielić ten wątek, bo moim zdaniem nie dotyczy samego artykułu. Funkcja delay() oraz jej zamiennik z millis() działają podobnie i obie nie są idealne. Natomiast mogłoby być bardzo ciekawe przedyskutowanie jak zaimplementować poprawnie odpowiednik vTaskDelayUntil(). Bo moim zdaniem to co jest w artykule jest poprawne, chociaż nie idealne. Natomiast kod który się zawiesi po 50 dniach jest po prostu błędny.

Edytowano przez Elvis
Link do komentarza
Share on other sites

2 godziny temu, Elvis napisał:

Nie wiem jak coś takiego uzyskać w Arduino

Już kiedyś o tym wspominałem, ale tu może się przydać rozwiązanie proponowane przez bibliotekę Timers.h

Prykład z tej biblioteki:

// Mruga wbudowaną diodą świecącą przy pomocy biblioteki Timers
// Autor: Łukasz Tretyn - http://nettigo.pl

#include <Timers.h>

Timer ledBlinkTimer;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  ledBlinkTimer.begin(SECS(2));
}

void loop() {
  if (ledBlinkTimer.available())
  {
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    ledBlinkTimer.restart();
  }
}

Biblioteka wykorzystuje sprytnie millis() oddając do dyspozycji "czasoodmierzacze", których można zadeklarować wiele. W programie wystarczy sprawdzać czy "czasoodmierzacz" odmierzył zadany czas by wykonać określony fragment kodu. Nie jest to rozwiązanie precyzyjne, ale dla prostych zastosowań z pewnością wystarczające i co najważniejsze samo w sobie nie blokujące kodu pętli loop(), nie korzysta z przerwań i systemowych timerów. Kilkukrotnie wykorzystywałem tę bibliotekę z pełnym powodzeniem.

Link do komentarza
Share on other sites

@Belferek jeśli masz na myśli tą bibliotekę Timers: https://github.com/nettigo/Timers to obawiam się, że jej używanie jest tylko gorsze niż przykład z artykułu. Sam kod jest prawie identyczny, tylko schowany w klasie. Więc zamiast:

  //Pobierz liczbe milisekund od startu
  aktualnyCzas = millis();

  //Jeśli różnica wynosi ponad 1 sekundę
  if (aktualnyCzas - zapamietanyCzasLED1 >= 1000UL) {
    //Zapamietaj aktualny czas
    zapamietanyCzasLED1 = aktualnyCzas;
}

Masz kod:

bool Timer::available()
{
  uint32_t actualTime = millis();
  uint32_t deltaTime = actualTime - _lastTime;
  if (deltaTime >= _time)
  {
    return true;
  }

  return false;
}

Natomiast to co gorsze, to po pierwsze dodawanie kolejne biblioteki, ale jeszcze rozdzielenie sprawdzenia czasu w available() od resetowania w metodzie restart():

void Timer::restart()
{
  _lastTime = millis();
}

O wywołaniu restart można zapomnieć, a poza tym kolejne wywołanie millis() może zwrócić inną wartość, czyli gubimy czas nawet na samym używaniu tego cuda.

Edytowano przez Elvis
Link do komentarza
Share on other sites

1 godzinę temu, Elvis napisał:

to co gorsze, to po pierwsze dodawanie kolejne biblioteki,

Przecież 99,9% programów korzysta z możliwości include i to nie tylko tych dla Arduino więc z całym szacunkiem, ale ten argument mnie nie przekonuje.

1 godzinę temu, Elvis napisał:

ale jeszcze rozdzielenie sprawdzenia czasu w available() od resetowania w metodzie restart()

To chyba można zrozumieć. Biblioteka jedynie odmierza czas wstecz. Jeśli zadany czas do odmierzenia minął to available() zwraca true i tyle. To nie jest ważne ile tego czasu minęło - ważne, że zadany czas minął. Zwrócone true można wykorzystać dowolnie w loop().

1 godzinę temu, Elvis napisał:

O wywołaniu restart można zapomnieć

Nie rozumiem tego. Jak zapomnieć? Nie ma żadnych problemów by użyć tej metody kiedy np. chcemy ponownie "czasoodmierzacz" uruchomić.

1 godzinę temu, Elvis napisał:

kolejne wywołanie millis() może zwrócić inną wartość, czyli gubimy czas

Przecież to nie przeszkadza. To nie działa jak sprzętowy timer, który pracuje non - stop, gdy się przepełni zaczyna np. od zera. To jak minutnik w kuchni, który odmierza czas gotowania jajek :-). Zgadzam się, że dokładności tu nie ma wielkiej. Eksperymentuję ostatnio z DCF-77 i okazuje się, że 1000ms wzorca czasu DCF to w przypadku millis() wartość od 995 - 1005. Ale jeśli dla kogoś pojedyncze ms błędu nie są krytyczne to co złego w korzystaniu z tego rozwiązania - przecież nie chodzi o to by biblioteka ta była podstawą jakiegoś RTOS?

Edytowano przez Belferek
Link do komentarza
Share on other sites

Oczywiście, że programy wykorzystują dyrektywę include  - to jednak nie to samo co korzystanie z dodatkowych bibliotek. Podział projektu na mniejsze moduły jest jak najbardziej sensowny. Natomiast wykorzystanie każdej biblioteki to pewne ryzyko, a biblioteki dla Arduino w szczególności. Kod takiej biblioteki nie zawsze jest idealny, więc trzeba uważać z czego się korzysta. I to miałem na myśli pisząc o gorszości takiego rozwiązania. Oczywiście czasem lepiej wykorzystać sprawdzoną, przetestowaną i poprawnie napisaną bibliotekę niż wynajdować koło. Ale jak napisałem, mało jest takich wśród dobrobytu dla Arduino.

Co do zapominania, to oczywiście w banalnym programie, nad którym siedzi jedna osoba pewnie nie zapomnimy, że po każdym użyciu timera trzeba go zresetować. Ale już pracując nad nieco większym projektem, być może w zespole więcej niż jednoosobowym łatwo jest o takie błędy podczas zmian w progamie. To pewnie uwaga nieco na wyrost, jednak lepiej być nieco przewrażliwionym na rozwiązania, które wymagają magicznych sekwencji wywołań. Tym bardziej że to wcale nie jest potrzebne w tym przypadku.

Nie bardzo rozumiem co miałeś na myśli pisząc o odmierzaniu czasu wstecz. Nie śmiałbym podejrzewać biblioteki o odmierzanie czasu w przód - chodziło mi o to, że test available() oraz restart() powinny być połączone w jedno wywołanie. Nie byłoby problemu z zapominaniem, oraz do _lastTime można byłoby wpisać to co zostało odczytane i sprawdzone przy pierwszym wywołaniu millis() - tak jak teraz jest po prostu kiepsko.

Więc podsumowując - fajnie że lubisz tą bibliotekę, możesz jak najbardziej z niej korzystać, pamiętaj tylko że nie jest idealna, trochę lepsze jest rozwiązanie opisywane w tym artykule.

Mam cichą nadzieję, że cały ten off-topic zostanie wydzielony i wrócimy wtedy do wersji proponowanej przez @narbej-a, bo to bardzo ciekawy temat.

 

 

  • Lubię! 1
Link do komentarza
Share on other sites

2 minuty temu, Elvis napisał:

Nie bardzo rozumiem co miałeś na myśli pisząc o odmierzaniu czasu wstecz. Nie śmiałbym podejrzewać biblioteki o odmierzanie czasu w przód - chodziło mi o to, że test available() oraz restart() powinny być połączone w jedno wywołanie. Nie byłoby problemu z zapominaniem, oraz do _lastTime można byłoby wpisać to co zostało odczytane i sprawdzone przy pierwszym wywołaniu millis() - tak jak teraz jest po prostu kiepsko.

Cała ta dyskusja wynika z przyjętej przez autora nazwy Timers i trzymaniu się tego nazewnictwa. W przypadku liczników, timerów pracujących non stop naturalnym jest dla ich działania automatyczny "restart" po przepełnieniu. Ta klasa działa inaczej - działa jak ten kuchenny minutnik. Nastawiasz czas do odmierzenia - minutnik pracuje, czas minął minutnik sygnalizuje i ... koniec. Jeśli chcesz znowu użyć minutnika musisz go od nowa, ręcznie nastawić. Ja tak rozumiem działanie tego kodu. No ale jaką inną nazwę mógł wymyślić autor - może Millisik.h 🙂

Link do komentarza
Share on other sites

Ok, ale niezależnie od nazwy, ta biblioteka nie działa jak vTaskDelayUntil(), jest tylko zamiennikiem delay() / albo tego co mamy opisane tutaj używając millis().

Natomiast @narbej chciał mieć wywołanie procedury co ustalony czas, ale co ważniejsze brak kumulowania błędów. Czyli powiedzmy wywołania są co 1 sekundę +- 10ms, a po 24h mamy 86400 wywołania (+- 1). Zarówno delay(), opisywane w artykule użycie millis(), ani tym bardziej biblioteka Timers czegoś takiego nie zapewnia.

Edytowano przez Elvis
Link do komentarza
Share on other sites

Faktycznie powstała tutaj ciekawa dyskusja, więc zgodnie z propozycją pozwoliłem sobie wydzielić posty do osobnego tematu. Dla osób, które dołączyły dopiero teraz - posty zostały wydzielone z poniższego wątku:

Link do komentarza
Share on other sites

(edytowany)

Chciałbym się usprawiedliwić 😉 Czytając [przerobiłem cały I i II stopień kursu] o wielozadaniowości w arduino, dowiedziałem się, jakie to delay() jest bee. I jak najbardziej się z tym zgadzam. Dalej [błednie] zrozumiałem, że millis() jest lekarstwem na wszelkie zło, na grafice [w lekcji] tak ładnie  wyświetlały się końcowe zera, więc błędnie założyłem, że mogę spokojnie użyć millis() do swoich niecnych celów 😉  Teraz wiem już trochę więcej i wiem, że nie będzie tak prosto. Swoją drogą uruchomiłem ten swój nieszczęsny zegarek na ardunio i okazało się, że po pół dniu "pracy" sporo się rozminął z prawidłowym czasem. No ale za taką cenę przecież nie powinienem oczekiwać szwajcarskiego zegarka z automatu, no i funkcja adjustTime() po coś w użytej przeze mnie bibliotece jest.

Do sterowania czasem wyświetlania diody, czy kilku diod, sterowania silnikiem, czy samym robotem itd drobne nieścisłości czasu są zupełnie bez znaczenia i podana metoda jest ok. No i w końcu jest to dla początkujących. Zaawansowani dobrze wiedzą, że trzeba użyć przerwań zegarowych [lub, najlepiej świadomie,  użyć odpowiednich bibliotek], a kurs jest przecież dla początkujących a nie zaawansowanych.

Edytowano przez narbej
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

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