Die Arduino-IDE ist ein gutes Tool, wenn man anfängt, mit Microcontrollern zu basteln. Einfach zu bedienen und gute Unterstützung durch viele Beispiele und Community-Support. Was Entwickler, die das Arbeiten mit "großen" IDEs aber sofort vermissen, ist, dass es keine richtige Projektstruktur gibt, mit der man Bibliotheken und Code-Abhängigkeiten projektspezifisch und innerhalb eines Projektes auch Zielplattform-spezifisch organisieren kann.

Eine Alternative ist das Tool PlatformIO, welches als Plugin für die bekannte Entwicklungsumgebung Visual Studio Code entwickelt wurde, aber auch für andere Editoren bzw. IDEs erhältlich ist. Weil PlatformIO sich an der Art und Weise orientiert, wie große Softwareprojekte organisiert und durchgeführt werden, hat man damit wesentlich flexiblere Möglichkeiten, sein Projekt zu strukturieren.

Leider bringt das auch eine erhöhte Komplexität im Setup mit sich, die für Einsteiger sicher eine größere Hürde ist, als die Nutzung z.B. der Arduino-IDE. Andererseits werden sich Menschen, die mit Umgebungen wie Visual Studio oder Eclipse sowieso schon arbeiten, schnell zurecht finden und haben den Vorteil, weiterhin ihr gewohntes Editor-Toolset benutzen zu können.

Der Grund, warum ich ausgerechnet für die Nutzung bei Projekten mit der TFT-Display-Bibliothek "TFT_eSPI" die PlatformIO empfehle, hat einen für mich wichtigen Grund: Gerade bei Projekten mit Displays arbeite ich gern mit einem "DEV"-Setup - also einem Aufbau für die Entwicklungs-, Test- und später Weiterentwicklungs-Phase - welches bei mir zu Hause auf einem Breadboard installiert ist, sich aber in der Regel von dem oder den Setups im produktiven Nutzungsszenario unterscheidet. Zum Beispiel weil dort ein anderes (größeres, teureres) Display verbaut ist oder ein anderer Microcontroller zum Einsatz kommt.

PlatformIO bietet dafür die Lösung, indem ein Projekt verschiedene Konfigurationen - genannt project environments - besitzen kann. Jede dieser Konfigurationen kann unterschiedliche Einstellungen hinsichtlich Ziel-Board und verwendete Bibliotheken haben. Außerdem kann man, wie es bei professionellen C-Compilern möglich und üblich ist, unterschiedliche defines in den verschiedenen Konfigurationen vorgeben und damit die Möglichkeiten der bedingten Compilierung nutzen. 

Installation und Einrichtung von PlatformIO

