 So good afternoon, everyone. Welcome back. Hope you had a nice break for our next workshop. We have Wolf with us on topic developing embedded applications in Rust. He's a principal software engineer at Red Hat in the research and product team. I'm looking forward to this session. Also, if you have any questions, please ask it in the Q&A section or tab on the right hand side of your screen. You would see behind chat polls people in that Q&A section. You can ask your questions and we can have a thread in the separate as well. Also, it's very easy to moderate any questions if you have there. That's all. The session is up to you now. Okay. Okay, let's just start then. So welcome to this workshop session on developing embedded applications in Rust. My name is Wolf and I work for Red Hat on the Drogu IoT project. That project is about creating a connectivity layer for IoT devices that support ingress of different events for HTTP, MQTT, co-app, or for a lower-round network. But it also has a device side, which is a framework for writing a microcontroller firmware. And this is where we are doing in Rust. Unfortunately, we couldn't have this workshop in person in Burno because I was imagining we would all have these boards that we're playing with. So I try to restructure it a bit so that you can follow it without having the hardware yourself. You might need to install the Rust tool chain and things in order to compile. So if you go to the session page on theshed.com, then you can find some of the links to the Git repositories and stuff there if you want to follow. So we're going to start a bit about why we're actually doing Rust for embedded in the first place. And because there's a lot of existing things written in C. And then also see what would actually Rust help you with. And then we're going to sort of go through these Rust applications layer by layer, starting with directly accessing the hardware, then sort of seeing what abstractions that you can use in order to make your software better and also more efficient. And we'll end up with some application that actually talks to the cloud. And I'll try to do most of these live, but I have some recordings in case there's a demo monster is showing up. So let's start first. What do I mean by embedded? Because some people mean different things about embedded than me. And I know people think that maybe a tiny device with four gigabytes of RAM is an embedded device running Linux. But in this case, it's a very tiny microcontrollers that only have kilobytes of RAM. They only have a kilobytes, maybe a megabyte or two of flash for the program itself. And then there's no operating system around to help you run the software. And there's also usually no memory allocator, although you can supply your own, of course. But it's very bare metal as we call it. And what is Rust? Well, it's a program language which has kind of found its niche in software where you need full control over your memory and also execution time. This is typically maybe operating systems, language run times or embedded. But it started out as a browser engine project. So it focuses on performance, of course, then, and also being reliable. It has a lot of compile time mechanisms for catching a lot of programming mistakes that you simply cannot catch in other languages at compile time. If you are not using some static analyzer, for instance. And also trying to still be productive, not very hard to use. But in my personal opinion, this might be a hard initial learning curve to Rust. But I think once you get familiar with the language, you're starting to see productivity. But what's wrong with C? We've been using that for a long time. Everybody writes applications in C. There's a lot of real-time operating systems and SDKs from all the hardware vendors written in C. So it's a very hard, tough decision actually to sort of say, oh no, we're going to write this in Rust instead. This new thing that we don't know if people are using. But there are a lot of companies using it and many people have Rust code in production. So it's not that new. But let's start with problems with C. Let's try and sort of see what can Rust offer. So we'll take a simple C code example here. Describing the issue of data races. And this is a common source and embedded, especially when you have these interrupts coming from all the peripherals of your hardware. And then you don't really have a good way to ensure that you're not accidentally writing and reading from the same data from two different contexts. So you might have some application that reads a counter in one thread or during the idle loop. And then you get an interrupt from your peripheral and then that modifies the counter and you don't really know if you have a consistent value. So this example is using a single global variable and that's generally a bad practice of course. But that's usually the way things are done in embedded C because you want to have predictable footprint and know how much memory you're actually consuming at the compile time. But it only gets more complicated once you have multiple interrupts at multiple priorities and so on. And another problem with C is this tooling. There's not really a single build tool or dependency manager. You have projects using Make, someone using CMake, then you have something called West for Zephyr and Python and Newt. And so I end up using a different dependency and build tool depending on what project you're using. So as everyone who's been programming higher level languages like Java, Go or JavaScript, they know that we need some dependency manager in order to manage your dependencies and ensuring that it makes it easier to be productive really. So can we do better? So by the way, this gnome that I keep putting on my slides, this is kind of our, let's say, mascot. So you can sort of imagine he's saying these things, right? Okay, so let's take a look at what the same example looks like in Rust. You have a global counter, you have something reading it and something modifying it. And this might be your first into the first thing Rust syntax, but it should hopefully not be too different from C. But you have a global variable which you note using the static keyword and then you say that it's mutable using the mut keyword. And then you can notice that whenever you're accessing it, you're actually wrapping your code in an unsafe block. And this is because if you didn't do that, then the compiler would not compile your program. It would see that you're accessing a global mutable variable from two different functions. We can't really know if this is not thread safe. Because an integer is not thread safe. So that's why I have to use unsafe. So your program can work, but in this case, it won't work more than the C version. It's just that you're sort of saying I can shoot myself in the foot. The proper way to do this would be to have maybe some synchronization types. And you can see how pretty that is having these types named mutex, wrapping a cell, wrapping an integer. It's not that pretty either, but it's actually safe and the compiler will allow it. But you probably want to do something like Atomics in this case, which has type signatures that makes the compiler allow you to use it without an unsafe block. Same way as the mutex that I just showing. So embedded Rust. Let's talk a bit about how this is done. So in Rust, you have something called the standard library. And with the standard library, you have the heap where you can do dynamic memory allocation. All the usual collections people know about. You have basically a lot of language framework for doing stuff, which you already have in all the other languages, right? But when you're running on a microcontroller, you can't really use this because it would consume too much memory and flash. So you have a mode called the noSTD, which means you are not able to access the standard library. There are replacements for the standard collections where you declare yours. You know how many items you're going to put in your vector before your program runs. So that's fine, but also then you can sort of waste space. But that mode is really ideal for writing firmware, kernels, or bootloader code where you want a minimal footprint. And more for microcontrollers of course, which is the whole point of this workshop really. And then in Rust, you have a single dependency tool called the cargo. And cargo is really great because it allows you to just specify the versions of your dependencies, and it will pull them from this central repository called crates.io. And you can also point it to just your file system or GitHub directly. So it's very flexible in this way and we will see how this works, but you have a manifest where it describes your software, your firmware, and then you declare everything it needs in order to build. And this tool can be used to compile your software for just your PC and cross compile to any embedded target architecture as well. So it's really nice not having to jump between modes. The Rust ecosystem in the community is built around a layered approach where you have, you start with a microcontroller on the bottom, and you can sort of, your software can access the hardware directly if you want to, but for most microcontrollers there is a peripheral access crate. And this provides the register definitions for how to access the different devices on your microcontroller, like the SPI bus, I2C bus, or CPU registers that you need to access. And when you're using the peripheral access crate, that's usually unsafe code because it doesn't guarantee that only one can access the registers at the same, or sorry, access devices in a safe way. Let's say you can have, maybe there's some higher level restrictions that you can't write to one register while doing this other thing. And that's usually enforced at the layer above, which is called a hardware abstraction layer implementation. And this is where you have maybe an SPI type or a sensor or whatever that works across multiple variants of a microcontroller. Like for SDM32 they have like probably several hundreds of variants, so having to sort of modify your code whenever you want to change it to work on one or the other would be really painful. But the nice thing is that it prevents you from doing stupid things with your microcontroller. And then if you want to write drivers for a particular temperature sensor or accelerometer or whatever, then you typically want it to work with all these different SPI buses that exist in the world. So that's why there's a hardware extraction layer trait or API as is typically known in other languages which defines a common behavior of all SPI peripherals. So when you're writing your driver, you're using those APIs as a base and then it will work on an microcontroller. And on the top is your application. So enough talk. Let's get to the workshop or demo itself. The hardware I'll be using is a development kit from SDM32. Let's see if you can see it. This is what it looks like in real life. The reason I chose this one is basically because it has a Wi-Fi chip on board which we'll use. But it also has Bluetooth radio and a lot of sensors like I think it even has a gyroscope and accelerometer. Humidity sensor, temperature sensor and we'll use a temperature sensor and then a few buttons. So you can use it to play around with embedded stuff. We're going to use it with our cloud instance eventually but we'll start off with just a hello world because you have to do that. So we'll look at that basic REST project. You'll notice there is two files here. One is the cargo tumble file and a cargo log file. So the cargo tumble file is the manifest or project. There's these very commonly known things like metadata about your software. The name of your package, its version and the edition which points to a specific edition of REST, the REST language that this package will work with. Then you have a list of dependencies in our case. This is dependencies that allow you to access the microcontroller registers itself. Our T stands for run time which is basically a very thin shim that sets up some of the very common registers that applies to all of the Cortex-M microcontrollers for running your application. So we don't really want to do anything different there usually. And then there's a peripheral access crate for our particular board. And you can see here I have some more information because in REST you can have compile time features for libraries so that when you're building your project you can skip features you don't want and enable features you want which allows, for instance, a library to support things that work with the standard library and a subset of that that only works for embedded, for instance. And then there's a few libraries for doing logging. This is commonly used because it's a very efficient logging implementation that doesn't actually transfer the whole log string across the debug probe. Then there's a panic behavior if your program crashes then it actually prints the backtrace across the debugger. So that's useful. The cargo log file is basically a manifestation of your dependencies and their dependencies so it keeps a full track of the whole list of dependencies that you need to build your application. This is useful if you want to audit your dependencies and track licenses and stuff like that. Before we look into the application there's also another file we want to look at which is a bit special for embedded. Basically it defines a few things like some flags to the compiler. You don't really need to know what these are doing but it basically provides some linker scripts so that it works correctly on the Cortex-M platforms. And then the target build architecture is specified here so that the cargo, the build tool will know which microcontroller to compile your code for. The build tool also has a way to run your application so if you just do cargo run you can run an application on your PC. For embedded you want to actually run it on your microcontroller so that's why you have a tool called probe run mentioned here and specifying actually which microcontroller it's using. So we can actually run our embedded application like any other application. So let's run it. This first environment variable I'm setting here enables logging at the info level. The logging is enabled at compile time because doing it at runtime comes with a cost of extra code. And then dash dash release is just to produce more compact code really. And then we run our application and it prints a lot of world. So that's the first step. Let's go back to slides. I'm just mentioning that if you're following, if you're trying to do these steps when you have the rest, if you have the rest installed using the rest up tool then once you have that, then you don't need to do anything else other than maybe installing this tool to run it if you have this board yourself. So let's do something more exciting. Let's blink an LED by pressing a button. It's like super advanced stuff. On this board we have a blue button that we can press and it will light an LED where I'm having it on the slide. And then we'll see how we can build an application from the ground up doing that and then see these different layers that I talked about earlier. So we'll start at the peripheral access crates. So we'll see how an application looks like for that. Let's look at the manifest first. In this case, it's almost the same dependency. It's like the hello world using a different peripheral access crates. That our team is contributing to. This allows you to select which microcontroller you want to use at compile time which is really nice because there's so many variants of them. There's not a lot of difference there. Our application though is quite different. So first you'll see we have declaration at the top which is the marker that this is an application that's not using the standard library. So the compiler will sort of fail if you're trying to use the standard library. Then you also have some of the debug logic being imported. Then finally you have this peripheral access create imported. So these are just import declarations. Your application starts in your main entry point which you will annotate with this macro as it's called. It basically sets up the microcontroller interrupt vectors and launches into your main application. To blink on LED using the peripheral access create that's pretty hard. First you need to enable the clock on your board which you can do like this. You can see we have to use unsafe code. We have to modify some registers, enable the various GPIO ports. So it's not trivial. Then you need to set up your button so that it can actually intercept these signals when you're pressing it. That's also not that very easy. You have to sort of say which button it is. You have some more unsafe code to modify registers. Don't worry if you don't understand this code it's not really important. It's just to show that this is really a lot of code that's hard to read. Finally we need to set up the LED as well. All of this if you look at the reference manual for your microcontroller that's when you see which register you need to set and which value. Then we have the REST API that helps us ensuring which values we can actually set. So there's some usefulness to the peripheral access criteria in that it prevents us from setting invalid values to the registers. That's good. Then our main loop is basically pick the pin of your button and if it's pressed then light the LED and if not you do not light the LED. It's just 70 lines of code to blink an LED. We can try and run this and see if it works in the live. Now it's running on my chip and I will press the button. You can see there's a green LED there with my finger where it's blinking. That's using the peripheral access crate. What we should take a look at now is how would you do this at the peripheral access crate or at the hardware abstraction layer. That's a lot better. Let's take a look at the manifest first. Now instead of the meta pack we are importing the HALC from Embassy which is this project that we're using in Drogh. This is a runtime but also a HALC for accessing the hardware. We'll see how much simpler this is. This is the entire application. We're importing these types from the HALC that specify what an input pin, an output pin and what these things are. Our main application is basically just saying to the hardware abstraction layer initialize by microcontroller and then you can use these higher level types to say that my LED is an output using this particular port and starts at high. The button is an input at this particular port. These port definitions are part of the user manual of the board so this will vary depending on which board you're using. Then there's the main loop which is just looping, checking if the button is pressed. If it is, then set the light up to LED if not turn it off. As you can see this is a lot simpler and more understandable application even if you don't really know Rust that well. You can sort of get a feel for what it's doing, I think. Let's try and run it. If it actually works, it does. I hit the cable. That's the HAL but there's one problem with it. The application is continuously busy looping and checking for the state of the button and that's not very power efficient. We want to do more low power stuff and the way to do that typically is using interrupts. Let's take a look at an example where you're actually doing that first and then see where we go from there. The dependencies are essentially the same so I won't really over that. Let's look at the application. Now you can see this is a lot more complex. You can initialize the HAL and initialize your buttons as before and the LED but now you have to think about it differently because you need these types to be accessible by your interrupt handler which is declared down here and then the interrupt handler needs to check the state of the button and then enable or disable the LED. The main application needs to... When it's created the LED and button it needs to enable interrupt for the button so that you get a trigger when it's pushed and then it also needs to set these global variables that reference the button and the LED. If you saw this earlier in the slides which is pretty ugly in my opinion where you have to declare all these types and have these complex type signatures for everything. The main loop is basically just sleeping all the time and then the interrupt handler is the one that's actually lighting up the buttons and so on. So that's that. But we need to test it and see if it runs. It runs I think. Still blinking. So now we're using low power. That's nice but it's not really nice for the power. Now it's not really nice code to maintain. So what we need to do is... Sorry, what we can do with this is to use some frameworks of course that deal with this ugliness for us. There is a protocol Arctic which is let's say a task schedule for Rust. There is a project called TOC that is an RTOS real-time operating system and also called hubris. These have some advantages in that they sell a lot of the low level access for you but also one of the great things about Rust in my opinion is that all of these libraries that I can use on the shared repository you can sort of mix and match exactly what you need for your application very easily. You don't have this in the C world so that's why I think you end up with all of these real-time operating systems and projects that sort of solve everything whereas in Rust you can sort of pick exactly what you want and compose your application that way. The thing we'll be using here is something, a project called Embassy which is a project my team and I have been contributing to to bring it up to speed but it was initially made by someone who actually run it on their production firmwares and Embassy is basically this hall that we've seen that abstracts the hardware but it's also a runtime and it's actually an async runtime and async is really a unique language feature of Rust especially in terms of embedded because people who are familiar with futures and promises and async in higher level languages often think they are nice because you avoid this callback hell or these complex event loops and Rust has the same thing but in Rust they are actually let's say zero cost they don't need an allocator to work and Embassy is actually a runtime that allows you to run these asynchronous tasks continuously so just quickly explaining how this works is that you have an executor which runs these tasks and you have a set of tasks in your firmware and then what the executor does is just check with each task is can you make progress? If you can then it will try to do so when it needs to yield execution because it can do anything else then it returns something called pending and then when it does that then the executor just checks with the next task and so on and this is very similar to any like scheduling system but it can do so with this task which are declared asynchronously which means they can be written in this async syntax so what happens when you have interrupts for instance is that your task might say that I'm going to wait for this interrupt now and then when the executor pulls it it says oh I don't have anything else to do I'm just waiting for this interrupt and when the interrupt occurs then the interrupt handler will sort of set some state in the peripheral saying that you should you're done with your operation now and then it tells the executor that and then that again checks with the task can you make progress and then yes the task can make progress so that's very simplified how this works it's a very complex topic as well but I just wanted to I wanted to cover it quickly so let's take a look at how this looks in REST now in addition to this hardware hardware abstraction layer we have a runtime dependency and we'll see how our application is structured again now and now it's a lot simpler again you have still a main function this time it's a different type signature it has an async keyword it has some arguments for instance the peripherals of your microcontroller which you can access this way so you don't need to call this init function manually and it has a spawner which allows you to create spawn new tasks and you set up your you create your peripherals the same way so before and the main loop is almost identical to the first busy looping one except for one minor detail which is that before checking the button state is actually waiting for any event on the button to occur and in this case that means an interrupt and it's using the await keyword for that what this does is saying that this task that is running here is just saying to the executor when it's calling await it's saying I'm waiting for a button interrupt I can't do anything else right now and then the executor will sort of put the whole system to sleep until it receives this event and then wake the tasks again and then it will continue and check if the button has changed state or not so this so this is clearly very readable compared to this interrupt version and let's see if it runs it does and the great thing with this is that this is just as low power as the interrupt version it just has a minor overhead of switching the tasks but you can write you know low power interrupt driven code in a very similar way as you would with blocking code let's go to the next step which is actors I need to lead up a bit so the actors is something used in web frameworks and you know people are familiar with ACA which is a framework for composing actors and by actors are really async tasks that receive some messages in their inbox to check this and then they act on that state and that's what drug device is about so let's look at how this looks for that so with actors you're still using the embassy runtime but now you're creating actors called LEDs and buttons and then you're sort of mounting them into the runtime and what I'm showing is this is that if you look at the previous example the async version then you have to sort of say that this is I'm checking the button for being in a low state and then I'm setting the lead to be high and then if instead the system knew what that meant like if this button does something this lead should do something then that can be modeled as actors where you have a button actor that emits this event to an LED actor and then the LED actor knows what to do so that's what we're doing here we're not defining any behavior in our application we're just wiring things together it's a bit more complex type signatures because actors need to be global because they keep some state and need to be able to run the processing loop forever so you have this it's the same as spawning a task in embassy instead this task is a task plus a message queue basically okay actors we can talk more about that later if we have time so enough with blinking let's try and see if we can do something more complex so in this case our real application is using this board sending some data recording some temperature data using the temperature sensor and then sending that to the cloud so first let's take a look at temperature application look at the manifest we now have a dependency in addition here that's depending on drug device so this is the drug outt project for the firmware and then our main application is basically a bit more more of the same really but instead of just a button on the led it's an I2C bus where you declare which pins it's supposed to use on the board it's actually using DMA which is our really beauty of async rust is that you can do DMA like it was just regular code because when a DMA operation is completed then you sort of program can continue so it's a good fit for that we have the some pins related to the sensor we're using the temperature sensor and then you have these actors in the system so I'm using actors there is one actor for the I2C bus this is using an actor for it allows you to share it between parts of your application because it will ensure that only one one request is being handled by the peripheral at a time so actors are great for sharing state and then there's the sensor itself we're using a temperature driver that's part of drug device and our application which is our own actor which we'll take a look at so the sensor will use the I2C actor to read the data and then there's a button actor which when pressed will invoke the application which will then ask the sensor for the data so I'll skip looking at the actor itself because we don't really have time for it or it can do we'll try and run it so I'm just pressing the button here and it's seems to be quite hot in my room that's it but you'll notice that the program I wrote didn't have much logic application logic so let's go to the next so now we want to report the temperature to the cloud this will be our last example so once I've done with this we can do some Q&A I'll go to the terminal again and we'll have a quick look at the application so it's building on this sensor application that I was just showing in addition now we're adding some configuration of the cloud endpoint which is first you need to connect to the Wi-Fi network and the cloud endpoint we're going to use username and password for accessing and then there's some board support crates that help us use the correct peripherals of our board so yeah basically you instantiate the board you set up Wi-Fi and then you join the Wi-Fi network and you're basically setting up the actors of the system again the only additional actor now is the Wi-Fi actor and the Wi-Fi actor is passed on to the application which will use it to send data so now your application can work with any any actor that implements these functionality of the Wi-Fi and that's it the button is about the same and in this case I'll run a video of the demo because I want to be sure that everything was working so now I'm using the Drogo IoT endpoint this is the console of Drogo IoT where you can view messages entering the system and you will run your application then it's joining the network on the board and then we can press the button to send data and then it appears in the cloud so you can if you want to look more into the details of the application itself it's all available online and I am open for questions about it as well so let's go to slides so final notes, what have we seen what have we learned so if you're, if you speak to your interest in Rest Embedded please join the community there's some matrix.org channels that you can join, they're pretty active with people doing all sorts of interesting projects and there's even a like a weekly meeting that you can join some links to useful resources for learning Embedded Rust there's a book in the Rest Embedded organization which explains a lot of these things more basically and in more detail than I've done today there's a few useful frameworks like the RTIC that I mentioned and the embassy which I'm also working on and the DROG IoT project itself which is like this cloud endpoint as well as the device stuff and that's it, I think we can move on to Q&A I'm sorry I had to move a bit fast over the actor stuff but you can find that in the examples and join us in the chat if you need more info Hey, thanks a lot a little of the presentation there's one question Q&A section okay actually there are three now okay which one, I'll take the oldest one first important embassy STM32 instead of regular STM32 crate does it mean embassy replaces the HALCrate? well the embassy project is fairly new and I assume that when you say STM32 crate you mean this the ones that are part of the STM32 RS organization I don't think it replaces it but there are different approaches and structural layers one of the reasons for writing the embassy HALCrate was that with STM32 you have several hundred variants but there's mostly similar they just have three versions of SPI so instead of writing for hundred access crates for HALS for these you have just one and you have these three different versions implemented instead so there's a lot less code to maintain basically with embassy but you know it's open source and everyone can do use what they feel like I happen to just choose embassy because I think it's a good approach I think you mentioned embassy was used in production do you know for what kind of project embassy is used in some door locks and then I also know that someone's building some electrical vehicle chargers using it that's why I know Rust in general is also used in more projects but I think there's a list of at least publicly known projects in the Rust embedded organization what about error handling Rust has some idiomatic error handling is there anything markedly different in the embedded world no it's the same type of error handling where you have a result type that you can use to get the value or check your error and then you get compile time warnings if you don't to actually check your result and so on so that part of the language is essentially the same that's part of the core language okay I just need some water our actor's programming is the actor programming model generally more convenient performance than async in embedded context the actor programming model that we're using is actually built on top of the async thing so you can send messages to actors using async await in Rust and the actors deal with propagating that message to the appropriate task so it's every actor is an embassy task async task that can do async stuff and then basically it has this channel or inbox where it checks for its messages and it can have handles to other actors so I see it more as a layer on top of the async that simplifies maintaining some applications I think many of the examples we're using macros did you run into conflicts between various crates doing similar things or just in your experience just work so I only done Cortex-M and this Cortex-M entry macro is I haven't seen anyone doing anything else than using that so that's really very much used and tested for everyone so yeah I don't think that's an issue it might be I've seen issues where macros are not correctly written in that they are assuming some dependency being imported with the specific name and so on but those are really edge cases and in general they just work so macros in Rust are a lot better than macros in C that's that's all the questions and we are right under the time so that's amazing if anyone has any more questions and want to discuss they can definitely go to work adventure if I'm not wrong we'll be hanging around for a while yeah perfect thank you very much we really enjoyed the session and have a good day and we have a break now thanks everyone