STM32 mit C++ programmieren
Meine Toolchain um STM32 Microcontroller zu programmieren habe ich bereits in diesem Post vorgestellt. Dabei ging es aber ausschließlich um C Code. Jetzt geht es darum zusätzlich C++ zu verwenden. Dabei soll die Funktionssoftware in der modernen Hochsprache geschrieben werden, während die Basisfunktionen weiterhin von den bestehenden C-Libraries umgesetzt werden.
C/C++ Mischsoftware
Aus einem C++ Programm heraus C-Funktionen aufzurufen funktioniert problemlos, wenn man die entsprechenden Header-Dateien inkludiert, und das ganze mit einem C++ Compiler übersetzt.
Dementsprechend versuche ich also zunächst bei dem von CubeMX generierten Code die main.c in main.cpp umzubennen, und im Makefile den gcc durch g++ zu ersetzen. Ein erster Testlauf ergibt einen Haufen Fehler der Form:
Src/usbd_conf.c:371:36: error: invalid conversion from 'void*' to 'PCD_HandleTypeDef*' [-fpermissive]
Dies liegt darani, dass in C++ weniger lax mit Pointern umgegangen wird. Eine einfache aber pfuschige Lösung ist es, -fpermissive zu den C-Flags im Makefile hinzuzufügen. Dann werden aus den Fehlern Warnungen, und das Ganze kompiliert zumindest mal. Das soll aber nicht so bleiben, denn dass der Compiler einen sauberen Umgang mit Pointern erzwingt ist grundsätzlich sinnvoll.
Die Lösung hierfür ist ein weiterer Umbau des Makefiles, sodass alle C-Dateien wieder durch einen gcc Aufruf übersetzt werden, und nur für die Cpp-Dateien und das Linken ein g++ Aufruf verwendet wird.
C-Standardbibliothek: Newblib
So lassen sich jetzt alle C- und Cpp-Dateien erfolgreich (und ohne Warnungen) übersetzen, beim Linkem gibt es aber noch Fehler der Form:
/usr/lib/gcc/arm-none-eabi/9.2.1/../../../arm-none-eabi/bin/ld: /usr/lib/gcc/arm-none-eabi/9.2.1/../../../arm-none-eabi/lib/thumb/v7-m/nofp/libc_nano.a(lib_a-abort.o): in function `abort':
/build/newlib-CVVEyx/newlib-3.3.0/build_nano/arm-none-eabi/thumb/v7-m/nofp/newlib/libc/stdlib/../../../../../../../../newlib/libc/stdlib/abort.c:59: undefined reference to `_exit'
Bei meinem Testszenario sind es z.B. die Funktionen _exit, _sbrk, _kill, und _getpid welche nicht gefunden werden. Es handelt sich dabei um Funktionen der C-Standardbibliothek libc, welche hier offensichtlich fehlen. Es gibt unterschiedliche implementierungen der libc. Die arm-none-eabi-Toolchain verwendet standardmäßig die Newlib. Ein Blick ins Makefile zeigt dass der Linker mit dem Parameter
-specs=nano.specs
aufgerufen wird. Es wird also sogar nur die nano Variante der Newlib verwendet.
Es gibt zwei Möglichkeiten das Problem zu beheben. Die eine ist, eine andere Newlib Variante zu verwenden. Mit
-specs=nosys.specs
wird erfolgreich ein Binary erstellt.
Alternativ wird weiterhin die nano Variante der Newlib verwendet, und die fehlenden Funktionen werden von Hand implementiert. Ich habe mich hierbei an diesem Blogartikel orientiert (auch sonst eine sehr interessante Serie zur bare metal Programmierung von Mikrocontrollern). Wichtig ist es, nicht nur eine .c-Datei sondern auch eine zugehörige .h-Datei anzulegen, damit die Symbole gefunden werden.
Die fehlenden Funktionen manuell zu implementieren hat den Vorteil, dass man explizit festlegen kann wie diese sich verhalten sollen (z.B. wie sbrk mit dem Speicher des Mikrocontrollers umgeht). Außerdem verbraucht die nano Variante der Newlib weniger Speicherplatz als die nosys Variante, welche für sämtliche Syscalls Stubs enthält, selbst für die, welche gar nicht verwendet werden. Bei meinem Testszenario ist die resultierende Binärdatei mit der nano Variante 25kB groß, und mit der nosys Variante 31kB.
Hello World für Embedded C++
Um bei der ganzen Bastelei im Blick zu behalten, dass ich auch wirklich mit C++ arbeite und nicht einfach C-Code mit dem G++ kompiliere, habe ich die folgenden Code als Hauptschleife verwendet:
std::vector<bool> ledmuster {true, false, true, false, true, false, false, false, false};
while (1)
{
for(auto ledstate : ledmuster)
{
auto pin_state = GPIO_PIN_RESET;
if(ledstate) pin_state = GPIO_PIN_SET;
HAL_GPIO_WritePin(GrueneLed_GPIO_Port, GrueneLed_Pin, pin_state);
HAL_Delay(300);
}
}
STL-Container, Range-based for-loops und automatische Typenerkennung sind alles Elemente die es in C nicht gibt, und am Ende zeigt das Blinken eines definierten Musters, dass der Code tatsächlich läuft.
Und tatsächlich, nachdem alle oben genannten Probleme behoben waren, blinkte die LED im vorgesehenen Muster.