Integração do BSEC no CircuitPython (Um trabalho em andamento)

Algumas dicas sobre o uso da bandeira de construção USER_C_MODULES

Este post de blog vai tocar em dois tópicos diferentes. Por um lado, vou falar sobre a abordagem geral para incluir e testar o seu próprio código C na sua construção personalizada de CircuitPython . Por outro lado, vou falar de desafios específicos na utilização do BME688 Quadro de DiscussãoA Bosch desenvolveu o sensor BME688, baseado no sensor BME688 da Bosch, com a placa de quebra específica sendo projetada por nós, e sua biblioteca BSEC no RP2040. Esta biblioteca permite a análise avançada dos dados do sensor fornecidos pelo BME688 e cálculos baseados nos dados do sensor.

Os benefícios de embrulhar seu próprio código C ou bibliotecas estáticas no CircuitPython são bastante óbvios. Muitas placas micro-controladoras, neste caso o Raspberry Pi Pico que é baseado no RP2040, permitem a execução do código C. Escrever C puro pode ser assustador, especialmente para programadores que ainda não gastaram muito tempo com essa linguagem. O CircuitPython, por outro lado, oferece fácil acesso através da sintaxe Python, um monte de bibliotecas bem escritas e uma grande comunidade para ajudá-lo.

Nosso objetivo era testar se é teoricamente possível envolver a biblioteca da BSEC. Esta biblioteca é fornecida para uma multiplicidade de plataformas e compiladores, incluindo o Cortex M0+ e o compilador arm-none-eabi-gcc.

Enquanto trabalhar em torno do BSEC pode ser específico para o nosso caso, vou fornecer algumas dicas mais gerais sobre como incluir um módulo C personalizado escrito no CircuitPython para testar rapidamente sua funcionalidade, como ligar uma biblioteca binária adicional e como construir o CircuitPython para que tanto o módulo C quanto a biblioteca sejam incluídos. Existem algumas pequenas armadilhas pelo caminho, que eu espero que este post no blog irá cobrir.

Se você quiser escrever seu próprio módulo CircuitPython C, existem atualmente duas abordagens diferentes. Você é capaz de ler sobre a primeira nas diretrizes oficiais do Adafruit website que estão actualmente desactualizados mas ainda contêm muita informação útil. Portanto, a abordagem recomendada é olhar para o código fonte do CircuitPython e segui-lo como um exemplo. Sem entrar em muitos detalhes, isto é baseado em colocar seus arquivos de cabeçalho e código fonte nos diretórios 'shared-bindings' e 'shared-module', respectivamente, enquanto usa os métodos de invólucro fornecidos.

Se o seu objectivo é o uso a longo prazo ou contribuir para o lançamento oficial, deve seguir essas directrizes. Se você quiser ver primeiro se o código roda em uma plataforma específica ou fazer alguns testes rápidos, a abordagem descrita aqui é um pouco mais fácil e menos trabalhosa.

Como usar o USER_C_MODULES?

Primeiro passamos pelo processo de construção do CircuitPython, usando as instruções oficiais que podem ser encontradas aqui. Depois de nos certificarmos que tudo funciona como pretendido, CircuitPython constrói e corre na sua plataforma alvo, podemos começar a trabalhar, incluindo o nosso próprio código. Isto é feito através da bandeira de construção USER_C_MODULES. Esta funcionalidade é herdada do MicroPython. Se você estiver interessado, você pode dar uma olhada no Documentação MicroPython que parece ser aplicável ao CircuitPython também.

Primeiro criamos uma nova pasta no diretório CircuitPython e a preenchemos com os arquivos necessários:

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

Neste caso, 'bsec_datatypes.h', 'bsec_interface.h' e 'libalgobsec.a' são fornecidos por Bosh. Nós só precisamos nos preocupar com mircopython.mk e testingBSEC.c.

Como acontece o CircuitPython já inclui um exemplo em C (e C++) e seu respectivo Makefile dentro do código fonte. Você pode encontrá-lo em:

circuitpython/exemplos/usercmodule/cexample/

