Problem mit ESP32 multithreadig

Arbeite mich gerade ins multi-threading des ESP32 etwas ein.

Ich habe dafür den Beispiel-Code hier

TaskHandle_t  Core0TaskHnd ;  
TaskHandle_t  Core1TaskHnd ; 

void setup() 
{
  Serial.begin(9600);  
  xTaskCreatePinnedToCore(CoreTask0,"CPU_0",1000,NULL,1,&Core0TaskHnd,0);
  xTaskCreatePinnedToCore(CoreTask1,"CPU_1",1000,NULL,1,&Core1TaskHnd,1);
}

void loop() 
{
  Serial.print ("Application CPU is on core:");
  Serial.println (xPortGetCoreID());
  delay (500);
}  

void CoreTask0( void * parameter ) 
{ 
  for (;;) 
  { 
    Serial.print("CoreTask0 runs on Core: "); 
    Serial.println(xPortGetCoreID()); 
    yield();
    delay (600);
  }
  
} 

void CoreTask1( void * parameter ) 
{ 
  for (;;) 
  { 
    Serial.print("CoreTask1 runs on Core: "); 
    Serial.println(xPortGetCoreID()); 
    delay (700);
  } 
}

abgeändert in

TaskHandle_t  Core0TaskHnd ;  
TaskHandle_t  Core1TaskHnd ; 

void setup() 
{
  Serial.begin(9600);  
  xTaskCreatePinnedToCore(CoreTask0,"CPU_0",1000,NULL,1,&Core0TaskHnd,0);
  xTaskCreatePinnedToCore(CoreTask1,"CPU_1",1000,NULL,1,&Core1TaskHnd,1);
}

void loop() 
{
  Serial.print ("Application CPU is on core:");
  Serial.println (xPortGetCoreID());
  delay (500);
}  

void CoreTask0( void * parameter ) 
{ 
    Serial.print("CoreTask0 runs on Core: "); 
    Serial.println(xPortGetCoreID()); 
    delay (600);
} 

void CoreTask1( void * parameter ) 
{ 
    Serial.print("CoreTask1 runs on Core: "); 
    Serial.println(xPortGetCoreID()); 
    delay (700);
}

Wie man sehen kann, habe ich die "For"-Schleifen rausgenommen.

Der Beispiel-Code läuft einwandfrei, der abgeänderte nicht.

Warum ich das getan habe?
Nun, ich will Code nicht einfach nur abkupfern sondern die Hintergründe verstehen.

Ich habe erwartet, dass void loop ständig aufgerufen wird, die funktionen "void CoreTask0" und "void CoreTask1" jeweils einmal aufgerufen werden.

Ohne die "For"-Schleifen werden die task-Funktionen verlassen und der ESP stürzt ab. Er macht ständig einen reboot.

Es gibt aber Beispielcode, da wird die "For"-Schleife mit "return" und damit auch der task verlassen

void Task2code( void * pvParameters ){
   Serial.print("Task2 running on core ");
   Serial.println(xPortGetCoreID());
   for(;;){
       float h = dht.readHumidity();
     float t = dht.readTemperature();
      float f = dht.readTemperature(true);
       Serial.print("Temperature: ");
      Serial.print(t);
    Serial.print(" *C \n ");
      if (isnan(h) || isnan(t) || isnan(f)) {
     Serial.println("Failed to read from DHT sensor!");
     return;
   }
     delay(2000);
   }
 }

Ich bräucht Info, wie die task-Functions vom Betriebssytem gehandelt werden. Wann ggf wie oft werden die task-functions aufgerufen?
Warum stürzt der ESP ab, wenn im abgeänderten Beispiel ohne die For-Schleifen eine task-funktion verlassen wird?
Warum müssen die endlos "For"-Schleifen sein?

Warum müssen die endlos "For"-Schleifen sein?

Das ist einfach!

Der Grund:
Damit das Programm(die Task) nicht sofort zu ende ist.

Bei den Tasks muss sichergestellt sein, dass sie nicht verlassen werden.
Das erreichen der letzten schließenden geschweiften Klammer ist gleichbedeutet mit return. Doch durch das Multitasking gibt es keinen gültigen Return-Point deshalb führt return zu einem Absturz.
Der Task muss also eine Endlosschleife sein, oder ohne return beendet werden. Letzteres geht mit dem Aufruf von vTaskDelete(NULL); am Ende des Tasks. Damit wird FreeRTOS mitgeteilt, das der Task ordentlich beendet ist und vom Scheduler nicht mehr aufgerufen werden soll.
Hoffe das war verständlich erklärt.

