Nos posts anteriores eu apresentei o port do .NET nanoFramework para POSIX .NET nanoFramework rodando no Linux e NuttX. E pra quem acompanhou o segundo dia do NuttX Online Workshop viu alguns spoilers, como .NET rodando no StartFive JH7100
e no ESP32-C3
. O que estas placas tem em comum?
Aqui a lista de plataformas que testei o suporte inicial (são estas RISC-V que eu tenho disponíveis por aqui na minha bancada 😅):
-
Espressif ESP32-C3 (32-bit core RISC-V microcontroller)
-
Kendryte K210 (RISC-V Dual Core 64bit microcontroller)
-
StarFive JH7100 (RISC-V U74 Dual Core microprocessor)
-
Allwinner D1 (RISC-V 64bit C906 Core microprocessor)
Desde que comecei a portar o .NET nanoFramework para POSIX um dos meus objetivos era também ter a oportunidade de rodar .NET em arquitetura RISC-V. Com o porte POSIX isso ficou "fácil". Eu consigo "cross compilar" o nanoFramework para Linux RISC-V e para as placas RISC-V que o NuttX suporta.
Para demonstrar o .NET nanoFramework rodando no hardware RISC-V vamos fazer o de sempre, piscar LEDs. Estou usando o seguinte projeto https://github.com/dotnuttx/nanoFrameworkPOSIX-samples/tree/main/Blink. Eu já tinha usado esse exemplo anteriormente para o Raspberry Pi Zero
e Raspberry Pi Pico
, mas agora temos no Plataforms.cs
4 targets novos:
namespace DotnetNFPosix {
public class Platforms {
public const string WSL = "wsl";
public const string PI_PICO = "pi-pico";
public const string PI_ZERO = "pi-zero";
public const string ESP32_C3 = "esp32c3";
public const string STARFIVE_JH7100 = "jh7100";
public const string NEZHA_ALLWINNER_D1 = "nezha";
public const string MAIX_BIT_K210 = "maix-bit";
}
}
E no Program.cs
4 novos cases respectivamente para registrar os dados do hardware:
case Platforms.STARFIVE_JH7100:
// pin 16 in the header is the gpio21
ledPinNumber = 21;
buttonPinNumber = 19;
buttonTriggerLevel = PinValue.Low;
break;
case Platforms.ESP32_C3:
// pin 6 from DevKit is gpio8
ledPinNumber = 7;
buttonPinNumber = 4;
break;
case Platforms.NEZHA_ALLWINNER_D1:
// pin 16 in the header is the ioexpander pp1
ledPinNumber = 1;
buttonPinNumber = 2;
buttonTriggerLevel = PinValue.Low;
break;
case Platforms.MAIX_BIT_K210:
// onboard rgb blue / onboard boot button
ledPinNumber = 12;
buttonPinNumber = 16;
buttonTriggerLevel = PinValue.Low;
break;
Como podem ver além de piscar LEDs vamos também ter um input de botão. Esses dados serão utilizados para configurar os pinos corretamente para cada target, no final o sistema entra em um loop infinito para verificar o sinal do input e realizar o blink do LED a cada 500ms:
// initiliaze pin
led = gpioController.OpenPin(ledPinNumber, PinMode.Output);
button = gpioController.OpenPin(buttonPinNumber, PinMode.Input);
// blink forever
while (true)
{
Debug.WriteLine($"Blinking {ledValue}");
ledValue = !(bool)ledValue;
led.Write(ledValue);
if (button.Read() == buttonTriggerLevel) {
Debug.WriteLine(
$"Button is pressed on {SystemInfo.OEMString} {plus}");
plus++;
}
Thread.Sleep(500);
}
⚠️ Se caso você deseje rodar a demo você precisa das seguintes dependências:
Para Linux (Ubuntu ou outra Debian based de preferência)
docker
mono-complete
nuget
Para Windows
Docker Desktop
WSL 2 (Ubuntu ou Debian)
mono-complete
nuget
Para compilar, gravar e rodar a demo para o ESP32-C3 vamos usar as tasks
já configuradas no projeto para o VS Code. Execute a task flash-esp32c3
, essa task tem dependências e vai executar automaticamente a seguinte sequência: restore
-> build
-> generate-firmware-esp32c3
-> flash-esp32c3
. Ao rodar a task o VS Code irá mostrar uma caixa de entrada para input da porta serial do ESP32-C3, para que seja executado o flash do firmware.
⚠️ O arquivo em.vscode/tasks.json
está configurado para comandos inicializando owsl
no Windows. Caso você esteja utilizando Linux modifique o arquivo para que as propriedadescommand
sejam ao invés dewsl
o primeiro argumento deargs
da respectiva task.
⚠️ Oflash-esp32c3
depende doesptool
instale compip install esptool
Se tudo deu certo você deverá ter o seu ESP32-C3 piscando LEDs. Você também pode se conectar na serial para verificar o output, quando o botão for pressionado a versão do NuttX será colocada no output:
Para o K210 temos a task flash-maix-bit-k210
. Essa task vai executar restore
-> build
-> generate-firmware-k210
-> flash-maix-bit-k210
. Ao rodar a task o VS Code irá mostrar uma caixa de entrada para input da porta serial do Maix Dock, para que seja executado o flash do firmware.
⚠️ Oflash-maix-bit-k210
depende dokflash
instale compip3 install kflash
Se tudo deu certo você deverá ter o seu Maix Dock piscando o LED onboard blue. Você também pode se conectar na serial para verificar o output, quando o botão onboard BOOT
for pressionado a versão do NuttX será colocada no output:
Para o Nezha Dev board temos a task run-ssh
. Essa task vai executar restore
-> build
-> deploy-ssh
-> run-ssh
. Ao rodar a task o VS Code irá mostrar uma caixa de entrada para input do username
e ip ou hostname
para conexão ssh
com a placa, para que os assemblies .pe
sejam copiados e executados na placa.
Se tudo deu certo você deverá ter o seu Nezha Dev Board piscando o LED e quando o botão for pressionado a versão do Linux será colocada no output:
Para o StarFive JH7100 (estou usando o, infelizmente descontinuado, Beagle-V beta para meus testes com esse SoC) também vamos rodar a task run-ssh
. Sem segredo, entre com o usuário e endereço ou hostname da placa e pronto.
Se tudo deu certo você deverá ter o JH7100 piscando o LED e quando o botão for pressionado a versão do Linux será colocada no output:
Esses portes foram bem diretos, com o porte para Linux e NuttX já "validados" anteriormente foi mais uma questão de configuração de ambiente para cada plataforma e build do sistema. O único que deu trabalho foi o K210. Pelo que parece o .NET nanoFramework não suporta arquiteturas de 64bits. O ESP32-C3 é um RISC-V 32bits, então o porte para ele foi bem direto e sem problemas. Para Linux eu tinha adicionado uma gambiarra 😅, que adicionava suporte para 64bits, mas que como efeito colateral gerava acessos não alinhados à memória (Matheus mal 😈). O Linux tem traps
para acessos não alinhados e corrige o acesso durante a execução do programa, então sem problemas (Matheus muito mal 😝). Agora o K210 eu usei o NuttX, o NuttX não me parece ter esses traps
implementados e um acesso não alinhado gera um "panic" no sistema. Dá pra implementar e usar os traps
do Linux como base (inclusive existe Linux "no MMU" para o K210). Mas eu tomei vergonha na cara e agora, no meu fork do .NET nanoFramework, temos suporte à arquiteturas 64bits (espero no futuro contribuir com isso no upstream). Além que os traps
do sistema operacional tem que lidar com as exceções e mudar de contexto para cada acesso não alinhado, é beeeem menos eficiente do que seu programa acessando a memória alinhada como deve ser.
Talvez você esteja se perguntando qual o objetivo disso ou a onde eu quero chegar com esse projeto. Por enquanto a minha resposta é: "diversão". Sempre que coloco esses desafios na minha cabeça ("ah vamos rodar .NET na Raspberry Pi Pico", "ah vamos rodar .NET em RISC-V") eu aprendo muito e também me estresso divirto muito durante o processo. Sem ambições aqui.
É isso, se você gostou de ver .NET nanoFramework rodando em hardware RISC-V deixe me saber. Me mande um alô no https://twitter.com/math_castello ou no Linkedin 👍
Feliz Natal e Boas Festas! 🎅