Learning Rust with Embedded Programming on RP2040
First post in a series diving into Rust and Microcontrollers
This is the first in a (so far) seven-part series. My goal has been to document the journey I’ve been taking in exploring embedded development in Rust based on my Raspberry Pi Pico W board. My initial successes depended a lot of the prior work of other people who, like me, wanted to help others get started on their own journeys. There were many places where a blog was helpful, but I had to make some changes to the process because the ecosystem had changed. I soon realized that I might be able to help some people out by chronicling my own journey, pointing out my own stumbling blocks and trying to provide my own perspectives to help people who are new to Rust, to embedded programming, or both.
Click on the card below to see a summary of all the articles in this series:
It’s been a while since I’ve written any articles on my favorite topics—Scala and ZIO—and the reason is that honestly I haven’t done much of anything new with either. Don’t get me wrong: I love Scala and find it downright soothing to solve problems with what has evolved into an elegant language. After the long journey into Functional Programming paradigms, my brain has been suitably rewired to be able to solve tough problems elegantly. ZIO is also amazing, and I’ve enjoyed following its development over the past several years into a mature effects-based framework with a vibrant development community, but the fact of the matter is I don’t work on any problems where ZIO is applicable.
But an interesting thing happened on both my recent Scala and ZIO escapades: key influential people have shifted their energies into Rust. One is a local person who had actually founded the local Austin Scala Enthusiasts MeetUp and was one of the really really smart people who taught me over the years. Once I remember he had organized a co-meetup with the local Rust group so we could talk about similarities and differences of the languages. Fast forward maybe four years and I found that he’s now the organizer of the local Rust Meetup, and it looks like he’s working in that language professionally.
The same thing applies to the father or ZIO, John De Goes. (Who I think is one of the best speakers out there. I’ve watched about every presentation he’s ever made on YouTube over the years.) His company, Ziverge, has radically shifted gears and is working on the new Golem platform, and if you follow the key developers, it’s clear they’re also working in Rust.
I don’t consider myself a follower of trendy things, per se. Instead, I’ve made a career of following the smartest, sage-like, people and endeavoring to learn the technologies and paradigms they’ve embraced in an attempt to try to get more “sage-like” myself. Today these people all seem to be embracing Rust. And as it happens to be, Rust appears to be a not technology that is increasingly in demand.
So if these people are diving head-first into Rust, I’m going to see what it’s all about. And interestingly, Rust demand seems to be growing steadily. Here’s just a example post by John De Goes in LinkedIn a few weeks ago:
For all of these reasons, I’ve decided it’s time to dive into something new and see where Rust can take me. In addition to learning Rust and really kicking the tires with some concrete applications, I want to capture my experience. I want to share the challenges, the pitfalls, the “ah-hah” moments, and the learnings along the journey. Thus I am beginning a new series of articles.
I’m doing this both because I enjoy writing (and haven’t published anything in a long time) and because I have not seen a lot of good books or articles out there about learning and excelling at Rust development.
Why Embedded?
Let me take a few minutes to talk about why I’m writing about Rust related to the Embedded world. In other words, why start with Embedded Rust rather than workin on an (easier) desktop or server application?
Here’s the thing: if I wanted to prototype some simple application, like when I wrote an app to help me solve Wordle, I would write it in Scala. (I also wrote that as a use case to play around with ZIO streams.) The fact of the matter is, if I want to solve a problem quickly and elegantly, like any of the Advent of Code puzzles, I’m going to knock it out with Scala. I could try to solve it with Rust, but I have a hunch it would be unnecessarily painful, dealing with explicit declarations of state, yada yada.
I guess this goes straight to the important question: what does Rust excel at? I’m sure a year from now I’ll have a clearer perspective of this, but for now let’s look at what I currently know:
- Rust is super high-performance, based on its ability to function without garbage collection and its ability to compile down to highly optimized machine language.
- Rust enables the development of very robust, fault-tolerant code due to the development patterns it enforces and the power of its advanced compiler and strong type system.
I’ve read about Rust being used in the development of operating systems (like Windows and Linux), and the few Rust developers I’ve talked to here in Austin are all working in Fintech and Crypto. I’ve got no experience with any of these things.
The truth is: most modern languages are high-level enough to abstract away the concepts of how data is stored and managed in memory. A variable is a variable, right? It stores some sort of information. It’d the underlying value we can about, right? If we’re writing something in Java or Python or JavaScript (node.js or otherwise) or Scala or Ruby, we’re not thinking about whether a value is being allocated on the stack or in the heap.
I do have a little experience with microcontrollers. (Which I’ll ramble about a little in the next section.) They are accessible and affordable, and there are plenty of fun applications associated with IoT and/or home automation. And yes, for the beginning tinkerer, CircuitPython and MicroPython are nice tools to make embedded programming accessible, but for the most part, the target language is C/C++ because you are working so “close to the metal”.
A bit of my own “Maker” history…
For many years, I’ve been fascinated by the whole “Maker” movement. I have been a computer nerd since I was eight years old, programming an Apple II computer, but I had never gotten into the nuts and bolts (resisters and LEDs?) of electronics. At that same time I was first programming computers, my parents got me a Radio Shake “150-in-One” kit. It was a clever design with these little springs that you could bend and stick wires in to put together a circuit.
There were 150 circuit designs (thus the name) to teach electronics, but apart from following the prepared circuits, I couldn’t understand enough to really do my own advanced projects. Resistors and capacitors and A/C circuits were just beyond my grasp.
But when the Arduino Uno came out, and thanks to the wonderful Adafruit store and tutorials by Limor “Ladyada” Fried, I was excitedly soldering my first projects about ten years ago. I loved the fact that the Uno’s tiny 8-bit ATmega328 architecture and 32KB flash memory space was very reminiscent of what the old PC-era computers had to work with. If you were going to use it, you were going to have to keep track of every byte of data you were working with.
It was nostalgic, but a little constrained, and even a bit impractical given the more advanced controller chips that have been coming out.
The Raspberry Pi
I also enjoyed the emergence of the Raspberry Pi. I got my first Pi 2B board about eight years ago, and I loved how I could have an entire Unix operating system on a tiny board. That same little old 2B is wired into a hidden closet in my home, running a variety of local services, including a Z-Wave controller for my home automation projects.
But I’ve also got to say that I feel like the standard full-powered Raspberry Pi’s have been going in the wrong direction. They are power-hungry (literally) boards that also generate enough heat to require a heat-sink to run reliably. I like the less-is-more mindset and the idea of doing great things with a $15 piece of hardware, so my most recent board was a Raspberry Pi Zero 2 W.
But the Pi Zero 2 W is still running a full-fledged Unix OS with half a gigabyte of RAM, and I’m writing about Embedded Rust. So let’s look at the other chip/board that captured my attention.
In-between Arduino and Raspberry Pi: the Pico
In January 2021, the Raspberry Pi Ltd company announced that they were actually designing their first microcontroller, the RP2040. It could deliver some punch with a 32-bit dual core ARM Cortex-M0+ architecture, but was still scrappy enough that you wouldn’t dream of loading a whole bloated Unix operating system on it: like the Arduino, you would be doing some good old-fashioned low-level programming. Hardware-wise, the RP2040 chip is small and efficient enough to cost about $1. I loved it!
Raspberry Pi released a couple small boards with the RP2040: the Pico and Pico W with it’s on-board Wifi/Bluetooth chip. Pictured above is another RP2040-powered board I bought from SparkFun, their Pro Micro RP2040.
Embedded Goals, Objectives, and Hardware
In the Rust embedded world, the RP2040 isn’t the only game in town. There’s a Nordic Semiconductor nRF family of hardware as well as a wide family of STM32 microcontrollers. But since I already had a couple RP2040 devices, that’s what I’ve been working with, so this series of articles will focus on specifically the Raspberry Pi Pico boards, and specifically I’ll write about the Pi Pico W.
Why the W? It’s all about the networking. My general interest is in creating projects that don’t have to be tethered to a power line (i.e. let’s make it solar, baby!) or wired via a serial interface to some other computer. (If I have a Macbook Pro attached to the project, the romance of the tiny hardware doesn’t make sense.) I want to make a project that, for example, wires up to some sensors, captures the data, and then efficiently “beams” some telemetry to my little $15 Raspberry Pi Zero 2 W board for broader data processing capability.
You might ask why I’m not writing about the brand new Raspberry Pi Pico 2 board that has just been released. There are a couple of reasons for this:
- Primarily, I wrote the first seven articles before the board was even released, but also…
- The tooling around the rp2040 is much more mature right now.
- There’s no Raspberry Pi Pico 2 W yet, and as I just wrote above, I’m going to put some emphasis on wireless communications.
In time, I might try to retool the articles to address both boards.
By the way: if you have or and interested in working with the other two main architectures, there are a pair of official Rust Embedded “Discovery” books that are designed to take a beginner slowly through a beginning tutorial process. There was an original Discovery book that was based on the STM32F3DISCOVERY board, and an updated version of the book was written, switching to the Nordic nRF52833 micro:bit v2 board. But if you want to play with the Raspberry Pi Pico series or similar SparkFun (or other) products, this series of articles will hopefully be of value.
One more curve ball: Async
If it isn’t enough to try learning Rust and Embedded simultaneously, I’m also going to dive into Async development, thanks to the emergence of the newly GA-released Embassy project! That might seem like a tall order, but I’ve found that there are enough starter templates out there to use as a foundation that this isn’t all that onerous. Where it makes sense I’ll try to point out specific conceptual tidbits as I see them and provide references for further study.
This isn’t going to be easy, but…
When I first scanned the Embedded Rust Book. In the Assumptions and Prerequisites section, it assumed…
You are comfortable using the Rust Programming Language, and have written, run, and debugged Rust applications on a desktop environment. You should also be familiar with the idioms of the 2018 edition as this book targets Rust 2018.
Okay, let’s accept that there’s going to be a bit of a chicken-and-egg learning curve here. The Embedded Rust Book is going to expect a working understanding of the Rust language. I (we) are going to have to jump between reading the Rust Language book and tinkering with the basic examples, and then trying to systematically apply them to our embedded projects. I’ve already proven to myself that this is doable, and once again, that’s why I’m writing this series: to organize and share my own breadcrumbs along the journey.
And so here is my theory: I think a good way to learn Rust will be to start with some embedded application templates (where the initial framework code has already been provided) and to iterate through some simple experiments and enhancements. Incrementally, I will attempt to weave in a discussion about some of the tougher Rust concepts (like lifetimes and mutability and references) by playing with these small incremental embellishments.
But to that end, let me offer a bit of a warning: these articles are meant for people who have a long history of tinkering, learning things on the fly, and who have already learned a few different programming languages. You may or may not have done any development with a microcontroller like the classic Arduino. I’ll explain some of the terminology, and in the next article, I’ll show you what electronics bits you’ll need to get started and follow along.
I will endeavor to both show some code (with GitHub links) so you can try these things yourself, but I’ll also try tell a narrative story so readers can follow my Rust adventure with some useful takeaways without necessarily needing to buy a pair of $5 Picos, etc.
The second article in this series will talk a little bit about why now—early 2024—is such a great time to dive into this, notably the major mature releases of frameworks that have been in active development for years! I’ll try to disambiguate the major Crates (packages) I’ll be working with. Finally, I’ll detail the hardware I’m working with (so you can order your own) and point you to resources for setting up your computer to build and upload your first program to the hardware.