Introduction
Code portability is about creating code that can adapt and function across various platforms with ease. In the diverse ecosystem of IoT, code portability takes on a new meaning. It’s no longer “just” about running the same piece of code or binary on different CPU architectures or Operating Systems. Embedded systems, with limited resources, close hardware interaction and huge variety of form factors, present a whole new set of challenges to developers who aim at writing portable code.
Within this context, Wilderness Labs Meadow offers an approach based on the .NET framework. It aims to leverage the cross-platform nature of .NET to ease the development process for IoT applications. It’s an example of how modern frameworks can be utilized to address the need for code portability in embedded systems. While not the sole solution in this space, Meadow represents one of the various tools available to developers seeking to create adaptable and reusable code for IoT devices. 🚀
The exponential multiplication of hardware platforms and the growing need for code portability
Let’s kick it back and chat about how software platforms have evolved, like we’re reminiscing about the good ol’ days. It all started in the 40’s with the ENIAC, the granddaddy of computers. Then came the mainframe era in the 50’s, where these big, room-sized machines were all the rage. Fast forward a bit, and personal computers started popping up everywhere in the late 70’s and 80’s, making it possible for everyone to have a computer at home. The '90s were all about the internet—hello, web servers and online apps! Then, the 2000s rolled in with virtual machines and cloud computing, letting software float around in cyberspace. The 2010s were a wild ride with smartphones and smart-everything, from coffee maker to robotic arm, to cars. Now, software is like this invisible magic that’s everywhere, running on clouds, in your pocket, and even in your car, making sure you’re connected and up to date.
So, in such a context, here is the deal with code portability: it’s super important. Why? Because we’ve got software running on everything from clouds to coffee makers, and developers need their code to work on as many platforms as possible with a minimum effort. The ultimate goal is to be able to write your code once and then being able to run it anywhere—like a tech version of a Swiss Army knife. This is why developers are all about languages and tools that play nice with everything.
Avoiding getting trapped with one vendor is a big plus, too. And let’s not forget, being able to reach more users and gear up for whatever new tech comes next has become essential to any piece of software. In short, portable code keeps things rolling smoothly and keeps developers sane.
How is code portability achieved?
In the realm of software development, achieving code portability can be approached in several ways. Initially, it involved recompiling native code for each specific platform, ensuring that applications could run on various hardware and operating systems. However, this process was labor-intensive and prone to compatibility issues. The introduction of frameworks and middleware like .NET and Java marked a significant advancement, providing a layer of abstraction over the underlying system differences. These technologies compile code into an intermediate language, which is then interpreted or compiled just-in-time on any supported platform, streamlining the development process.
More recently, virtualization and containerization have taken center stage, with tools such as VMs (Virtual Machines) and Docker containers encapsulating applications along with their environment, making them easily transferable and executable across diverse infrastructures without the need for recompilation. This evolution reflects a continuous effort to simplify the deployment and execution of software in a multi-platform world.
And this evolution of code portability has been supported by (and couldn’t have been possible without) the rapid evolution of compute and storage capacity. Virtualization, containers, and modern frameworks can deliver a high level of abstraction of the hardware and portability of the code at a cost: they require a considerable amount of compute and memory capacities!
What about code portability for embedded devices in IoT?
In the embedded systems arena, we’ve witnessed a significant leap in computing and storage prowess as well, but from the perspective of modern software development, they are still resource-constrained, and this field remains a tough nut to crack when it comes to balancing performance with cost and power efficiency. Unlike their beefier counterparts, embedded devices can’t really get cozy with the likes of virtualization and containerization—they’re just too resource-intensive for these petite powerhouses. Same for the common runtimes, middleware and frameworks used in other software domains: these are often too big or too resource-hungry to be used efficiently on Embedded devices, and this is why most are still coded using C/C++.
Another dimension that’s unique to embedded devices is the variety of hardware used, from different types of microcontrollers or microprocessors to a multitude of form factors and how close to the hardware your code ends up being. It’s not just about which CPU architecture you need to compile for, it’s also about which variation of a temperature sensor are you using, or what type of display is available, or will the user interact with the application using a touch screen or a button, or both?
To make their code portable across embedded platforms developers can take advantage of libraries and solutions that abstract hardware variations at different levels like Embedded Artistry’s libc, Boost, PlatformIO, Wiring, but they still have much to do, like using conditional compilation flags all over their C/C++ code, then master the compiler configuration files… And I know by experience that this is NOT fun!
But it’s not all doom and gloom. Modern frameworks and languages are making their way into microcontrollers, and they offer a buffet of perks for embedded development. They allow for a quick and easy prototyping and smoother testing ride on different hardware or even PC platforms. Plus, they scale like a dream and are future-proof, keeping you one step ahead. And let’s not forget, they tap into a goldmine of existing skills and code, making life a whole lot easier for developers diving into the embedded deep end. Note that this perfect picture does entail the need for more powerful embedded hardware and slimmer and more efficient frameworks.
A search for a portability champion among languages and frameworks used for embedded development
Many candidates
In the IoT dev world, picking the right programming language is like choosing the right superhero for the job—it’s gotta fit just right. C and C++ are the old-school heroes, super reliable for embedded stuff where you gotta squeeze performance out of every byte. Python is the friendly sidekick, easy to work with for quick ideas, but not the one you’d call for heavy lifting. Java is like the all-rounder, good for running the same code on all sorts of gadgets, but it can be a bit heavy. Lua is the nimble ninja, small and speedy for simple tasks. JavaScript/TypeScript are the cool kids, great for web-connected things but not so much for the nitty-gritty hardware chat. Rust is the new kid on the block, tough on bugs and slick with multitasking. And then there’s .NET, the versatile one, especially if you’re already in the C# club.
Each language has its own superpower and kryptonite, and is more or less portable, so you will have mix and match to nail your IoT project’s needs, considering stuff like how grown-up the tech is, how tight security needs to be, and how well it plays with others.
How is .NET a relevant candidate for modernizing IoT device development?
To answer this question, it is important to first look at its evolution in terms of cross-platform capabilities. Let’s go down memory lane to look at .NET’s journey towards supporting ever more platforms:
2000: .NET was announced by Microsoft as a Windows development framework (almost 25 years!!)
2001: the open-source project Mono was launched to make .NET available on platforms based on Linux (Android and iOS)
2002: .NET Compact Framework was created as a subset of .NET targeting embedded devices running the Windows CE RTOS
2007: .NET Micro Framework was created targeting resource-constrained devices, offering a smaller version of the CLR and a subset of the .NET base class library, and running directly on the silicon (no underlying OS)
2011: Xamarin was founded and took over the Mono project bringing .NET to Android and iOS devices but as a third-party solution
2012: Xamarin.Mac is introduced adding support for MacOS
2016: Microsoft acquires Xamarin, introduces .NET Core (supporting Windows, Linux and macOS on Intel x64 and x86, as well as ARM architectures), and ASP.NET Core (adding Web platform support).
2016: launch of the open-source project .NET nanoFramework
2016: Wilderness Labs was founded
2019: .NET Core 3.0 adds support for ARM64 architecture
2020: .NET Core becomes .NET 5
2021: Xamarin.Android, Xamarin.iOS and ASP.NET Core are integrated into .NET 6, making .NET support natively Windows, Linux, MacOS, Android, iOS, and Web platforms.
2021: .NET nanoFramework v1.0
2023: Wilderness Labs releases Meadow 1.0, supporting .NET Standard 2.1 and adding support for microcontrollers to .NET 6, and all subsequent versions of .NET
2024: Xamarin is retired, Xamarin.forms becomes .NET MAUI, part of .NET 8
In addition to its obvious trajectory towards supporting as many platforms as possible, the .NET framework is certainly a serious contender in the modernization of IoT device development thanks to its rich ecosystem of libraries providing a robust foundation for building versatile IoT applications, as well as its strong typing and safety features significantly reduce the risk of errors, making it a reliable choice for developers. The supportive and large .NET community and comprehensive documentation further enhance its appeal.
The only thing that could still hinder .NET use on microcontrollers is its considerable size and compute requirements. But this is what Wilderness Labs team is focusing on: reducing its size, optimizing its compilation and making the runtime efficient on resource-constrained devices. I recently published an article introducing Wilderness Labs Meadow and how it brings the power of full .NET to the embedded space. Let me know what you think!
The Wilderness Labs Meadow advantage
Meadow is a platform that’s built on the inherently cross-platform foundation of .NET (we just discussed this at length!). This means that with Meadow, you’re not just writing code; you’re crafting a portable masterpiece that can run across a multitude of devices and environments.
A solid cross platform legacy
At the core of Meadow is Mono: Remember Mono? It’s that open-source and cross-platform implementation of the .NET framework I mentioned in the timeline above. Created by Miguel de Icaza, Mono paved the way for .NET to run on Linux and other platforms. It’s like the rebel cousin of the .NET family, breaking free from Windows exclusivity (that was before the introduction of .NET Core in 2016). Wilderness Labs has a direct lineage from Mono to Xamarin. This lineage allows Meadow to run on various embedded hardware platforms, including the STM32F7 microcontroller, the ESP32 Wi-Fi module, and even the Raspberry Pi. It’s like taking .NET out for an adventure beyond Windows. And Meadow doesn’t stop at Mono nostalgia. Wilderness Labs engineers have embraced modern .NET, supporting .NET Standard 2.1 and working on porting to .NET 8 as soon as it was released. Additionally, Meadow leverages familiar patterns and practices from web and mobile development, making it feel like home for .NET developers.
Hardware abstraction done right
Meadow takes the .NET framework’s cross-platform capabilities to the next level by offering a suite of libraries designed to make hardware abstraction not just possible, but elegant and efficient. The Meadow.Foundation is a treasure trove of drivers and helpers that streamline the process of interfacing with sensors and actuators. It’s like having a universal translator for hardware communication. Here are some examples illustrating this statement below.
A large and easily extendable collection of peripherals libraries…
The Wilderness Labs team, supported by the community, has already put together a large collection of peripherals libraries, from core peripherals such as PushButton, Relay, PiezoSpeaker, PwmLed, RotaryEncoder which basically only require to simply set the right pins at initialization, to more sophisticated and functional drivers for displays, audio, dataloggers, motors, you name it!
…built taking advantage of .NET object-oriented constructs
One of the reasons why the Wilderness Labs team was able to build such a long list of peripherals libraries is the way they architected drivers in Meadow.Foundation: they use the object-oriented constructs of .NET to allow developers to interact with hardware at the level they prefer. For example, to control an LED, a developer can decide to use the IDigitalOutputPort class directly or they can decide to drive their LED using a driver class inheriting from IDigitalOutputPort like PwmLed that exposes high-level methods such as StartBlink() or StartPulse().
Another example of this is the one of a moisture sensor: the driver class Fc28 inherits from the following interfaces: IMoistureSensor, ISamplingSensor and ISensor, like other moisture sensor drivers, which means developers will use the same properties and methods in their app (the ones exposed by IMoistureSensor) independently of which moisture sensor they have on their hardware. The only thing they need to change is the class name and parameters at initialization. And if you need to develop a new Moisture sensor driver, most of the work is already done for you in these “generic” classes.
This not only makes you more efficient at writing code that interacts with hardware without having to understand all the intricacies of electronics and protocols like I2C or SPI, but it also allows for more of your code to be portable or reusable.
But wait, there are more ways Meadow abstracts hardware!
Have you heard of a reactive-style pattern to deal with events from sensors called the IObservable pattern? Peripheral drivers in Meadow.Foundation can use a static CreateObserver() method to create filterable observers that listen for change notifications. This means you can subscribe to an observable with an optional predicate to filter the events, giving you control over which changes you’re notified about. Yet another cool way to abstract hardware elegantly.
You are switching from a device where a sensor is connected directly to the board to another device that uses an expansion chip to connect the same sensor? Meadow’s Unified IO architecture is a clever bit of design that makes it a breeze to extend your Meadow board’s peripheral support. Think of it as a universal adapter that lets you plug in all sorts of expansion peripherals—like I2C/SPI I/O expansion chips—and have them work as if they were directly connected to the Meadow board, abstracting your application code from hardware differences from one target platform to another. In many of the Meadow.Desktop samples, you can see such an expansion chip used to connect sensors and other peripherals and use them in a Meadow app like if they were connected directly.
You need to render graphics on various types of small displays? Meadow’s got you covered with the MicroLayout library that makes creating lightweight HMIs a breeze and the MicroGraphics library that simplifies the complexities of rendering graphics on such small displays. It’s like a paintbrush for pixels, allowing developers to create rich user interfaces without getting bogged down in the minutiae of display protocols. I have myself lost some hair developing display drivers, and I can tell you, it’s not fun at all!
You need to do some testing but don’t have the hardware handy? No worries, just test your code on your desktop (including the graphics). Meadow.Desktop is like your personal dev playground for Meadow apps. It’s super handy because you can whip up and test your code on your desktop, whether you’re a Windows fan, a Mac diehard, or a Linux enthusiast. It’s all about making things easy and efficient. You get to play with virtual hardware controls and even design interfaces without needing the actual gadgets on hand. And the best part? Once you’re happy with how everything works in this virtual space, you can smoothly transition your code to a real Meadow device. It’s all about cutting down on the hassle and getting your projects from concept to reality in a snap.
With these tools (and more) at your disposal, Meadow not only promises code portability but delivers it with the finesse and power that modern IoT development demands.
Playing time!
To test things out and see some of these features and capabilities in action, I put together a little demonstration:
I started with a Meadow.Desktop app on Windows with a simulated sensor
I used an USB IO expander to connect an actual I2C environment sensor to my PC and hooked it up in my desktop application
I used the same code (with slight adaptations) on a ProjectLab connecting the same sensor directly to the board
Last but not least, I ran the same code on a Raspberry Pi using a tiny 2 lines LCD display connected over I2C, once again adapting a minimum amount of code.
Conclusion
As an embedded developer by trade, I would argue that C is the most portable language there is for IoT… but really, making your C code, libraries and projects really portable is a real nightmare. Yes, C can be compiled for any platform out there, but the amount of work required to achieve this is considerable.
Not only does Wilderness Labs Meadow bring .NET and its intrinsic portability to IoT device development, allowing you to use any library that’s out there in your embedded code, but it also presents an architecture that makes it simple to target different hardware platforms, form factors, and revisions with a minimum, of code changes and adaptations. Furthermore, it allows simulating platforms, sensors and other peripherals, which makes your life as an embedded developer so much more pleasant! If you don’t believe me, just go try it out… on your PC, Mac or Linux devbox!
Comments