Skocz do zawartości
wojtekizk

Wielozadaniowość w Arduino - biblioteka Timers

Pomocna odpowiedź

Napisano (edytowany)

Witam

Od niedawna wczytuję się w Forum na Forbocie (wcześniej robiłem tylko zakupy) i przez te 5 lat jakoś nie było okazji tu zaglądać.

Teraz widzę jak wiele straciłem - kursy programowania Arduino  a teraz genialne kursy dla  STM i to z biblioteką HAL ... duży szacun 🙂

Jest już lepiej z polskimi odpowiednikami (i tłumaczeniami prawie dosłownymi) obcojęzycznej literatury, ale taki kurs bez konieczności kupowania drogich i często nietrafionych pozycji jest kopalnią wiedzy dla początkujących w temacie.

Arduino, mimo że ma już więcej niż10 lat ciągle jest popularne i budzi zainteresowanie wśród szerokich rzeszy zapaleńców i majsterkowiczów.

Gdy robiłem podyplomówkę w ramach akcji (50 + ... tak, to kryteruim to wiek 🙂 )... to na naszej uczelni  nikt z pracowników tejże nawet nie słyszał o projekcie Arduino... a ja wybrałem sobie kontrowersyjny temat "Projekt robota MINISUMO w oparciu o moduł Arduino UNO ". Na szczęście teraz jest już o niebo lepiej. Sami studenci robotyki orientują się w temacie o wiele lepiej od swoich wykładowców 🙂

Ale do rzeczy... czytając liczne wpisy na Forum zorientowałem się, że spora grupa entuzjastów Arduino wcześniej lub później boryka się z problemem wielozadaniowości. Jakieś 5 lat temu udzielałem się troszkę na forum majsterkowo.pl i tamże popełniłem kilka artykułów mniej lub bardziej przydatnych 🙂

Chciałbym zainteresować kolegów (i koleżanki oczywiście) zastosowaniem w projektach biblioteki Timers ( nie Timer One ... Timer Tree itp.)

Biblioteka Timers jest tak banalnie prosta, że jej autor nawet nie wysilił się na podział jej na pliki *.h i *.cpp, a po prostu umieścił wszystko w pliku nagłówkowym.

Przy okazji przypomnę, bo też o tym kiedyś pisałem, jak czytać pliki bibliotek....że w tejże bibliotece i w każdej innej wartościowej...jest także bardzo przydatny pliczek : keywords.txt,  dzięki niemu i wpisom w sekcjach KEYWORD1  KEYWORD2 mamy kolorowanie składni przy użyciu zmiennych i funkcji bibliotecznych 🙂

Biblioteka Timers.zip jest do pobrania i zalokowania w IDE ( bo trudno to nazwać instalacją) w załączniku poniżej.

A o to opis najważniejszych funkcji tejże biblioteki:

OPIS BIBILOTEKI (skrócony):
----------------------------
Czym biblioteka Timers różni się od biblioteki Timer, TimerOne czy TimerTree? 
Przede wszystkim prostotą użycia jej w programie. Zasadniczo korzystamy z 4 wygodnych funkcji składowych klasy Timers:

1) Konstruktor - inicjalizacja obiektu klasy Timers. 
Przykład:
Timers <8> akcja;  // - Powołujemy do życia obiekt klasy Timers o przykładowej nazwie akcja, który może obsłużyć 8 niezależnych wątków (zdarzeń)

2) Funkcja attach(nr wątku, interfał wywołania, funkcja obsługi), gdzie:
- numer wątku, to numer jednego z 8 wcześniej zdefiniowanych w konstruktorze wątków;
- interwał - odstęp czasu w ms. W tych odsępach będzie wywoływana funkcja obsługi
- funkcja obsługi - nazwa funkcji, jak ma być wykonywana.
W tym zakresie funkcja attach jest łudząco podobna do funkcji attachInterrupt.
Przykłady:
akcja.attach(2,5000,pokazTemp); - co 5 sekund wątek 3 (liczymy od 0) wywołuje funkcję pokazTemp()
akcja.attach(0, 1000, pokazCzas); - co 1 sekundę wątek pierwszy wywołuje funkcję pokazCzas()
akcja.attach(1,0,flopKierunek); - definiujemy wątek nr 2, ale nie mamy na razie zamiaru z niego korzystać - interwał =0 

