ESp32 Multithreading

Gast #7419383
Lesenswert?

Moin,

auch wenn alles mit einem 1s Timer Int läuft, mich reizt jetzt die Auslagerung der Datenerfassung eines ADC115 auf den anderen Core. Ich habe mir das angeschhaut wie man mit xcreate... Tasks zeugt aber versteht folgendes nicht:

  1. Wann wird eine task aufgerufen?
  2. wie wird sie verlassen?
  3. wie oft wird sie aufgerufen?

In Beispielen stehen da ständig Endlos Schleifen drin. Wozu das? Ohne stürzt der ESp32 ab.

Ich muss 1x die Sekunde daten in Arrays einschreiben. Das dauert fast 150ms. Das soll nicht mehr das Hauptprogramm auf Core1 machen sondern der andere Kern. Core 1 greift nur atomar lesend auf Variablen zu, d.h. Core 0 kann da reinschhreiben.

Persönliche Seite #7419388
Lesenswert?

Thorsten M. schrieb:

Wann wird eine task aufgerufen?

Die Task-Funktion wird aufgerufen sobald man den Task erzeugt hat.

Thorsten M. schrieb:

wie wird sie verlassen?

Nie, bei FreeRTOS sollten Tasks nicht zurück kehren sondern in einer Endlosschleife laufen.

Thorsten M. schrieb:

wie oft wird sie aufgerufen?

Genau so oft wie du xTaskCreate aufrufst, also typischerweise 1x.

Thorsten M. schrieb:

In Beispielen stehen da ständig Endlos Schleifen drin. Wozu das? Ohne stürzt der ESp32 ab.

Weil man unter FreeRTOS Threads nicht beenden kann. Ist auch meistens nicht nötig. Du lässt den Task halt endlos laufen.

Gast #7419398
Lesenswert?

Ok .. ich will aber keine delay 1000 da reinschreiben muessen. Die Task wird ja sicherlich vom scheduler unterbrochen. Sonst saesse der Core ja fest. Wie kriege ich es hin, dass die Task nur alle 1s Daten holt?

Persönliche Seite #7419404
Lesenswert?

Thorsten M. schrieb:

Ok .. ich will aber keine delay 1000 da reinschreiben muessen.

Warum nicht?

Thorsten M. schrieb:

Wie kriege ich es hin, dass die Task nur alle 1s Daten holt?

Mit Delay, oder präziser mit dem Timer API:

https://www.freertos.org/RTOS-software-timer.html

Die Timer Callbacks laufen aber alle in einem Thread.

PS: Wenn du deinen Task nicht permanent benötigst, und die Datenerfassung ein-und ausschalten möchtest, kannst du den Task (in der Endlosschleife) zwischenzeitlich schlafen legen und bei Bedarf aufwecken. Das geht u.a. mit dem Notification API:

https://www.freertos.org/RTOS-task-notifications.html

Das reduziert die Leistungsaufnahme.

Persönliche Seite #7419409
Lesenswert?

Thorsten M. schrieb:

Also quasi jede Sekunde die Task wrzeugen und mit vdeleteTask wieder verlassen? Und das alles von Core 1 aus? Die spielt dann ja von allein denke ich

Nein, den Task permanent laufen lassen. Entweder per Delay zwischendurch schlafen legen oder per Software Timer und xTaskNotify aufwecken. Tasks ständig zu erzeugen und wieder löschen macht wenig Sinn.

Gast #7419412
Lesenswert?

Ich muss mir das erstmal bildlich aufmalen. Der delay(1000) in der Tasks blockiert also den Core nicht? Die Datensammlung laeuft rund um die Uhr bei mir. Die Arrays liegenn im RTC Ram und werden nur gelesen von der loop im Arduino.

Persönliche Seite #7419422
Lesenswert?

Thorsten M. schrieb:

Der delay(1000) in der Tasks blockiert also den Core nicht?

Nein, FreeRTOS führt dann andere Tasks aus, oder eben den Idle-Task. Ein schlafender Task verbraucht keine Rechenzeit (und keine Energie!). Das kannst du am PC ausprobieren: Starte in einem Programm so viele Threads wie CPU-Cores existieren (oder auch viel mehr), die alle nur Sleep aufrufen. Friert der PC ein? Geht die CPU-Last auf 100%?

Gast #7419425
Lesenswert?

Ok, ich bedanke mich erstmal. Viele Texte im Netz vergessen es, dass man erstmal das Ganze verstehen muss, bevor man sich mit den Details befasst.

Also das ginge auch:

Core 1 (der wo alles drauf läuft)):

Global: 3x arrays

loop () {

if (Flags.1s) erzeuge task Datensammeln

}

