During the latest Flight into Azure IoT, the long haul event, a number of devices were connected to Azure IoT.
One of them was an ESP32 running a .Net application written in C#.
Wait!
.Net, known for running applications on Windows and Linux, on PCs, laptops, and in the cloud, Can also run on a five-dollar MCU?
Well, yes 🙂
Using the .Net NanoFramework it is made easy to write C# code for embedded systems:

Our mantra is about making it easy to write C# code for embedded systems! And all what we’re doing here is about that. This free and Open Source platform that enables the writing of managed code applications for constrained embedded devices.
.Net NanoFramework
During that TechDays UK event, that ESP32 was connected to Azure, sending simulated telemetry to an IoT Hub (read from files while being controlled using Direct Methods).
In this post, we check out how to get started with the .Net NanoFramework and have some fun with it.
Preparing the microcontroller
All you need for getting started with the .Net NanoFramework is a compatible device, a USB cable, and a laptop. You need an Azure subscription if you want to connect to Azure.
So, you need a get a microcontroller board, like an ESP32; this is called a target.
There are a number of reference targets including the Espressif ESP32 series, boards from STMicroelectronics, and
boards from TI SimpleLink and NXP:

You can also try the community targets, there is a GitHub repository that holds target boards provided by community members. Check it out here:

We go for a generic ESP32 I bought from a Chinese website.
There are a number of steps you need to execute so your device is prepared for running .Net:
- Get and install the USB drivers so your laptop understands the board when you connect it to a USB port
- Notice a COM port should be assigned to the board in the Windows Device Manager

- Install the NanoFF flash tool (try ‘dotnet tool install -g nanoff’ on the Dos prompt or ‘dotnet tool update -g nanoff’ if you had installed it in the past)
- Note: you need to have installed the DotNet SDK on your laptop already. It’s just needed for that tooling.
- Flash the ESP32 without a specific target platform (this is the best fit/guess and is usually enough) using ‘nanoff –serialport COMXYZ –update’ (fill in the right comport number)
- Note: Stop the VS Device watcher first, only one tool at a time can have access to the ESP32 (The VS Device watcher is seen in the Device Manager windows as mentioned below)
- Note: Sometimes you need to hold the ESP32 boot button on the device if needed. A message will be shown telling you the device is not ready to get new boot logic
- Check device details: ‘nanoff –serialport COMXYZ –devicedetails’ (fill in the right comport number)
So, these simple steps need to be executed:
dotnet tool install -g nanoff
dotnet tool update -g nanoff
nanoff --serialport COMXYZ --update
nanoff --serialport COMXYZ --devicedetails
Make sure the installation succeeds with all the hashes being verified:

See how the Firmware Flasher selects the most obvious target and downloads it.
It’s interesting to see the list of features being supported by the device:

At this moment, the device should have been prepared to run .Net applications. There are more elaborate ways to get your device prepared but this is good enough for our needs. Check the documentation if needed.
Fun fact, also a number of M5Stack devices support the NanoFramework. See the GitHub page for details.
Finally, install the .Net NanoFramework extension for Visual Studio:

Note: Install the nano framework extension (you have to restart VS2022 after installation) via menu Extensions | Manage extensions | Online | Visual Studio Marketplace.
Note: there is also an extension for Visual Studio Code. In this post, we concentrate on the full Visual Studio.
Note: A free community edition of Visual Studio is available for download.
Hello World from .Net NanoFramework
In Visual Studio, you will now be able to create .Net NanoFramework projects:

Note: As you can see, you can even add class libraries and unit tests to your blank application.
We create a new blank application:

Once the application is created, you will see a familiar C# program template:

It has essentially two lines of code:
- Write a debug line on the debug output (in the Visual Studio output window)
- Sleep forever (endless loop)
This is the Hello World of .Net NanoFramework.
Set a breakpoint on the line with the debug text:

Yes, this tooling supports active debugging of your code while it runs on the ESP32 microcontroller.
Now, go to the “‘Device Explorer” window. This is a new window, part of the .Net NanoFramework tooling:

If your ESP is configured correctly, your device will be added and selected almost always automatically to the list of devices. You can also connect try to the device by pressing the button with the magnifying glass:

The output window confirms this:

Once your ESP32 device is seen there, you can run the application on the microcontroller:

The application will be built and deployed.
Notice the output window during these steps. You can see eg. the size of your application:

Once the application is deployed and started, the breakpoint will be reached:

