Czy istnieje sposób, w jaki mogę mieć wiele części programu działających razem bez robienia wielu rzeczy w tym samym bloku kodu?
Jeden wątek czeka na urządzenie zewnętrzne, jednocześnie mrugając diodą LED w innym wątku.
Czy istnieje sposób, w jaki mogę mieć wiele części programu działających razem bez robienia wielu rzeczy w tym samym bloku kodu?
Jeden wątek czeka na urządzenie zewnętrzne, jednocześnie mrugając diodą LED w innym wątku.
Odpowiedzi:
W Arduino nie ma obsługi wieloprocesowej ani wielowątkowej. Możesz jednak zrobić coś blisko wielu wątków za pomocą oprogramowania.
Chcesz spojrzeć na Protothreads :
Protothreads to niezwykle lekkie wątki bez stosu, przeznaczone do systemów o bardzo ograniczonej pamięci, takich jak małe systemy osadzone lub węzły sieci czujników bezprzewodowych. Protothreads zapewniają liniowe wykonywanie kodu dla systemów sterowanych zdarzeniami zaimplementowanych w C. Protothreads mogą być używane z podstawowym systemem operacyjnym lub bez niego w celu zapewnienia blokowania obsługi zdarzeń. Protothreads zapewniają sekwencyjny przepływ kontroli bez skomplikowanych maszyn stanów lub pełnego wielowątkowości.
Oczywiście istnieje tutaj przykład Arduino z przykładowym kodem . To pytanie SO może być również przydatne.
ArduinoThread też jest dobry.
Arduino oparte na AVR nie obsługuje wątków (sprzętowych), nie znam Arduino opartych na ARM. Jednym ze sposobów obejścia tego ograniczenia jest użycie przerwań, zwłaszcza przerwania czasowego. Możesz zaprogramować timer, aby przerywał główną procedurę co tyle mikrosekund, aby uruchomić określoną inną procedurę.
Możliwe jest wykonywanie wielowątkowości po stronie oprogramowania na Uno. Gwintowanie na poziomie sprzętu nie jest obsługiwane.
Aby osiągnąć wielowątkowość, konieczne będzie wdrożenie podstawowego harmonogramu i utrzymanie listy procesów lub zadań w celu śledzenia różnych zadań, które należy uruchomić.
Struktura bardzo prostego harmonogramu nieprzekazującego byłaby następująca:
//Pseudocode
void loop()
{
for(i=o; i<n; i++)
run(tasklist[i] for timelimit):
}
Tutaj tasklist
może być tablica wskaźników funkcji.
tasklist [] = {function1, function2, function3, ...}
Z każdą funkcją formularza:
int function1(long time_available)
{
top:
//Do short task
if (run_time<time_available)
goto top;
}
Każda funkcja może wykonywać osobne zadanie, takie jak function1
wykonywanie manipulacji diodami LED i function2
wykonywanie obliczeń zmiennoprzecinkowych. Każde zadanie (funkcja) będzie odpowiedzialne za przestrzeganie przydzielonego mu czasu.
Mam nadzieję, że to powinno wystarczyć, aby zacząć.
Zgodnie z opisem twoich wymagań:
Wygląda na to, że możesz użyć jednego przerwania Arduino dla pierwszego „wątku” (w rzeczywistości wolałbym to nazwać „zadaniem”).
Przerwania Arduino mogą wywoływać jedną funkcję (Twój kod) na podstawie zdarzenia zewnętrznego (poziom napięcia lub zmiana poziomu na cyfrowym pinie wejściowym), które natychmiast uruchomią twoją funkcję.
Jednak jedną ważną rzeczą, o której 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ć żadnego delay()
wywołania ani żadnego innego interfejsu API, który byłby zależny delay()
).
Jeśli musisz aktywować długie zadanie po wyzwoleniu zdarzenia zewnętrznego, możesz potencjalnie użyć harmonogramu kooperacyjnego i dodać do niego nowe zadanie z funkcji przerwania.
Drugą ważną kwestią dotyczącą przerwań jest to, że ich liczba jest ograniczona (np. Tylko 2 w UNO). Jeśli więc zaczniesz mieć więcej zdarzeń zewnętrznych, będziesz musiał zaimplementować pewnego rodzaju multipleksowanie wszystkich wejść w jednym, a funkcja przerwania określi, który zmultipleksowany inut był rzeczywistym wyzwalaczem.
Prostym rozwiązaniem jest użycie harmonogramu . Istnieje kilka implementacji. W skrócie opisano tę, która jest dostępna 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 polecenie taskSetup, a następnie wielokrotnie wywoła taskLoop, tak jak działa szkic Arduino. Zadanie ma swój własny stos. Rozmiar stosu jest parametrem opcjonalnym. Domyślny rozmiar stosu to 128 bajtów.
Aby umożliwić przełączanie kontekstu, zadania muszą wywoływać fed () lub delay () . Istnieje również makro obsługi oczekiwania na warunek.
await(Serial.available());
Makro to cukier składniowy dla następujących elementów:
while (!(Serial.available())) yield();
Oczekiwania można także 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);
}
Aby uzyskać więcej informacji, zobacz przykłady . Istnieją przykłady od wielokrotnego mrugnięcia diody LED do przycisku odbicia i prostej powłoki z odczytem nieblokującego wiersza poleceń. Szablony i przestrzenie nazw mogą służyć do uporządkowania i ograniczenia kodu źródłowego. Poniższy szkic pokazuje, jak używać funkcji szablonu do wielokrotnego mrugania. 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ż punkt odniesienia, aby dać wyobrażenie o wydajności, tj. Czas na rozpoczęcie zadania, zmianę kontekstu itp.
Na koniec istnieje kilka klas wsparcia dla synchronizacji i komunikacji na poziomie zadań; Kolejka i semafor .
Z poprzedniej inkantacji tego forum następujące pytanie / odpowiedź zostało przeniesione do inżynierii elektrycznej. Posiada przykładowy kod arduino do mrugania diody LED za pomocą przerwania timera podczas używania głównej pętli do szeregowego we / wy.
Repost:
Przerwania są powszechnym sposobem wykonywania zadań, gdy dzieje się coś innego. W poniższym przykładzie dioda LED miga bez użycia delay()
. Przy każdym Timer1
uruchomieniu isrBlinker()
wywoływana jest procedura obsługi przerwań (ISR) . Włącza / wyłącza diodę LED.
Aby pokazać, że mogą się zdarzyć 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 wspólnych bibliotek jest implementowanych przy użyciu ISR.
Do tego tematu doszedłem również przy wdrażaniu matrycowego wyświetlacza LED.
Jednym słowem, możesz zbudować harmonogram odpytywania za pomocą funkcji millis () i przerwania timera w Arduino.
Proponuję następujące artykuły 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
Możesz także wypróbować moją bibliotekę ThreadHandler
https://bitbucket.org/adamb3_14/threadhandler/src/master/
Używa harmonogramu przerywającego, aby umożliwić przełączanie kontekstu bez przekazywania wydajności () lub opóźnienia ().
Stworzyłem bibliotekę, ponieważ potrzebowałem trzech wątków i potrzebowałem dwóch z nich, aby działały dokładnie w tym samym czasie, bez względu na to, co robili inni. Pierwszy wątek obsługiwał komunikację szeregową. Drugim działał filtr Kalmana wykorzystujący mnożenie macierzy zmiennoprzecinkowej za pomocą biblioteki Eigen. Trzeci to szybka nić pętli sterującej, która musiała być w stanie przerwać obliczenia macierzy.
Każdy cykliczny wątek ma priorytet i kropkę. Jeśli wątek o wyższym priorytecie niż bieżący wątek osiągnie swój następny czas wykonania, program planujący wstrzyma bieżący wątek i przełączy się na wyższy. Po zakończeniu wykonywania wątku o wysokim priorytecie program planujący powraca do poprzedniego wątku.
Schemat szeregowania biblioteki ThreadHandler jest następujący:
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 poprzez createThread i funkcję lambda
Thread* myThread = createThread(priority, period, offset,
[]()
{
//code to run
});
Obiekty wątków automatycznie łączą się z uchwytem wątków podczas ich tworzenia.
Aby rozpocząć wykonywanie utworzonych obiektów wątków, wywołaj:
ThreadHandler::getInstance()->enableThreadExecution();
A oto kolejna biblioteka wielozadaniowości współpracującej z mikroprocesorem - PQRST: kolejka priorytetowa do wykonywania prostych zadań.
W tym modelu wątek jest zaimplementowany jako podklasa a Task
, która jest planowana na pewien czas w przyszłości (i być może planowana w regularnych odstępach czasu, jeśli, jak to zwykle bywa, podklasy LoopTask
). run()
Metoda obiektu jest wywoływana, gdy zadanie staje się wymagalne. run()
Metoda ma pewne należytą pracę, a następnie zwraca (jest to spółdzielnia bit); zazwyczaj będzie utrzymywał maszynę stanu do zarządzania swoimi działaniami przy kolejnych wywołaniach (trywialnym przykładem jest light_on_p_
zmienna w przykładzie poniżej). Wymaga to drobnego przemyślenia sposobu organizacji kodu, ale okazało się bardzo elastyczne i niezawodne przy dość intensywnym użyciu.
Jest agnostyczny w stosunku do jednostek czasu, więc jest równie szczęśliwy, biegając w jednostkach millis()
co micros()
, lub w dowolnym innym dogodnym tiku.
Oto program „blink” zaimplementowany przy użyciu tej biblioteki. Pokazuje to tylko jedno uruchomione zadanie: inne zadania byłyby zazwyczaj tworzone i uruchamiane w ramach 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());
}
run()
wywołaniu metody nie jest ona przerywana, więc jest odpowiedzialna za jej szybkie zakończenie. Zazwyczaj jednak wykona swoją pracę, a następnie przełoży się (ewentualnie automatycznie, w przypadku podklasy LoopTask
) na pewien czas w przyszłości. Powszechnym wzorem dla zadania jest utrzymanie pewnej wewnętrznej maszyny stanów (trywialnym przykładem jest light_on_p_
powyższy stan), aby zachowywała się odpowiednio, gdy będzie następna.
run()
. Jest to w przeciwieństwie do wątków kooperacyjnych, które mogą uzyskać wydajność procesora, np. Przez wywołanie yield()
lub delay()
. Lub wątki zapobiegawcze, 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 prawdziwych wątków, które dają procesor, jest w porządku. Blokowanie zadań RtC nie jest.