Integration des BSEC in CircuitPython (Eine laufende Arbeit)

Einige Hinweise zur Verwendung des Build-Flags USER_C_MODULES

Dieser Blog-Beitrag wird zwei verschiedene Themen behandeln. Einerseits werde ich über den allgemeinen Ansatz zum Einbinden und Testen Ihres eigenen C-Codes in Ihrem benutzerdefinierten Build von SchaltkreisPython . Andererseits werde ich über spezifische Herausforderungen bei der Nutzung der BME688 Breakout Boardbasierend auf dem BME688-Sensor von Bosch, wobei das spezifische Breakout-Board von uns entwickelt wurde, und deren BSEC-Bibliothek auf dem RP2040. Diese Bibliothek ermöglicht eine erweiterte Analyse der vom BME688 bereitgestellten Sensordaten und Berechnungen auf der Grundlage dieser Sensordaten.

Die Vorteile, entweder eigenen C-Code oder statische Bibliotheken in CircuitPython zu verpacken, liegen auf der Hand. Viele Mikrocontroller-Boards, in diesem Fall der Raspberry Pi Pico, der auf dem RP2040 basiert, erlauben die Ausführung von C-Code. Reines C zu schreiben kann entmutigend sein, besonders für Programmierer, die noch nicht viel Zeit mit dieser Sprache verbracht haben. CircuitPython hingegen bietet einen einfachen Zugang über die Python-Syntax, eine Menge gut geschriebener Bibliotheken und eine großartige Community, die Ihnen hilft.

Unser Ziel war es, zu testen, ob es theoretisch möglich ist, die BSEC-Bibliothek zu umhüllen. Diese Bibliothek wird für eine Vielzahl von Plattformen und Compilern angeboten, darunter der Cortex M0+ und der arm-none-eabi-gcc-Compiler.

Während die Umgehung des BSEC für unseren Fall spezifisch sein könnte, werde ich einige allgemeinere Hinweise geben, wie man ein selbst geschriebenes C-Modul in CircuitPython einbindet, um seine Funktionalität schnell zu testen, wie man eine zusätzliche Binärbibliothek bindet und wie man CircuitPython so baut, dass sowohl das C-Modul als auch die Bibliothek eingebunden sind. Auf dem Weg dorthin gibt es einige kleinere Fallstricke, die dieser Blogbeitrag hoffentlich abdeckt.

Wenn Sie Ihr eigenes CircuitPython C-Modul schreiben wollen, gibt es derzeit zwei verschiedene Ansätze. Über den ersten können Sie in den offiziellen Richtlinien auf der Adafruit Website die derzeit veraltet sind, aber immer noch eine Menge nützlicher Informationen enthalten. Daher wird empfohlen, sich den Quellcode von CircuitPython anzusehen und ihm als Beispiel zu folgen. Ohne zu sehr ins Detail zu gehen, geht es darum, Ihre Header- und Quellcodedateien in den Verzeichnissen "shared-bindings" bzw. "shared-module" abzulegen und dabei die bereitgestellten Wrapper-Methoden zu verwenden.

Wenn Ihr Ziel die langfristige Nutzung oder die Mitarbeit an der offiziellen Version ist, sollten Sie diese Richtlinien befolgen. Wenn Sie zunächst sehen wollen, ob der Code auf einer bestimmten Plattform läuft oder einige schnelle Tests durchführen wollen, ist der hier beschriebene Ansatz etwas einfacher und weniger arbeitsintensiv.

Wie verwendet man USER_C_MODULES?

Zuerst gehen wir den Prozess der Erstellung von CircuitPython durch, indem wir die offizielle Anleitung verwenden, die Sie hier finden können hier. Nachdem wir uns vergewissert haben, dass alles wie vorgesehen funktioniert, CircuitPython gebaut wird und auf der Zielplattform läuft, können wir damit beginnen, unseren eigenen Code einzubinden. Dies geschieht über das Build-Flag USER_C_MODULES. Diese Funktionalität ist von MicroPython geerbt. Wenn Sie daran interessiert sind, können Sie einen Blick auf die offizielle MicroPython-Dokumentation was auch auf CircuitPython anwendbar zu sein scheint.

Zuerst erstellen wir einen neuen Ordner im CircuitPython-Verzeichnis und füllen ihn mit den notwendigen Dateien:

circuitpython
├──circuitpython/
| ├───data/
| ├───Geräte/
| ├───docs/
... ...
|
└───externeC-Module/
        └──BSEC_Test
                ├─── bsec_datatypes.h
                ├─── bsec_interface.h
                ├─── libalgobsec.a
                ├─── micropython.mk
                └── testingBSEC.c

In diesem Fall werden 'bsec_datatypes.h', 'bsec_interface.h' und 'libalgobsec.a' von Bosh bereitgestellt. Wir müssen uns nur um mircopython.mk und testingBSEC.c kümmern.