UWAGA!!!
Funkca attach musi być zainicjowana w funkcji setup() dla każdego z wątków.
Jeśli nie mamy zamiaru od razu korzystać z danego wątku, to w funkcji attach ustawiamy interwał na 0.

3) Funkcja updateInterval(nr wątku, akt interwał), gdzie:
- nr wątku, to numer jednego z 8 wcześniej zdefiniowanych w konstruktorze wątków;
- akt interwał - dynamiczna zmiana czasu wywoływania funkcji, w szczególnym przypadku dla interfał=0 wyłączamy obsługę wątku.
Przykłady:
akcja.updateInterval(2,0); - zatrzymanie obsługi wątku nr 3
akcja.updateInterval(4,189); - zmiana lub ustawienie dla wątku nr 5 czasu wywoływania funkcji obsługi na 189 ms.

4) funkcja process() - wywoływana w pętli loop, uruchamia globalną obsługę wszystkich zadeklarowanych
w konstruktorze wątków
Przykład:
akcja.process();

...a oto przykładowy program: 

[/code]

#include <Timers.h> // dołączona biblioteka Timers (konieczna do poprawnej pracy ... jest w załączniku)
Timers <8> akcja; // na poczatek 8 niezależnych wątków (procesów, zadań, procedur, akcji itp.)
int x=1, y=2; // takie zmienne pomocnicze, dla testów;
// --- poniżej przykładowe definicje zadań: ------
void zadanie1() {   // tutaj obsługa jakieś sekwencji zdarzeń, zapalenie diody, ruch silnika itp
}
void zadanie2() {   // tutaj obsługa jakieś sekwencji zdarzeń dla zadania 2
}
void zadanie3() {   
}
void zadanie4() {   // analogicznie dla pozostałych zadań
}
void zadanie5() {   
}
void zadanie6() {   
}
// ------------------------------------------------
void setup()
{
  // Teraz najważniejsze :-)
  akcja.attach(0, 1000, zadanie1); // Wątek 1: wcześniej zdefiniowana funkcja o nazwie zadanie1, wywoływana co ok  1 sek.
  akcja.attach(1,0,zadanie2);      // anlogicznie dla zadanie2 , ale tutaj na razie nie robimy nic, bo interwał =0;.
  akcja.attach(2,5000,zadanie3);   // zadanie3 wykonuje się co 5 sek. (np. pomiar temperatury)
  akcja.attach(3,200,zadanie4);    // co 200 ms sprawdzamy np stan przycisku
  akcja.attach(4,189,zadanie5);    // a co 189 ms sprawdzamy stan czujnika odległości  
  akcja.attach(5,1211,zadanie6);    // a co 1211 ms wysyłamy jakis komunikat na Seriala
}
void loop()
{
  akcja.process(); // inicjalizacja lub aktualizacja wszystkich procedur(wątków, zdarzeń itp.)
  if(x==1){akcja.updateInterval(1,300);} // odpalamy akcje dla zadania 2, co 300 ms.
  if(y!=2) {akcja.updateInterval(4,0);} // zatrzymujemy zadanie 5
}

Proszę zauważyć, że w loop nie ma delay.:-)

Możemy się zatem skupić na definicji funkcji obsługi zdarzeń, czyli zająć się logiką programu i nie borykać się już więcej z zawieszaniem się obsługi programu.
mamy zatem tu namiastkę wielozadaniowości .... nasz minisystem "nasłuchuje" w określonych przez nas odstępach czasu co się wydarzyło i zależnie od sytuacji zajmuje się obsługą zdarzeń.

Na co należy uważać?

Lepiej aby interwały nie zachodziły na siebie, czyli należy dokładnie przemyśleć i oszacować czasy wykonywania poszczególnych zadań.

