Nesse último mês eu estive bem ocupado com alguns experimentos legais e gostaria de compartilhar com vocês. Nesse artigo irei escrever sobre minhas aventuras com o .NET nanoFramework, portando o nanoCLR para Linux e NuttX.

.NET nanoFramework

O .NET nanoFramework é um trabalho incrível da comunidade, membro da .NET foundation, que faz possível rodar .NET, C#, em dispositivos microcontrolados! Trazendo inclusive um ambiente familiar de desenvolvimento para quem já está acostumado com Visual Studio e amigos. E ele consegue rodar com os incríveis requisitos mínimos de 256KB de armazenamento e 64KB de RAM 🤯. Lógico que com requisitos tão "nanos", se trata de um subset do .NET em uma implementação customizada do CLR (Common Language Runtime), chamada nanoCLR. E o melhor? É 200% código aberto 😎! (Como o fundador do projeto @Jose_Simoes gosta de mencionar)

Confira esse blog post do Laurent Ellerbach (@Ellerbach) onde ele apresenta um overview bem bacana da arquitetura do nanoCLR: Show dotnet: Build your own unit test platform? The true story of .NET nanoFramework. | .NET Blog (microsoft.com)

.NET nanoFramework -> NuttX

Falando a verdade, esse projeto todo foi apenas uma desculpa para fazer alguma coisa com o NuttX. É um RTOS que eu tenho ouvido falar muito bem há muito tempo, mas que nunca tinha realmente feito nada "mão na massa". O interpretador do nanoCLR roda em cima de um RTOS. Daí a ideia de portar o nanoFramework para a Raspberry Pi Pico, quando adicionaram suporte à ela no NuttX.

Mas, eu também queria desafiar o NuttX 🤔. Um dos pontos fortes reivindicados por esse RTOS é o ótimo suporte à POSIX. Então, eu não comecei diretamente trabalhando no porte para a Pi Pico, mas sim para Linux. O desafio foi analisar o quão fácil seria portar, ou o quanto de um projeto, primeiramente desenvolvido para rodar em Linux, eu conseguiria reaproveitar no NuttX.

.NET nanoFramework win32 App -> Linux

O nanoFramework tem um porte para compilar o interpretador como uma aplicação console win32. É bem bacana para quem quer estudar o funcionamento interno do nanoCLR, ou até mesmo fazer alguns testes e debug usando o Visual Studio 2019. Eu usei essa solução como base e migrei o código para uma aplicação console Linux.

Na migração eu tentei converter todas as partes específicas escritas para win32, em chamadas portáveis, do padrão C/C++ e POSIX. Também adicionei muitos #if defined(__linux__), para cobrir alguns casos específicos da aplicação Linux. Em vários trechos você vai achar algo como #if defined(_WIN32) || defined(__linux__):

// provide platform dependent delay to CLR code
#if defined(_WIN32) || defined(__linux__) || defined(__nuttx__)
#define OS_DELAY(milliSecs) ;
#else
#define OS_DELAY(milliSecs) PLATFORM_DELAY(milliSecs)
#endif

A princípio isso parece não fazer sentido, mas faz. O core do nanoCLR é bem portável, mas em alguns casos uma ou outra chamada dependente da plataforma deve ser executada. Nesse caso a chamada tem uma definição bem conhecida pelo nanoCLR, só que essa será implementada no código dependente da plataforma. Como eu usei o projeto do win32 como base, usei as mesmas definições de chamadas e segui o mesmo comportamento.

Deixo aqui o link do Github para o porte caso você queira se aprofundar: https://github.com/dotnuttx/nf-Community-Targets/tree/linux/posix

⚠️ Todo o código do projeto customizado, nanoFramework para Linux, NuttX e etc estão disponíveis, conservando suas respectivas licenças, no Github.

Desse código eu consigo gerar um binário, que quando compilado em um static link não passa de 2.7MB. Também estou mantendo "releases" de binários pré-compilados desse código para diferentes arquiteturas (das que eu consigo testar) no Github: https://github.com/dotnuttx/nf-Community-Targets/releases

Por serem binários static linked, não é necessário instalar nenhuma dependência, só executar e ser feliz.

Confira o blog post sobre como executar aplicações .NET nanoFramework no Linux:

.NET nanoFramework Linux -> NuttX

Com a aplicação Linux console rodando com sucesso, pude colocar o NuttX à prova. E fiquei realmente impressionado com o resultado. Não foi a toa que ouvi falar tão bem do NuttX. Segui o modo que achei mais fácil de compilar um app para o NuttX, que é fazendo o link dos objetos diretamente no binário do NuttX, o chamado built-in application. Para isso, adicionei os arquivos Kconfig, Make.defs e Makefile. Commit: a5e837c

