Integratie van de BSEC in CircuitPython (werk in uitvoering)

Enkele tips over het gebruik van de USER_C_MODULES bouwvlag

Deze blog post zal twee verschillende onderwerpen behandelen. Enerzijds zal ik het hebben over de algemene benadering van het opnemen en testen van je eigen C-code in je aangepaste build van CircuitPython . Aan de andere kant zal ik het hebben over specifieke uitdagingen bij het gebruik van de BME688 Breakout Raadgebaseerd op de BME688 sensor van Bosch, waarbij het specifieke breakout board door ons is ontworpen, en hun BSEC bibliotheek op de RP2040. Deze bibliotheek maakt geavanceerde analyse mogelijk van de sensorgegevens die door de BME688 worden geleverd, alsmede berekeningen op basis van die sensorgegevens.

De voordelen van het verpakken van je eigen C code of statische bibliotheken in CircuitPython zijn overduidelijk. Veel microcontroller boards, in dit geval de Raspberry Pi Pico die gebaseerd is op de RP2040, staan het uitvoeren van C code toe. Het schrijven van pure C code kan ontmoedigend zijn, vooral voor programmeurs die nog niet veel tijd met die taal hebben doorgebracht. CircuitPython daarentegen biedt eenvoudige toegang via de Python syntaxis, veel goed geschreven bibliotheken en een geweldige gemeenschap om je te helpen.

Ons doel was om te testen of het theoretisch mogelijk is om de BSEC bibliotheek te wrappen. Deze bibliotheek wordt aangeboden voor een veelheid van platformen en compilers, waaronder de Cortex M0+ en de arm-none-eabi-gcc compiler.

Hoewel het omzeilen van de BSEC specifiek kan zijn voor ons geval, zal ik enkele meer algemene aanwijzingen geven over hoe een op maat geschreven C-Module in CircuitPython kan worden opgenomen om snel de functionaliteit ervan te testen, hoe een extra binaire bibliotheek kan worden gekoppeld en hoe CircuitPython kan worden gebouwd zodat zowel de C-Module als de bibliotheek worden opgenomen. Er zijn een paar kleine valkuilen onderweg, waarvan ik hoop dat deze blog post die zal behandelen.

Als u uw eigen CircuitPython C module wilt schrijven, bestaan er momenteel twee verschillende benaderingen. Over de eerste kunt u lezen in de officiële richtlijnen op de Adafruit website die momenteel verouderd zijn, maar nog steeds veel nuttige informatie bevatten. Daarom is de aanbevolen aanpak om naar de broncode van CircuitPython te kijken en die als voorbeeld te volgen. Zonder al te veel in detail te treden, is dit gebaseerd op het plaatsen van je header en broncode bestanden in respectievelijk de 'shared-bindings' en 'shared-module' directories terwijl je gebruik maakt van de meegeleverde wrapper methodes.

Als je doel langdurig gebruik is of het bijdragen aan de officiële release, zou je die richtlijnen moeten volgen. Als u eerst wil zien of de code op een specifiek platform draait of wat snelle tests wil doen, is de hier beschreven aanpak iets eenvoudiger en minder werkintensief.

Hoe gebruikt u USER_C_MODULES?

Eerst gaan we door het proces van het bouwen van CircuitPython met behulp van de officiële instructies die kunnen worden gevonden hier. Nadat we er zeker van zijn dat alles werkt zoals bedoeld, CircuitPython bouwt en draait op uw doelplatform, kunnen we beginnen met het opnemen van onze eigen code. Dit wordt gedaan via de USER_C_MODULES build vlag. Deze functionaliteit is geërfd van MicroPython. Als u geïnteresseerd bent, kunt u een kijkje nemen op de officiële MicroPython documentatie die ook van toepassing lijkt te zijn op CircuitPython.

Eerst maken we een nieuwe map aan in de CircuitPython directory en vullen die met de nodige bestanden:

circuitpython
├──circuitpython/
| ├──data/
| ├──devices/
| ├──docs/
... ...
|
└──externalCmodules/
        └──BSEC_Test
                ├── bsec_datatypes.h
                ├── bsec_interface.h
                ├── libalgobsec.a
                ├── micropython.mk
                └── testingBSEC.c

