delay() Funkcja Arduino: Tight Loops and Blocking Code
Czy kiedykolwiek tworzyłeś projekt Arudino i chciałeś, aby coś się wydarzyło w określonym odstępie czasu? Może co 3 sekundy chcesz, aby serwomechanizm się poruszył, a może co 1 minutę chcesz wysłać aktualizację statusu do serwera WWW.
Jak to zrobić? Czy istnieje funkcja, która jest prosta i nieskomplikowana, która nam w tym pomoże? Tak, jest! Omówimy funkcję delay, jak również funkcję millis(), w poniższym filmie:
To jest część 2 naszej mini-serii funkcji millis(). Część 1 pomaga nam zrozumieć, co robi funkcja millis(), część 2 omawia ciasne pętle i kod blokujący, a część 3 omawia, kiedy funkcja millis() przebija funkcję delay().
Tematy poruszane w tej lekcji
- Ciasne pętle
- Kod blokujący
- Starożytny przepis na inspirującą muzykę
Millis() kontra Delay()
Więc chcesz, aby coś wystąpiło w określonym odstępie czasu i szukasz rozwiązania. Jeśli twoją odpowiedzią jest użycie funkcji opóźnienia, cóż, masz trochę racji. Ale jest też inny sposób.
Najfajniejszą, bardziej elegancką opcją jest funkcja Arduino millis(). A im bardziej będziesz zaznajomiony z używaniem funkcji millis, aby pomóc ci w odmierzaniu czasu zdarzeń w twoim kodzie Arduino, tym łatwiej będzie ci później włączyć inne części do twojego programu.
Kiedy zaczniesz używać funkcji millis(), będziesz szczęśliwszy, bardziej radosny, i cholera jasna, ludzie będą cię lubić.
Ciasne pętle
Przedyskutujmy najpierw pojęcie ciasnej pętli. A kiedy mówimy o ciasnej pętli, co to oznacza?
Przyjrzyjmy się szkicowi Arduino, aby zademonstrować ciasną pętlę. Zaczynając od najbardziej podstawowego szkicu, mamy tylko dwie funkcje: void setup i void loop. Jak zapewne wiesz, void setup działa tylko raz, a następnie przekazuje program do void loop.
Void loop następnie przechodzi przez każdą linię kodu, która może znajdować się wewnątrz pętli (wewnątrz tych nawiasów klamrowych).
Wykonuje pierwszą linię, potem wykonuje drugą, potem trzecią, i tak dalej, i tak dalej, aż dojdzie do końca. A potem wraca na górę.
Jak szybko wykonuje pętlę? To zależy od tego, jakiej płytki Arduino używasz, ale Arduino Uno ma prędkość zegara 16 megaherców. Oznacza to, że w każdej sekundzie na Arduino wykonuje się 16 milionów instrukcji!
Każda linia kodu to niekoniecznie jedna instrukcja. W rzeczywistości jest najbardziej prawdopodobne, że jest to wiele instrukcji. Ale i tak jest to stosunkowo szybkie (procesor Twojego komputera prawdopodobnie pracuje z prędkością gigaherców… to są miliardy).
Czy zatem uznałbyś ten pusty szkic za ciasną pętlę? Zdecydowanie, to jest tak szybkie i tak ciasne, jak tylko możesz zrobić pętlę. Ponieważ wewnątrz pętli nie ma nic do wykonania, czas potrzebny na przejście przez szkic jest praktycznie zerowy. Mówiąc inaczej, odstęp czasu od początku pętli do jej zakończenia jest krótki (dlatego jest ona szybka, czyli „ciasna”).
Dodajmy kilka linijek kodu. Uruchomimy komunikację szeregową, a następnie wydrukujemy coś do okna monitora szeregowego.
Czy to jest ciasna pętla? To znaczy, czy od początku pętli do jej końca zajmuje to dużo czasu? Zajmuje bardzo mało czasu, więc jest to szybka, ciasna pętla.
Warto jednak zauważyć, że ta pętla nie jest tak ciasna jak poprzedni przykład. W poprzednim przykładzie nie mieliśmy żadnego kodu. Więc to był po prostu wyścig przez pętlę. Teraz, gdy mamy tutaj funkcję, serial print, zajmie to (trochę) czasu, aby wydrukować „Ice Ice Baby” do monitora szeregowego.
Ale to wciąż jest dość szybka pętla. Więc dodajmy trochę więcej kodu. Program sprawdzi, czy przycisk jest wciśnięty, a jeśli tak, to wyślemy coś nowego do monitora szeregowego
Zadeklarowaliśmy więc przycisk i użyliśmy instrukcji if, aby sprawdzić, czy przycisk został wciśnięty. Czy napięcie na pinie piątym jest wysokie? Jeśli tak, to drukujemy coś innego do okna monitora szeregowego.
Czy to jest ciasna pętla? Tak więc, od początku pętli do jej końca, czy to dość szybko?
Tak, to wciąż dość szybko. To jest dość ciasna pętla. Mamy cztery linie kodu. Drukujemy do monitora szeregowego, a następnie sprawdzamy, czy przycisk jest wciśnięty. A jeśli tak, to drukujemy coś na zewnątrz. Nadal ciasno, nadal szybko.
Następnie dodajmy opóźnienie do tego programu używając funkcji Arduino delay(). Poniżej widać, że dodaliśmy do pętli opóźnienie o tysiąc milisekund (1 sekunda).
Czy to nadal jest ciasna pętla? Czy czas, od początku pętli do jej końca, to dużo czasu? Nie, to na pewno nie jest ciasna pętla. Kod zaczyna się szybko, wykonujemy seryjne drukowanie, ale potem zostajemy zatrzymani w funkcji delay.
Cały program zatrzymuje się, gdy czekamy na zakończenie tego kodu delay.
Gdy Arduino dociera do tej linii kodu, jest to trochę jak pójście do sklepu spożywczego. Wchodzisz do kolejki z 12 lub mniej pozycjami, ale wtedy facet przed tobą wyciąga swoją książeczkę czekową i zaczyna wypisywać czek. Wiesz, że będziesz tam stał przez minutę. Tak samo jest tutaj.
Więc to nie jest ciasna pętla. Czas od początku pętli do jej końca jest dość znaczny. Szczególnie w porównaniu do kilku ostatnich programów. Rząd wielkości czasu jest ogromny.
Teraz to, co próbowaliśmy tutaj zademonstrować, to fakt, że cała ta idea o ciasnych pętlach jest względna. Wszystko zależy od twojej aplikacji. Jeśli musisz sprawdzić stan czujnika co 10 milionowych części sekundy, to program, który ma trzy linie kodu może nie być wystarczająco ciasny, to po prostu zależy.
Innym punktem do zrobienia jest to, że nie wszystkie linie kodu zajmują tyle samo czasu na wykonanie. Jeśli wywołujesz funkcję, która robi wiele rzeczy, na przykład drukowanie seryjne, wtedy ta jedna linia kodu może trwać o wiele dłużej niż 10 innych linii kodu.
Więc ciasność pętli jest względnym pomysłem.
Kod blokujący
Gdy program zatrzymuje się w pewnym momencie i zajmuje trochę czasu na wykonanie jakiegoś kodu, ten kod można nazwać kodem blokującym. Jest to termin ogólny.
W naszym programie mamy funkcję delay działającą jako kod blokujący. Żaden kod po opóźnieniu nie może zostać uruchomiony, dopóki opóźnienie się nie skończy, więc jest blokowany.
Kod blokujący nie jest jednak tylko wtedy, gdy używamy funkcji delay().
Weźmy nasz program i pozbądźmy się opóźnienia, ale dodajmy pętlę for. Nasza pętla for będzie wypisywać liczby i tekst na port szeregowy.
Więc jak długo będzie działać ta pętla? Będzie działać przez jakiś czas, ponieważ musi przejść przez 100 iteracji zanim się zatrzyma.
A co z kodem po tej pętli for? Czy jest on w stanie działać? Nie, musi czekać, ponieważ jest blokowany przez pętlę for.
Ta pętla for jest zagnieżdżona wewnątrz pętli głównej. Czy pętla for jest „ciasną” pętlą? Zanim odpowiesz, podkreślmy jeszcze raz, jak powinieneś o tym myśleć: czy przejście przez tę pętlę for od góry do dołu zajmuje dużo czasu?
No cóż, nie bardzo. Ma ona tylko dwie linie kodu. Jest to więc dość ciasna pętla.
Ale jest to ciasna pętla, która musi przejść przez wiele iteracji, zanim program dotrze do kodu znajdującego się pod nią. Tak więc, nawet ciasna pętla, jeśli zostanie zmuszona do przejścia przez kilka iteracji, może zablokować nasz kod.
Więcej o funkcji delay()
Porozmawiajmy jeszcze trochę o tej funkcji delay. Co ustaliliśmy do tej pory?
Po pierwsze powiedzieliśmy, że funkcja delay zmniejsza ciasnotę pętli. Jeśli masz ciasną pętlę i dodajesz funkcję opóźnienia, zajmie ona więcej czasu i sprawi, że będzie mniej ciasna. To znaczy, ilość czasu potrzebnego na dotarcie od początku pętli do jej końca, wzrośnie wraz z funkcją opóźnienia.
Wiemy również, że funkcja opóźnienia blokuje kod. To idzie w parze z tym, co właśnie powiedzieliśmy. Kiedy funkcja opóźniająca jest uruchomiona, blokuje inny kod przed uruchomieniem podczas opóźniania.
Możesz pomyśleć, że ta funkcja opóźniająca jest totalnym obibokiem! Nigdy do niczego się nie przyda w naszych projektach. Ale dla wielu prostych programów, które piszemy, funkcja delay działa fantastycznie. Jest prosta w użyciu, naprawdę łatwa do przeliterowania i robi dokładnie to, co mówi.
Nie powinniśmy więc koniecznie wyrzucać funkcji delay() z naszego zestawu narzędzi programistycznych. Powinniśmy po prostu uznać, że jest to prosta funkcja programistyczna, która może działać w wielu przypadkach.
Jest jednak moment, w którym zaczynamy napotykać problemy. Ma to związek z efektem blokowania, który funkcja opóźnienia ma w naszym programie.
W następnej lekcji w serii, część 3, zamierzamy zidentyfikować, gdzie to naprawdę staje się problemem. Dowiesz się, kiedy używanie funkcji delay() w programie ma sens, a kiedy należy przejść na używanie funkcji millis().
Przegląd
Po pierwsze, rozmawialiśmy o szczelności pętli. Powiedzieliśmy, że ciasnota pętli jest względna. To zależy od tego, czym jest twoja aplikacja.
Po drugie, rozmawialiśmy o kodzie blokującym, lub kodzie, który blokuje. Zasadniczo, jest to ogólny termin, który możemy nadać kodowi, który zajmie trochę czasu, aby wykonać, i zatrzyma inne części naszego programu od działania, podczas gdy on wykonuje.
Mamy nadzieję, że ta lekcja się podobała! W następnej części tej serii, będziemy kontynuować naszą podróż ucząc się jak używać funkcji millis do tworzenia czasowych, powtarzających się zdarzeń w naszym kodzie Arduino. Do zobaczenia następnym razem!
.