Skocz do zawartości

(pseudo) wielowątkowość - jakie optymalne podejście?


DCH

Pomocna odpowiedź

cześć,

potrzebuję Waszej porady w temacie "wielowątkowości". Konkretnie chodzi mi o to jakie podejście będzie optymalne do mojego przypadku.

Rozwijam miernik powietrza zbudowany w oparciu o SeedStudio Xiao ESP32 C3. Miernik korzysta z kilku czujników - wilgotności, ciśnienia, AQI, cząstek stałych. Z uwagi na optymalizację zużycia energii pomiary będą robione w różnych interwałach. Najbardziej wymagającym jest czujnik cząstek stałych. W jego przypadku algorytm postępowania jest następujący (podejście rekomendowane wg. dokumentacji):

1. Wybudź czujnik ze stanu czuwania i włącz tryb pomiarów;

2. Odczekaj 30s. (czujnik pozostaje w trybie pomiarów - nic tu akurat nie muszę robić) w celu ustabilizowania pomiarów.

3. wykonaj 30 pomiarów co 1 sek. Uśrednij wyniki, i wyświetl wynik na wyświetlaczu.

4. zakończ tryb pomiarów. Uśpij czujnik na 15 minut.

Inne czujniki to prostsze przypadki - zazwyczaj odczytaj pomiar raz na X (5-30s) sekund w zależności od tego, który jest to czujnik.

Pierwotnie chciałem wykorzystać millis(). Ponieważ obudowanie w to dłuższego kodu nie jest zadaniem trywialnym to zacząłem szukać i trafiłem informacje o tym, że millis() nie koniecznie jest optymalne. Google podpowiedziało mi hasła FreeRTOS oraz bibliotekę TaskScheduler. Ta druga wydawała się być w miarę prosta i ciekawa. Miała między innymi pozwalać na przełączanie mikrokontrolera w "low power mode" na czas gdy żadne zadanie nie jest wykonywane. Poświęciłem więc trochę czasu na zapoznanie się z biblioteką TaskScheduler. Okazało się, że jest to ładnie opakowany millis(), a zarządzanie energią możliwe jest tylko dla AVR (czyli jeżeli dobrze rozumiem, nie dla mojego Xiao C3). Do tego funkcje wywoływane przez scheduler powinny być "szybkie" - na pewno nie 30 sekkundową serią pomiarów. No chyba, że rozbiję te pomiary na 30 kolejnych wywołań co 1 sekunda, ale to oczywiście skomplikuje kod aplikacji. 

Tak się zastanawiam jak podejście do pseudo wielowątkowości byście mi radzili? Zagłębić się w FreeRTOS? Coś innego? A może nie ma to większego znaczenia bo każde rozwiązanie (pomijając oczywiście delay) da podobne efekty i będzie wymagało podobnego wysiłku przy dostosowaniu kodu aplikacji do pseudo wielowątkowości? 

Link do komentarza
Share on other sites

(edytowany)

FreeRTOS jest natywny dla ESP32 i nie ma sensu szukać czegoś innego. A z poziomu Arduino możesz po prostu wywoływać wszystkie funkcje bezpośrednio bez dołączania jakichś dziwacznych bibliotek.

Tyle że ja bym raczej wybrał coś innego niż C3...

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

11 minut temu, DCH napisał:

Google podpowiedziało mi hasła FreeRTOS

ESP32 pracują w RTOS tyle że pod Arduino tego nie widać na pierwszy rzut oka. Co do samego RTOS to jest to ciekawe rozwiązanie ale wymusza zmianę myślenia i dużo pracy na ze sprzętem gdyż jest tam wiele "pułapek" przez które kod nagle przestaje działać. (Dead locki, problemy wyścigów, zagłodzenie wątków i inne) 
Jeśli chcesz potrenować sobie z RTOS to mogę polecić tę stronę http://www.embeddeddev.pl/kurs-freertos-wprowadzenie/ ten kurs chyba nie jest ukończony ale daje wstępne pojęcie tym systemie. Są kursy płatne np: od kolegi @msalamon ale widzę że cena poszybowała dosyć wysoko. Dla ESP32 to chyba zostają kursy zagraniczne.  

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

dzięki za podpowiedzi. Douczę się o FreeRTOS. Wiem już, że jest to dobry kierunek (bo natywny) dla ESP. A gdyby uogólnić pytanie do poziomu szeroko rozumianego Arduino? Czyli chcę zrobić to co opisałem, ale nie wiem jeszcze czy finalnie skończę na ESP32, Uno R4, Nano, czy może Giga? W takim wypadku odpowiedź byłaby taka sama?

