Im ersten Beispiel in dem Kapitel ESP-IDF: Erste Schritte – LED-Blink-Programm, haben wir eine LED in einer sogenannten „Superloop“ zum Blinken gebracht. Das ist ein typisches Beispiel, bei dem noch kein Betriebssystem erforderlich ist.
Um der Funktionalität eines Betriebssystems etwas näher zu kommen, und zu sehen wie FreeRTOS ungefähr funktioniert, werden wir das gleiche Beispiel mit einer LED in einer Task wiederholen. Da man bei einer Task aber noch keine Relevanten Vorteile gegenüber eine Superloop erkennen kann, werden wir das ganze etwas interessanter gestalten, mit drei LEDs gemeinsam in einer Superloop und dann in drei getrennten eigenen Tasks.
Wie man in Abb. 1 sehen kann, ist der Aufbau des Codes ähnlich, die Funktionsweise aber grund verschieden.
Um das schon mal einwenig vorweg zu nehmen, kann man sagen, dass die Funktionen in der Superloop-Variante, in der main über eine Endlosschleife nacheinander immer wieder ausgeführt werden. Die Betonung liegt hier auf Nacheinander! Das bedeutet, egal wie lang die Ausführungszeit von funktion1() ist, funktion2() kann erst ausgeführt werden, wenn funktion1() vollständig abgearbeitet wurde und funktion3() erst, wenn funktion2() vollständig beendet wurde. Wenn sich eine der Funktionen aufhängt oder auf eine Eingabe wartet, die nicht kommt, dann geht es überhaupt nicht mehr weiter. Wir bleiben dauerhaft in der main, soweit es keinen Austiegspunkt gibt, den wir für Ausnahmefälle einprogrammieren sollten.
Bei der Task-Variante sieht das schon ganz anders aus. Im Gegensatz zur Superloop, wird hier die main verlassen! Die drei Tasks werden einmalig nach einander aufgerufen und damit endet die main. FreeRTOS übernimmt jetzt. Die Tasks selbst haben jetzt eigene Endlosschleifen und dürfen nie verlassen werden! Die Tasks selbst laufen aber quasi-parallel ab. D.h. ganz einfach ausgedrückt, dass man eine Task wie ein eigenes Programm behandeln kann welches vollkommen unabhängig von der anderen Task arbeitet. Keine Task muss darauf warten das eine andere Task beendet wird, sie laufen vollkommen unabhängig von einander!
Logisch, das ist jetzt sehr einfach ausgedrückt und natürlich laufen die Tasks intern nicht parallel. Das ginge nur, wenn jede Task einen eigenen Prozessorkern hätte, aber intern passiert das so schnell, das es für uns so aussieht, als wären die Tasks von einander unabhängig. Und genau so können wir sie tatsächlich auch behandeln.
Wann welche Task läuft und wie sie abgearbeitet werden, wird über den Scheduler geregelt, der Teil des Betriebssystems ist. Auch kann man hier mit Prioriäten arbeiten, damit weniger wichtige Tasks, wie z.B. das ansteuern eines LCD-Displays, höher priorisierten Tasks immer den Vortritt lassen, wie z.B. Eingabe Tasks, die z.B. in Echtzeit Signale Abtasten müssen. D.h. Die Echtzeit-Task wird immer und in jedem Fall zu den festgelegten Zeiten, z.B. im 10 ms Takt gestartet. Niedrig priorisierte Tasks werden abgearbeitet, wenn die Task mit der hohen Priorität gerade nichts zu tun hat.
An dieser Stelle müssen wir uns als Programmierer natürlich an Konventionen halten, damit eine hochpriorisierte Task nicht alle Ressoucen verbraucht und die niedrig priorisierten Tasks nicht zum Zuge kommen lässt. Aber dazu kommen wir noch.
Blinkende LED mit Task
Im folgenden Code wird statt der Superloop in der main eine Task gestartet. Die Endlosschleife befindet sich jetzt in der Task. In diesem Programm werden wir im Vergleich zur Superloop aber noch keinen sichtbaren Unterschied erkennen können.
/*
Blinkende LED mit Task
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#define LED1 5 // LED an Pin 5 anschließen.
void BlinkTask() // LED blinkt jede Sekunde
{
while(1)
{
gpio_set_level(LED1, 1);
vTaskDelay(500 / portTICK_PERIOD_MS);
gpio_set_level(LED1, 0);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void app_main(void) // Hier startet das Programm
{
// Pin 5 als Ausgang definieren
gpio_set_direction(LED1, GPIO_MODE_OUTPUT);
// Task einmalig starten
xTaskCreatePinnedToCore(&BlinkTask, "BlinkTask", 1024 * 2, NULL, 5, NULL, 1);
}
Beispielcode – Eine blinkende LED mit einer Task
Drei blinkende LEDs in Superloop
In diesem Beispiel werden drei LEDs in einer Superloop in drei Funktionen void function1(), void function2() und void function3()nacheinander zum Blinken gebracht. Wie man in der main erkennen kann, werden die Funktionen nacheinander aufgerufen.
Erst wird function1() aufgerufen, dann function2() und dann function3(). D.h. erst blinkt LED1, dann LED2 und dann LED3, immer nacheinander jeweils für eine Sekunde.
Das wichtigste an dieser Stelle ist, dass die Funktionen von einander abhängig sind. Erst wenn die Erste Funktion ihre Aufgaben vollständig abgearbeitet hat, beginnt die nächste Funktion, und so weiter. Sie können nicht gleichzeitig laufen oder unabhängig.
/*
Drei Leds blinken in Superloop.
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#define LED1 2 // LED1 an Pin 2 anschließen.
#define LED2 4 // LED2 an Pin 4 anschließen.
#define LED3 5 // LED3 an Pin 5 anschließen.
void function1()
{
gpio_set_level(LED1, 1);
vTaskDelay(500/ portTICK_PERIOD_MS);
gpio_set_level(LED1, 0);
vTaskDelay(500/ portTICK_PERIOD_MS);
}
void function2()
{
gpio_set_level(LED2, 1);
vTaskDelay(500 / portTICK_PERIOD_MS);
gpio_set_level(LED2, 0);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
void function3()
{
gpio_set_level(LED3, 1);
vTaskDelay(500 / portTICK_PERIOD_MS);
gpio_set_level(LED3, 0);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
void app_main(void)
{
// Pin 2, 4, 5 als Ausgänge definieren
gpio_set_direction(LED1,GPIO_MODE_OUTPUT);
gpio_set_direction(LED2,GPIO_MODE_OUTPUT);
gpio_set_direction(LED3,GPIO_MODE_OUTPUT);
// Superloop
while(1)
{
function1();
function2();
function3();
}
}
Beispielcode – Drei LEDs in einer Superloop
Dieses Verhalten kann man schön mit einem Logic Analyzer beobachten.

Abb. 2 – Zeitlicher Verlauf Superloop
Drei blinkende LEDs und drei Tasks
In diesem Beispiel verwenden wir pro LED eine eigene Task. In Abb. 3 können wir erkennen, dass die LEDs tatsächlich gleichzeit gestartet werden. Alle LEDs blinken im Sekundentakt. Dieses gleichmäßige Verhalten liegt daran, dass die Tasks zur gleichen Zeit gestartet wurden und alle mit den gleichen Parametern ausgestattet sind.
In Abb. 4 haben wir die Zeiten unterschiedlich gestaltet, also verschiedene Parameter verwendet. Wieder werden alle drei Tasks gleichzeitig gestartet, aber jede LED blinkt mit verschiedenen Frequenzen. Man kann trotzdem erkennen, das die LEDs nicht aufeinander warten müssen und unabhängig von einander blinken.
/*
Blinkende 3 LEDs in eigenen Tasks
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#define LED1 2 // LED an Pin 2 anschließen.
#define LED2 4 // LED an Pin 4 anschließen.
#define LED3 5 // LED an Pin 5 anschließen.
void Task1() // LED blinkt jede Sekunde
{
while(1)
{
gpio_set_level(LED1, 1);
vTaskDelay(500 / portTICK_PERIOD_MS);
gpio_set_level(LED1, 0);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void Task2() // LED2 blinkt jede Sekunde
{
while(1)
{
gpio_set_level(LED2, 1);
vTaskDelay(500 / portTICK_PERIOD_MS);
gpio_set_level(LED2, 0);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void Task3() // LED3 blinkt jede Sekunde
{
while(1)
{
gpio_set_level(LED3, 1);
vTaskDelay(500 / portTICK_PERIOD_MS);
gpio_set_level(LED3, 0);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void app_main(void) // Hier startet das Programm
{
// Pin 2, 4, 5 als Ausgänge definieren
gpio_set_direction(LED1, GPIO_MODE_OUTPUT);
gpio_set_direction(LED2, GPIO_MODE_OUTPUT);
gpio_set_direction(LED3, GPIO_MODE_OUTPUT);
// Tasks einmalig starten
xTaskCreate(&Task1, "Task1", 1024 * 2, NULL, 5, NULL);
xTaskCreate(&Task2, "Task2", 1024 * 2, NULL, 5, NULL);
xTaskCreate(&Task3, "Task3", 1024 * 2, NULL, 5, NULL);
}
Beispielcode – Drei LEDs in drei getrennten Tasks

Abb. 3 – Zeitlicher Verlauf Tasks
Im folgenden haben wir die Zeiten wie folgt geändert.
- Task1:
vTaskDelay(100 / portTICK_PERIOD_MS); - Task2:
vTaskDelay(250 / portTICK_PERIOD_MS); - Task2:
vTaskDelay(500 / portTICK_PERIOD_MS);

Abb. 4 – Zeitlicher Verlauf Tasks geänderte Zeiten
Zusammfassung Superloop vs. Tasks
Zusammengefasst kann man sagen, dass alles in einer Superloop sequentiell abgearbeitet wird, Tasks jedoch parallel laufen.