PlatformIO ist als Plugin für Visual Studio Code (VS Code) erhältlich, am einfachsten installiert man es über den Erweiterungsmanager in VS Code. Sofern also noch nicht vorhanden, installiere zunächst VS Code (https://code.visualstudio.com). Installiere dann PlatformIO als Erweiterung (Abb. 1).
VSC-install-PlatformIO.pngAbb. 1 - PlatformIO installieren

Die weitere Einrichtung geschieht direkt im jeweiligen konkreten Projekt selbst. PlatformIO lädt und installiert alle für ein Projekt benötigten Ressourcen dann automatisch aus dem Netz herunter. Ein Nachteil dieser Vorgehensweise ist, dass man den Code einer verwendeten Bibliothek im Gegensatz zur Arduino-IDE nicht nur einmal auf dem Rechner hat, sondern immer für jedes Projekt, ja sogar für jede einzelne Konfiguration des Projektes. Das entspricht genau der Vorgehensweise, wie es auch für andere, größere Softwareprojekte gehandhabt wird und stellt sicher, dass man in jedem Projekt genau die Komponenten in der gewünschten Version verwendet, wie man sie für dieses Projekt benötigt. Wer's unbedingt möchte, kann PlatformIO aber per Einstellung bei der Projekterstellung dazu überreden, schon installierte Arduino-Bibliotheken zu verwenden.

Wer weitere Informationen zur Inbetriebnahme von PlatformIO sucht, findet hier einen guten Artikel.

Den Arduino-Sketch in PlatformIO verwenden

In meinem Beispiel will ich nicht von Null starten, sondern einen schon existierenden Arduino-Sketch verwenden und in PlatformIO nutzen. Deshalb wird kein neues Projekt angelegt, sondern mit der Funktion "Import Arduino Project" das bestehende Projekt importiert und für die weitere Bearbeitung mit der IDE fit gemacht.

Ich habe also die "Import"-Funktion aufgerufen, zunächst den Microcontroller für das Projekt ausgewählt (in diesem Beispiel "Wemos D1 R2 and Mini") und den Pfad zum existierenden Arduino-Projekt ausgewählt. Als Projekt in diesem Beispiel fungiert mein "ESP-Bardisplay", der Code kann hier auf Github heruntergeladen werden.

PlatformIO generiert automatisch einen Projektordner und eine Projektkonfiguration in der Datei platformio.ini. Den automatisch vergebenen Namen des Projektorders kann man einfach durch umbenennen des Verzeichnisses verschönern.
Bardisplay-Platformio-1.jpg Abb. 2 - Automatisch generierter Projektordner

Etwas Handarbeit ist leider mit der derzeitigen Version des Import-Assistenten trotzdem noch zu tun. Alle Dateien des ursprünglichen Sketch-Verzeichnisses sind einfach im Unterordner src des neuen Projektes gelandet, aber natürlich gehören nicht alle dorthin. Ich verschiebe also per Drag & Drop manuell folgende Dateien:

  • README- und LICENSE-Dateien direkt in den Projektwurzelordner,
  • Header-Dateien (Endung .h) in den Unterordner include.

Was mit einer eventuell schon vorhandenen .gitignore-Datei passiert, habe ich noch nicht ausprobiert. PlatformIO generiert jedenfalls automatisch eine neue Datei mit den für das Plugin notwendigen Verzeichnisausnahmen. Aber Achtung: Das geclonte Git-Repository steckt nur im Unterverzeichnis src. PlatformIO legt beim Importieren kein neues Git-Repository für die gesamte neue Projektordnerstruktur an!

Auch die vom Sketch verwendeten zusätzlichen Bibliotheken werden nicht automatisch in das neue Projekt aufgenommen. Glücklicherweise besitzt PlatformIO einen sehr komfortablen Bibliotheken-Manager. Am einfachsten kommt man dorthin, indem man das PlatformIO-Icon in der linken Iconleiste klickt und im Fenster "Quick Access" unter "PIO Home" auf "Libraries" klickt. Im Suchfeld einfach den Namen der gesuchten Bibliothek eingeben, dann in der Liste der gefundenen Libs die gewünschte Bibliothek anklicken und "Add to Project" auswählen. In dem sich öffnenden Dialogfenster über die Dropdown-Auswahl das Zielprojekt auswählen und mit "Add" bestätigen. Auf diese Weise werden die beiden für das Programm benötigten Bibliotheken "TFT_eSPI" und "TJpg_Encoder" dem Projekt hinzugefügt.
Bardisplay-Platformio-2.pngAbb. 3 - Bibliotheken zum Projekt hinzufügen

Einstellungen für die Display-Bibliothek vornehmen

Wie Anfangs schon geschrieben, liegt einer der Vorteile von PlatformIO darin, defines für die bedingte Compilierung im Projektsetup angeben zu können. Deshalb können die Einstellungen für das verwendete Display und die Pin-Verdrahtung jetzt in der Konfigurationsdatei des Projekts, platformio.ini, vorgenommen werden, anstatt, wie bei Arduino erforderlich, die Quellcodedateien der Bibliothek selbst zu ändern. Der Platz dafür ist die Direktive build_flags im [env]-Abschnitt der Datei. Damit die TFT-Bibliothek weiß, dass unser Display-Setup nunmehr "von außen" über die Projekteinstellungen kommt, muss auf jeden Fall das Flag USER_SETUP_LOADED=1 gesetzt werden. Sodann übernimmt man noch alle define-Direktiven für das passende eigene Display-Setup, die man sonst in der Datei User_Setup.h der Bibliothek angegeben hätte. Define-Direktiven notiert man mit dem Buildflag -D.

Für mein Entwicklungs-Setup für das Bardisplay-Programm verwende ich hier ein ILI9341-basiertes Displayboard mit 320x240 Pixeln, so dass die fertige platformio.ini dann so aussieht:

; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:d1_mini]
platform = espressif8266
board = d1_mini
framework = arduino
lib_deps =
bodmer/TFT_eSPI@^2.3.56
bodmer/TJpg_Decoder@^0.1.0