You can even inspect some of the classes (like the program in this case).
You can continue the execution of the application (use either F5 or F10).
You just built your own .Net NanoFramework application and it’s now running on this cheap microcontroller.
Stop the application so we can modify it for the next phase.
GPIO interaction
Let’s extend this example and play with the general-purpose input and output GPIO of this device (the pins).
You can search for the Blinky sample along all other sample applications but I do it by hand.
I found this pin-layout for my ESP32 device and please notice the names in the column of GPIO pins:

These are the names I need to reference in my code when I want to interact with my device.
I want to connect some LEDs and push a couple of buttons.
First, I need to add a NuGet package for controlling GPIO.
We need to search and add the nanoFramework.System.Device.Gpio package:

Notice there are dozens of .Net NanoFramework packages. This is the key to keeping the application small: just load the NuGet packages you need with specific .Net logic.
For example, if you want to do something with the I2C bus, there is a separate library for that. If not, this logic is not loaded into the device.
Alter the project code to this:
using System.Device.Gpio;
using System.Diagnostics;
using System.Threading;
namespace NFApp1
{
public class Program
{
static GpioController _gpioController;
static GpioPin _led_Internal;
static GpioPin _led;
static GpioPin _button;
public static void Main()
{
Debug.WriteLine("Hello from nanoFramework!");
_gpioController = new GpioController();
_led_Internal = _gpioController.OpenPin(2, PinMode.Output);
_led = _gpioController.OpenPin(27, PinMode.Output);
_button = _gpioController.OpenPin(12, PinMode.InputPullUp); // low when attached to ground
while(true)
{
_led_Internal.Toggle();
_led.Write(_button.Read());
Thread.Sleep(1000);
}
}
}
}
Pin 2 is pre-defined for the LED attached to the ESP board.
Note: Sometimes this is pin 4 or even another pin. This differs per device brand and make…
I attached a separate LED and resistor to pin 27 (via the 3V3 pin).
I also made it possible to connect and disconnect pin 12 to GND acting like a button.
Run the code.
You will see the LED attached to the ESP board blink every second.
The other LED will toggle along with the button.
So, we can play with the GPIO in C#.
If you look a bit further regarding the GpioPin class, you also see events being supported:

Microcontrollers normally work with a main message loop. As expected in C#, events are supported too in the NanoFramework.
In my example, I use an infinite loop to test for buttons being pressed.
Just try it out and see what works for you.
Fun with playing MP3 files
But what about actual hardware like motors or temperature sensors?
Take a look at the list of hardware, currently supported by the platform:

The devices seen in this screenshot is just a fraction of the devices being supported.
Notice, every device comes with a separate NuGet package.
I want to play with an MP3 player.
I found out that both the YX5200 and YX5300 are supported:

Note: there are 15+ categories so plenty of devices are already supported.
The YX5200 works with a loudspeaker and the YX5300 comes with a headphone jack. Both cost four dollars or less. Notice you have to bring a separate MicroSD card (formatted in Fat32 and filled with one or more MP3 files and folders containing MP3 files):

I found this documentation regarding the pin layout.
If you select the NanoFramework library link, you get background information and even a sample code snippet:

This is perfect for me.
Note: it seems the ‘mp3Player.Play();’ lines can be omitted. I suggest to ignore/remove those lines.
So, based on the documentation, I used this layout:

I actually added that resistor although I’m not sure if it was needed. The sound is of good quality though and loud enough for my needs.
I also experienced the player device was ‘crashing’ a number of times while the power line was connected to 3V3 only. Although the specs say the MP3 player works from 3.2V up to 5V, I expect this cheap ESP32 is not delivering the right amount of voltage. I switched over to VIN (5V in my case due to being USB powered) and now the player keeps on working as expected.
I then needed to reference the Nuget package for the Yx5300 device too.
Finally, based on the sample code, I added this logic:
using System.Diagnostics;
using System.Threading;
using Iot.Device.Yx5300;
using nanoFramework.Hardware.Esp32;
namespace NFApp3
{
public class Program
{
private static Yx5300 mp3Player;
public static void Main()
{
Configuration.SetPinFunction(Gpio.IO17, DeviceFunction.COM2_TX);
Configuration.SetPinFunction(Gpio.IO16, DeviceFunction.COM2_RX);
// Open RX/TW on COM2 and instantiate player
mp3Player = new Yx5300("COM2");
// Start player and play some files
Thread.Sleep(1000);
mp3Player.Volume(25); // Yx5300.MaxVolume = 30
Debug.WriteLine("Play first sound");
mp3Player.PlayTrack(1);
Thread.Sleep(10000);
Debug.WriteLine("Play eerste constFileNumberFrother");
mp3Player.PlayTrack(2);
Thread.Sleep(20000);
// play other sounds or complete folders
Thread.Sleep(Timeout.Infinite);
}
}
}
Here, I play two different sounds, stored on the MicroSD card.
First, I configure the RX and TX port numbers being on par with COM2 on the MP3 player class.
I also configure the sound level. It is set to 25 while the maximum is 30.
After that, I play two sounds after each other.
Notice two things:
- Instead of entering the filename, I need to provide a number. The first file is number 1, not 0!
- I need to wait until the sound is wholly played before I can proceed to the next line, hence waiting for a number of seconds.
Note: I’m not yet sure how to get the names of the files and duration from the disk…
The MP3 player class is capable of executing more logic like shuffling files.
This MP3 code is a nice example of handling external hardware.
How about setting up a WIFI connection?
Setting up a WIFI connection
Your ESP32 can connect to the (cloud over) WIFI.
So, let’s try this out.
You could check out the sample like this sample app for scanning the WIFI.
There, you will see that you need to add both the SSID and password of the network you connect to, in the source code.
I go for a different solution that gives me more control over the WIFI settings.
First, create a new .Net NanoFramework project and install the nanoFramework System.Device.Wifi NuGet package:

Once this is done, use this code snippet:
using System;
using System.Diagnostics;
using System.Threading;
using nanoFramework.Networking;
namespace ConnectToWIFITest
{
public class Program
{
public static void Main()
{
ConnectToWifi();
Thread.Sleep(Timeout.Infinite);
}
public static bool ConnectToWifi()
{
// Connect the ESP32 Device to the Wi-Fi and check the connection...
Debug.WriteLine("Program Started, connecting to WiFi.");
CancellationTokenSource cs = new(60000);
var success = WifiNetworkHelper.Reconnect(requiresDateTime: true, token: cs.Token);
if (!success)
{
Debug.WriteLine($"Can't connect to wifi: {WifiNetworkHelper.Status}");
if (WifiNetworkHelper.HelperException != null)
{
Debug.WriteLine($"NetworkHelper.ConnectionError.Exception");
}
}
Debug.WriteLine($"Date and time is now {DateTime.UtcNow}");
return success;
}
}
}
This code connects to a Wifi network of your choice.
Note: the WIFI chip on the ESP32 only supports older types of WIFI versions like B, G, and N.
But if you check the code, where are the SSID and password entered?
Well, we need to provide them but I do not like and need to add them in the source code.
Where are these added?
Go to the Device Explorer windows and push the Edit Network Configuration button:

You will get this dialog. Please navigate directly to the Wi-Fi profiles tab:

Note: These settings are saved on the device itself, apart from the code but accessible by code.
There, fill in the SSID, the password, the Security type (probable WPA2), the Encryption type (experiment with either WPA2 or WPA2 Pre-Shared), the Radio type (check your network, for me 802.11g works), and options (select Enable).
Note: I recommend trying out different combinations. It turns out it’s less science than expected, especially with the cheaper boards 🙂
Just select OK to have these settings saved.
Once saved, run the application.
Check the output window once it is running:

As you see, my device is connected to WIFI and it synchronizes the internal clock (the device does not remember the date and time when turned off due to the absence of a battery-powered clock)!
If the WIFI is absent or the settings are invalid, the code will timeout after sixty seconds. Then, the clock will start with that typical Linux start time:

Keep in mind that when you use an invalid target, the WIFI could not work…
Also, if you reflash your microcontroller, you need to add the credentials again!
So, now you are able to do nifty things with WIFI. Check out the other examples for more fun.
But, how about cloud connectivity over WIFI?
Azure IoT integration
The last example I show when coding the ESP32 in C# using the .Net NanoFramework is creating a cloud connection to an Azure IoT Hub.
Here, we are going to use a sample application.
A number of Azure IoT-related sample projects are available for you to experiment with:

We check out the project named AzureSDK because we are only interested in making a simple connection and checking the features.
The first thing we notice in the code is that we need to supply a number of variable values:

You probably created a device using a symmetric key already in the Azure Portal so getting the device credentials is not that hard:

Regarding the SSID and Password, remove these (we want to use those secrets as seen in the previous WIFI related paragraph).
This requires a little code change for connection to the WIFI:
Replace:
var success = WifiNetworkHelper.ConnectDhcp(Ssid, Password, requiresDateTime: true, token: new CancellationTokenSource(sleepTimeMinutes).Token);
With:
var success = WifiNetworkHelper.Reconnect(requiresDateTime: true, token: new CancellationTokenSource(sleepTimeMinutes).Token);
Also, this sample expects you to have a Bmp280 (pressure sensor) device connected to your ESP32.
If you have one, this is a good opportunity to learn about the library needed for this sensor.
Else, if you do not have one, there are a number of comment lines in the sample code to guide you around this.
I changed and enhanced the code myself a number of times:
Replace:
const int busId = 1;
I2cConnectionSettings i2cSettings = new(busId, Bmp280.DefaultI2cAddress);
I2cDevice i2cDevice = I2cDevice.Create(i2cSettings);
Bmp280 bmp280 = new Bmp280(i2cDevice);
With:
Bmp280 bmp280 = null;
Surround:
bmp280.TemperatureSampling = Sampling.LowPower;
bmp280.PressureSampling = Sampling.UltraHighResolution;
With:
if (bmp280 != null)
{
...
}
Comment out:
var values = bmp280.Read();
azureIoT.SendMessage($"{{\"Temperature\":{values.Temperature.DegreesCelsius},\"Pressure\":{values.Pressure.Hectopascals}}}");
Restore:
azureIoT.SendMessage($"{{\"Temperature\":42,\"Pressure\":1023}}");
Add an extra debug text after the previous line:
Debug.WriteLine($"Message sent");
Now, start the application and see how the device connects to the Azure IoT Hub, reads the twins, and sends messages every twenty seconds:

Because we use a X509 certificate, we need that correct date and time.
Please check the documentation regarding the X509 root certificate, part of this sample.
You can even see your Root certificate can be stored on the device, not in the application.
This is called pinning and not flexible. If you use this technique in combination with Azure, check the documentation and IoT Hub.
This ‘soon-to-expire’ Baltimore CyberTrust Root and must be updated to the DigiCert Global G2 root (by Februari 2023).
Now, we can see the arrival of the messages in Visual Studio Code using the IoT Hub extension:

Our device is now connected to the IoT hub and supports almost all Azure IoT (DeviceTwin) features:
- Sending device-to-cloud messages
- Reading desired properties at the start and while being connected:

- Changing reported properties
- Executing Direct Methods
- Reading cloud messages at the start and while being connected
There are a number of differences, compared to the original C# Azure Device SDK, I encountered:
- I did not find support for file upload
- The names of the direct methods are defined by the callback method name:

- Sending an error code as a response to a Direct Method call let my device crash
- Cloud messages are automatically accepted. Alternatives like Abandon are not supported
- Sending application properties and content type is a bit awkward but it works:

In the end, being able to connect a microcontroller to Azure using C# feels like something magic.
Do not underestimate to turn this in a robust solution while interacting and integrating with actual hardware.
Conclusion
We have seen how to successfully program an ESP32 using .Net C#.
We played with GPIO, accessed an external MP3 player, connected to WIFI, and connected the device to the Azure IoT Hub using WIFI.
Why should you go for the .Net NanoFramework?
If you are a C# developer and you have no previous experience with microcontrollers, this is a nice and simple introduction that will bring you very far without having to leave this productive environment.
For more experienced developers, this platform provides a solid solution for developing applications.
The list of supported bus protocols and sensors and devices is not unlimited.
Yes, if you need to connect other devices you will have to get help. You can discuss the platform and community contributions at Discord.
And you are invited to contribute with community contributions if you want.
Real life example
The project I used to test the .Net NanoFramework with was this Azure IoT demonstrator in the form of a (broken) coffee maker made cloud connected:

It mimics a real coffee maker but instead of making coffee, it plays sounds associated with making coffee (there is even a ‘working’ frother):

The coffee making can be started both using the big turning wheel on the front (I managed to rewire that two-way button) and using Direct Methods in the cloud.
The status of the coffee maker can be seen using the three LEDs at the front of the coffee maker.
Bonus: Challenges with STM32 Nucleo
I also played around with a couple of STM32 Nucleo boards (like the F401RE = 512KB Flash size; documentation).
I wanted to connect over USB. So, I needed a driver. Get your driver here (free registration via email confirmation). I had to install the dpinst_amd64.exe on my 64bits Windows 11.
Flash this particular Nucleo with:
nanoff --update --target ST_NUCLEO64_F401RE_NF --serialport COMABC
It works for me!
I also tried the Blinky example on my Nucleo.
I did some trial-and-error (and checking paragraph 6.5 of the documentation) and it seems the STM32F091RC is working for me. I was able to get the red / GREEN led blinking on a couple of Nucleo boards.
So, if you are interested in all currently supported (STM32) devices, get them listed with:
nanoff --listtargets --platform stm32 --preview
If you omit the platform filter and the preview filter, you see all current stable targets.
Een gedachte over “Fun with NanoFramework, running .Net C# on ESP32”
Reacties zijn gesloten.