EmbeddedEra EmbeddedEra

← Notes from the bench.

Bare-metal or RTOS on a small MCU

Sep 23, 2026firmwaresecurityDrafted with AI assistance, reviewed and edited by our engineer.

The firmware for your product has grown past the point where one loop and a few interrupts feel comfortable. Someone on the team wants FreeRTOS or Zephyr. Someone else says an RTOS is bloat on a part with 32 KB of RAM and that a state machine will do. Both are right about something, and the decision gets made by whoever argues loudest rather than by looking at what the product actually has to do.

This note is the version of that argument we would have with ourselves before starting a new STM32, nRF52 or ESP32 design.

What a superloop is good at, and where it breaks

A bare-metal design is a while(1) loop that polls flags, plus interrupt handlers that set them. There is no scheduler. The only thing that can pre-empt the main loop is an interrupt, and the only shared state is whatever the interrupts touch. That is a small system to reason about. You can read the whole thing in an afternoon, you can see every path from an event to a response, and you can measure the worst-case loop time with a scope on a GPIO pin.

It breaks in a predictable way. The loop is cooperative, so one long operation delays everything behind it. A flash erase, a blocking I2C transaction with a stuck slave, or a long CRC over a firmware image all stall the loop, and now the UI is unresponsive or a sensor sample is missed. The usual fix is to break the long job into a state machine that does a slice per iteration. That works, and a well-built product can run for its whole life this way. But every long job you slice adds one more state machine, and the interactions between them live in your head rather than in the code.

A rule that has held up for us: if you can list every timing requirement of the product and each one is met by the worst-case loop time with margin, stay bare-metal. If you find yourself writing a scheduler, even a tiny one with a table of function pointers and periods, you have started writing an RTOS without the parts of an RTOS that make it debuggable.

What an RTOS actually buys, and what it actually costs

The thing an RTOS gives you is not “multitasking”. It is pre-emption with priorities. A high-priority task that services a radio or a motor control loop runs when its event fires, regardless of what the low-priority logging task was in the middle of. You stop slicing long jobs into state machines. Each task is written as straight-line code that blocks on a queue or a semaphore, and the kernel handles the interleaving.

The costs are concrete.

Flash. The FreeRTOS kernel is small. The project’s own FAQ puts the kernel at roughly 5 to 10 KB of ROM with a typical configuration, and notes that surprising growth in a tiny test program usually comes from C library string and floating-point code, not the kernel (FreeRTOS memory usage FAQ). Zephyr is larger because it carries a device model and a driver layer, and its footprint depends heavily on what you enable through Kconfig.

RAM, and this is the real one. Every task has its own stack. On a bare-metal design there is one stack and the linker tells you how big it is. With an RTOS you now have one stack per task plus the kernel’s own data, and you size each stack by guessing, then measuring. Undersize one and you get a corruption that shows up as a crash in unrelated code. Oversize all of them and a 32 KB part is full before the application is written. Any printf-style formatting, TLS, or a filesystem call inside a task adds hundreds of bytes to that task’s stack in ways that are hard to see from the source.

Debugging. A bare-metal crash has one stack to unwind. An RTOS crash has several, and the one that faulted may not be the one that caused the fault. The kernels have tooling for this. FreeRTOS offers two stack-overflow check methods and a per-task high-water-mark query, and its own documentation is honest that neither method is guaranteed to catch every overflow (FreeRTOS stack overflow checking). Zephyr’s user mode goes further on parts with a memory protection unit: thread stacks are laid out so that exceeding them raises a hardware fault, and user threads are treated as untrusted with permission tracking on kernel objects and drivers (Zephyr user mode overview). That is a real safety and security feature, but it costs configuration effort and it only applies to threads you have deliberately placed in user mode.

Shared state. The moment you have two tasks, every variable both of them touch needs a mutex, a queue, or an atomic. Bare-metal code has the same problem between the loop and its interrupt handlers, but the surface is smaller and the tools are simpler.

The decision is sometimes already made for you

Before you debate at all, check what the silicon vendor’s SDK assumes.

Espressif’s ESP-IDF is built on a modified FreeRTOS with symmetric multiprocessing support. On the ESP32 you are writing FreeRTOS tasks whether you call them that or not, and on single-core targets the same kernel runs with one core (ESP-IDF FreeRTOS SMP guide). There is no meaningful bare-metal option for a Wi-Fi or Bluetooth product on that family, because the radio stack itself runs as tasks.

Nordic’s nRF Connect SDK integrates Zephyr and adds its own libraries, drivers and the MCUboot bootloader on top (nRF Connect SDK and Zephyr). Nordic’s older bare-metal SDK still exists for the nRF52 line, but new BLE work on nRF54 and current SoftDevice controllers is steered toward Zephyr. Choosing bare-metal there means choosing to be off the vendor’s main path for updates and examples.

STMicroelectronics is the opposite case. STM32 parts run fine with nothing but the HAL or LL drivers, and the same tooling can generate a FreeRTOS project through the CMSIS-RTOS layer. Microchip’s SAM and PIC families and TI’s MSP430 are similar: bare-metal is a first-class, supported path.

So the honest first question is not “RTOS or not” but “which vendor ecosystem, and what does it assume”. A BLE product on Nordic is a Zephyr product. A Wi-Fi product on Espressif is a FreeRTOS product. A sensor node on an STM32L0 or an MSP430 is a genuine choice.

The checks a careful engineer runs before committing

When the choice is real, we work through this list rather than arguing from taste.

QuestionPoints toward bare-metalPoints toward an RTOS
How many independent timing requirements?One or two, all fastSeveral, at different rates
Any operation that must block for a long time?No, or it can be slicedYes, and slicing it is ugly
Who maintains this in three years?The same one engineerSomeone new, needs structure
Vendor SDK assumes a kernel?NoYes, use theirs
Need isolation between trusted and untrusted code?NoYes, use an MPU-aware kernel

Then, whichever way it goes, the same verification applies. Measure worst-case timing on the bench with a GPIO toggle and a scope, not by inspection — the same bench discipline that finds a sleep-current problem an RTOS’s idle task can otherwise hide. If it is an RTOS, turn on the kernel’s stack checking during development, read the high-water marks after a long soak test, and only then set final stack sizes with margin. If it is bare-metal, instrument the loop time the same way and log the maximum. Either way, the number you want is the one measured on the real board with the real radio and the real flash, because those are the parts that stall unexpectedly.

One more check that people skip: think about the firmware update path now. A bootloader that has to validate an image and swap slots is a long blocking operation. On an RTOS it is a task. On a bare-metal design it usually runs before the main loop starts, which is fine, as long as you have decided that on purpose.

We take on bare-metal and RTOS work across STM32, Nordic and Espressif parts, and the useful thing to send us is the list of things the product must do at the same time, with the timing each one needs.

Deciding between bare-metal and an RTOS for your next board? Send us the requirements and we will write the firmware either way.

ENGINEERING NOTES

One note a week on PCB design, firmware and getting hardware built. No sales email.

Double opt-in. Unsubscribe in one click.