build_flags =
-DUSER_SETUP_LOADED=1
-DILI9341_DRIVER=1
-DTFT_WIDTH=320
-DTFT_HEIGHT=240
-DTFT_MOSI=13 ;D7
-DTFT_SCLK=14 ;D5
-DTFT_CS=4 ;D2
-DTFT_DC=2 ;D4
-DTFT_RST=-1
-DTFT_BL=5 ;D1
-DTFT_BACKLIGHT_ON=HIGH
-DTFT_BACKLIGHT_OFF=LOW
-DLOAD_GLCD=1
-DSPI_FREQUENCY=27000000

upload_speed = 460800

Die Zeile upload_speed = 460800 stellt die Übertragungsrate für die serielle (USB)-Schnittstelle ein, der Standardwert von 9600 ist für den Upload des Programmes dann doch eher nur etwas für Geduldige. Mit dem Eintrag monitor_speed = 115200 kann außerdem die Übertragungsrate für den ebenfalls in PlatformIO vorhandenen seriellen Monitor an die Angabe im Beispielprogramm angepasst werden.

In der blauen Zeile unten links im VS Code-Hauptfenster hat PlatformIO einige Icons platziert. Mit dem Pfeil nach rechts kann das Programm compiliert und auf das angeschlossene Board hochgeladen werden. Wenn alles passt, sollte das jetzt schon erfolgreich funktionieren. Damit das Programm auch auf dem Board funktioniert, müssen in der Datei ESP-Bardisplay.ino in Zeile 29 und 30 natürlich noch korrekte WLAN-Zugangsdaten eingetragen werden.
Bardisplay-Platformio-3.jpg
Abb. 4 - Funktions-Icons von PlatformIO

Von .ino zu .cPP

Wenn man versucht, die Programmdatei (ESP-Bardisplay.ino)  im VS Code Editor zu bearbeiten, wird man mit einem Popup gewarnt, dass "IntelliSense" diese Art Dateien nicht unterstütze. Das bedeutet, dass VS Code in diesem Dateiformat nicht alle Informationen so vorfindet, wie sie für Syntaxvervollständigung und Codenavigation wie z.B. "Gehe zu Definition" erforderlich wären. VS Code bevorzugt hier "richtige" C oder C++ - Dateien. Praktischerweise zeigt das Popup auch den Link zu einer Hilfeseite von PlatformIO, auf der erklärt wird, mit welchen Schritten man aus dem Arduino-File ein "richtiges" C/C++ - Projekt macht. Das sind im Wesentlichen

  • umbenennen der Dateiendung von .ino in .cpp,
  • ergänzen des includes #include <Arduino.h> am Beginn der Datei,
  • erstellen einer passenden header-Datei mit den Deklarationen aller Funktionen außer setup() und loop().

Leider ist gerade der letzte Punkt bei größeren Programmen ziemlich nervig und ich konnte auf die Schnelle kein Plugin für VS Code finden, welches das automatisiert. Für das vorliegende Beispielprogramm sieht die Include-Datei ESP-Bardisplay.h wie folgt aus:

/*
 * Funktionsdeklarationen für ESP-Bardisplay.cpp
 */
#include <Arduino.h>

void initWiFi();
void handleRoot();
void handleVersion();
void drawText(String s, uint8_t x, uint8_t y, uint8_t fontsize, uint32_t color, bool centered, bool clear);
void handleShowText();
void drawJpg(String url, uint8_t x, uint8_t y);
void handleShowImage();
void handleClearScreen();
void handleOn();
void handleOff();
void handleReboot();
void handleNotFound();bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap);
void displayOn();
void displayOff();

Diese neue Datei landet im include-Verzeichnis des Projekts und in der Datei ESP-Bardisplay.cpp müssen ganz zu Beginn diese beiden Zeilen ergänzt werden:

#include <Arduino.h>
#include "ESP-Bardisplay.h"

Vor dem Neucompilieren sollten die Projektausgabe-Verreichnisse mit clean (Papierkorbsymbol in der Iconleiste unten) gelöscht werden. Ein clean empfiehlt sich immer dann, wenn im Projektverzeichnis größere Änderungen wie z.B. entfernen oder hinzufügen von Bibliotheken oder verschieben von Dateien in andere Unterverzeichnisse gemacht wurden.

