This past month I've been pretty busy with some cool experiments and I'd like to share with you. In this article I will write about my adventures with the .NET nanoFramework, porting nanoCLR to Linux and NuttX.

.NET nanoFramework

The .NET nanoFramework is an amazing work by the community, member of the .NET foundation, which makes it possible to run .NET, C#, on microcontrolled devices! Bringing even a familiar development environment for those who are already used to Visual Studio and friends. And, it can run with the incredible minimum requirements of 256KB of storage and 64KB of RAM 🤯. But, of course with such "nano" requirements this is a subset of .NET in a custom CLR (Common Language Runtime) implementation called nanoCLR. And the best? It's 200% open source 😎! (As the founder of the project @Jose_Simoes likes to mention)

Check out this blog post by Laurent Ellerbach (@Ellerbach) where he gives a really cool overview of the nanoCLR architecture: Show dotnet: Build your own unit test platform? The true story of .NET nanoFramework. | .NET Blog (microsoft.com)

.NET nanoFramework -> NuttX

The true is that this whole project was just an excuse to do something with NuttX. It's an RTOS I've heard very well about, for a long time, but I've never really done anything hands-on with it. The nanoCLR interpreter runs on top of an RTOS. So, the idea of porting nanoFramework to the Raspberry Pi Pico when they added support for it in NuttX.

But I also wanted to challenge NuttX 🤔. One of the strengths claimed by this RTOS is the great support for POSIX. So, I didn't directly start working on the port to Pi Pico, but start it to Linux. And, the challenge was to analyze how easy it would be to port, or how much of a project first developed to run on Linux, I could reuse in NuttX.

.NET nanoFramework win32 App -> Linux

nanoFramework has a port to compile the interpreter as a win32 console application. It's pretty cool for anyone who wants to study the internals of nanoCLR or even do some testing and debugging using Visual Studio 2019. I used this solution as a base, and migrated the code to a Linux console application.

In the migration process I tried to convert all the specific parts written for win32 into portable, standard C/C++ calls and POSIX calls. I also added a lot of #if defined(__linux__) to cover some specific cases for the Linux application. In several locations you will find something like #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

At first this doesn't seem to make sense, but it does. The nanoCLR core is quite portable, but in some cases one or another platform dependent call must be executed. In this case, the call has a definition very known to nanoCLR, but this will be implemented in platform dependent code. As I used the win32 project as the base, I used the same call definitions and tried follow the same behavior also for Linux application.

Here the Github link for the port, if you want to get deeper: https://github.com/dotnuttx/nf-Community-Targets/tree/linux/posix

⚠️ All code for the custom project, nanoFramework for Linux, NuttX and etc are available, preserving their respective licenses, on Github.

From this code I can generate a binary, that when compiled into a static link is no more than 2.7MB. I'm also keeping "releases" of pre-compiled binaries of this code for different architectures (that which I can test) on Github: https://github.com/dotnuttx/nf-Community-Targets/releases

Because they are static linked binaries, you don't need to install any dependencies, just run it.

Check out the blog post on how to run .NET nanoFramework applications on Linux:

.NET nanoFramework Linux -> NuttX

With the Linux console application running successfully, I was able to put NuttX to the test. And, I was really impressed with the result. No wonder I heard so much about NuttX. I followed the way I found easiest to compile an app for NuttX, which is by linking objects directly into the NuttX binary, the so-called built-in application. For that, I added the files Kconfig, Make.defs and Makefile. Commit: a5e837c

In the Makefile I simply added the relative paths to the source code of my local nf-interpreter fork and added the necessary flags/defines for the compiler. In the NuttX configuration, to compile the Kernel, it was necessary to add the following configs to add C++ support:

# C++
CONFIG_HAVE_CXX=y
CONFIG_LIBCXX=y

An interesting problem I faced was with an include, which should work and share the same behavior between Linux and NuttX. In NuttX some includes are not, let's say, "prepared" to be used with C++. Example, using utsname.h to get system information:

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

If we inspect the header on Linux, we will see that we have the following snippet at the very beginning of the file:

#ifndef    _SYS_UTSNAME_H
#define    _SYS_UTSNAME_H    1

#include <features.h>

__BEGIN_DECLS

And __BEGIN_DECLS resolves to:

/* 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

In NuttX's utsname.h, there is no such care with __cplusplus, which leads to undefined reference errors during build. To solve this on the application side I added:

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

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

But in the future I hope to be able to contribute with this treatment, similar to what we have in Linux, in the NuttX header (NuttX already have it in other includes, I had the bad luck to use one without it).

You also might have noticed that in addition to defined(__linux__) I'm using defined(__nuttx__), to handle these specific cases (and also because the NuttX build config pattern doesn't define __linux__). But, apart from that detail, nothing else had to be changed from the Linux port specifically to NuttX. The port was very straightforward 😎!

And now, I can run .NET nanoFramework on Raspberry Pi Pico, for example, taking advantage of its support from NuttX:

See the blog post to know how to run .NET nanoFramework with NuttX on Pi Pico: Running .NET on Raspberry Pi Pico

Contributing back

This is the right thing to do. If you are working with an open source project and found something that could be improved, fixed or etc, and you have the knowledge to contribute it, CONTRIBUTE! It's the least you can do, the value of the project you're using, and for free, it's priceless. If you cannot contribute financially, donate your technical knowledge. I get upset when I know a company or group that is using the "core" of a certain open source technology, visibly applying modifications and improvements, but not contributing back or not supporting the project 😔.

During my research and development I managed to make the following contributions:

And more contributions from this project will likely come.

Conclusion

POSIX 🤘! With .NET nanoFramework running on Linux, the porting to NuttX was pretty straightforward, with really minor modifications. And, with that I can now run nanoCLR on an arsenal of +67 microcontrolled boards of different architectures, which NuttX supports, and an infinite list of microprocessed platforms and architectures that runs Linux 😎.

There is a lot of work ahead, for now only GPIO, from hardware-specific features, are implemented (support for i2c is coming soon). But the .NET nanoFramework also has a remote debug protocol, which stops at breakpoints and inspects the managed code inside the nanoCLR, very interesting piece of software, of which I haven't touched the code yet to analyze Linux and NuttX support.

⚠️ Remembering that this is something I am working on weekends and during my free time. It's 'EXTREMELY EXPERIMENTAL' and I'm not being funded by any group or institution for do it.

If this is in any way helpful, or makes sense to you, let me know. Send me a hello on Twitter @math_castello or Linkedin 👍