gruß lorenz

corvus_:
Das erreichen der letzten schließenden geschweiften Klammer ist gleichbedeutet mit return. Doch durch das Multitasking gibt es keinen gültigen Return-Point deshalb führt return zu einem Absturz.

Danke für die Antwort.
Aber da gibt es noch eine Frage.
Ich habe ja in meinem Post einen Beispielcode gezeigt, in dem return benutzt wird, um den task zu verlassen.
Ist der Beispielcode falsch?

protolus:
Ich habe ja in meinem Post einen Beispielcode gezeigt, in dem return benutzt wird, um den task zu verlassen.
Ist der Beispielcode falsch?

Auch in dem Beispielcode führt der Aufruf von return zum Absturz. Was vermutlich gewollt ist, da return nur aufgerufen wird wenn das Auslesen des DHT-Sensors fehlschlägt. Aber die Frage wird dir wohl nur der Autor des Beispielcodes vollständig beantworten können.

corvus_:
Auch in dem Beispielcode führt der Aufruf von return zum Absturz. Was vermutlich gewollt ist, da return nur aufgerufen wird wenn das Auslesen des DHT-Sensors fehlschlägt. Aber die Frage wird dir wohl nur der Autor des Beispielcodes vollständig beantworten können.

O.K.
Danke für die Info.

Ich habe jetzt das nächste Problem:

In meinem kleinen Projekt sollen ja ein Audiosignal kontinuierlich gesampelt werden und die samples dann Blockweise einer FFT zugeführt und das Ergebnis der FFT per WiFi an einen Server geschickt werden.
Die Datenübertragung an den Server ist schon fertig und funzt.
Auch das ganze Drum-Herum um die FFT ist fertig und funzt.
Das sampeln soll auf CPU 1 laufen und die FFT und die Übertragung zum Server auf CPU 0 (da auf CPU 0 auch das ganze WiFi-Zeug läuft).
Ich benutze ein "buffer flip-flop"
Um das zu testen habe ich folgenden Test-Code erstellt:

volatile double t0_vReal[1024];
volatile double t1_vReal[1024];
volatile byte tp;
volatile byte indi;

TaskHandle_t  Core0TaskHnd ;  
TaskHandle_t  Core1TaskHnd ; 

void setup() 
{

  xTaskCreatePinnedToCore(CoreTask0,"CPU_0",1000,NULL,1,&Core0TaskHnd,0);
  xTaskCreatePinnedToCore(CoreTask1,"CPU_1",1000,NULL,1,&Core1TaskHnd,1);
  
  tp = 0;
  indi = 0;
  Serial.begin(115200);
  
}

void loop() 
{
  
}  

void CoreTask0( void * parameter ) 
{ 
  for (;;) 
  { 
    //-----------------------------------------------------
      while(indi == 0)
      {
        
      }
      indi = 0;
      if(tp == 0)
        {
          double erg = t1_vReal[0];
          Serial.println(erg, DEC);
        }
        else
        {
          double erg = t0_vReal[0];
          Serial.println(erg, DEC);
        }



    //-----------------------------------------------------
  }
  
} 

void CoreTask1( void * parameter ) 
{ 
  long cnt = 0;
  for (;;) 
  { 
    //-----------------------------------------------------
      if(tp == 0)
        {
          for(int i = 0; i < 1024; i++)
          {
            t0_vReal[i] = cnt;
            cnt++;
            delay(1);
          }
        }
        else
        {
          for(int i = 0; i < 1024; i++)
          {
            t1_vReal[i] = cnt;
            cnt++;
            delay(1);
          }
        }
        indi = 1;
        tp = (tp - 1) * -1;

    //------------------------------------------------------
  } 
}

In diesem Test-code werden im CoreTask1 auf CPU 1 abwechselnd zwei buffer mit einem Counterwert gefüllt. Ist ein buffer voll, wird ein Indikator (Indi) auf "1" gesetzt und der task-pointer (tp) geflippt
CoreTask0 idelt derweil auf CPU 0 in einer while-Schleife bis Indi zu "1" wird, setzt dann Indi wieder zu "0" und sendet an das terminal den Inhalt von Stelle "0" des buffers, der gerade nicht von CoreTask1 befüllt wird.
Das funktioniert 4 mal, dann kommt eine Fehlermeldung und dwer ESP macht einen reboot.

========================= Fehlermeldung ===================================