task datensammeln() {

Hole 1 Datum pro Durchlauf und schreibe es ein Bilde Mittelwerte zerstöre task; }

alternativ wird die task nur einmal im setup erzeugt und dann

task datensammeln() {

while (1) { Hole 1 Datum pro Durchlauf und schreibe es ein Bilde Mittelwerte delay(1000) } }

Gast #7419426
Lesenswert?

Niklas G. schrieb:

Nein, FreeRTOS führt dann andere Tasks aus, oder eben den Idle-Task.

da liest man noch etwas von yield() und vdelay(), die benutzt werden sollen, da delay scheinbar doch blockiert.

https://www.esp32.com/viewtopic.php?t=8944#:~:text=This%20is%20vTaskDelay%20%28pdMS_TO_TICKS%20%2810%29%29%20a%20delay%20of,pdMS_TO_TICKS%20%28X%29%20which%20will%20do%20it%20for%20you.

Gast #7419430
Lesenswert?

Niklas G. schrieb:

Was erhoffst du dir davon den Task ständig zu zerstören und neu zu erstellen? Das ist eine Menge Overhead.

Bei einer CPU, die 240 Mio Ops/s macht ist mir das völlig wumpe..... aber ich werde wohl die bessere Möglichkeit wählen. Ich nutze kein FreeRTOS, nur die beidden Funktionen daraus. Mein programm existiert schon und ist sehr gross.

Persönliche Seite #7419433
Lesenswert?

Thorsten M. schrieb:

da delay scheinbar doch blockiert.

Ich denke du meinst vTaskDelay? Ja, das blockiert, im Sinne von dass der Scheduler diesen Task nicht mehr ausführt bis die Zeit abläuft. Der CPU Kern ist dann frei für andere Tasks.

Thorsten M. schrieb:

Bei einer CPU, die 240 Mio Ops/s macht ist mir das völlig wumpe

Du machst dir ja schon Gedanken beide Cores zu benutzen, also scheinen einmal 240 Mio OPs nicht zu reichen...?

Thorsten M. schrieb:

Ich nutze kein FreeRTOS, nur die beidden Funktionen daraus

Auf den ESP32 musst du FreeRTOS benutzen. Das läuft immer im Hintergrund. ESP-IDF startet das automatisch.

Gast #7419437
Lesenswert?

Niklas G. schrieb:

Du machst dir ja schon Gedanken beide Cores zu benutzen, also scheinen einmal 240 Mio OPs nicht zu reichen...?

Kannst ja mal reinschauen wo für das alles gut ist, der Link ist noch ein paar Tage gültig, habe das im Gast wlan laufen. Der ESP32 Firebeetle steuert mittlerweile so viele Relais und Geräte... ist schon ein geiles Teilchen.

http://l11vzu256z3bfz2j.myfritz.net/abfrage?pass=myesp32

Beitrag #7419465 wurde von einem Moderator gelöscht.
Gast #7420089
Lesenswert?

Mike R. schrieb:

Aber nur wenn der Kopierer auch weiß, dass er den Semaphore vorher auch noch erzeugen muss. Ansonsten gibt es Schiffbruch.

Nein, das nicht aber ein Reset :-) Überdies ist der Reset die Antwort des ESP32 auf alles, ähnlich "42".

In meiner Awendung läuft tasking nicht, vermutlich weil ich die Hardware Timer benutze und ne Menge Interfaces. In einer Beispeilanwendung klappt alles prima. Wenn FReeRtOS dann auch nur diese API nutzen,. die zb für die Timer nur soft Timer vorsieht.

Persönliche Seite #7420094
Lesenswert?

Thorsten M. schrieb:

Nein, das nicht aber ein Reset

Haha, bei Race Conditions passieren irgendwie unvorhersehbaren, gerne auch unbemerkte, Probleme. Irgendwann macht das Programm nicht reproduzierbar irgendwas komisches und keiner weiß warum. Ein reset wäre ja schön, da bemerkt man wenigstens dass was schief läuft. Aber woher soll der ESP32 wissen dass da eine Race Condition vorliegt?

Thorsten M. schrieb:

In meiner Awendung läuft tasking nicht

Wie hast du das hinbekommen? Das ESP-IDF (welches ja Teil des Arduino ESP32 Framework ist) startet automatisch FreeRTOS und den Scheduler. Ohne das funktioniert weder das ESP-IDF, noch das Arduino Framework, noch die WiFi-Funktion.

Thorsten M. schrieb:

die zb für die Timer nur soft Timer vorsieht.

Die Soft Timer funktionieren ebenfalls nur wenn FreeRTOS "richtig" läuft.

