Download: from GitHub
Oft ist es so, dass große Programme irgendwann unübersichtlich werden. Ab diesen Punkt hilft einem nur noch Abstraktion des Codes und eine Zerlegung auf mehrere Dateien (Tabs oder Libs). Um diesem Prozess zu vereinfachen habe ich eine Lib geschrieben die Threads generiert. Jeder Thread kann zeitbasiert (timebased) oder eventbasiert (eventbased) angelegt werden. Bei den zeitbasierten Threads wird nochmal zwischen Threads mit statischer und dynamischer Ausführzeit unterschieden. Bei den dynamischen Threads kann die Zeit zur Laufzeit des Programmes noch verändert werden. Wenn mehrer Threads angelegt sind, müssen diese nicht mehr einzeln in der Loop Funktion aufgerufen werden. Dazu gibt es eine Run-Funktion (simpleThread_run()) die automatisch, entweder der Reihenfolge nach oder Priorität orientiert die einzelnen Threads aufruft.
Verwendungsmöglichkeiten dieser Lib:
- strukturierter Programmablauf von mehreren Unterprogrammen
- Display aktualisieren (750ms), Buttons auslesen (50ms), Sensorwerte auslesen (500ms), Messerte per Ethernet übertragen (jede Minute)
- Verschiedene Statusanzeigen die Zeitlich gesteuert auf einem LCD angezeigt werden sollen
- Verschachtelte Zeitabläufe, z.B: bei der Fotografie (mehrere Bilder in bestimmten Zeitabfolgen), oder beim Aquarium Beleuchtung an/aus /Ströumungsrichtungen ...
- oder Andere Funktionen die zyklisch oder Zustands gesteuert ausgeführt werden
Mir ist bewusst das es schon eine ähnliche Lib gibt. Es mir sehr wichtig so wenig wie möglich an Ram zu belegen, daher befinden sich in der Klasse nur Makros. Durch die Makros wird der Quellcode für die Threads beim kompilieren generiert und im Flash-Speicher abgelegt. Je nach Thread Art wird unterschiedliche viel Ram fest belegt.
Allgemein gilt für das Thread Management
Für jeden Thread werden zwei Bit benötigt. In diesen wird gespeichert ob der Thread gestartet/gestoppt ist und ob der Therad zurückgesetzt werden soll. Für den ersten Thread der angelegt wird, werden zwei Byte reserviert. Jeder weitere Thread bis zum achten greift auf diese zwei Byte zurück. Erst der neunte Thread reserviert erneut zwei Byte.
1 - 8 => zwei Byte
9 - 16 => zwei weitere Byte ...
Diese Speicherbelegung trifft auf eventbasierte Threads zu.
zeitbasierter Thread mit statischer Zeit
Auf Basis der algemeinen Belegung, werden hier vier weitere Bytes im Ram belegt
zeitbasierter Thread mit dynamischer Zeit
Auf Basis der algemeinen Belegung, werden hier acht weitere Bytes im Ram belegt
Initialisierung von Threads:
Zuerst muss die simpleThread.h eingebunden werden. Anschließen muss außerhalb von allen existierenden Funktionen die Initialisierung eingeleitet werden. Bei dieser wird angegeben wie viel neue Threads angelegt werden sollen. Wenn der Wert über die Anzahl nicht der existierenden Thread Anzahl entspricht, treten Fehler auf, die sich dadurch außern, dass das Programm einfach durchläuft ohne start / stop / wait_time / events.
Nach dem festlegen der Thread Anzahl, werden die einzelnen Threads angelegt. Jeder Thread muss zuerst ein eindeutige ID bekommen, die von 0 an aufwärts gezählt werden muss.
Befehle:
- simpleThread_init( anzahl an threads )
- simpleThread_new_timebased_static(id, thread_name, wait_time)
- simpleThread_new_timebased_dynamic(id, thread_name, wait_time)
- simpleThread_new_eventbased(id, thread_name)
Anschließen wird die Priorität festgelegt. Hierzu werden die Thread namen in einer Liste eingetragen. Der oberste Eintrag hat die größte Priorität. Dies ist in den Beispielen am besten verdeutlicht.
Für jeden Therad der oben angelegt wurde, muss zudem eine Setup- und Loop-Funktion angelegt werden. Die Loop Funktion besitzt einen Rückgabe Wert, auf den weiter unten eingegangen wird. Dieser kann gesetzt werden, muss aber nicht.
Der erstellte Code könnte so aussehen:
void simpleThread_setup(thread_name)
{
}
boolean simpleThread_loop(thread_name)
{
}
Aufbau eines Threads:
Jeder Thread besteht aus einer SETUP und LOOP Funktion. Die Setup Funktion wird einmal zu Beginn aufgerufen. Anschließend wird ausschließlich die Loop Funktion ausgeführt. Ein Thread kann gestartet und gestoppt werden. Mit dem Reset Befehl setzt man einen Thread zurück, sprich beim nächsten Aufruf wird die Setup-Funktion erneut aufgerufen. Jeder Thread kann mit "isRun" überprüft werden, ob dieser gestartet oder gestoppt ist. Zudem gibt es die Möglichkeit
Befehle:
- simpleThread_start(thread_name) oder simpleThread_event_start(thread_name)
- simpleThread_stop(thread_name)
- simpleThread_stopStable(thread_name)
- simpleThread_reset(thread_name) oder simpleThread_event_reset
- simpleThread_restart(therad_name) oder simpleThread_event_restart
- simpleThread_loop(thread_name)
- simpleThread_setup(thread_name)
- simpleThread_stable(thread_name)
- simpleThread_run(_simpleThread_M_without_priority oder _simpleThread_M_with_priority)
Den Programmablauf eines einzelnen Threads kann man sich wie folgt vorstellen:
Zur Vereinfachung gibt es auch die Möglichkeit die Setup- oder Loop- Funktion eines Threats direkt aufzurufen. Dafür wurden folgende Befehle eingeführt:
- simpleThread_call(thread_name)
- simpleThread_call_loop(thread_name)
- simpleThread_call_setup(thread_name)
- simpleThread_call_stable(thread_name)
Da es Event- Zeitbasierte Threads gibt wird der Ablauf wie im folgenden Bild generiert. Der Thread der dort eingezeichnet ist, wurde vom Ablauf her weiter oben schon beschrieben.
Dieser Ablauf der nun Beschrieb wurde ist in den gesamten Ablauf eingebettet. Der gesamte Ablauf kann der Reihenfolge nach oder mittels einer Priorität durchlaufen werden.
der Reihenfolge nach (A):
Bei jedem Thread wird der Reihenfolge nach überprüft ob dieser gestartet wurde und ob bei zeitbasierten Threads die Wartezeit abgelaufen ist oder bei eventbasierten Threads das ein Event gestartet wurde. Wenn keine Bedingung erfüllt ist, wird der nächste Thread überprüft. Ansonsten wird der Thread ausgeführt.
mittels Priorität (B)
Es findet die gleiche Überprüfung wie schon unter "der Reihenfolge nach (A)" beschrieben statt. Der Unterschied liegt in der Überprüfung der Bedingungen. Wenn keine Bedingung erfüllt ist, passiert das gleich wie unter (A). Wenn der Thread ausgeführt wird, beginnt anschließend die Schleife von neuem. Je nach Rückgabewert der Thread-Loop-Funktion. Ist der Rückgabewerte "null" oder "false" startet die Schleife die die einzelnen Threads aufruft neu. Wenn der Wert "true" ist, beginnt die Prozedur von neuem.
Das System kann man wie folgt verstehen:
Im Anhang ist ein Archiv in dem sich die simpleThreads Lib befinden. Der Ordner muss nach dem Enpacken in das libraries Verzeichnis kopiert werden. Wenn die Arduino Software anschließend gestartet wird kann ein Beispiel unter Datei->Beispiele->Beispiele->simpleThread geöffnet werden. Im Moment werden 5 Beispiele mit angeboten. Ich werde die Beispiel in der nächsten Zeit noch sinnvoller gestalten. Im Moment zeigen diese nur, wie man die Funktionen anlegt.
Beispiel Code
#include <simpleThread.h>
#define _sT_cnt _sT_cnt_1
simpleThread_init(_sT_cnt ); //init one new thread
simpleThread_new_timebased_static (_sT_P1 , _sT_millis, 1000 , _sT_start , Loop_1);
void setup()
{
Serial.begin(9600);
simpleThread_initSetup(_sT_cnt);
}
void loop()
{
simpleThread_run(_sT_priority);;
}
//global var
uint8_t g_thread_counter = 0;
//new setup for loop_1
void simpleThread_setup(Loop_1)
{
}
// new loop function for thread loop_1
boolean simpleThread_loop(Loop_1)
{
Serial.println(g_thread_counter); // output counter value every 1000 ms
g_thread_counter++; // increment counter
return false;
}