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)

.NET + RISC-V

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.

💡 Vamos Piscar LEDs no RISC-V com C#

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

ESP32-C3

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 o wsl no Windows. Caso você esteja utilizando Linux modifique o arquivo para que as propriedades command sejam ao invés de wsl o primeiro argumento de args da respectiva task.

⚠️ O flash-esp32c3 depende do esptool instale com pip 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:

Maix Bit / Maix Dock (Kendryte K210)

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.

⚠️ O flash-maix-bit-k210 depende do kflash instale com pip3 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:

Nezha Dev Board (Alwinner D1)

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:

StarFive JH7100

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:

Conclusões

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! 🎅