Projektvorstellung: simpleThreads auf dem Arduino mit Hilfe von Makros

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;    
}
1 Like

Hi,

erstmal Daumen hoch für die geleistete Arbeit. Solche Projekte bringen meiner Meinung nach die arduino::std::Lib ein Stückchen weiter.

Mir sind zwei Punkte nicht ganz klar:

  1. Wo siehst du den Vorteil von Makros? Wie du schon geschrieben hast, werden Sie zur Laufzeit an die Stellen kopiert wo sie aufgerufen werden.
    D. h. es wird kein Speicher (weder SRAM noch Flash) eingespart.

  2. Die 4 Byte+3 Bit pro Thread ist für mich nicht nachvollziehbar. Threadname: (uint8_t) 1 Byte, Timer: (unsigned long) 4 Byte = 5 Byte pro Thread.
    Dazu kommen noch etliche Rechnungen mit statischen Zahlen (7,1,0) und LOOP uint8_t. Aber das müssten 1-2 Byte (je nachdem wie viele gleichzeitig
    benötigt werden, habs mir nicht zu genau angeschaut) unabhängig von der Anzahl der Threads sein.
    Könntest du es vielleicht näher erläutern? Die 3 Bit interessieren mich besonders :slight_smile:

Ansonsten finde ich deinen Einsatz der Makros sehr kreativ. Mit drei functions viele Threadfunktionen abzubilden ist nicht ohne.

Interessant wäre auch die Auswertung ab wie viel Threads überhaupt sich lohnt so ein Konstrukt zu verwenden (Laufzeit, Speicherbedarf).
Denn für zwei Threads z. B. könnte ich mir schlankere Lösung vorstellen.

Grüße
Nighti

1 Like

Hi, das stimmt, die Berechnungen habe ich nicht mit eingerechnet und den Namen des Threads auch nicht. Wobei wie die Speicherauslastung beim Namen ist kann ich nicht genau sagen, da es ja nur ein Zeiger auf eine Funktion ist.

In den 3 Bit des Threads wird gespeichert: start/stop , reset, first_init.

Die Variablen werden erstmal als uint8_t angelegt, also bei einem Thread werden 3 Byte belegt. Aber wenn 8 Threads verwendet werden sind es immer noch 3 Byte die den Status speichern. Erst ab dem 9 Thread müssen 6 Byte belegt werden. Daher werden "grob gesagt" 3 Bit pro Thread für das Management im Hintergrund benötigt. Vielleicht fällt mir noch eine bessere Variante ein um die Divisionen zu minimieren.

Der Vorteil von Makros ist, das ich den Quellcode den ich überall einfügen müsste durch verhältnismäßig einfache Aufrufe einheitlich darstellen kann. Zudem hat der Programmierer nicht mehr das Problem Änderungen an allen Stellen im Programm durchführen wenn es um eine Erweiterung geht. Ich bin mir nicht sicher ab wann ein Objekt das ich für jeden Thread erstellen könnte, kleiner wäre als der direkte Funktionsaufruf.

Zum Aufbau des Konstrukts:
Ich verwende dieses Konstrukt mit einem LCDMenu in dem ich für unterschiedliche Unterpunkte Threads starten und Stoppen kann, je nachdem wie ich diese gerade brauche. Bei größeren Projekten gefiel mir nicht, dass ich um eine Unterfunktion zu starten oder zu Stoppen immer gleich eine Variable(1 Byte) angelegt hatte. Zudem, wenn man ein Menü mit 30 Buttons für verschiedene Funktionen hat, kommt man schnell mit den Variabel Namen durcheinander und es wird unübersichtlich.

Für kleine Projekte lohnt sich das nur, wenn man die Möglichkeit zur Erweiterung für später offen halten möchte.

Edit:
Es lässt mir ja so gerade keine Ruhe. Also ich habe nun nochmal versucht herauszubekommen, wie lange ein Leerer Thread benötigt um ausgeführt zu werden. Dafür habe ich mein Beispiel Umgeschrieben, so das 20 Threads ausgeführt werden.
Bei 20 Threads ist die Auszuführende Zeit <1 ms.

Flash-Speicher:

  • Einbindung der Thread Struktur für einen Thread: 364 Byte
  • Einbindung für jeden weiteren Thread: 106 Byte

Sorry für die schlaflose Nacht, aber als Student bist es ja gewohnt :wink:

Das mit den 3 Bits ist nun klarer, danke. Um schnell Bits auslesen zu können ist immer noch Shiften die bessere Wahl.
Könntest auch direkt die Funktionen bitWrite(), bitSet(), bitClear() verwenden. Oder Makros daraus erstellen :slight_smile:

Bei größeren Projekten gefiel mir nicht, dass ich um eine Unterfunktion zu starten oder zu Stoppen immer gleich eine Variable(1 Byte) angelegt hatte..

Ja, das meinte ich mit der Skalierung. Bei 2,3,4 Threads ist der Verwaltungsaufwand in Bits zu speichern nicht angemessen,
aber bei 20,30,40 Threads sieht die Sache schon ganz anders aus. Wenn du dafür eine Grafik erstellen könntest, dann
kriegst du potenziell mehr Anhänger der Lib.

Ich habe ein Update des ersten Threads durchgeführt. Die Datei die im Anhang ist, wurde ebenfalls überarbeitet.

Änderungen:

  • die simpleTools Lib wird nicht mehr benötigt
  • die Threads werden nur noch durch Macros und bestehende Arduino Funktionen generiert
  • Vorhandene Divisionen wurden bis auf die Initialisierung entfernt
  • Die Thread Funktionen sehen nun wieder wie Funktionen aus (mit Geschweiften Klammern)
  • Es gibt für das Thread_Setup und die Thread_Loop Schleife zwei getrennte Funktionen, so wie es für ein Arduino Programm üblich ist
  • Threads können mit dynamischen Zeiten erstellt werden
  • Intern: Umstellung auf bitWrite und bitRead

Die Dokumentation einschließlich Grafiken werde ich mal erstellen, wenn ich mir sicher bin das die größten Fehler behoben sind.

Ein letztes Update:

  • Initialisierung weiter vereinfacht (_L_id, _H_id, und Zeit Definitionen entfernt)
  • Divisionen wieder eingefügt, da hierdurch die Initialisierung von einem neuem Thread vereinfacht wird.
    Die Divisionen werden vom Compiler berechnet und als Konstante Zahl in die generierten Funktionen eingesetzt.
  • Beispiel ins Englische übersetzt
  • "default loop time" Definition mit in die Erstellung des Threads integriert.

Nighti:
Sorry für die schlaflose Nacht, aber als Student bist es ja gewohnt :wink:

Das mit den 3 Bits ist nun klarer, danke. Um schnell Bits auslesen zu können ist immer noch Shiften die bessere Wahl.
Könntest auch direkt die Funktionen bitWrite(), bitSet(), bitClear() verwenden. Oder Makros daraus erstellen :slight_smile:

Kannst du mir bitte mal sagen wo und man das getestet wurde? Ich würde sogar sagen das das bitshiften auf einem AVR deutlich langsamer sein kann, als ein Vergleich mit einer Maske. Den je Shift wird ein Takt gebraucht!
Außerdem hab ich mir dazu auch mal das Listing angeschaut und der Compiler baut das gerne mal zu einem Vergleich um! Vermutlich eben weils schneller ist.

1<<7 ist natürlich eine Konstante, die der Compiler gleich aussrechnet, schlau wie er ist.

Hi k4ktus,

hab keine Laufzeittests gemacht wenn du das meinst. Ich kenne es aus der Softwareentwicklung für x86 Chips
und wenn der Compiler dieses Konstrukt erkennt und für Atmega richtig optimiert, dann umso besser.
Falls du Vergleichsbeispiele in C->Assembler hast, könntest sie gerne posten. Denke bin nicht der einzige
der gerne dazu lernt :slight_smile:

Grüße
Nighti

michael_x:
1<<7 ist natürlich eine Konstante, die der Compiler gleich aussrechnet, schlau wie er ist.

Das weiß ich. Mir ging es um die Aussage: Shiften ist eh viel schneller. Und das ist, sorry, bullshit auf einem AVR. Würde der Compiler nicht optimieren würde die Zuweisung eins und zwei mindestens 8 Zyklen brauchen.

eins = 1 << 7;
0000036D LDI R24,0x80 Load immediate
0000036E STD Y+3,R24 Store indirect with displacement
zwei = bitSet(zwei,7);
0000036F LDD R24,Y+2 Load indirect with displacement
00000370 ORI R24,0x80 Logical OR with immediate
00000371 STD Y+2,R24 Store indirect with displacement
00000372 STD Y+2,R24 Store indirect with displacement
drei |= B10000000;
00000373 LDD R24,Y+1 Load indirect with displacement
00000374 ORI R24,0x80 Logical OR with immediate
00000375 STD Y+1,R24 Store indirect with displacement

