Integración del BSEC en CircuitPython (Un trabajo en curso)

Algunos consejos sobre el uso de la bandera de construcción USER_C_MODULES

Esta entrada del blog tocará dos temas diferentes. Por un lado, hablaré del enfoque general para incluir y probar tu propio código C en tu construcción personalizada de CircuitoPython . Por otro lado, hablaré de los retos específicos que supone el uso de la Placa de interconexión BME688El sistema se basa en el sensor BME688 de Bosch, con la placa de circuito impreso específica diseñada por nosotros, y su biblioteca BSEC en el RP2040. Esta biblioteca permite el análisis avanzado de los datos del sensor proporcionados por el BME688 y los cálculos basados en esos datos del sensor.

Los beneficios de envolver su propio código C o bibliotecas estáticas en CircuitPython son bastante obvios. Muchas placas de microcontroladores, en este caso la Raspberry Pi Pico que se basa en el RP2040, permiten la ejecución de código C. Escribir C puro puede ser desalentador, especialmente para los programadores que no han pasado mucho tiempo con ese lenguaje. CircuitPython, por otro lado, proporciona un fácil acceso a través de la sintaxis de Python, un montón de bibliotecas bien escritas y una gran comunidad para ayudarte.

Nuestro objetivo era probar si es teóricamente posible envolver la biblioteca BSEC. Esta biblioteca se proporciona para una multitud de plataformas y compiladores, incluyendo el Cortex M0+ y el compilador arm-none-eabi-gcc.

Mientras que trabajar en torno a la BSEC puede ser específico para nuestro caso, voy a proporcionar algunos consejos más generales sobre cómo incluir un módulo C personalizado en CircuitPython para probar rápidamente su funcionalidad, cómo enlazar una biblioteca binaria adicional y cómo construir CircuitPython para que tanto el módulo C como la biblioteca estén incluidos. Hay algunos escollos menores en el camino, que espero que esta entrada del blog cubra.

Si quieres escribir tu propio módulo C de CircuitPython, actualmente existen dos enfoques diferentes. Puedes leer sobre el primero en las directrices oficiales del Sitio web de Adafruit que actualmente están desactualizados, pero aún contienen mucha información útil. Por lo tanto, el enfoque recomendado es mirar el código fuente de CircuitPython y seguirlo como ejemplo. Sin entrar en demasiados detalles, esto se basa en la colocación de sus archivos de cabecera y de código fuente en los directorios 'shared-bindings' y 'shared-module', respectivamente, mientras se utilizan los métodos de envoltura proporcionados.

Si tu objetivo es el uso a largo plazo o contribuir a la versión oficial, deberías seguir esas directrices. Si primero quieres ver si el código se ejecuta en una plataforma específica o hacer algunas pruebas rápidas, el enfoque descrito aquí es un poco más fácil y requiere menos trabajo.

¿Cómo utilizar USER_C_MODULES?

En primer lugar, vamos a pasar por el proceso de construcción de CircuitPython utilizando las instrucciones oficiales que se pueden encontrar aquí. Después de asegurarse de que todo funciona como se pretende, CircuitPython construye y se ejecuta en su plataforma de destino, podemos empezar a trabajar en la inclusión de nuestro propio código. Esto se hace a través de la bandera de construcción USER_C_MODULES. Esta funcionalidad es heredada de MicroPython. Si estás interesado, puedes echar un vistazo a la página oficial de Documentación de MicroPython que parece ser aplicable también a CircuitPython.

Primero creamos una nueva carpeta en el directorio de CircuitPython y la llenamos con los archivos necesarios:

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

En este caso 'bsec_datatypes.h', 'bsec_interface.h' y 'libalgobsec.a' son proporcionados por Bosh. Sólo tenemos que preocuparnos de mircopython.mk y testingBSEC.c.

Resulta que CircuitPython ya incluye un ejemplo en C (Y C++) y su respectivo Makefile dentro del código fuente. Puedes encontrarlo en:

circuitpython/examples/usercmodule/cexample/