Wie sich herausstellt, enthält CircuitPython bereits ein C (und C++) Beispiel und das dazugehörige Makefile im Quellcode. Sie können es finden unter:

circuitpython/examples/usercmodule/cexample/

Wir können den Code einfach kopieren und für unseren eigenen Gebrauch bearbeiten. Dazu binden wir die Header-Dateien der Bibliothek ein, die wir verwenden wollen (in unserem Fall #include "bsec_interface.h") und passen den Code darin an. Die Datei "examplemodule.c" ist ein gut gewähltes Beispiel, da sie eine sehr einfache Funktion enthält, die Integer-Eingaben entgegennimmt und Integer-Ausgaben erzeugt. Die Anpassung an unsere Anforderungen bestand hauptsächlich darin, die Anzahl der Eingabevariablen zu ändern und einige Funktionen umzubenennen. Wenn Ihr Ziel nur darin besteht, die Durchführbarkeit einer groß angelegten Integration zu testen, sollte dies wahrscheinlich ausreichend sein. Dies kann leicht erreicht werden, indem man Fehlercodes und einige Beispielausgaben zurückgibt und prüft, ob sie wie erwartet aussehen. Ansonsten ist das Hinzufügen neuer Funktionen oder das Vornehmen grundlegenderer Änderungen an bestehenden Funktionen nicht sehr schwierig und in der MicroPython-Dokumentation gut beschrieben.

Sie können es bereits dabei belassen. Je nach Komplexität Ihres Testfalls kann es notwendig sein, weitere Funktionen hinzuzufügen, aber in unserem Fall war das Hauptziel einfach zu sehen, ob der Code läuft.

Zusätzlich ändern wir das Makefile - micropython.mk - so, dass es etwa so aussieht:

BSEC_TEST_DIR := $(USERMOD_DIR)
# Fügen Sie unsere C-Datei zu SRC_USERMOD hinzu.
SRC_USERMOD += $(BSEC_TEST_DIR)/testingBSEC.c
# Hinzufügen der vorkompilierten Bibliothek zu SRC_USERMOD.
SRC_USERMOD += $(BSEC_TEST_DIR)/libalgobsec.a

Wir machen dann einfach weiter und bauen CircuitPython wie in der offiziellen Dokumentation beschrieben:

  • 1. Gehen Sie in das Verzeichnis des Ports, für den Sie bauen wollen (in unserem Fall circuitpython/ports/raspberrypi)
  • 2. make BOARD=raspberry_pi_pico USER_C_MODULES=../../../externalCmodules

Das Build-Flag verweist auf das Verzeichnis, in das wir unseren Code und unsere Bibliotheken stellen.

Probleme während des Erstellungsprozesses

Wenn Sie die Anweisungen bis hierher genau befolgt haben, werden Sie auf das erste Problem stoßen. Das Build-System findet die Bibliothek (libalgobsec.a) nicht.

Es scheint, dass das Build-System an zwei verschiedenen Stellen während des Build-Prozesses danach sucht. In unserem Fall an den folgenden zwei Stellen:

circuitpython
├──circuitpython/
| ├───data/
| ├───Geräte/
| ├───docs/
| └──BSEC_Test
| └── libalgobsec.a
...
|
└──externeC-Module/
        └──BSEC_Test
                ├─── bsec_datatypes.h
                ├─── bsec_interface.h
                ├── libalgobsec.a
                ├── micropython.mk
                └── testingBSEC.c

Dieses Hindernis hat offenbar seinen Ursprung in der Art und Weise, wie das Build-System mit dem USER_C_MODULE-Buildflag interagiert. Selbst nachdem ich einige Zeit damit verbracht habe, mich damit zu befassen, war jede Lösung, die ich ausprobiert habe, in irgendeiner Weise unzureichend oder führte zu weiterer Komplexität. Sollte jemand dies lesen und eine sauberere Lösung finden, würde ich mich freuen, wenn Sie diese mit mir teilen würden! In diesem Fall werde ich den Text entsprechend ändern.

Zum Glück lässt sich das Problem leicht vermeiden, indem man die Bibliothek dupliziert und ebenfalls am zweiten Speicherort ablegt. Dies ist zwar nicht die sauberste Lösung, aber es gibt keinen Größenunterschied im endgültigen Build.

Wenn Sie versuchen, Ihren eigenen Code zum Laufen zu bringen, sollte dies alles sein, was Sie brauchen, um weitere Tests durchführen zu können. Durch die Umgehung dieses kleinen Problems sollten Ihr eigener Code und Ihre Bibliotheken bereits kompiliert, korrekt gelinkt, gebaut und aufrufbar sein.

Aber was ist mit unserem Testfall, dem BSEC?

Wenn Sie diesen Weg einschlagen, werden Sie schnell enttäuscht sein. Sie werden sich mit Fehlermeldungen über undefinierte Funktionen konfrontiert sehen:

/home/hanno/System/bin/ARM_GCC/gcc-arm-none-eabi-10-2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld: IaqEstimator.c:(.text+0xea): undefinierte Referenz auf `fminf'
/home/hanno/System/bin/ARM_GCC/gcc-arm-none-eabi-10-2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld: IaqEstimator.c:(.text+0x112): undefinierte Referenz auf `fmaxf'
/home/hanno/System/bin/ARM_GCC/gcc-arm-none-eabi-10-2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld: IaqEstimator.c:(.text+0x162): undefinierte Referenz auf `fmaxf'
/home/hanno/System/bin/ARM_GCC/gcc-arm-none-eabi-10-2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld: IaqEstimator.c:(.text+0x190): undefinierte Referenz auf `fmaxf'
/home/hanno/System/bin/ARM_GCC/gcc-arm-none-eabi-10-2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld: IaqEstimator.c:(.text+0x198): undefinierte Referenz auf `fminf'
........

Nun, wie sich herausstellt, gibt es derzeit ein großes Hindernis. Da CircuitPython für den Einsatz auf Mikrocontrollern gedacht ist und der Speicher auf diesen oft sehr begrenzt ist, haben die Entwickler ihre eigene Mathematikbibliothek geschrieben und verwenden diese. Während dies je nach persönlichem Anwendungsfall kein Problem sein mag, verwendet BSEC die Funktionalität der Standard-C-Mathe-Bibliothek. Und obwohl es ein Buildflag gibt, um CircuitPython mit der Standard-C-Mathe-Bibliothek zu bauen (circuitpython/ports/raspberrypi/mpconfigport.mk → INTERNAL_LIBM), scheint es derzeit nicht mehr zu funktionieren. Es wieder zum Laufen zu bringen, würde zusätzliche Zeit und eine gute Kenntnis des Build-Systems erfordern und ging für unseren kleinen Testfall etwas zu weit.

Aber es gibt eine (etwas 'hacky') Lösung, um das BSEC zumindest dazu zu bringen, sich zu initialisieren und einige grundlegende Informationen auszugeben, was beweist, dass es wahrscheinlich möglich ist, es in der Theorie zu integrieren. Wir wickeln die fehlenden Funktionen in unseren C-Code ein oder implementieren sie neu, indem wir die eingebauten mathematischen Funktionen verwenden, die die internal_libm bereitstellt. Dies kann etwa wie folgt aussehen:

float fminf ( float x, float y ) {
    if (isnan(x))
		y zurückgeben;
	if (isnan(y))
		return x;
	// Behandlung von Nullen mit Vorzeichen, siehe C99 Anhang F.9.9.2
	if (signbit(x) != signbit(y))
		return signbit(x) ? x : y;
	return x < y ? x : y;
}

In den meisten Fällen wurde der Code aus dem arm-none-eabi-gcc-Compiler die quelloffen ist und ohne Probleme eingesehen werden kann.

Jetzt wird CircuitPython gebaut, das BSEC ist aufrufbar und gibt die entsprechenden Werte zurück. Trotzdem bleiben einige Probleme bestehen, wenn man versucht, bestimmte Funktionen zu nutzen. Es scheint wahrscheinlich, dass diese das Ergebnis des CircuitPython-Build-Prozesses sind, da sie verschwinden, wenn die BSEC nativ mit C verwendet wird. Um das BSEC in seiner Gesamtheit zum Laufen zu bringen und es einfach von CircuitPython aus aufrufen zu können, ist zusätzliche Arbeit und wahrscheinlich auch eine gewisse Koordination notwendig, um Zugang zu einer korrekten Version des BSEC zu bekommen (das derzeit mit der Version 9-2019-q4-major der ARM GNU Tool Chain gebaut wird, während CircuitPython die Version 10-2020-q4-major verwendet) und um den Build-Prozess von CircuitPython leicht zu modifizieren und anzupassen. Ob dies in Zukunft von uns gemacht wird, bleibt abzuwarten, aber ich hoffe, dass dieser Blogbeitrag Entwicklern, die ihren eigenen Code mit dem USER_C_MODULES-Buildflag testen und dabei einige Fallstricke vermeiden wollen, eine Orientierung geben kann.

Sollte es eine Aktualisierung unserer Fortschritte geben, wird diese Seite entsprechend aktualisiert.

Wenn Sie weitere Fragen haben, die ich nicht ausführlich genug (oder gar nicht) behandelt habe, können Sie entweder einen Kommentar hinterlassen oder sich per E-Mail an mich wenden! Ich werde gerne antworten.

Hinterlassen Sie einen Kommentar

Um die Kommentarfunktion nutzen zu können, müssen Sie angemeldet sein.