Nie zalecam użycia w samych funkcjach delay(). Przecież możesz w jednym zadaniu np. uruchomić silnik a w drugim go zatrzymać (zależnie od warunku, który możesz sprawdzać np w zadaniu nr 7 :-). Całkowita dowolność, a jak 8 zadań to za mało to możesz zawsze utworzyć i obsłużyć kolejny obiekt klasy Timers, na przykład taki " Timers <6> akcja2;

Jedynym ograniczeniem może być tylko ograniczona pamięć  mikrokontrolera 🙂 

Bardziej obszerny przykład zamieściłem 5 lat temu na forum majsterkowo.pl

Gorąco zachęcam także do zapoznania się (przejrzeniem źródła Tmers.h)... 🙂

Leci własnie mecz siatkówki, grają nasze złotka ...więc kończę

Pozdrawiam

Wojtek

 

Timers.zip

Edytowano przez wojtekizk
  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Dopiero miałem okazję zapoznać się z tym programem. Faktycznie jest to nieskomplikowana biblioteka i może być dużo lepsza od pollingu z wykorzystaniem delay... 

Dnia 4.09.2019 o 22:15, wojtekizk napisał:

keywords.txt,  dzięki niemu i wpisom w sekcjach KEYWORD1  KEYWORD2 mamy kolorowanie składni przy użyciu zmiennych i funkcji bibliotecznych

Dziękuję, tego nie wiedziałem. Jak sobie przejrzałem kilkanaście bibliotek jakie mam pobrane, to nawet te od dużych producentów nie mają tego pliku, a kolorowanie składni jest czymś przydatnym.

Temat spodobał mi się, gdyż kiedyś jak w ramach powiedzmy pracy dyplomowej/hobby pisałem silnik graficzny do symulacji 3D. Jeden z elementów to była aktualizacja animacji, zdarzeń i coś podobnego było dla mnie bardzo odkrywcze. I nie tylko dla mnie, bo zauważam, że wykonywanie zadań "równolegle" opartych na zliczaniu osobno czasu dla każdego z nich jest bardzo proste ale do końca oczywiste.

Udostępnij ten post


Link to post
Share on other sites

Zmieniłbym tylko nazwę - taka biblioteka np. już jest zobacz. Pozwoli to uniknąć jej użytkownikom nieporozumień.

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

 @wojtekizk witam ziomala z Kryr:). Sam z powodzeniem korzystam z biblioteki od stoperów, bardzo ułatwia sprawę "wielozadaniowości;)" Arduino. Wojtku w kursie Arduino również jest odcinek dotyczący tego zagadnienia:

https://forbot.pl/blog/kurs-arduino-ii-wielozadaniowosc-opoznienia-z-millis-id18418

jednakże Twoja biblioteka bardzo upraszcza stosowanie liczników. Tym niemniej polecam ww. odcinek kursu wszystkim początkującym aby zrozumieli samą zasadę tych operacji.

Belferek też ma rację co do nazewnictwa, pamiętam, że też się naszukałem "tej" biblioteki, kiedyś, wśród wielu o podobnej lub identycznej nazwie. 

Edytowano przez SOYER
  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

Witam

Fakt jest kilka co najmniej bibliotek Timers, stąd nieporozumienia.

Ta, którą opisałem powyżej jest z 2013 roku, więc jest szansa,że jest jedną z pierwszych. Nie jest to jednak chyba do końca ta sama, która jest opisana na kursie.

Zmiana nazwy jest dobrym pomysłem, więc aby uniknąć nieporozumień proponuję spakować sobie ją ponownie w formie ZIP-a pod inna nazwą... i potem "bezczelnie" dołączyć do swojego IDE 🙂

ups.... trzeba troszkę pogrzebać w pliku *.h zanim to zrobimy 🙂 niech to będzie mały sprawdzian dla czytelnika tego postu 🙂

Pozdrawiam

 

Edytowano przez wojtekizk

Udostępnij ten post


Link to post
Share on other sites