Wiem, teraz to już teoretyzuję ... ale w celach edukacyjnych. 😉

Link do komentarza
Share on other sites

Ja używam millis, jest to bardzo proste, jak coś ma trwać minutę, ale ważne są odstępy co 1s, to wywołuję switch case co 1s, gdzie:

case 1 Sprawdź czy wybudzić czujnik, jakaś zmienna++ zliczająca sekundy 15 minut, jeśli tak to wybudź czujnik i przejdź dalej

case 2 odczekaj tu te 30 wywołań, np. zmienna++ i jak zrobi się większa >30 to przejdź dalej

case 3 wykonaj 1 pomiar, zlicz pomiary do zmiennej będącej przy okazji indeksem tablicy i ją uzupełniaj przez te kolejne 30s, uśpij czujnik i przejdź dalej

case 4 wyciągnij średnią, przekaż wynik, wyzeruj zmienne, tak by były gotowe na nowy cykl, wróć do case 1 

Jest przykład migania led w blinkWithoutDelay, wystarczy tam wywalić miganie i  wstawić taki switch case i masz tą część programu, która nie zakłóca reszty, którą można zrobić w analogiczny sposób. Zmienne można tworzyć w danym bloku jako static, wtedy są widoczne tylko w tym {} obszarze, ale zachowują wartości do kolejnych wywołań tego bloku w {}. Dzięki temu każdy taki blok w {} może mieć swoją zmienna++ o tej samej nazwie.

Trzeba też sprawdzić jak czujnik się komunikuje, bo np. taki DS18B20 zapytasz o temperaturę i on tam sobie będzie tkwił przez cały pomiar, bo tak ma domyślnie ustawione w bibliotece Dallas Temperature, to blokuje program na prawie sekundę, można też odpowiednią funkcją wyłączyć czekanie na wynik, takim switch case wywoływanym co 100ms zlecić pomiar, przez kolejne 8 nic nie robić, w 9 odczytać, w kolejnym znowu zlecić kolejny pomiar, dzięki temu mamy odczyt co 1s, ale uC jest tym zajęty tylko 2x po około 20ms na czas transmisji One Wire.

 Albo jakaś biblioteka z timerem programowym:

#include <DallasTemperature.h>
#include <OneWire.h>
#include <TickTwo.h>
 
#define ONE_WIRE_BUS 4
 
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

float tempDS;

void temp();
void serial1000();
void serial500();
void ledBlink();

TickTwo timer1(temp, 100);
TickTwo timer2(serial500, 500);
TickTwo timer3(serial1000, 1000);
TickTwo timer4(ledBlink, 50);

void setup() {
  Serial.begin(115200);

  timer1.start();
  timer2.start();
  timer3.start();
  timer4.start();
  
  sensors.setWaitForConversion(0);
  sensors.setResolution(12);
  sensors.begin();
  }

void loop() {
  timer1.update();
  timer2.update();
  timer3.update();
  timer4.update();
 
}

 

void temp() {
static uint8_t ktoraSetkaSekundy=0;

switch (ktoraSetkaSekundy)
{
  case 0:
sensors.requestTemperatures();  
  break;
  case 3:
Serial.println("Kuku z trojki");
  break;
  case 8:
tempDS = sensors.getTempCByIndex(0);
  break;
  default:
// gdy 1,2,3,4,5,6,7 i 9 nic ta funkcja nie robi poza ktoraSetkaSekundy++
  break; 
}
ktoraSetkaSekundy++; 
if( ktoraSetkaSekundy>9) ktoraSetkaSekundy=0;
}
 

void serial500()
{
Serial.println("test 500 ms");

}

void serial1000()
{
Serial.print("Temperatura: ");
Serial.println(tempDS);
}

void ledBlink()
{
static bool startujemy=0;  
if(! startujemy) 
{
  pinMode(LED_BUILTIN,OUTPUT);
  startujemy=1;
}  
digitalWrite(LED_BUILTIN, ! digitalRead (LED_BUILTIN));

} 

 

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

13 godzin temu, DCH napisał:

Tak się zastanawiam jak podejście do pseudo wielowątkowości byście mi radzili? Zagłębić się w FreeRTOS? Coś innego? A może nie ma to większego znaczenia bo każde rozwiązanie (pomijając oczywiście delay) da podobne efekty i będzie wymagało podobnego wysiłku przy dostosowaniu kodu aplikacji do pseudo wielowątkowości? 