PS: Von mehreren Threads aus auf I²C zugreifen (und das per Semaphore absichern) finde ich übrigens nicht besonders clever, weil I²C ja ziemlich langsam ist und somit mehrere Threads für längere Zeit blockiert werden und nichts anderes tun können. Ich würde das eher von einem einzelnen Thread aus machen und diesen asynchron mit den anderen Threads kommunizieren lassen.

#7888225
Lesenswert?

Arduino F. schrieb:

Man kann auch in den Quellcode schauen, wenn man wissen will was delay() tut.

Aha, es wird vTaskDelay() aufgerufen und durch portTICK_PERIOD_MS dividiert. https://github.com/espressif/arduino-esp32/blob/31d22e6/cores/esp32/esp32-hal-misc.c#L209

portTICK_PERIOD_MS = 1000 / configTICK_RATE_HZ https://github.com/espressif/esp-idf/blob/36a5a71/components/freertos/FreeRTOS-Kernel/portable/xtensa/include/freertos/portmacro.h#L116

configTICK_RATE_HZ = 100 https://github.com/espressif/esp-idf/blob/36a5a71/components/freertos/Kconfig#L37

Also ist portTICK_PERIOD_MS = 10 ms. Wenn ich delay(1) aufrufe, wird dann 1 durch 10 dividiert. Da es ein uint32_t ist kommt da = 0 heraus.

Also ist delay(1) = 0 delay(2) = 0 delay(3) = 0 delay(4) = 0 delay(5) = 0 delay(6) = 0 delay(7) = 0 delay(8) = 0 delay(9) = 0 gar nicht anwendbar!

Kleinstes zulässiges delay(10) ist also 10 ms.

#7926303
Lesenswert?

Ich bräuchte hier noch mal Hilfe. Ich hab vermutlich irgendwo ein Problem mit einer Race Condition, und ich vermute es liegt am Timing. Ich hab mir von ChatGPT ein non-blocking delayMicroseconds() basteln lassen, hab aber keine Ahnung ob es funktioniert. Hauptproblem ist, dass diesselbe Funktion auf verschiedenen Cores aus verschiedenen Tasks aufgerufen wird, daher ist für jede Instanz ein eigener Timer notwendig. Momentan wird bei jedem Aufruf ein neuer Timer erstellt, das erzeugt unnötigen Overhead. Versuche den Timer außerhalb der Funktion anzulegen scheitern an den parallelen Instanzen. Vielleicht kann sich das mal jemand mit wirklich Ahnnung angucken?

1
// non-blocking delayMicroseconds()
2
void delay_us(uint32_t us) {
3
    TaskHandle_t thisTask = xTaskGetCurrentTaskHandle();
4
    // Timer callback: notify the sleeping task
5
    auto timerCallback = [](void* arg) {
6
        TaskHandle_t task = static_cast<TaskHandle_t>(arg);
7
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
8
        vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken);
9
        if (xHigherPriorityTaskWoken) {
10
            portYIELD_FROM_ISR();
11
        }
12
    };
13
    // Create one-shot esp_timer
14
    esp_timer_handle_t timer;
15
    esp_timer_create_args_t timer_args = {
16
        .callback = timerCallback,
17
        .arg = (void*)thisTask,
18
        .dispatch_method = ESP_TIMER_TASK,
19
        .name = "delay_us_timer"
20
    };
21
    esp_timer_create(&timer_args, &timer);
22
    esp_timer_start_once(timer, us);  // Delay in microseconds
23
    // Wait for notification (puts task to sleep)
24
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
25
    esp_timer_delete(timer);
26
}
#7926321
Lesenswert?

Alexander schrieb:

hab aber keine Ahnung ob es funktioniert

Probiere es aus!

Momentan wird bei jedem Aufruf ein neuer Timer erstellt, das erzeugt unnötigen Overhead. Versuche den Timer außerhalb der Funktion anzulegen scheitern an den parallelen Instanzen.

Wenn es anders nicht geht, ist der Overhead offenbar nicht unnötig, sondern notwendig.

Hat das Betriebssystem keine fertige Lösung für diese Aufgabe? Ich kann mir nicht vorstellen, daß jeder Anwendungsprogrammierer dieses Rad für sich neu erfinden muss. In der Doku von vTaskDelay() steht nichts von solchen Einschränkungen.

: Bearbeitet durch User
#7926345
Lesenswert?

Nemopuk schrieb:

Probiere es aus!

Ich hab verschiedene Tests mit Debugausgaben über Serial.print gemacht, die Funktion scheint im so darstellbaren Millisekundenbereich ordentlich zu timen. Allerdings sagt das nichts über den Mikrosekundenbereich aus.