Podemos simplemente copiar el código y editarlo para nuestro propio uso. Para ello incluimos los archivos de cabecera de la biblioteca que queremos utilizar (en nuestro caso #include "bsec_interface.h") y adaptamos el código que contiene. El 'examplemodule.c' es un ejemplo bien elegido, ya que contiene una función muy básica que toma una entrada entera y produce una salida entera. Editarlo a nuestras demandas fue principalmente un caso de cambiar la cantidad de variables de entrada y renombrar algunas funciones. Si tu objetivo es sólo probar la viabilidad de una integración a gran escala, esto debería ser suficiente. Esto puede lograrse fácilmente devolviendo códigos de error y alguna salida de muestra y comprobando si se ve como se espera. Por lo demás, añadir nuevas funciones o hacer cambios más fundamentales en las existentes no es muy difícil y está bien descrito en la documentación de MicroPython.

Ya puedes dejarlo así. Dependiendo de la complejidad de tu caso de prueba podría ser necesario añadir más funciones, pero en nuestro caso el objetivo principal era simplemente ver si el código se ejecuta.

Adicionalmente modificamos el Makefile - micropython.mk - para que se vea así:

BSEC_TEST_DIR := $(USERMOD_DIR)
# Añade nuestro archivo C a SRC_USERMOD.
SRC_USERMOD += $(BSEC_TEST_DIR)/pruebaBSEC.c
# Añadir la biblioteca precompilada a SRC_USERMOD.
SRC_USERMOD += $(BSEC_TEST_DIR)/libalgobsec.a

A continuación, simplemente seguimos adelante y construimos CircuitPython como se describe en la documentación oficial:

  • 1. Entra en el directorio del puerto para el que quieres construir (En nuestro caso circuitpython/ports/raspberrypi)
  • 2. make BOARD=raspberry_pi_pico USER_C_MODULES=../../../externalCmodules

La bandera de construcción apunta al directorio en el que ponemos nuestro código y bibliotecas.

Problemas durante el proceso de construcción

Si has seguido las instrucciones al pie de la letra hasta ahora, te encontrarás con el primer problema. El sistema de construcción no encuentra la biblioteca (libalgobsec.a).

Parece que el sistema de construcción lo busca en dos lugares diferentes durante el proceso de construcción. En nuestro caso en los dos lugares siguientes:

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

Este obstáculo aparentemente tiene su origen en la forma en que el sistema de construcción interactúa con el buildflag USER_C_MODULE. Incluso después de pasar bastante tiempo analizándolo, todas las soluciones que probé se quedaron cortas de alguna manera o introdujeron más complejidad. Si alguien lee esto y encuentra una solución más limpia, le agradecería que la compartiera. En ese caso, editaré el texto en consecuencia.

Afortunadamente, el problema se puede evitar fácilmente duplicando la biblioteca y colocándola también en la segunda ubicación. Aunque obviamente no es la solución más limpia, no hay diferencia de tamaño en la construcción final.

Si estás intentando que tu propio código funcione, esto debería ser todo lo que necesitas para poder hacer más pruebas. Evitando este pequeño problema, tu propio código y las bibliotecas ya deberían compilar, estar enlazadas correctamente, construirse y ser invocables.

¿Pero qué pasa con nuestro caso de prueba, el BSEC?

Si sigues este proceso, te decepcionarás rápidamente. Te encontrarás con mensajes de error sobre funciones no definidas:

/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'
........

Pues bien, resulta que actualmente existe un obstáculo importante. Como CircuitPython está pensado para ejecutarse en microcontroladores y la memoria suele ser bastante limitada en ellos, los desarrolladores escribieron y utilizan su propia biblioteca matemática. Mientras que esto podría no ser un problema dependiendo de su caso de uso personal, el BSEC utiliza la funcionalidad proporcionada por la biblioteca matemática estándar de C. Y aunque existe una bandera de compilación para construir CircuitPython con la biblioteca matemática estándar de C (circuitpython/ports/raspberrypi/mpconfigport.mk → INTERNAL_LIBM), actualmente parece estar desaparecida. Hacerlo funcionar de nuevo requeriría tiempo adicional, un buen conocimiento del sistema de construcción y fue un poco demasiado lejos para nuestro pequeño caso de prueba.

Pero hay una solución (algo 'hacky') para al menos conseguir que el BSEC se inicialice y dé alguna información básica, demostrando que su uso es probablemente posible para integrarlo en teoría. Simplemente envolvemos o reimplementamos las funciones que faltan en nuestro código C utilizando las funciones matemáticas incorporadas que proporciona la internal_libm. Esto puede parecerse a lo siguiente:

float fminf ( float x, float y ) {
    si (isnan(x))
		devuelve y;
	si (isnan(y))
		return x;
	// manejar ceros con signo, ver C99 Anexo F.9.9.2
	if (signbit(x) != signbit(y))
		return signbit(x) ? x : y;
	return x < y ? x : y;
}

En la mayoría de los casos el código fue tomado o ligeramente adaptado del compilador arm-none-eabi-gcc que es de código abierto y se puede consultar sin problemas.

Ahora CircuitPython construye, el BSEC es llamable y devuelve los valores apropiados. A pesar de ello, algunos problemas persisten si se intenta utilizar cierta funcionalidad. Parece probable que estos sean el resultado del proceso de construcción de CircuitPython, porque desaparecen si el BSEC se utiliza de forma nativa con C. Para conseguir que el BSEC se ejecute en su totalidad y sea fácilmente invocable desde dentro de CircuitPython es necesario un trabajo adicional y probablemente alguna coordinación para conseguir acceso a una versión correcta del BSEC (que actualmente se construye utilizando la versión 9-2019-q4-major de la cadena de herramientas ARM GNU, mientras que CircuitPython utiliza la versión 10-2020-q4-major) y modificar y adaptar ligeramente el proceso de construcción de CircuitPython. Si esto será hecho por nosotros en el futuro tiene que ser visto, pero espero que esta entrada del blog pueda proporcionar alguna dirección a los desarrolladores que están interesados en probar su propio código utilizando el buildflag USER_C_MODULES y quieren evitar algunas trampas.

En caso de que haya una actualización de nuestros progresos, esta página se actualizará en consecuencia.

Si tienes más preguntas que no he cubierto con suficiente detalle (o en absoluto), no dudes en dejar un comentario o en ponerte en contacto por correo electrónico. Estaré encantado de responder.

Deja un comentario

Debes iniciar sesión para escribir un comentario.