Nós podemos simplesmente copiar o código e editá-lo para nosso próprio uso. Para isso, incluímos os ficheiros de cabeçalho da biblioteca que queremos utilizar (no nosso caso #include "bsec_interface.h") e adaptamos o código dentro. O 'examplemodule.c' é um exemplo bem escolhido, uma vez que contém uma função muito básica que toma a entrada de números inteiros e produz a saída de números inteiros. Editar para nossas demandas foi principalmente um caso de alterar a quantidade de variáveis de entrada e renomear algumas funções. Se o seu objetivo é apenas testar a viabilidade de uma integração em larga escala, isto provavelmente deve ser suficiente. Isto pode ser facilmente alcançado retornando códigos de erro e algumas amostras de saída e verificando se parece como esperado. Caso contrário, adicionar novas funções ou fazer alterações mais fundamentais às existentes não é muito difícil e está bem descrito na documentação do MicroPython.

Já podes deixar as coisas assim. Dependendo da complexidade do seu caso de teste pode ser necessário adicionar mais funções, mas no nosso caso o objectivo principal era simplesmente ver se o código corre.

Adicionalmente, modificamos o Makefile - micropython.mk - para se parecer com algo assim:

BSEC_TEST_DIR := $(USERMOD_DIR)
# Adicione nosso arquivo C ao SRC_USERMOD.
SRC_USERMOD += $(BSEC_TEST_DIR)/testingBSEC.c
# Adicionando a biblioteca pré-compilada ao SRC_USERMOD.
SRC_USERMOD += $(BSEC_TEST_DIR)/libalgobsec.a

Então, basta ir em frente e construir o CircuitPython como descrito na documentação oficial:

  • 1. Entre no diretório da porta para a qual você quer construir (No nosso caso circuitpython/ports/raspberrypi)
  • 2. make BOARD=raspberry_pi_pico USER_C_MODULES=.../.../.../externalCmodules

A bandeira de construção aponta para o diretório em que colocamos nosso código e bibliotecas.

Problemas durante o processo de construção

Se você seguiu as instruções de perto até agora, você vai se deparar com o primeiro número. O sistema de construção não encontra a biblioteca (libalgobsec.a).

Parece que o sistema de construção o procura em dois lugares diferentes durante o processo de construção. No nosso caso, nos dois lugares seguintes:

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 tem origem na forma como o sistema de construção interage com a buildflag USER_C_MODULE. Mesmo depois de passar algum tempo olhando para ele, todas as soluções que tentei ficaram aquém das expectativas de alguma forma ou introduziram mais complexidade. Se alguém ler isto e encontrar uma solução mais limpa, eu gostaria que a partilhasse! Eu vou editar o texto de acordo com esse caso.

Felizmente, o problema pode ser facilmente evitado duplicando a biblioteca e colocando-a também no segundo local. Embora esta obviamente não seja a solução mais limpa, não há diferença de tamanho na construção final.

Se você está tentando fazer seu próprio código funcionar, isto deve ser tudo que você precisa para permitir mais testes. Ao contornar este pequeno problema, o seu próprio código e bibliotecas já devem compilar, estar ligados adequadamente, construir e poder ser chamados.

Mas e o nosso caso de teste, o BSEC?

Se você seguir este processo, você ficará rapidamente desapontado. Você se verá confrontado por mensagens de erro sobre funções indefinidas:

/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): referência indefinida a `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): referência indefinida a `fmaxf': IaqEstimator.c:(.text+0x112): referência indefinida a `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): referência indefinida a `fmaxf': IaqEstimator.c:(.text+0x162): referência indefinida a `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): referência indefinida a `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): referência indefinida a `fminf'.
........

Bem, como acontece, existe actualmente um grande obstáculo. Uma vez que o CircuitPython destina-se a funcionar em microcontroladores e a memória é muitas vezes bastante limitada nestes, os programadores escreveram e usam a sua própria biblioteca matemática. Embora isso possa não ser problema dependendo do seu caso de uso pessoal, a BSEC usa a funcionalidade fornecida pela biblioteca matemática C padrão. E embora exista uma bandeira de construção para construir CircuitPython com a biblioteca matemática padrão C (circuitpython/ports/raspberrypi/mpconfigport.mk → INTERNAL_LIBM), ela parece estar defunta atualmente. Fazê-la funcionar novamente exigiria tempo adicional, um bom conhecimento do sistema de construção e foi um pouco longe demais para o nosso pequeno caso de teste.

Mas existe uma solução (um tanto 'hacky') para, pelo menos, conseguir que a BSEC inicialize e dê algumas informações básicas, provando que seu uso é provavelmente possível de integrá-la em teoria. Nós simplesmente embrulhamos ou reimplementamos as funções em falta no nosso código C usando as funções matemáticas embutidas que o internal_libm fornece. Isto pode parecer algo como o seguinte:

float fminf ( float x, float y ) {
    se (isan(x))
		Devolver y;
	se (isan(y))
		devolver x;
	// manusear zeros assinados, ver C99 Anexo F.9.9.2
	if (signbit(x) != signbit(y))
		signbit(x) ? x : y;
	retorno x < y ? x : y;
}

Na maioria dos casos, o código foi retirado ou ligeiramente adaptado do compilador arm-none-eabi-gcc que é de código aberto e pode ser visto sem problemas.

Agora o CircuitPython constrói, o BSEC é chamável e retorna os valores apropriados. Apesar de alguns problemas persistirem ao tentar usar certas funcionalidades. Parece provável que estes sejam o resultado do processo de construção do CircuitPython, porque eles desaparecem se a CMEB for usada nativamente com C. Para que o BSEC funcione em sua totalidade e possa ser facilmente chamado de dentro do CircuitPython é necessário trabalho adicional, e provavelmente alguma coordenação para obter acesso a uma versão correta do BSEC (que atualmente é construído usando o lançamento 9-2019-q4-maior da cadeia de ferramentas ARM GNU, enquanto o CircuitPython usa o lançamento 10-2020-q4-maior) e para modificar ligeiramente e adaptar o processo de construção do CircuitPython. Se isso será feito por nós no futuro tem de ser visto, mas espero que este post no blog possa fornecer alguma direção aos desenvolvedores que estão interessados em testar seu próprio código usando o buildflag USER_C_MODULES e querem evitar algumas armadilhas.

Caso haja uma actualização ao nosso progresso, esta página será actualizada em conformidade.

Se você tiver mais perguntas que eu não cobri com detalhes suficientes (ou não cobri), sinta-se à vontade para deixar um comentário ou entrar em contato via e-mail! Terei todo o prazer em responder.

Deixe um comentário

Tem de ter a sessão iniciada para publicar um comentário.