W bibliotece jest jednak jedna rzecz, która mnie zastanowiła. Jak pisałem wcześniej, miałem do czynienia z podobnymi funkcjami aktualizacji w głównej pętli symulatora/gry, w szczególności w sytuacji, gdy na skutek dużego opóźnienia w renderowaniu, pętla aktualizacji powinna zostać wykonana kilkukrotnie w celu nadgonienia. Było to bardzo proste w realizacji, gdyż wystarczyło zamienić warunek if, na pętlę while, która wykonywała się aż dekrementowany upływ czasu nie będzie większy od interwału (ale mądrze napisane).

Do migania LEDem się nie przyda, ale jeżeli spodziewam się 3 zapisów w tablicy, a mam 1, bo w jakimś miejscu coś się zawiesiło? Można by wzbogacić strukturę o informację o tym czy można utracić jakąś aktualizację.

Udostępnij ten post


Link to post
Share on other sites

Gieneq - nie jestem pewien o co dokładnie chodzi w Twoim zliczaniu / odświeżaniu ale nasuwają mi się 2 konkluzje:

1). Każde wywołanie jakiejkolwiek funkcji, czyli nawet obsługi przerwań, sprzętowych, programowych czy Timerów  wiąże się z "zaprzęgnięciem do pracy procesora", który stara się na boku robić coś jeszcze, prawda? Niestety procesor póki co jest jeden, więc na czas obsługi np przerwania robi to, co do niego należy i nie robi niczego w LOOP-ie !

Jeśli procedura obsługi przerwania właśnie zawiera w sobie wiele instrukcji, to procesor musi wykonać sporo więcej taktów (nawet dla procesorów "riskowych"), czyli jednak upływa troszkę czasu, prawda?

Wyobraźmy sobie taki oto scenariusz:

- w Loop-ie zapuściłeś silnik, który się kręci, powiedzmy 5000 obr/sek.

- masz do niego podpięty enkoder i zliczasz impulsy, na przykład przez inkrementację zmiennej licznik...(w pętli loop) , coś w stylu:

if(digitalRead(1) ==LOW) licznik ++; 

- to jeśli wywołasz na boku przerwanie (wykonasz jakieś instrukcje) to silnik będzie się przecież nadal kręcić.... ale już Twoja zmienna w tym czasie nie będzie zwiększać swojej wartości, prawda? czyli ileś tam impulsów przeleci bez echa (i nie pomoże nawet przedrostek volatile) 😀 Po powrocie z przerwania licznik będzie się zwiększał. Nie będzie gwarancji, że nic nie uciekło niestety.

2). Wobec powyższego modyfikujesz kod i impulsy z enkodera podpinasz do Pinu INT0 (przerwanie sprzętowe) a w funkcji obsługi zwiększasz wartość zmiennej licznik.... Teoretycznie jest OK, teraz każdy impuls będzie zapisany.... ale czy oby na pewno?

Co jeśli w tym czasie będziesz miał podpięte inne przerwania, np . od Timera lub drugi silnik z drugim enkoderem? Nikt nie da Ci gwarancji, że w skutek zajęcia procesora obsługą jednego z 3 przerwań silnik nie wykona maleńkiego obrotu o kąt który zarejestruje enkoder?

...Czyli  rodzi nam się kolejny problem - priorytety przerwań, a tutaj bez gruntownego zaznajomienia z architekturą procesora i notą katalogową procka, łącznie z analizą przedstawianych tam wykresów... raczej się nie ujedzie 🙂

Wcześniej czy później należy bowiem zrozumieć, że mamy oto przed sobą tylko mały procesor 8 bitowy, a marzy nam się na nim wielozadaniowość, z którą nie radzą sobie nawet wypasione 8 rdzeniowe procesory w naszych telefonach, porażka, prawda? 🙂

Pozdrawiam

 

  • Pomogłeś! 1

Udostępnij ten post


Link to post
Share on other sites
18 minut temu, wojtekizk napisał:

Co jeśli w tym czasie będziesz miał podpięte inne przerwania, np . od Timera lub drugi silnik z drugim enkoderem? Nikt nie da Ci gwarancji, że w skutek zajęcia procesora obsługą jednego z 3 przerwań silnik nie wykona maleńkiego obrotu o kąt który zarejestruje enkoder?