E (10177) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (10177) task_wdt: - IDLE0 (CPU 0)
E (10177) task_wdt: Tasks currently running:
E (10177) task_wdt: CPU 0: CPU_0
E (10177) task_wdt: CPU 1: loopTask
E (10177) task_wdt: Aborting.
abort() was called at PC 0x400d385f on core 0

Backtrace: 0x4008b560:0x3ffbe170 0x4008b78d:0x3ffbe190 0x400d385f:0x3ffbe1b0 0x400845ed:0x3ffbe1d0 0x400d0bcf:0x3ffb8510 0x40088215:0x3ffb8530

========================= Ende Fehlermeldung ===================================

Wenn ich die Fehlermeldung richtig interpretiere, schlägt ein Watch-dog timer an, weil die while-schleife in CoreTask0 die CPU blockiert.
kann sowas sein?
Der task läuft doch mit der niedrigsten Priorität.

Was läuft da falsch?

Kannst Du mal ordentlich kennzeichnen, was Zitat und was Antwort von Dir ist? So ist das sehr schlecht nachvollziehbar.

Gruß Tommy

Diese Warteschleife blockiert zu lange, dadurch beißt der Watchdog zu:

      while(indi == 0)
      {
        
      }

Abhilfe schafft hier vTaskDelay():

      while(indi == 0)
      {
        vTaskDelay(10);
      }

gruß lorenz

corvus_:
Abhilfe schafft hier vTaskDelay():

Treffer!

Es ginge wohl auch yield();

agmue:
Es ginge wohl auch yield();

leider nein, und das hab ich irgendwie auch noch nie verstanden, warum yield() das nicht auch richtet..
Interessanter Weise beißt der Watchdog mit yield() sogar noch früher zu als ohne irgendwas.

Tommy56:
Kannst Du mal ordentlich kennzeichnen, was Zitat und was Antwort von Dir ist? So ist das sehr schlecht nachvollziehbar.

Gruß Tommy

Als ich gemerkt habe, dass ich alles in Quote geschrieben habe, war's schon gepostet. Shit happens, sorry.

Das kannst Du korrigieren.

Gruß Tommy

Ja, stimmt leider, dann geht aber wenigstens vTaskDelay(1); :slight_smile:

agmue:
Ja, stimmt leider, dann geht aber wenigstens vTaskDelay(1); :slight_smile:

die Hoffnung hatte ich auch schon öfter, reichte aber nicht immer.

corvus_:
Diese Warteschleife blockiert zu lange, dadurch beißt der Watchdog zu:

      while(indi == 0)

{
       
     }




Abhilfe schafft hier vTaskDelay():


while(indi == 0)
     {
       vTaskDelay(10);
     }




gruß lorenz

Danke für die Info, mit vTaskDelay(10) läuft das Testprogramm einwandfrei. Hab mal im Internet gestöbert bzgl vTaskDelay und das soweit verstanden. Mit vTaskDelay wird vermutlich für eine Zeit (Ticks) die Ausführung an das Operatingsystem zurückgegeben. Entspricht ein Tick der Systemzeit oder muss man das was umrechnen?

      while(indi == 0)
      {
        
      }

Muss nicht innerhalb eines leeren Anweisungsblocks mindestens
ein Semikolon stehen?

Nö.

Gruß Tommy

f91w:

      while(indi == 0)

{
       
      }




Muss nicht innerhalb eines leeren Anweisungsblocks mindestens
ein Semikolon stehen?

Nöö

Eine Anweisung muss mit einem Semikolon abgeschlossen werden.
Aber ein Anweisungsblock, darf leer bleiben.

Dann habe ich wohl was falsch in Erinnerung...

protolus:
Mit vTaskDelay wird vermutlich für eine Zeit (Ticks) die Ausführung an das Operatingsystem zurückgegeben. Entspricht ein Tick der Systemzeit oder muss man das was umrechnen?

Die Tickrate ist abhängig von configTICK_RATE_HZ. Diese ist bei der Arduino Umgebung standardmäßig auf 1000 gesetzt. In diesem Fall entspricht ein Tick einer ms. Wenn man sich darauf nicht verlassen möchte, gibt es zwei Möglichkeiten:

vTaskDelay(1000 / portTICK_PERIOD_MS);  
vTaskDelay(pdMS_TO_TICKS(1000));

Beide Möglichkeiten führen zu einem Delay von einer Sekunde (1000ms) - unabhängig davon auf welchen Wert configTICK_RATE_HZ gesetzt ist.