Update 01.06.2013

  • neue Funktionen zur Lib hinzugefügt

  • simpleThread_new_timebased_static legt einen Thread an der Zeitbasiert arbeitet und eine feste Zeit besitzt

  • simpleThread_new_timebased_dynamic legt einen Thread an der Zeitbasiert arbeitet und eine dynamische Zeit besitzt

  • simpleThread_new_eventbased legt einen Thread an, der nur gestartet wird, wenn ein Event ausgelöst wurde

  • simpleThread_stopStable ruft eine Funktion auf, mit der der Thread in einem stabilen Zustand gestoppt wird

  • simpleThread_all_start

  • simpleThread_all_stop

  • simpleThread_all_reset

  • simpleThread_all_restart

  • simpleThread_group_init Threads können in Gruppen definiert werden

  • simpleThread_group fügt einer Gruppe ein Element hinzu

  • simpleThread_group_start

  • simpleThread_group_stop

  • simpleThread_group_stopStable

  • simpleThread_group_reset

  • simpleThread_group_restart

  • simpleThread_dynamic_setLoopTime verändert bei einem timebased_dynamic Thread die gesetzte Zeit

  • simpleThread_dynamic_getLoppTime ließt die Zeit aus

  • simpleThread_dynamic_setDefaultTime setzt die Zeit auf die als Default definierte Zeit zurück

  • simpleThread_dynamic_restartTimer startet den Zähler neu

  • simpleThread_dynamic_timeToZero führt den Thread sofot aus

  • simpleThread_signal_init hier werden signale definiert jedes signal ist 1 bit groß, für die erste 8 signale wird 1 byte belegt

  • simpleThread_signal definiert ein signal mit id und namen

  • simpleThread_signal_set

  • simpleThread_signal_get

  • simpleThread_signal_reset

  • simpleThread_call hier kann direkt ein Thread aufgerufen werden

  • simpleThread_call_setup ruft das Setup direkt auf

  • simpleThread_call_loop ruft die Loop Funktion direkt auf

  • simpleThread_call_stable ruft die Funktion die bei stopStable aufgerufen wird, direkt auf

  • Funktionsbezeichnungen Einheitlich umbenannt

  • Beispiele überarbeitet

  • Speicherplatz und Ram Belegung weiter minimiert

  • Initialisierung deutlich vereinfacht, gegenüber der ersten Lib Version

  • Dokumentation überarbeitet und mit Grafiken ergänzt, so dass der Ablauf verdeutlicht wird (hoffentlich)

So, erstmal viel erledigt :wink:
Rechtschreibfehler und Unschlüssigkeiten werde ich in den nächsten Tagen Stück für Stück abarbeiten.

Werden eigentlich auch ältere Versionen angeboten? Habe gestern mal diese Lib getestet, verbraucht 2kb mehr als die ältere.

Hi,
ich habe die älteren Versionen nicht mehr auf dem Rechner, da in diesen immer noch kleine Fehler enthalten waren. Bisher hatte ich in meinen Projekten nie ein Problem damit, das zuviel Speicher belegt wird.

Wie viele Threads verwendest du ?

[edit]
Die jetzige Version verbraucht etwas mehr Flash Speicher, da die Initialisierung der Threads vereinfacht wurde. Das es soviel mehr ist, kann ich mir kaum vorstellen.
[/edit]

mit der alten Lib 17.212 und der neuen 19.168

/* *********************************** */
/* Thread_init_and_get_temp  
/* *********************************** */
/* thread setup */
  void simpleThread_setup(Thread_init_and_get_temp)
  {       
  }
/* thread loop */
  boolean simpleThread_loop(Thread_init_and_get_temp)
  {
    if ( !ds.search(addr)) 
    {
      // No more Dallas chips present:
      ds.reset_search();            
      return false;
    }

    // Check for family code which is not DS18B20:
    if ( addr[0] != 0x28) {
      return false;
    }

    // The DallasTemperature library can do all this work for you!
    ds.reset();
    ds.select(addr);
    ds.write(0x44,0);          // start conversion, with parasite power off
    
 
    // Check whether chip is present:
    present = ds.reset();
    ds.select(addr);    
    ds.write(0xBE);             // Read Scratchpad
    for ( dsread = 0; dsread < 9; dsread++) 
    {   // we need 9 bytes of data
      data[dsread] = ds.read();
    }
 
    Temp =  ((data[1] << 8) + data[0] ) * 0.0625;  // 12Bit = 0,0625 C per Bit

    //Überprüfen ob die minimale Temperatur überschritten ist
    if (Temp < temp_min)
    {
      simpleThread_start(Thread_check_temp_warning);
    }
    //Überprüfen ob die maximale Temperatur überschritten ist
    else if (Temp > temp_max)
    {
      simpleThread_start(Thread_check_temp_warning);
    }
    else
    {
      simpleThread_stop(Thread_check_temp_warning);
      digitalWrite(alarm, HIGH);
      
    }
    if (Temp < temp1)
    {
      digitalWrite(heizung, HIGH);

    }
    else if (Temp > temp1)
    {
      digitalWrite(heizung, LOW);

    }
}
  
  
  
/* *********************************** */
/* Thread_check_temp_warning  
/* *********************************** */
/* thread setup */
  //status variable
  boolean alarm_status = 0;
  
  void simpleThread_setup(Thread_check_temp_warning)
  {
    alarm_status = 0;    
  }
/* thread loop */
  boolean simpleThread_loop(Thread_check_temp_warning)
  {
    //signal invertieren, von 0->1 und umgekehrt, jedesmal wenn der Thread aufgerufen wird
    alarm_status = !alarm_status;
    digitalWrite(alarm, alarm_status);    
  }
  

    
/* *********************************** */
/* Thread_wendung
/* *********************************** */

  void simpleThread_setup(Thread_wendung)
  {   
    g_thread_counter = 0;  
  }

  boolean simpleThread_loop(Thread_wendung)
  {  
      g_thread_counter++;
    if(g_thread_counter > wendung) {
  digitalWrite(motorpin1,LOW);
  digitalWrite(motorpin2,HIGH);
    if(LCDML.Timer(g_timer_wait, wendung_dauer))
    {  
      digitalWrite(motorpin1,LOW);
      digitalWrite(motorpin2,LOW);
    }
     }
      // Counter zurücksetzen
    g_thread_counter = 0; 
    simpleThread_restart(Thread_wendung);
  }
/* init thread system */
  simpleThread_init( 3 );   
/* create a new static thread, the id (max 255) must incremented */
  simpleThread_new_timebased_static (0, Thread_init_and_get_temp, 750);
  simpleThread_new_timebased_static (1, Thread_check_temp_warning, 1000);
  simpleThread_new_timebased_static (2, Thread_wendung, 60000); // 10800000=3Std, 7200000=2Std, 3600000=1Std
/* this table contains the function pointer, all threads must be registered here */
/* it is possible to define the priority here */
  FuncPtr g_simpleThread_priority[] = 
  {
    simpleThread(Thread_init_and_get_temp),
    simpleThread(Thread_check_temp_warning),
    simpleThread(Thread_wendung)
  };

Der Code entspricht nun noch der alten Version deines Projektes ?

Also bei mir belegen 5 Threads mit festen Zeiten (simpleThread_new_timebased_static) nur gerade mal 908 Bytes im Flash speicher. Hier müsste ich um auf die 2 KB zu kommen erstmal wesentlich mehr Threads anlegen.

Schick mir mal per PM oder hier im Forum deinen Code der Initialisierung mit der neusten Lib Version.