Jeśli będzie to tylko jedno przerwanie (np. od drugiego enkodera) wszystko będzie zliczone prawidłowo.

  • Pomogłeś! 1

Udostępnij ten post


Link to post
Share on other sites
3 godziny temu, wojtekizk napisał:

nie jestem pewien o co dokładnie chodzi w Twoim zliczaniu / odświeżaniu ale nasuwają mi się 2 konkluzje:

Chodzi o sytuacje w której w jednej z wyzwalanych funkcji jest coś powolnego, co może spowodować "lag". Np raz na 100 wywołań zostanie wysłane coś blokujaco przez co wykonywanie zawiśnie na jakiś czas. Jeżeli czas ten jest większy niż wielokrotność interwałów poszczególnych czynności, to wypadałoby je wykonać kilka razy żeby nadgoniły.

Udostępnij ten post


Link to post
Share on other sites

Co może spowodować "lag" w takiej funkcji? Jeśli tak jest - oznacza to, że funkcja jest źle napisana i należy ją przepisać od początku - tym razem dostosowując do ilości zadań wykonywanych przez procesor w jednostce czasu.

Jeśli to oczekiwanie na jakieś zewnętrzne zdarzenie - równie dobrze można oczekiwać robiąc coś konstruktywnego.

Poza tym biblioteka typu Timers to nie jest "pacaneum na kaca" i nie do wszystkiego się nadaje.

Udostępnij ten post


Link to post
Share on other sites

Jakoś trudno mi jest sobie wyobrazić taką sytuację, aby jakieś zadanie trwało aż tak długo.... jeśli na przykład jakiś pomiar trwa np 750 ms. (słynny czujnik temperatury DS...) to ten pomiar wstawiasz do loopa, niech tam sobie tak potwornie długo mierzy... a np. w innym wątku załączasz silnik, jeszcze w innym sprawdzasz czy np ramię dojechało do okr. pozycji a jeszcze w innym wyłączasz silnik. Loop sobie robi co trzeba.... wtedy kiedy może.... poza obsługą wątków.

Udostępnij ten post


Link to post
Share on other sites
53 minuty temu, wojtekizk napisał:

Loop sobie robi co trzeba.... wtedy kiedy może.... poza obsługą wątków.

Możesz to jakoś przybliżyć, bo nie za bardzo mogę sobie wyobrazić na jakiej zasadzie miałoby to niby działać.

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

w dużym uproszczeniu loop robi swoje pod warunkiem że procesor nie zajmuje się obsługą przerwań...o to mniej więcej mi chodziło w ost. zajawce 🙂

ps. musisz być aż  tak cholernie  zasadniczy? 🙂 

pozdrawiam

Udostępnij ten post


Link to post
Share on other sites

Fajnie - a co ma do tego Timers i jego wątki? Przecież funkcje zdeklarowane w Timers jako obsługa wątków nie działają na przerwaniach i jeśli coś wsadzę nawet w loop to i tak będzie się międlić dopóki się nie skończy. Owszem, da się coś takiego zrobić dla długich obliczeń ale nie polega to na wrzuceniu funkcji do loop i oczekiwaniu aż wszystko samo się magicznie wykona.

PS. Sorry, informatyka jest nauką ścisłą...

 

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

 

Cytat

loop robi swoje pod warunkiem że procesor nie zajmuje się obsługą przerwań

jest mowa o przerwaniach... nie o wątkach Timera 🙂

Timers jest między innymi po to, aby nie używać delay. Na przykład w jednym wątku zapuszczam jakieś czasochłonne działanie (grzałkę) a w innym sprawdzam np temperaturę i jak jest OK to wyłączam grzałkę.

Timers jak słusznie zauważył ethanak nie jest panaceum na wszystkie bolączki tego świata, 8-io bitowy procesor także 🙂

Prostym językiem, bo to jest forum pasjonatów, majsterkowiczów, zapaleńców, amatorów i wszystkich innych ciekawych świata  ... nie tylko informatyków... chyba, że mi coś przez te kilka lat umknęło 🙂 

Udostępnij ten post


Link to post
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!

Gość
Napisz odpowiedź...

×   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...