Jak mogę utworzyć wiele działających wątków?

Czy istnieje sposób, aby wiele części programu działało razem bez wykonywania wielu czynności w tym samym bloku kodu?

Jeden wątek oczekuje na urządzenie zewnętrzne, jednocześnie migając diodą LED w innym wątku.

Komentarze

  • Powinieneś najpierw zadać sobie pytanie, czy naprawdę potrzebujesz wątków. Timery mogą już być odpowiednie dla twoich potrzeb i są natywnie obsługiwane w Arduino.
  • Możesz również sprawdzić Uzebox. Jest to ' konsola do gier wideo z dwoma układami homebrew. Tak więc, chociaż nie jest to ' dokładnie Arduino, cały system jest zbudowany na przerwaniach. Tak więc dźwięk, wideo, elementy sterujące itp. Są sterowane przerwaniami, podczas gdy program główny nie ' nie musi się o to martwić. To może być dobre odniesienie.

Odpowiedź

Nie ma obsługi wielu procesów ani wielowątkowości Arduino. Możesz jednak zrobić coś w pobliżu wielu wątków za pomocą jakiegoś oprogramowania.

Chcesz przyjrzeć się Protothreads :

Prototreads to wyjątkowo lekkie wątki bez stosu przeznaczone do systemów o bardzo ograniczonej pamięci, takich jak małe systemy wbudowane lub węzły sieci czujników bezprzewodowych. Protothreads zapewniają liniowe wykonanie kodu dla systemów sterowanych zdarzeniami zaimplementowanych w C. Protothreads mogą być używane z lub bez bazowego systemu operacyjnego w celu zapewnienia blokowania obsługi zdarzeń. Prototreads zapewnia sekwencyjny przepływ sterowania bez skomplikowanych maszyn stanowych lub pełnej wielowątkowości.

Oczywiście istnieje przykład Arduino tutaj z przykładowym kodem . To pytanie SO też może być przydatne.

ArduinoThread to też dobry.

Komentarze

Odpowiedź

Arduino oparte na AVR nie obsługuje (sprzętowych) wątków, nie jestem zaznajomiony z Arduino opartymi na ARM. Jednym ze sposobów obejścia tego ograniczenia jest użycie przerwań, zwłaszcza przerwań czasowych. Możesz zaprogramować licznik czasu tak, aby przerywał główną procedurę co kilka mikrosekund, aby uruchamiał określoną inną procedurę.

http://arduino.cc/en/Reference/Interrupts

Odpowiedź

Istnieje możliwość wielowątkowości po stronie oprogramowania na Uno. Wątkowanie na poziomie sprzętu nie jest obsługiwane.

Aby osiągnąć wielowątkowość, będzie wymagało implementacji podstawowego harmonogramu i utrzymywania procesu lub listy zadań w celu śledzenia różnych zadań, które należy uruchomić.

Struktura bardzo prostego programu planującego z wywłaszczaniem wyglądałaby tak:

//Pseudocode void loop() { for(i=o; i<n; i++) run(tasklist[i] for timelimit): } 

Tutaj tasklist może być tablicą wskaźników funkcji.

tasklist [] = {function1, function2, function3, ...} 

Z każdą funkcją w postaci:

int function1(long time_available) { top: //Do short task if (run_time<time_available) goto top; } 

Każda funkcja może wykonywać oddzielne zadanie, takie jak function1 wykonując manipulacje LED i function2 wykonując obliczenia typu float. Obowiązkiem każdego zadania (funkcji) będzie przestrzeganie przydzielonego mu czasu.

Miejmy nadzieję, że to wystarczy, aby zacząć.

Komentarze

  • Nie jestem pewien, czy będę mówić o ” wątkach ” przy korzystaniu z nie wywłaszczającego harmonogramu. Nawiasem mówiąc, taki harmonogram istnieje już jako biblioteka arduino: arduino.cc/en/Reference/Scheduler
  • @jfpoilpret – Cooperative wielowątkowość to prawdziwa rzecz.
  • Tak, ' masz rację! Mój błąd; to było tak dawno temu, że nie miałem do czynienia z wielowątkowością opartą na współpracy, że moim zdaniem wielowątkowość musi być wywłaszczeniowa.

Odpowiedź

Zgodnie z opisem Twoich wymagań:

  • jeden wątek czeka na urządzenie zewnętrzne
  • jeden wątek miga diodą LED

Wygląda na to, że możesz użyć jednego przerwania Arduino dla pierwszego „wątku” (właściwie nazwałbym to „zadaniem”).

Przerwania Arduino mogą wywołać jedną funkcję (twój kod) w oparciu o zewnętrzną zdarzenie (poziom napięcia lub zmiana poziomu na cyfrowym pinie wejściowym), które natychmiast uruchomi twoją funkcję.

Jednak jednym ważnym punktem, o którym należy pamiętać w przypadku przerwań, jest to, że wywoływana funkcja powinna być tak szybka, jak to możliwe (zazwyczaj nie powinno być wywołania delay() ani żadnego innego interfejsu API, który zależałby od delay()).

Jeśli masz długie zadanie do aktywacji po wyzwoleniu zdarzenia zewnętrznego, możesz potencjalnie użyć harmonogramu współpracy i dodać do niego nowe zadanie z funkcji przerywania.

Druga ważna kwestią przerwań jest to, że ich liczba jest ograniczona (np. tylko 2 w UNO). Więc jeśli zaczniesz mieć więcej zdarzeń zewnętrznych, będziesz musiał zaimplementować jakiś rodzaj multipleksowania wszystkich wejść w jedno i mieć funkcję przerywania, aby określić, który multipleksowany inut był rzeczywistym wyzwalaczem.

Odpowiedź

Prostym rozwiązaniem jest użycie harmonogramu . Istnieje kilka implementacji. Opisuje to w skrócie jeden, który jest dostępny dla płyt opartych na AVR i SAM. Zasadniczo pojedyncze połączenie rozpocznie zadanie; „szkic w szkicu”.

#include <Scheduler.h> .... void setup() { ... Scheduler.start(taskSetup, taskLoop); } 

Scheduler.start () doda nowe zadanie, które uruchomi zadanie taskSetup raz, a następnie wielokrotnie wywoła taskLoop, tak jak Szkic Arduino działa. Zadanie ma własny stos. Rozmiar stosu jest opcjonalnym parametrem. Domyślny rozmiar stosu to 128 bajtów.

Aby umożliwić zmianę kontekstu, zadania muszą wywoływać yield () lub delay ( ) . Istnieje również makro pomocnicze do oczekiwania na warunek.

await(Serial.available()); 

Makro jest cukrem składniowym w następujących przypadkach:

while (!(Serial.available())) yield(); 

Można również oczekiwać służy do synchronizacji zadań. Poniżej znajduje się przykładowy fragment:

volatile int taskEvent = 0; #define signal(evt) do { await(taskEvent == 0); taskEvent = evt; } while (0) ... void taskLoop() { await(taskEvent); switch (taskEvent) { case 1: ... } taskEvent = 0; } ... void loop() { ... signal(1); } 

Więcej informacji można znaleźć w przykładach . Istnieją przykłady od migania wielu diod LED do przycisku odbicia i prostej powłoki z nieblokującym odczytem wiersza poleceń. Szablony i przestrzenie nazw mogą służyć do tworzenia struktury i ograniczania kodu źródłowego. Poniżej szkic pokazuje, jak używać funkcji szablonu do wielokrotnego mrugnięcia. Wystarczy 64 bajty na stos.

#include <Scheduler.h> template<int pin> void setupBlink() { pinMode(pin, OUTPUT); } template<int pin, unsigned int ms> void loopBlink() { digitalWrite(pin, HIGH); delay(ms); digitalWrite(pin, LOW); delay(ms); } void setup() { Scheduler.start(setupBlink<11>, loopBlink<11,500>, 64); Scheduler.start(setupBlink<12>, loopBlink<12,250>, 64); Scheduler.start(setupBlink<13>, loopBlink<13,1000>, 64); } void loop() { yield(); } 

Istnieje również test porównawczy , który daje wyobrażenie o wydajności, tj. aby rozpocząć zadanie, przełączyć kontekst, itp.

Na koniec jest kilka klas pomocniczych do synchronizacji i komunikacji na poziomie zadań; Kolejka i Semafor .

Odpowiedź

Do tego tematu doszedłem również podczas wdrażania matrycowego wyświetlacza LED.

Jednym słowem , możesz zbudować harmonogram odpytywania, używając funkcji millis () i przerwania timera w Arduino.

Proponuję następujące artykuły od Billa Earla:

https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview

https://learn.adafruit.com/multi-tasking-the-arduino-part-2/overview

https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview

Odpowiedź

Z poprzedniego inkantacji na tym forum następujące pytanie / odpowiedź zostało przeniesione do działu Elektrotechnika. Zawiera przykładowy kod arduino do mrugania diodą LED przy użyciu przerwania timera podczas używania głównej pętli do szeregowego IO.

https://electronics.stackexchange.com/questions/67089/how-can-i-control-things-without-using-delay/67091#67091

Repost:

Przerwania to powszechny sposób załatwiania spraw, gdy dzieje się coś innego. W poniższym przykładzie dioda LED miga bez użycia delay(). Ilekroć Timer1 uruchamia się, wywoływana jest procedura obsługi przerwań (ISR) isrBlinker(). Włącza / wyłącza diodę LED.

Aby pokazać, że jednocześnie mogą się wydarzyć inne rzeczy, loop() wielokrotnie zapisuje foo / bar do portu szeregowego niezależnie od migania diody LED .

#include "TimerOne.h" int led = 13; void isrBlinker() { static bool on = false; digitalWrite( led, on ? HIGH : LOW ); on = !on; } void setup() { Serial.begin(9600); Serial.flush(); Serial.println("Serial initialized"); pinMode(led, OUTPUT); // initialize the ISR blinker Timer1.initialize(1000000); Timer1.attachInterrupt( isrBlinker ); } void loop() { Serial.println("foo"); delay(1000); Serial.println("bar"); delay(1000); } 

To jest bardzo proste demo. ISR mogą być znacznie bardziej złożone i mogą być wyzwalane przez timery i zdarzenia zewnętrzne (piny). Wiele popularnych bibliotek jest zaimplementowanych przy użyciu ISR.

Odpowiedź

Możesz także wypróbować moją bibliotekę ThreadHandler.

https://bitbucket.org/adamb3_14/threadhandler/src/master/

Wykorzystuje harmonogram przerywania, aby umożliwić przełączanie kontekstu bez konieczności przekazywania yield () lub delay ().

Bibliotekę stworzyłem, ponieważ potrzebowałem trzech wątków, a dwa z nich były uruchamiane w określonym czasie, niezależnie od tego, co robili inni. Pierwszy wątek obsługiwał komunikację szeregową. Drugi polegał na uruchomieniu filtru Kalmana przy użyciu mnożenia macierzy typu float z biblioteką Eigen. Trzecim był wątek pętli sterowania szybkim prądem, który musiał być w stanie przerwać obliczenia macierzy.

Jak to działa

Każdy cykliczny wątek ma priorytet i okres. Jeśli wątek o wyższym priorytecie niż aktualnie wykonywany wątek osiągnie swój następny czas wykonania, program planujący wstrzyma bieżący wątek i przełączy się na ten o wyższym priorytecie. Gdy wątek o wysokim priorytecie zakończy swoje działanie, harmonogram przełącza się z powrotem do poprzedniego wątku.

Reguły planowania

Schemat planowania biblioteki ThreadHandler jest następujący:

  1. Najpierw najwyższy priorytet.
  2. Jeśli priorytet jest taki sam, to wątek z najwcześniejszym terminem zostanie wykonany jako pierwszy.
  3. Jeśli dwa wątki mają ten sam termin ostateczny, pierwszy utworzony wątek zostanie wykonany jako pierwszy.
  4. Wątek może zostać przerwany tylko przez wątki o wyższym priorytecie.
  5. Gdy wątek jest wykonywany, blokuje wykonywanie wszystkich wątków o niższym priorytecie do czasu powrotu funkcji run.
  6. Funkcja pętli ma priorytet -128 w porównaniu z wątkami ThreadHandler.

Jak używać

Wątki można tworzyć poprzez dziedziczenie c ++

 class MyThread : public Thread { public: MyThread() : Thread(priority, period, offset){} virtual ~MyThread(){} virtual void run() { //code to run } }; MyThread* threadObj = new MyThread();  

Lub przez createThread i funkcję lambda

 Thread* myThread = createThread(priority, period, offset, []() { //code to run });  

Obiekty wątku automatycznie łączą się z ThreadHandlerem po ich utworzeniu.

Aby rozpocząć wykonywanie utworzonych obiektów wątku, wywołaj:

 ThreadHandler::getInstance()->enableThreadExecution();  

An swer

A oto kolejna wielozadaniowa biblioteka oparta na mikroprocesorach – PQRST: kolejka priorytetowa do wykonywania prostych zadań.

W tym modelu, wątek jest zaimplementowany jako podklasa Task, która jest zaplanowana na jakiś czas w przyszłości (i prawdopodobnie zmieniana w regularnych odstępach czasu, jeśli, jak to jest powszechne, podklasy ). Metoda run() obiektu jest wywoływana, gdy zadanie stanie się terminem. Metoda run() wykonuje należną pracę, a następnie zwraca (to jest bit współpracy); zazwyczaj będzie utrzymywał jakąś maszynę stanową do zarządzania swoimi działaniami przy kolejnych wywołaniach (trywialnym przykładem jest zmienna light_on_p_ w poniższym przykładzie). Wymaga to ponownego przemyślenia sposobu uporządkuj swój kod, ale okazał się bardzo elastyczny i niezawodny w dość intensywnym użyciu.

Jest niezależny od jednostek czasu, więc równie dobrze działa w jednostkach millis() jako micros() lub jakikolwiek inny wygodny wybór.

Oto program „blink” zaimplementowany przy użyciu tej biblioteki. Pokazuje tylko jedno uruchomione zadanie: inne zadania są zwykle tworzone i uruchamiane w ciągu setup().

#include "pqrst.h" class BlinkTask : public LoopTask { private: int my_pin_; bool light_on_p_; public: BlinkTask(int pin, ms_t cadence); void run(ms_t) override; }; BlinkTask::BlinkTask(int pin, ms_t cadence) : LoopTask(cadence), my_pin_(pin), light_on_p_(false) { // empty } void BlinkTask::run(ms_t t) { // toggle the LED state every time we are called light_on_p_ = !light_on_p_; digitalWrite(my_pin_, light_on_p_); } // flash the built-in LED at a 500ms cadence BlinkTask flasher(LED_BUILTIN, 500); void setup() { pinMode(LED_BUILTIN, OUTPUT); flasher.start(2000); // start after 2000ms (=2s) } void loop() { Queue.run_ready(millis()); } 

Komentarze

  • To są zadania typu „od początku do końca”, prawda?
  • @EdgarBonet I ' Nie jestem pewien, co masz na myśli. Po wywołaniu metody run() nie jest ona przerywana, więc odpowiada za jej szybkie zakończenie. Zwykle jednak ' wykona swoją pracę, a następnie sam zmieni harmonogram (prawdopodobnie automatycznie, w przypadku podklasy LoopTask) dla niektórych czas przyszły. Typowym wzorcem dla zadania jest utrzymywanie jakiegoś wewnętrznego automatu stanowego (trywialnym przykładem jest powyższy stan light_on_p_), tak aby zachowywał się odpowiednio, gdy zbliża się następny termin.
  • Tak, są to zadania typu run-to-complete (RtC): żadne zadanie nie może zostać uruchomione, zanim bieżące nie zakończy swojego wykonywania, zwracając z run(). W przeciwieństwie do wątków współpracujących, które mogą przynieść procesor do CPU np. Wywołując yield() lub delay(). Lub wątki wyprzedzające, które można zaplanować w dowolnym momencie. Uważam, że to rozróżnienie jest ważne, ponieważ zauważyłem, że wiele osób, które przychodzą tutaj w poszukiwaniu wątków, robi to, ponieważ wolą pisać kod blokujący niż maszyny stanowe. Blokowanie rzeczywistych wątków, które generują procesor, jest w porządku. Blokowanie zadań RtC nie jest.
  • @EdgarBonet To ' to przydatne rozróżnienie, tak. Uznałbym zarówno ten styl, jak i wątki typu yield za po prostu różne style wątków kooperacyjnych, w przeciwieństwie do wątków wyprzedzających, ale prawdą jest, że ' wymagają innego podejścia kodowanie ich. Byłoby interesujące zobaczyć przemyślane i dogłębne porównanie różnych podejść wymienionych tutaj; jedna fajna biblioteka niewymieniona powyżej to protothreads . W obu przypadkach mogę krytykować, ale także chwalić. Wolę (oczywiście) moje podejście, ponieważ wydaje się ono najbardziej jednoznaczne i nie wymaga dodatkowych stosów.
  • (poprawka: wspomniano o protothreads w @sachleen ' odpowiedź )

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *