Integrare il BSEC in CircuitPython (Un lavoro in corso)

Alcune indicazioni sull'uso del flag di compilazione USER_C_MODULES

Questo post del blog toccherà due diversi argomenti. Da un lato, parlerò dell'approccio generale per includere e testare il proprio codice C nella vostra build personalizzata di CircuitPython . D'altra parte, parlerò delle sfide specifiche nell'uso del Scheda di breakout BME688La libreria BSEC, basata sul sensore BME688 di Bosch, con la specifica scheda di breakout progettata da noi, e la loro libreria BSEC sulla RP2040. Questa libreria permette l'analisi avanzata dei dati del sensore forniti dal BME688 e i calcoli basati su questi dati del sensore.

I vantaggi di avvolgere il proprio codice C o le librerie statiche in CircuitPython sono abbastanza ovvi. Un sacco di schede a microcontrollore, in questo caso il Raspberry Pi Pico che è basato sull'RP2040, permettono l'esecuzione di codice C. Scrivere C puro può essere scoraggiante, specialmente per i programmatori che non hanno ancora passato molto tempo con questo linguaggio. CircuitPython d'altra parte fornisce un facile accesso attraverso la sintassi Python, un sacco di librerie ben scritte e una grande comunità per aiutarvi.

Il nostro obiettivo era testare se è teoricamente possibile avvolgere la libreria BSEC. Questa libreria è fornita per una moltitudine di piattaforme e compilatori, tra cui il Cortex M0+ e il compilatore arm-none-eabi-gcc.

Mentre lavorare intorno al BSEC potrebbe essere specifico per il nostro caso, fornirò alcune indicazioni più generali su come includere un modulo C personalizzato in CircuitPython per testare rapidamente la sua funzionalità, come collegare una libreria binaria aggiuntiva e come costruire CircuitPython in modo che sia il modulo C che la libreria siano inclusi. Ci sono alcune piccole insidie lungo la strada, che spero che questo post del blog copra.

Se volete scrivere il vostro modulo C di CircuitPython, esistono attualmente due diversi approcci. Potete leggere il primo nelle linee guida ufficiali sul sito Sito web di Adafruit che sono attualmente obsoleti ma contengono ancora molte informazioni utili. Quindi l'approccio raccomandato è quello di guardare il codice sorgente di CircuitPython e seguirlo come esempio. Senza entrare troppo nei dettagli, questo si basa sul mettere i vostri file header e il codice sorgente rispettivamente nelle directory 'shared-bindings' e 'shared-module' mentre usate i metodi wrapper forniti.

Se il vostro obiettivo è l'uso a lungo termine o contribuire al rilascio ufficiale, dovreste seguire queste linee guida. Se vuoi prima vedere se il codice funziona su una piattaforma specifica o fare qualche test veloce, l'approccio descritto qui è un po' più facile e meno impegnativo.

Come usare USER_C_MODULES?

Per prima cosa passiamo attraverso il processo di costruzione di CircuitPython utilizzando le istruzioni ufficiali che possono essere trovate qui. Dopo essersi assicurati che tutto funzioni come previsto, che CircuitPython costruisca e funzioni sulla vostra piattaforma di destinazione, possiamo iniziare a lavorare sull'inclusione del nostro codice. Questo viene fatto tramite il flag di compilazione USER_C_MODULES. Questa funzionalità è ereditata da MicroPython. Se siete interessati, potete dare un'occhiata al sito ufficiale Documentazione MicroPython che sembra essere applicabile anche a CircuitPython.

Per prima cosa creiamo una nuova cartella nella directory CircuitPython e la riempiamo con i file necessari:

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

In questo caso 'bsec_datatypes.h', 'bsec_interface.h' e 'libalgobsec.a' sono forniti da Bosh. Abbiamo solo bisogno di preoccuparci di mircopython.mk e testingBSEC.c.

A quanto pare CircuitPython include già un esempio in C (e C++) e il suo rispettivo Makefile nel codice sorgente. Potete trovarlo a:

circuitpython/examples/usercmodule/cexample/

Possiamo semplicemente copiare il codice e modificarlo per il nostro uso. Per fare questo includiamo i file header della libreria che vogliamo usare (nel nostro caso #include "bsec_interface.h") e adattiamo il codice all'interno. L'"examplemodule.c" è un esempio ben scelto, dato che contiene una funzione molto semplice che prende un input intero e produce un output intero. Modificarlo secondo le nostre esigenze è stato principalmente un caso di cambiare la quantità di variabili di input e rinominare alcune funzioni. Se il vostro obiettivo è solo quello di testare la fattibilità di un'integrazione su larga scala, questo dovrebbe probabilmente essere sufficiente. Questo può essere facilmente ottenuto restituendo i codici di errore e alcuni esempi di output e controllando se appare come previsto. Altrimenti aggiungere nuove funzioni o apportare modifiche più fondamentali a quelle esistenti non è molto difficile ed è ben descritto nella documentazione di MicroPython.

Si può già lasciare tutto così. A seconda della complessità del vostro caso di test potrebbe essere necessario aggiungere altre funzioni, ma nel nostro caso l'obiettivo principale era semplicemente vedere se il codice gira.

Inoltre modifichiamo il Makefile - micropython.mk - per assomigliare a questo:

BSEC_TEST_DIR := $(USERMOD_DIR)
# Aggiungiamo il nostro file C a SRC_USERMOD.
SRC_USERMOD += $(BSEC_TEST_DIR)/testBSEC.c
# Aggiungere la libreria precompilata a SRC_USERMOD.
SRC_USERMOD += $(BSEC_TEST_DIR)/libalgobsec.a

Poi andiamo semplicemente avanti e costruiamo CircuitPython come descritto nella documentazione ufficiale:

  • 1. Andate nella directory della porta per cui volete costruire (nel nostro caso circuitpython/ports/raspberrypi)
  • 2. make BOARD=raspberry_pi_pico USER_C_MODULES=../../../externalCmodules

Il flag di costruzione punta alla directory in cui mettiamo il nostro codice e le librerie.

Problemi durante il processo di costruzione

Se avete seguito le istruzioni abbastanza attentamente finora, vi imbatterete nel primo problema. Il sistema di compilazione non trova la libreria (libalgobsec.a).

Sembra che il sistema di compilazione lo cerchi in due posti diversi durante il processo di compilazione. Nel nostro caso nei seguenti due posti:

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

Questo ostacolo apparentemente ha la sua origine nel modo in cui il sistema di compilazione interagisce con il buildflag USER_C_MODULE. Anche dopo aver passato un bel po' di tempo a guardarlo, ogni soluzione che ho provato si è rivelata insufficiente in qualche modo o ha introdotto ulteriore complessità. Se qualcuno dovesse leggere questo e trovare una soluzione più pulita, apprezzerei che la condividesse! In tal caso modificherò il testo di conseguenza.

Fortunatamente il problema può essere facilmente evitato duplicando la libreria e mettendola anche nella seconda posizione. Anche se questa ovviamente non è la soluzione più pulita, non c'è differenza di dimensioni nella build finale.

Se state cercando di far funzionare il vostro codice, questo dovrebbe essere tutto ciò di cui avete bisogno per permettere ulteriori test. Aggirando questo piccolo problema il vostro codice e le vostre librerie dovrebbero già compilare, essere collegate correttamente, costruire ed essere richiamabili.

Ma che dire del nostro caso di prova, il BSEC?

Se seguite questo processo rimarrete rapidamente delusi. Vi troverete di fronte a messaggi di errore su funzioni non definite:

/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): undefined reference to `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): undefined reference to `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): undefined reference to `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): undefined reference to `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): undefined reference to `fminf'
........

Beh, come si è scoperto, attualmente esiste un grosso ostacolo. Poiché CircuitPython è pensato per funzionare su microcontrollori e la memoria è spesso piuttosto limitata su questi, gli sviluppatori hanno scritto e usano la loro propria libreria matematica. Mentre questo potrebbe non essere un problema a seconda del vostro caso d'uso personale, il BSEC usa le funzionalità fornite dalla libreria matematica standard C. E mentre esiste un buildflag per costruire CircuitPython con la libreria matematica standard C (circuitpython/ports/raspberrypi/mpconfigport.mk → INTERNAL_LIBM), attualmente sembra essere defunto. Farlo funzionare di nuovo richiederebbe tempo aggiuntivo, una buona conoscenza del sistema di compilazione ed è andato un po' troppo oltre per il nostro piccolo caso di test.

Ma c'è una soluzione (un po' "hacky") per ottenere almeno che il BSEC si inizializzi e dia alcune informazioni di base, dimostrando che il suo utilizzo è probabilmente possibile integrarlo in teoria. Semplicemente avvolgiamo o reimplementiamo le funzioni mancanti nel nostro codice C usando le funzioni matematiche integrate che internal_libm fornisce. Questo può apparire come il seguente:

float fminf ( float x, float y ) {
    se (isnan(x))
		ritorna y;
	se (isnan(y))
		ritorna x;
	// gestisce gli zeri firmati, vedi C99 Allegato F.9.9.2
	se (signbit(x) != signbit(y))
		return signbit(x) ? x : y;
	return x < y ? x : y;
}

Nella maggior parte dei casi il codice è stato preso o leggermente adattato dal compilatore arm-none-eabi-gcc che è open source e può essere guardato senza problemi.

Ora CircuitPython costruisce, il BSEC è richiamabile e restituisce i valori appropriati. Nonostante ciò alcuni problemi persistono se si cerca di usare certe funzionalità. Sembra probabile che questi siano il risultato del processo di compilazione di CircuitPython, perché scompaiono se il BSEC viene usato nativamente con C. Per far funzionare il BSEC nella sua interezza ed essere facilmente richiamabile dall'interno di CircuitPython è necessario un lavoro aggiuntivo e probabilmente un po' di coordinamento per ottenere l'accesso ad una versione corretta del BSEC (che attualmente è costruito usando la release 9-2019-q4-major della catena di strumenti ARM GNU, mentre CircuitPython usa la release 10-2020-q4-major) e per modificare e adattare leggermente il processo di costruzione di CircuitPython. Se questo verrà fatto da noi in futuro è da vedere, ma spero che questo post sul blog possa fornire qualche indicazione agli sviluppatori che sono interessati a testare il proprio codice usando il buildflag USER_C_MODULES e vogliono evitare alcune insidie.

Se ci sarà un aggiornamento dei nostri progressi, questa pagina sarà aggiornata di conseguenza.

Se avete altre domande che non ho coperto abbastanza in dettaglio (o per niente) sentitevi liberi di lasciare un commento o contattatemi via e-mail! Sarò felice di rispondere.

Lascia un commento

Devi essere connesso per inviare un commento.