/* ===============================================
 * Threads anlegen
 * ===============================================
 */
  #define _sT_cnt         _sT_cnt_3
  simpleThread_init(_sT_cnt);    
  
  
  simpleThread_new_timebased_static  (_sT_P2  , _sT_millis, 750    , _sT_start  , Thread_init_and_get_temp); 
  simpleThread_new_timebased_static  (_sT_P3  , _sT_millis, 1000      , _sT_start  , Thread_check_temp_warning);
  simpleThread_new_timebased_static  (_sT_P1  , _sT_millis, 60000      , _sT_start  , Thread_wendung);
      void setup()
  { 
    /* Setup for LcdMenuLib */
    LCDMenuLib_setup(_LCDMenuLib_cnt);
  /* Threads initialisieren */
  simpleThread_initSetup(_sT_cnt);
  void loop()
  {
     /* calls the threads */ 
  simpleThread_run(_sT_priority);
    LCDMenuLib_ButtonAnalog();
    //LCDMenuLib_ButtonSerial();    
    LCDMenuLib_loop();  
  }

Ich kann mir das so nicht erklären, 3 Threads dürften niemals 2 Kb belegen und auch nicht 2KB mehr wie die alte Version.

Selbst das folgenden Beispiel mit 7 Threads zwei Gruppen und ein paar Signalen belegt nur insgesamt 1,3 Kb im Flash. (+ Bootloader größe)

simpleThread_new_timebased_static  (_sT_P1  , _sT_millis, 1000    , _sT_stop  , Thread_A); 
  simpleThread_new_timebased_static  (_sT_P2  , _sT_millis, 50      , _sT_stop  , Thread_B); 
  simpleThread_new_timebased_dynamic (_sT_P3  , _sT_millis, 5535    , _sT_start  , Thread_C); 

  simpleThread_new_eventbased       (_sT_P4, Thread_D);
  simpleThread_new_eventbased       (_sT_P5, Thread_E);
  simpleThread_new_timebased_static (_sT_P6  , _sT_millis, 1000    , _sT_start  , Thread_X);
  simpleThread_new_eventbased       (_sT_P7, Thread_Y);

/ Here is a thread group defined
  simpleThread_group_init(group_one, 2) 
  {
    simpleThread_group(Thread_A),
    simpleThread_group(Thread_C),
  };

// here are some signals defined
  simpleThread_signal_init(2);
  simpleThread_signal(0, signal_from_thread_x_to_y);
  simpleThread_signal(1, signal_from_thread_y_to_x);

Hat sich sonst noch etwas im Programm verändert ?

Nein nichts. nur diese 3 Codes geändert und die neue Lib eingefügt

Hallo,

ich hab mich jetzt mit dieser Library beschaftigt und schaffe es auch, dass ich einen Thread aufrufen und wieder schliessen kann. Leider wird beim aufrufen des Threads die Setup Routine des Threads nicht ausgeführt. Ich nutze gleichzeitig noch die LCDMenuLib und rufe aus einer LCDM_Fnction meinen Thread auf. Wie gesagt, das funktioniert, aber eben leider nur für den Loop-teil.

Mein ThreadAufruf sieht so aus:

     if(LCDML.checkButtons()) {
      if(LCDMenuLib_checkButtonLeft()) {
        LCDMenuLib_resetButtonLeft();
         lcd.setCursor(2,1);
         lcd.write(B10100101);
         lcd.setCursor(8,1);
         lcd.write(B11111110);
         calibration = 1;
       }
       else if(LCDMenuLib_checkButtonRight()) {
        LCDMenuLib_resetButtonRight();
         lcd.setCursor(8,1);
         lcd.write(B10100101);
         lcd.setCursor(2,1);
         lcd.write(B11111110);
         calibration = 0;
       }
       else if(LCDMenuLib_checkButtonEnter()) {
        LCDMenuLib_resetButtonEnter();
          if (calibration == 1){
            calibration = 0;
            simpleThread_start(FUNC_CalibrationRoutine); //...then jump to calibration routine
            LCDML.FuncEnd(1,0,0,0,0,0); // quit this Function
          }
          else {
            LCDML.FuncEnd(1,0,0,0,0,0); // otherwise just quit this Function
          }
       }
     }

Der Thread selbst gestaltet sich wie folgt:

/* 
 ************************************* 
 * Thread: FUNC_CalibrationRoutine  
 ************************************* 
 */

/* Global variables for these functions */
 uint8_t g_func_timer1_info = 0;  // time counter for success message
 uint8_t g_func_timer2_info = 0;  // time counter for cancel message
 byte calibration_done; // store permanently if a re-calibration of Temp2 Sensor has been done or not
 
  /* --------- INIT ---------
     * Initialization of this function
     * is only being done once at the start of the function
     */
 void simpleThread_setup(FUNC_CalibrationRoutine)
  {   
      /* Init Function */
      //g_LCDMenuLib_cfg_initscreen_time = millis() + (120000); // set initscreen waiting time to a high value
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print(F("Kalibrierung!:"));
      calibratePT100_Temp2();
      g_func_timer1_info = 15;         // set waiting time for success message
      g_func_timer2_info = 6;          // set waiting time for cancel messsage
      controlRelay (true);
  }
     
  /* --------- LOOP ----------
     * This is the place for code which is going to be repeated constantly 
     * e.g. a time-diplay or the like.
     */
 boolean simpleThread_loop(FUNC_CalibrationRoutine)
  {    
      lcd.setCursor(3,1);
      lcd.print(F("ERFOLGREICH !"));         // Display a sucess Message
      delay(2000);
      return true;
  }

Bin für jeden Hinweis dankbar.

Grüße,
Jan

Versuch den Thread so aufzubauen das du kein delay verwendest.

Da der Arduino nur einen Prozessor besitzt wird der Code nacheinander ausgeführt. Die Threads laufen nicht parallel ab, daher unterbricht dein delay in der Loop schleife jegliche Ausführung.

Die Lib soll dir helfen Programme ohne "delay" zu schreiben. Tritt das Problem noch auf, wenn alle Delays im Programm entfernt sind ?