Rustifying a real world embedded example?

As is clear from this other thread (Rust applicability to small embedded codebase - getting discouraged - #28 by jandyman), I am investigating Rust as a C/C++ replacement for a small embedded application. I'm going to provide a real world design problem, which is a simplified version of some actual application code and is a pretty standard embedded design pattern. As a starting point, one has to accept that the memory model is heapless, and therefore there is a whole lot of static mutable data. Since that is a sticking point, a lot of what I'm trying learn here is how to convert the unsafe references into safe references.

Also, I'd like to learn the Rust like approaches to "thread" safety in this application space, though there are no true threads with their own stack etc. In this application there are two "threads". One background task is initiated by a timer interrupt which wakes up the MCU, and most application code runs there. But another interrupt can interrupt this task, and is the higher priority "thread". It's the DAC empty interrupt, which runs of course at a higher rate. The example is code which sends a recurring waveform to a DAC, and that waveform can be updated by outside actor. So here goes:

Actors / Modules:

An output generator, which outputs samples to the DAC from a waveform table repeatedly, triggered by the DAC empty interrupt.
A waveform generator, which generates waveform data based on parameters.
A conductor, which listens for command from an outside actor, and initiates a change in the waveform.


The output module owns two arrays of memory to hold waveform data, in a ping pong arrangement. There is an internal flag to indicate the need to switch buffers at the end of a waveform, and of course an index indicating the last sample sent to the ADC. Finally, it has a reference to the which of the two buffers is currently "playing"

The waveform generator owns private static mutable data it uses for waveform generation.

Switching Waveforms

Upon request from an outside agent, the conductor performs the following operations to change waveforms, all on the background thread (that's important).

  1. Calls the output generator to get a reference the unused waveform buffer.
  2. Passes that reference to the waveform generator, so that the new waveform can be written into it, along with new waveform parameters.
  3. Calls the output generator and tells it to switch buffers at the end of the playback of the current buffer.

Now, there are some obvious thread hazards here. They are dealt with by rules:

  1. Only the Conductor is allowed to interact with the waveform generator
  2. The Conductor cannot switch waveforms if the output module if a waveform switch is already pending
  3. Inside the output generator, the update to the internal flag signalling a buffer switch is protected by a critical section, so that the output generator doesn't get to the end of the table at that exact instant.

So, I'm interested in how one would do this in idiomatic Rust. As mentioned at the top, this is a heapless app, so the waveform buffers are static mutable data. What is the right way to pass the references between the actors, without falling back to unsafe everywhere? I'm guessing some kind of wrapper, but I'm a Rust newbie.

Also, without using formal threads or mutexes (which have their own issues), are there any Rust like alternatives to the management of thread hazards, other than the rules based approach above? I recognize that the existing approach is very bare metal C. We do want to keep it really simple, but I've got an open mind and a more formal approach is a possibility.

You pass references as references, usually. In some cases, if there are only a single buffer you can do some tricks to avoid moving the same address around, but often, if reference points to the static buffer, optimizer can optimize it away without any tricks.

You don't need OS-level threads to have concurrency but you need some primitives. These would, typically, be implemented with unsafe.

The simplest way is to just have “main” function which just takes your static buffer and then passes around its parts to the functions which do actual work. Something like this.

As you can see compiler can fully remove all these intermediate references after lifetime checking.

But note how compiler was able to merge two stores to buf2: the one that happened before change of flag and the other after.

That's why would you need some primitives: not just to ensure atomicity (when it's needed) but also to stop compiler from doing certain undesirable optimizations.


This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.