Nie strzelałbym z armaty do wróbla 🙂 No może do stada wron, ale zawsze 😉. Przy tak prostym algorytmie, jaki opisałeś, zdecydowanie wystarczy Ci jeden "wątek".

Rozwiązanie będzie szczególnie proste, jeśli czas trwania poszczególnych zadań (kroków obsługi czujnika) na Twojej liście jest znacznie, ale to znacznie krótszy niż 1 sekunda (wspominasz o rozdzielczości sekundowej w poszczególnych krokach obsługi czujnika). Wystarczyłaby tu jedna funkcja, która będzie wyzwalana regularnie co jedną sekundę jakimś mechanizmem "timerowym" dostępnym na HW i która sprawdzi listę wszystkich zadań - najprościej po prostu tablicę - wykonując to zadanie (zadania), które przypada w Twoim algorytmie na dany "kwant" czasu. Taka funkcja to już prawie jądro systemu operacyjnego 😉

Choć wymaga to pewnej minimalnej wprawy i pomysłowości, warto przećwiczyć taki elementarny przykład przed zagłębianiem się w RTOS.

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

FreeRTOS się przydaje jak oprócz synchronicznych zdarzeń są jakieś nieoczekiwane zdarzenia zewnętrzne (np połączono z siecią, zeskanowano adres BT, etc) o różnych priorytetach.

Klasycznie "na siłę" też da się to oprogramować, ale RTOS załatwia to w elegancki sposób.

Nawet jeżeli projekt tego nie wymaga, to warto spróbować tej metodologii, bo może się kiedyś przdać a wymaga jednak zmiany myślenia.

 

Robię teraz mój pierwszy większy projekt na FreeRTOS i ilość latającej "łaciny" jest niewiarygodna. To jak z socjalizmem - czowiek cały czas usuwa problemy nie występujące w innych ustrojach 🙂

 

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

Wystarczy że uświadomisz sobie jak działa w RTOS pewna niezbędna funkcja - czyli delay (nieważne, jak się tam naprawdę nazywa). Od tej chwili programowanie staje się proste i intuicyjne.

Link do komentarza
Share on other sites

8 minut temu, ethanak napisał:

Wystarczy że uświadomisz sobie jak działa w RTOS pewna niezbędna funkcja - czyli delay (nieważne, jak się tam naprawdę nazywa). Od tej chwili programowanie staje się proste i intuicyjne.

Taaak, jaaaasneeee... 🙂

Zwłaszcza jak taski zaczynają używać czegoś, co nie jest re-entrant i używają globalnych zasobów.

I zaczyna się: semafory, critical sections, kolejki, cuda-wianki

 

Link do komentarza
Share on other sites

(edytowany)

To jest myślenie "z małej atmegi", gdzie przerwania się włączało albo wyłączało. A proriorytet jest na zasadzie "główny program" i "przerwanie".

Sekcje critical można np zagnieżdżać. Ale to detal i egzotyka.

Bardziej chodzi o dostęp do zasobów.

Jak się miga kilkoma diodami w różnych taskach, to nie ma problemu - każdy ma swój pin/port i jest pięknie.

A jak się weźmie program, który coś wyświetla na tft, to nie można np taskowi mierzącemu RSSI pozwolić na zmianę ilości kreseczek na ekranie, bo w tym czasie może task-termometr zmierzył temperaturę i chce zmienić słupek na ekranie. Jak się ich nie pogodzi, to od razu guru meditation się pojawia.

W jednowątkowym kodzie nie ma tych problemów, bo OS nie przełącza kontekstu i nigdy nie wystąpi przerwanie zadań np w połowie rysowania.

 

Edytowano przez kostuch
Link do komentarza
Share on other sites

@kostuch ja przyszedłem do świata mikrokontrolerów trochę z zewnątrz - wielowątkowe aplikacje robiłem jeszcze na 286 i m68k, więc nie musisz mi tłumaczyć na czym to polega. Jeśli uważasz, że wbijać gwoździe to butem lepiej niż młotkiem bo młotkiem się można w palec uderzyć - twoja sprawa. Ale proszę - nie ucz ojca jak się dzieci robi.

Chcesz udowodnić, że jakieś protezy na millis są lepsze od specjalizowanego systemu? Komu?

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.