In dit geval worden 'bsec_datatypes.h', 'bsec_interface.h' en 'libalgobsec.a' geleverd door Bosh. We hoeven ons alleen zorgen te maken over mircopython.mk en testingBSEC.c.

Het blijkt dat CircuitPython al een C (en C++) voorbeeld en het bijbehorende Makefile in de broncode bevat. Je kunt het vinden op:

circuitpython/examples/usercmodule/cexample/

We kunnen de code gewoon kopiëren en aanpassen voor ons eigen gebruik. Om dit te doen includeren we de header bestanden van de bibliotheek die we willen gebruiken (In ons geval #include "bsec_interface.h") en passen de code daarin aan. De "examplemodule.c" is een goed gekozen voorbeeld, omdat het een zeer eenvoudige functie bevat die gehele getallen als invoer neemt en gehele getallen als uitvoer produceert. Het aanpassen aan onze eisen was vooral een kwestie van het aantal invoervariabelen veranderen en enkele functies een andere naam geven. Als het alleen de bedoeling is om de levensvatbaarheid van een grootschalige integratie te testen, zou dit waarschijnlijk voldoende moeten zijn. Dit kan eenvoudig worden bereikt door foutcodes en wat voorbeelduitvoer terug te sturen en te controleren of het eruit ziet zoals verwacht. Voor de rest is het toevoegen van nieuwe functies of het aanbrengen van meer fundamentele wijzigingen in bestaande functies niet erg moeilijk en goed beschreven in de MicroPython documentatie.

U kunt het hier al bij laten. Afhankelijk van de complexiteit van je testcase kan het nodig zijn om meer functies toe te voegen, maar in ons geval was het hoofddoel gewoon om te zien of de code loopt.

Daarnaast passen we de Makefile - micropython.mk - aan zodat het er ongeveer zo uitziet:

BSEC_TEST_DIR := $(USERMOD_DIR)
# Voeg ons C-bestand toe aan SRC_USERMOD.
SRC_USERMOD += $(BSEC_TEST_DIR)/testingBSEC.c
# Voeg de voorgecompileerde bibliotheek toe aan SRC_USERMOD.
SRC_USERMOD += $(BSEC_TEST_DIR)/libalgobsec.a

We gaan dan gewoon verder en bouwen CircuitPython zoals beschreven in de officiële documentatie:

  • 1. Ga naar de directory van de port waarvoor je wilt bouwen (In ons geval circuitpython/ports/raspberrypi)
  • 2. make BOARD=raspberry_pi_pico USER_C_MODULES=../../externalCmodules

De bouwvlag wijst naar de map waar we onze code en bibliotheken in zetten.

Problemen tijdens het bouwproces

Als je de instructies tot nu toe vrij nauwkeurig hebt gevolgd, zul je tegen het eerste probleem aanlopen. Het bouwsysteem vindt de bibliotheek (libalgobsec.a) niet.

Het lijkt erop dat het bouwsysteem er op twee verschillende plaatsen naar zoekt tijdens het bouwproces. In ons geval op de volgende twee plaatsen:

circuitpython
├──circuitpython/
| ├──data/
| ├──devices/
| ├──docs/
| └──BSEC_Test
| └── libalgobsec.a
...
|
└──ExternalCmodules/
        └──BSEC_Test
                ├── bsec_datatypes.h
                ├── bsec_interface.h
                ├── libalgobsec.a
                ├── micropython.mk
                └── testingBSEC.c

Dit obstakel heeft blijkbaar zijn oorsprong in de manier waarop het bouwsysteem omgaat met de USER_C_MODULE buildflag. Zelfs nadat ik er geruime tijd naar heb gekeken, schoot elke oplossing die ik heb geprobeerd op de een of andere manier tekort of introduceerde verdere complexiteit. Mocht iemand dit lezen en een schonere oplossing vinden, dan zou ik het waarderen als je die deelt! Ik zal de tekst in dat geval dienovereenkomstig aanpassen.

Gelukkig kan dit probleem gemakkelijk worden vermeden door de bibliotheek te dupliceren en ook op de tweede locatie te plaatsen. Hoewel dit natuurlijk niet de schoonste oplossing is, is er geen verschil in grootte in de uiteindelijke build.

Als u probeert uw eigen code werkend te krijgen, zou dit alles moeten zijn wat u nodig hebt om verder te kunnen testen. Door dit kleine probleem te omzeilen zou uw eigen code en bibliotheken al moeten compileren, correct gelinkt zijn, opgebouwd worden en aanroepbaar zijn.

Maar hoe zit het met onze testcase, de BSEC?

Als u dit proces volgt, zult u snel teleurgesteld zijn. U zult geconfronteerd worden met foutmeldingen over ongedefinieerde functies:

/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): ongedefinieerde verwijzing naar `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): ongedefinieerde verwijzing naar `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): ongedefinieerde verwijzing naar `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): ongedefinieerde verwijzing naar `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): ongedefinieerde verwijzing naar `fminf'
........

Wel, zoals blijkt, bestaat er momenteel een groot obstakel. Aangezien CircuitPython bedoeld is om op microcontrollers te draaien en het geheugen daarop vaak nogal beperkt is, hebben de ontwikkelaars hun eigen wiskunde bibliotheek geschreven en gebruikt. Hoewel dit geen probleem kan zijn, afhankelijk van uw persoonlijke gebruik, gebruikt de BSEC de functionaliteit van de standaard C wiskunde bibliotheek. En hoewel er een buildflag bestaat om CircuitPython te bouwen met de standaard C wiskunde bibliotheek (circuitpython/ports/raspberrypi/mpconfigport.mk → INTERNAL_LIBM), lijkt deze momenteel ter ziele te zijn. Om het weer te laten werken zou extra tijd nodig zijn, een goede kennis van het build systeem en ging een beetje te ver voor onze kleine test case.

Maar er is een (ietwat 'hacky') oplossing om de BSEC tenminste te laten initialiseren en wat basisinformatie te laten geven, waaruit blijkt dat het gebruik ervan in theorie waarschijnlijk mogelijk is om het te integreren. We omwikkelen of herimplementeren de ontbrekende functies in onze C code door gebruik te maken van de ingebouwde wiskundige functies die de internal_libm biedt. Dit kan er ongeveer als volgt uitzien:

float fminf ( float x, float y ) {
    als (isnan(x))
		geef y terug;
	if (isnan(y))
		return x;
	// handelt getekende nullen af, zie C99 Bijlage F.9.9.2
	if (signbit(x) != signbit(y))
		return signbit(x) ? x : y;
	return x < y ? x : y;
}

In de meeste gevallen werd de code overgenomen of licht aangepast uit de arm-none-eabi-gcc compiler die open source is en zonder probleem bekeken kan worden.

Nu CircuitPython bouwt, is de BSEC aanroepbaar en geeft de juiste waarden terug. Desondanks blijven er enkele problemen bestaan als geprobeerd wordt om bepaalde functionaliteit te gebruiken. Het lijkt waarschijnlijk dat deze het gevolg zijn van het CircuitPython bouwproces, omdat ze verdwijnen als de BSEC met C wordt gebruikt. Om de BSEC in zijn geheel te laten draaien en gemakkelijk aanroepbaar te maken vanuit CircuitPython is extra werk nodig en waarschijnlijk enige coördinatie om toegang te krijgen tot een correcte versie van de BSEC (die momenteel gebouwd wordt met de 9-2019-q4-major release van de ARM GNU tool chain, terwijl CircuitPython de 10-2020-q4-major release gebruikt) en om het bouwproces van CircuitPython enigszins te wijzigen en aan te passen. Of dit in de toekomst ook door ons zal worden gedaan valt nog te bezien, maar ik hoop dat deze blogpost enige richting kan geven aan ontwikkelaars die geïnteresseerd zijn in het testen van hun eigen code door gebruik te maken van de USER_C_MODULES buildflag en die enkele valkuilen willen vermijden.

Mocht er een update zijn van onze vorderingen, dan zal deze pagina dienovereenkomstig worden bijgewerkt.

Als je nog vragen hebt die ik niet gedetailleerd genoeg heb behandeld (of helemaal niet), voel je vrij om een reactie achter te laten of contact op te nemen via e-mail! Ik zal u met plezier antwoorden.

Laat een reactie achter

U moet ingelogged zijn om een reactie te plaatsen.