Mein kleinstes delay ist 1ms. Mal funktioniert es und mal nicht (der Task, nicht das delay), hab die Vermutung das hier der Overhead ausbremst.

: Bearbeitet durch User
#7926346
Lesenswert?

Niklas G. schrieb:

Alexander schrieb:

Ich hab vermutlich irgendwo ein Problem mit einer Race Condition

So ganz klar ist mir jetzt nicht, was genau da im Einzelnen laufen soll.

Aber wenn es nur darum geht, dass die Datenerfassungs-Tasks jede Sekunde laufen sollen, dann nimm doch einfach die Funktion xTaskDelayUntil(). Den Heckmeck mit den Timern brauchst Du dann gar nicht.

Die Task-Prioritäten musst Du allerdings auch so beachten. Die Funktion xTaskDelayUntil() setzt den Task nur in den Zustand Ready. Der Scheduler läßt den nur laufen, wenn er (mit) die höchste Priorität aller Ready Tasks hat.

In der FreeRTOS Config bestimmt übrigens der Parameter configTICK_RATE_HZ die Tick-Rate. Oft ist die auf 100Hz eingestellt.

#7926353
Lesenswert?

Richie schrieb:

Oft ist die auf 100Hz eingestellt.

Das ist ja das Problem. Siehe weiter oben.

Richie schrieb:

Aber wenn es nur darum geht, dass die Datenerfassungs-Tasks jede Sekunde laufen sollen, dann nimm doch einfach die Funktion xTaskDelayUntil().

Jede Millisekunde. Auch hier kleinstes zulässiges Delay 10ms (wegen ganzzahliger Division)

https://docs.espressif.com/projects/esp-idf/en/v4.4/esp32/api-reference/system/freertos.html#_CPPv415xTaskDelayUntilPC10TickType_tK10TickType_t

#7926948
Lesenswert?

Rick schrieb:

Warum schaust Du nicht in die entsprechende Anleitung (RTFM) und verwendest die mitgelieferte vTaskDelay()?

Würdest Du mir bitte zeigen wo in dieser entsprechenden Anleitung auf das Problem eingegangen wird (RTFT)?

Niklas G. schrieb:

Wie viele Mikrosekunden sind es denn?

1ms = 1000µs sind beim ESP32 240000 Clock cycles. In der Zeit soll ein anderer Task was machen dürfen.

Falk B. schrieb:

AUTSCH!

Ich finde ja "während der delay() Pause" ist korrektes deutsch, und über Dein Denglisch in so manchen KiCad T̶h̶r̶e̶a̶d̶s̶ Themen wollen wir nicht streiten :P

Rick schrieb:

Ja, davon hat ChatGPT auch keine Ahnung, es schwafelt nur beeindruckend.

ChatGPT ist zumindest auf das Problem eingegangen nachdem ich erklärt habe warum vTaskDelay() nicht brauchbar ist. Und schlägt nun vor den Overhead (~100µs) zu reduzieren mit einem Lazy-Init Timer in einer Klasse, und einem Makro Wrapper der für jede Instanz automatisch eine neue Funktion deklariert so dass man delay_us() wie bisher aufrufen kann. Gefällt mir aber nicht so, da das dann zur Compile Time passiert. Dann lieber die Tick-Rate erhöhen.

Die Funktion selbst war ja auch letztendlich nicht das Problem, der Fehler mit der vermeintlichen Race Condition lag beim Anwender (Namenskollision global/statisch timer)

: Bearbeitet durch User
#7927042
Lesenswert?

Sorry, da war ich noch mitten im Schreiben.

Alexander schrieb:

Non-blocking ist das Gegenteil von busy-waiting. Während der delay Pause soll CPU frei sein.

Alexander schrieb:

In der Zeit soll ein anderer Task was machen dürfen.

Wenn du während des Wartens zu einem anderen Task schalten lässt, wie soll FreeRtos dann. nach Ablauf der Wartezeit zu dem Wartetask zurückschalten? Dazu braucht es ein Ereignis, entweder einen Tick des Schedulers (nur alle 10ms), oder einen Interrupt, oder die Kooperation des anderen Tasks.

Die Timer-Interruptlösung hat dir ChatGPT aufgedröselt.

Für die Schedulerlösung müsstest du wohl tatsächlich die Tickrate erhöhen.

Kooperativ könnte noch funktionieren, je nachdem wie der "andere Task" strukturiert ist, also ob der "andere Task" häufig und regelmäßig genug deinen Wartetask über eine Semaphore oder so "anpingen" kann, und dieser dann prüft, ob die Wartezeit abgelaufen ist, und falls nicht die Kontrolle wieder "kurz" an den "anderen Task" abgibt.

LG, Sebastian

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren