Der ESP32 besitzt zwei unabhängige CPU-Kerne. Üblicherweise kommt in Projekten aber nur einer davon zum Einsatz. Wir zeigen, wie Sie den Chip auf volle Kraft bringen.
Mittlerweile kommt der Mikrocontroller ESP32 in vielen coolen Projekten zum Einsatz, allerdings oft nur mit halber Kraft. Der Grund: In den meisten Fällen nutzt die Software nur einen der zwei vorhandenen CPU-Kerne. Dabei ist es gar kein Hexenwerk, mit beiden Cores zu arbeiten. Sehen wir uns Schritt für Schritt an, wie Sie ein Programm auf mehreren CPUs arbeiten lassen.
Streng genommen bringt der ESP32 sogar drei CPU-Kerne mit. Der dritte Core verfügt zwar nur über eine vergleichsweise geringe Rechenleistung, verbraucht dafür aber sehr wenig Energie, womit er sich eher für Low-Power-Anwendungen eignet. Wir konzentrieren uns in diesem Artikel auf die zwei schnellen 32-Bit-LX6-Kerne.
FreeRTOS kurz erklärt
Das Geheimnis, wie man den zweiten CPU-Kern des ESP32 verwendet, ist schnell gelüftet: FreeRTOS. Die Abkürzung RTOS steht für Real Time Operating System, Näheres zum Thema finden Sie im Kasten “Was ist Echtzeit”. Bei Betriebssystem denken viele wahrscheinlich an Windows oder Linux. Bei FreeRTOS handelt es sich um ein viel einfacher aufgebautes Betriebssystem, das es erlaubt, mehrere Tasks unabhängig voneinander parallel laufen zu lassen.
Dadurch ist es nicht mehr zwingend erforderlich, Programme so zu schreiben, dass der Mikrocontroller sie zyklisch ausführt. Die Entwickler des ESP32 statten ihre MCU direkt mit einer angepassten Variante von FreeRTOS [1] aus. Das System bietet dem Programmierer viele zusätzliche Möglichkeiten, die das Entwickeln von Software für Mikrocontroller stark vereinfachen.
Was ist Echtzeit
Viele Menschen denken, dass Echtzeit (Realtime) in der IT bedeutet, dass etwas sofort erledigt wird – was so nicht stimmt. Es sagt lediglich aus, dass eine Aufgabe innerhalb eines definierten Zeitintervalls zuverlässig erledigt wird. Die Betonung liegt dabei auf zuverlässig. Bei Betriebssystemen wie Windows gibt es keinen Verlass darauf, dass sie eine bestimmte Aufgabe innerhalb einer definierten Zeit abarbeiten. Das liegt unter anderem daran, dass sich I/O-Operationen die CPU zum Verarbeiten von Daten exklusiv sichern können, indem sie einen Interrupt auslösen. Alle anderen Prozesse müssen dann erst einmal warten. Bei einem Echtzeitbetriebssystem (RTOS) passiert so etwas nicht. Es arbeitet Aufgaben innerhalb einer definierten Zeitspanne ab, der sogenannten Zykluszeit. Es gibt Echtzeitsysteme mit Zykluszeiten, die sich einer Sekunde annähern. Realtime bedeutet also nicht zwangsläufig schnell, sondern nur innerhalb einer vorher festgelegten Zeitspanne.
Testaufbau
Wie so oft hält auch für dieses Experiment das Beispielprogramm Blink her. Damit Sie sehen, dass unterschiedliche Blink-Tasks auf dem ESP32 laufen, spendieren wir der MCU ein Balken-Display mit acht LEDs und entsprechenden Vorwiderständen. Abbildung 1 zeigt den Schaltplan zu diesem Aufbau.
Hierbei gilt es, den Kondensator getrennt zu betrachten: Er ist nötig, um zu gewährleisten, dass die Programme auch wirklich automatisch hochgeladen werden können. Das liegt vereinfacht gesagt daran, dass hier ein mittelmäßiger Treiber auf eine nicht optimal designte Hardware trifft.
Die zwei Breadboards (Abbildung 2) verbinden wir so miteinander, dass ein ESP32-NodeMCU-Modul optimal darauf passt. Als Entwicklungsumgebung dient die Arduino IDE. Der Artikel “Schneller Einstieg” [2] beschreibt, wie Sie sie installieren und für den ESP32 anpassen. Um zu testen, ob das Setup funktioniert, verwenden Sie das Programm aus Listing 1. Im Array LED stehen die Pin-Nummern der IO-Ports.
So greifen wir mit LED[x] auf die einzelnen Leuchtdioden zu. Diese Herangehensweise erlaubt es, die IO-Ports mit einer Schleife zu initialisieren. Auch das eigentliche Programm in der loop()-Funktion gestaltet sich dadurch erheblich einfacher. Das Codefragment LED[++i%8] zählt die Variable i bei jedem Aufruf um eins hoch und führt eine Modulo-8-Operation darauf aus. So erzeugt das Programm immer Zahlen zwischen 0 und 7. Auf diese Weise programmieren wir mit nur wenigen Zeilen ein einfaches Lauflicht.
Listing 1
Test1.ino
int i=0
int LED[]={2,0,4,16,17,5,18,19};
void setup() {
for(i=0;i<8;i++)
pinMode(LED[i], OUTPUT);
Serial.begin(115200);
}
void loop() {
digitalWrite(LED[i%8], LOW);
digitalWrite(LED[++i%8], HIGH);
delay(100);
}
TIPP
Den Schaltplan, alle Beispielprogramme sowie den zitierten Grundlagenartikel zum ESP32 finden Sie im Download-Bereich zu diesem Beitrag.
Kerne ermitteln
Kommen wir nun zurück zu unseren zwei Kernen und dem Blink-Beispiel. Um zu überprüfen, ob unser nächstes Testprogramm das tut, was es soll, müssen wir zunächst herauszufinden, auf welchem Kern ein Task läuft. FreeRTOS stellt genau zu diesem Zweck die Funktion xPortGetCoreID() bereit, die die Nummer des aktuell verwendeten Kerns zurückgibt. Wir müssen sie also innerhalb des Codes aufrufen, von dem wir wissen wollen, auf welchem Core er läuft. Um einen Prozess einem bestimmten Kern zuzuweisen, kommt die Funktion xTaskCreatePinnedToCore() zum Einsatz. Die Beschreibung der Parameter dieser Funktion zeigt die Tabelle “xTaskCreatePinnedToCore(): Parameter”.
|
Position |
Name |
Funktion |
|---|---|---|
|
1 |
|
Der Programmcode, der als Task ausgeführt werden soll. Es ist wichtig, dass der Code sich nicht beendet. Er muss in einer Endlosschleife laufen, sonst stürzt FreeRTOS ab. Alternativ lassen sich Tasks mit der Funktion |
|
2 |
|
Sprechender Name für den Task. Der Name kommt hauptsächlich beim Debugging zum Einsatz. Die Längenvorgabe für Namen beträgt 16 Byte, lässt sich aber über die Systemvariable |
|
3 |
|
Anzahl der Bytes, die für den Stack-Speicher des Tasks verwendet werden sollen. Fällt dieser Wert zu gering aus, stürzt FreeRTOS mit der Fehlermeldung |
|
4 |
|
Pointer, der dazu dient, Startparameter an den Task zu übergeben. |
|
5 |
|
Die Priorität, mit der ein Task ausgeführt werden soll. |
|
6 |
|
Die Referenz auf den Task selbst. Sie dient dazu, um Aktionen auf dem Task auszuführen. |
|
7 |
|
CPU-Kern, auf dem der Task laufen soll ( |
Das Testprogramm aus Listing 2 startet auf den zwei Kernen acht Tasks. Jeder davon lässt je eine Leuchtdiode mit leicht unterschiedlichem Takt blinken. Das erweckt fast den Anschein, als würden die LEDs zufällig aufleuchten. Das Ändern des Zustands für alle Dioden übernimmt die Funktion toggle(), der Sie als Parameter die LED-Nummer übergeben. Innerhalb von toggle() wird eine Ausgabe auf dem seriellen Monitor erzeugt, über die Sie erkennen, welcher Prozess gerade aktiv ist.
Die loop()-Funktion enthält in unserem Beispiel nur den Aufruf delay(1000). Ließe man diese Funktion komplett leer, käme es zu einem Absturz, weil die restlichen Tasks dann kein Zeitfenster mehr zum Ausführen bekämen. Eine leere loop()-Funktion bedeutet, dass die CPU die volle Zeit darauf verwendet, nichts zu machen. Das Kommando delay(1000) schafft Freiräume, um alle anderen Prozesse auszuführen. Wie das Programm in der Praxis arbeitet, zeigt ein Youtube-Video des Autors [3].








