RTOS vs Bare Metal – Benefits for MCU Programming

armcoperating systemsreal time

Please note: This question specifically mentions two RTOSes but is more generic and can probably be answered by anybody who has written C code for embedded RTOSes before, and had their software run directly on MCUs.

I am interested in learning more about embedded RTOSes and writing applications for them. I am currently looking at Embox and RIOT because they're open source, modern, active and seem to have excellent documentation. My goal has two phases: Phase 1 is to figure out how to compile and flash these OSes to an MCU (probably AVR or ARM). Phase 2 is to then write a simple C program (basically a headless daemon), that would evolve over time as a "hobby app". I would then flash/deploy this program to the same MCU, thereby successfully deploying an appstack consisting of Embox/RIOT and my app residing on top of it.

Before I go down any roads that ultimately lead to dead ends, I stumbled across this article that does a pretty good job of explaining why real-time apps, written in C/assembler and flashed to MCUs, don't really need RTOSes underneath them.

So now I'm really confused, and am questioning some of my fundamental understanding of computing theory. I guess I'm trying to make the decision of whether or not to even use Embox/RIOT in the first place, either:

  • Stay the course and go with an "app stack" on the MCU of both OS + app; or
  • Heed the article's warning and just go with an MCU running my app "bare metal"

Obviously, the former is more work, and so there had better be a good reason/payoff for going that route. So I ask: what are the real benefits these (and similar) embedded RTOSes offer to MCU/C app developers? What specific features could my C app benefit from (perhaps by not reinventing the wheel?) by using an RTOS? What is lost by ditching the RTOS and going bare metal?

I'm asking for concrete examples here, not the media hype you get when you go to the wikipedia entry for RTOSes 😉

Best Answer

Microcontroller programs consist of a number of tasks. Let's say you wanted to make a computer-controlled telescope mount. The tasks would be:

  • Retrieve a new byte of input from the USB serial buffer.
  • Check if we've received a complete command.
  • If so, execute that command.
  • Read the sensors for the current telescope position.
  • Set the proper output to control the next step of the motors.

This is a fairly typical set of tasks for something you would use a microcontroller for, and are pretty easy to manage with an infinite loop, like:

while(TRUE) {
  uint8_t input = readUsbBuffer();
  parseCommand(input);
  readSensors();
  setMotors();
}

If you keep adding and adding features, you eventually start coming across common problems you want to create abstractions for, like:

  • Your buffer is overflowing, so you want to make sure that task can interrupt other tasks before it overflows.
  • You want to save battery by sleeping when nothing is needed.
  • You want to make sure all tasks get a fair shot. If readSensors takes too long, you want to be able to interrupt it and come back to it later.
  • You want to be able to just reset one task without affecting others.
  • You want a memory leak or other bug in one task to not take out the other tasks.
  • You want to be able to give different tasks different priorities.
  • You want to be able to sandbox an untrusted task.
  • You want to be able to execute tasks at different rates. Perhaps your sensors can only be read 10 times per second, but you want to execute a motor step 100 times per second.

One or two of these items can be handled manually relatively easily. If you have enough of these kinds of problems frequently enough that you start generalizing them into libraries, you've basically reinvented an RTOS. If your program's task management is complex enough, even if you don't use an off-the-shelf RTOS, you will eventually end up reinventing one poorly.

However, in my experience, the line where you want an RTOS is very close to the line where you want a microprocessor instead of a microcontroller. If you anticipate your firmware getting that complex, it's usually better to go the microprocessor route from the beginning.