Eine zweite Projektkonfiguration für die Produktivumgebung

An meiner "Produktivumgebung", der Bar vor Ort, ist ein anderes Display verbaut, ein größeres 3,5'' TFT mit ILI9488-Chipsatz. Außerdem habe ich dort einen Pin anders verdrahtet, allerdings eher unabsichtlich :-). Hier spielt die große Stärke von PlatformIO auf: Ich kann einfach eine weitere Projektkonfiguration - genannt environment - in der Datei platformio.ini hinterlegen und dann beim Compilieren und Hochladen zwischen den environments beliebig wählen. Ich kann also zukünftig ohne Codeänderungen sowohl mein Entwicklungssystem als auch mein Produktivsystem flashen. Nebenbei kann ich nun auch jederzeit Updates der Bibliothek TFT_eSPI einspielen, ohne dass mir dabei die individuell angepasste Datei TFT_eSPI/User_Setup.h mit meinem Displaytypen-Setup überschrieben wird.

Mehrere Konfigurationen kann man erstellen, indem man die individuellen Einstellungen in mehreren Sektionen unter dem Schlüsselwort [env:...] unterbringt. Dabei lassen sich Einstellungen auch "vererben", d.h. alles, was global für alle environments gilt, wird direkt unter [env] aufgeführt, alle individuellen Settings jeweils bei den "Unter-Konfigurationen" [env:abc], [env:xyz] usw.

Für meine Situation ergibt sich beispielsweise dann folgende Konfigurationsdatei:

[env]
platform = espressif8266
board = d1_mini
framework = arduino
lib_deps = 
    bodmer/TFT_eSPI@^2.3.56
    bodmer/TJpg_Decoder@^0.1.0
upload_speed = 460800
monitor_speed = 115200

build_flags =
  -DUSER_SETUP_LOADED=1
  -DTFT_BACKLIGHT_ON=HIGH
  -DTFT_BACKLIGHT_OFF=LOW
  -DLOAD_GLCD=1
  -DSPI_FREQUENCY=27000000

[env:debug]     ; Entwicklungssystem: ILI9341-TFT 320x240
build_flags = ${env.build_flags}
  -DDEBUG=1
  -DILI9341_DRIVER=1
  -DTFT_WIDTH=320
  -DTFT_HEIGHT=240
  -DTFT_MOSI=13   ;D7
  -DTFT_SCLK=14   ;D5
  -DTFT_CS=4      ;D2
  -DTFT_DC=2      ;D4
  -DTFT_RST=-1
  -DTFT_BL=5      ;D1
  -DSTASSID=\"my-ssid-dev\"
  -DSTAPSK=\"my-password-dev\"

[env:release]   ; Produktivsystem: ILI9488-TFT 480x320
build_flags = ${env.build_flags}
  -DDEBUG=1
  -DILI9488_DRIVER=1
  -DTFT_WIDTH=480
  -DTFT_HEIGHT=320
  -DTFT_MOSI=13
  -DTFT_SCLK=14
  -DTFT_CS=15
  -DTFT_DC=0
  -DTFT_RST=4
  -DTFT_BL=5
  -DSTASSID=\"my-ssid-prod\"
  -DSTAPSK=\"my-password-prod\"

Sinnvollerweise habe ich auch gleich die Angabe der WLAN-Zugangsdaten aus den Zeilen 29 und 30 des Programms in ESP-Bardisplay.cpp entfernt, da auch diese natürlich unterschiedlich für die beiden Umgebungen sind.

In der Statuszeile von VS Code unten kann man nun die aktuell ausgewählte Projektkonfiguration sehen und per Klick auch umschalten. Compile- und Upload-Aktionen werden dann immer mit der aktuell ausgewählten Konfiguration durchgeführt.
Bardisplay-Platformio-4.jpg
Abb. 5 - Unterschiedliche environments verwenden

Fazit

PlatformIO bietet einige Annehmlichkeiten für die Entwicklung von Microcontroller-Programmen, die gerade erfahrene Entwickler bei der Arduino-IDE vermissen. Im Gespann mit Visual Studio Code lassen sich Projekte mit mehreren unterschiedlichen Zielplattformen oder -Devices besser organisieren und auch die Git-Integration ist zumindest für mich eine Arbeitserleichterung.