No Makefile eu simplesmente adicionei os caminhos relativos para o código fonte do meu fork local do nf-interpreter, e adicionei as flags/defines necessárias para o compile. Na configuração do NuttX, para compilar o Kernel, foi necessário adicionar os seguintes configs adicionando suporte à C++:

# C++
CONFIG_HAVE_CXX=y
CONFIG_LIBCXX=y

Um interessante problema que enfrentei foi com um include, que deveria funcionar e compartilhar o mesmo comportamento entre Linux e NuttX. No NuttX alguns includes não estão "preparados" para serem usados com C++. Exemplo, usando o utsname.h para pegar informações do sistema:

#if defined(__linux__) || defined(__nuttx__) 
#include <sys/utsname.h>
#endif

Se inspecionarmos o header no Linux, vamos ver que temos o seguinte trecho bem no começo do arquivo:

#ifndef    _SYS_UTSNAME_H
#define    _SYS_UTSNAME_H    1

#include <features.h>

__BEGIN_DECLS

E __BEGIN_DECLS resolve para:

/* C++ needs to know that types and declarations are C, not C++.  */
#ifdef    __cplusplus
# define __BEGIN_DECLS    extern "C" {
# define __END_DECLS    }
#else
# define __BEGIN_DECLS
# define __END_DECLS
#endif

No utsname.h do NuttX não há esse cuidado com __cplusplus, o que leva a erros de undefined reference durante o build. Para resolver isso do lado da aplicação adicionei:

#if defined(__linux__)
#include <sys/utsname.h>
#endif

#if defined(__nuttx__)
extern "C" {
#include <sys/utsname.h>
}
#endif

Mas, no futuro eu espero poder contribuir com esse tratamento, parecido com o que temos no Linux, no header do NuttX (na verdade esse cuidado já existe em alguns outros headers, eu tive a má sorte de usar um sem). Você deve ter percebido que além do defined(__linux__), estou usando defined(__nuttx__), para tratar esses casos específicos (também porque o padrão do build config do NuttX não define __linux__). Mas tirando esse detalhe, nada mais teve de ser modificado do porte do Linux especificamente para o NuttX. O porte foi bem direto 😎!

E agora, eu posso rodar o .NET nanoFramework no Raspberry Pi Pico, por exemplo, aproveitando o suporte dele do NuttX:

Veja o blog post para saber como rodar o .NET nanoFramework com NuttX na Pi Pico: Rodando .NET na Raspberry Pi Pico

Contribuindo de Volta

Isso é o certo a se fazer. Se você está trabalhando com algum projeto open source e achou algo que pode ser melhorado, fix ou etc, e tem o conhecimento para contribuir, CONTRIBUA! É o mínimo que você pode fazer, o valor do projeto que você está usando, e de forma gratuita, é inestimável. Se você não pode contribuir de forma financeira, doe do seu conhecimento técnico. Eu fico chateado quando conheço alguma empresa ou grupo que está usando o "core" de uma certa tecnologia open source, visivelmente aplicando modificações e melhorias, mas não as contribui de volta ou não suporta o projeto 😔.

Durante a minha pesquisa e desenvolvimento consegui fazer as seguinte contribuições:

E provavelmente virá mais contribuições desse projeto.

Conclusão

POSIX 🤘! Com o .NET nanoFramework rodando no Linux, foi bem direto o porte para o NuttX, com realmente poucas modificações. E com isso, agora eu posso rodar o nanoCLR em um arsenal de + de 67 placas microcontroladas de diferentes arquiteturas, das quais o NuttX tem suporte, e + uma infinita lista de plataformas e arquiteturas microprocessadas que rodam Linux 😎.

Há muito trabalho pela frente, por enquanto das features específicas de hardware, só GPIO (in e out) estão implementadas (suporte há i2c vem em breve). Mas o .NET nanoFramework também tem um protocolo de debug remoto, que para em breakpoints e inspeciona o managed code dentro do nanoCLR, muito interessante, do qual eu ainda não toquei no código para analisar o suporte à Linux e NuttX.

⚠️ Lembrando que isso é algo que estou trabalhando nos finais de semana e durante meu tempo livre. É algo EXTREMAMENTE EXPERIMENTAL e não estou sendo financiado por nenhum grupo ou instituição para tal.

Se isso for de alguma forma útil, ou fizer sentido para você, deixe me saber. Me mande um alô no Twitter @math_castello ou no Linkedin 👍