Picking an RTOS is less about “which kernel is faster” and more about what you can ship and maintain. FreeRTOS vs Zephyr is a classic choice: FreeRTOS is lightweight and widely embedded into vendor SDKs, while Zephyr is a full OS with a modern build system, a unified driver model, and a strong upstream ecosystem. This guide gives you a practical decision process—tooling, drivers, networking, memory footprint, and the traps that make RTOS projects painful.
Quickstart
If you need to decide this week, don’t start by reading feature lists. Start by running two prototypes on your target hardware: (1) one peripheral you care about (UART/I2C/SPI/ADC) and (2) one connectivity path (BLE/Wi-Fi/Ethernet). That small test surfaces 80% of the real differences: drivers, build friction, debugging, and how quickly you can iterate.
Fast decision rules (practical, not philosophical)
- Choose FreeRTOS if your vendor SDK already uses it, you want a small kernel + your own architecture, or your device is extremely constrained.
- Choose Zephyr if you want a unified OS stack (drivers + networking + configuration) across multiple boards and you value upstream reuse over custom glue.
- If you need rapid hardware bring-up on many boards, Zephyr’s board + devicetree model usually scales better.
- If you need tight control over what’s included (and you’re okay assembling pieces), FreeRTOS stays simple and predictable.
What to measure in your prototype (30–90 minutes)
- Time to first build + flash (fresh machine, minimal docs)
- Driver support for your exact peripheral + board variant
- Debug loop: logs, RTT/UART, stack overflow checks, asserts
- Power features: sleep states, tickless idle, wake sources
- Networking: TLS fit, memory usage, stability under load
A quick comparison map (what’s “included”)
| Area | FreeRTOS (typical reality) | Zephyr (typical reality) |
|---|---|---|
| Core | Kernel primitives (tasks, queues, timers) you integrate into your project | RTOS kernel plus OS subsystems (threads, kobjects, services) |
| Build / config | Often via vendor IDE + FreeRTOSConfig.h (or CMake in custom setups) | West + CMake + Kconfig + devicetree (consistent across boards) |
| Drivers | Commonly vendor HAL / SDK drivers (varies by chip vendor) | Unified driver model with devicetree binding & upstream drivers |
| Networking | Depends on add-ons / vendor stack (common in IoT SDKs) | Integrated networking stack options + consistent APIs |
| Best at | Small, focused firmware with maximum flexibility | Products that benefit from upstream reuse and multi-board scaling |
Ask: “Who will update this in 18 months?” RTOS choice becomes obvious when you think about patches, new hardware revisions, and what happens when the one engineer who understands the build system goes on vacation.
Overview
This post is a practical guide to choosing between FreeRTOS and Zephyr for embedded and IoT devices. Both can power reliable products. The difference is how much the OS provides out of the box versus what you assemble yourself. If you’re building a sensor node, a smart appliance, an industrial controller, or anything that needs networking and long-term support, the choice affects your team’s speed and your risk profile more than it affects raw scheduler performance.
What you’ll learn
- How FreeRTOS and Zephyr differ in architecture and ecosystem
- Which one fits better for vendor SDK workflows vs upstream portability
- How to evaluate drivers, networking, power, and debug in a prototype
- Common mistakes that cause “mystery bugs” (and how to prevent them)
Who this is for
- Solo builders shipping firmware on ESP32 / STM32 / nRF / RP2040-class boards
- Teams moving from Arduino-style loops to a real RTOS architecture
- Developers inheriting a codebase and trying to make it maintainable
- Anyone who needs a clear decision process (not internet debates)
FreeRTOS is often a kernel you embed. Zephyr is often a platform you build on. Many real products use both patterns successfully—what matters is matching the pattern to your constraints.
Core concepts
Before comparing features, align on the concepts that actually drive RTOS success: scheduling behavior, interrupt design, configuration model, and how hardware support is expressed and maintained.
RTOS basics: threads, priorities, and determinism
Determinism is a system property
A “real-time” system is real-time because the worst-case path is bounded: interrupt latency, scheduling latency, and the maximum time you hold critical resources. Your OS can help, but your design can still break it.
- Keep ISRs short; defer work to threads
- Bound queue sizes; avoid unbounded work in high-priority threads
- Watch priority inversion; use proper mutexes/inheritance patterns
Tick, tickless, and power
Many devices spend most time asleep. Tickless idle and clean wake sources matter more than “fast context switch” marketing. Your best win is usually correct sleep configuration + fewer wakeups.
- Measure wake frequency and timer sources
- Validate deep-sleep entry/exit paths
- Confirm clocks/peripherals reinitialize correctly after sleep
Two different philosophies: “bring your own stack” vs “unified OS”
In practice, FreeRTOS is frequently used in a bring-your-own architecture: you choose the HAL/SDK, the networking stack, the TLS library, the file system (or none), and you glue them together. Zephyr leans toward a unified architecture: there’s a consistent configuration system, a common driver model, and upstream conventions for boards and peripherals.
| Concept | Why it matters | How it shows up |
|---|---|---|
| Configuration | Reproducible builds across machines and boards | FreeRTOSConfig.h vs Kconfig + prj.conf |
| Hardware description | Portability and clarity when hardware variants multiply | Board files / vendor SDK vs devicetree + overlays |
| Drivers & APIs | How quickly you can integrate sensors/IO and replace parts later | Vendor HAL APIs vs Zephyr driver APIs |
| Upgrades | Security fixes and long-term maintenance | Pinning versions vs upstream updates + merge strategy |
Tooling: what “developer experience” really means on embedded
FreeRTOS: common workflow patterns
- Often lives inside a chip vendor’s SDK (startup, HAL, drivers included)
- Configuration commonly centralized in FreeRTOSConfig.h
- Build may be IDE-first (but can be made CI-friendly)
- Debug success depends on consistent assert/stack overflow settings
Zephyr: common workflow patterns
- West for workspace management and builds
- Kconfig for feature selection (what gets compiled)
- Devicetree for hardware description (what exists on your board)
- Consistent board targets and “sample app” structure across vendors
Many painful projects combine a vendor SDK, custom drivers, a networking stack, and an RTOS without a clear “source of truth” for configuration. Pick a primary ecosystem (vendor-first or upstream-first) and define strict boundaries for everything else.
Step-by-step
Here’s a decision process you can use for real projects. It’s written like a build checklist: define constraints, run two prototypes, and make the choice based on what will be easiest to ship, test, and update.
Step 1 — Write your constraints in plain language
If your constraints aren’t written down, you’ll unconsciously optimize for “what’s familiar” instead of what the device needs. Use this format: “We must fit in X RAM / Y flash, wake within Z ms, and support A/B/C interfaces.”
- Resources: RAM/flash budget, CPU speed, available debug interfaces
- Power: sleep depth, wake sources, duty cycle, timing requirements
- Connectivity: BLE/Wi-Fi/Ethernet, TLS needs, OTA requirements
- Lifecycle: security patch cadence, certification needs, how long the product lives
Step 2 — Decide what you want “the OS” to own
This is the real FreeRTOS vs Zephyr question. Do you want a kernel and you own the stack, or do you want an OS platform where many choices are standardized?
When “kernel + your stack” is the right fit
- You already have a stable vendor SDK path you trust
- You need to keep the firmware minimal and predictable
- Your product is single-board, single-vendor, and unlikely to expand
- You want full control over which libraries enter the binary
When a unified OS platform helps
- You expect multiple boards, revisions, or vendors over time
- You want consistent configuration and repeatable builds in CI
- You value upstream drivers and community maintenance
- You want OS-level patterns for devices, power, and networking
Step 3 — Prototype Zephyr: build, flash, and log on your board
A Zephyr prototype should answer: “Can I build and flash reliably, and do I understand how to express my hardware and configuration?” If this feels smooth, Zephyr tends to scale well as your device grows.
# Zephyr: minimal workspace + build + flash (typical flow)
# Assumes you have the Zephyr SDK/toolchain set up for your host OS.
west init -m https://github.com/zephyrproject-rtos/zephyr.git zephyr-workspace
cd zephyr-workspace
west update
west zephyr-export
# Build a sample for a specific board (replace with your board target)
west build -b nrf52dk_nrf52832 zephyr/samples/basic/blinky
# Flash to device (runner depends on board; often "nrfjprog", "openocd", "pyocd", etc.)
west flash
# Optional: open a serial monitor to confirm logs/blinky behavior
# (Your port name varies: /dev/ttyACM0, COM3, etc.)
# screen /dev/ttyACM0 115200
You can reproduce the build on a second machine, select features via config, and adjust a peripheral through a devicetree overlay without rewriting half the project. That’s the “platform” benefit.
Step 4 — Prototype FreeRTOS: validate your scheduling and communication patterns
A FreeRTOS prototype should answer: “Can I implement the concurrency patterns I need (queues, event groups, timers), and do I have robust debug visibility?” Most FreeRTOS issues are not “kernel bugs”—they’re stack sizing, priorities, or ISR misuse.
/* FreeRTOS: minimal pattern with two tasks + a queue.
* Use this to validate your scheduling, priorities, and ISR-to-task design.
* Notes:
* - Keep ISRs short: send to a queue (or task notification), do work in a task.
* - Turn on configASSERT and stack overflow checks in FreeRTOSConfig.h.
*/
#include <stdint.h>
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
static QueueHandle_t q;
static void SensorTask(void *arg) {
(void)arg;
uint16_t sample = 0;
for (;;) {
/* Replace with real sensor read (I2C/SPI/ADC). */
sample = (uint16_t)((sample + 17u) % 1024u);
/* Send sample to consumer; block briefly if queue is full. */
(void)xQueueSend(q, &sample, pdMS_TO_TICKS(10));
vTaskDelay(pdMS_TO_TICKS(20));
}
}
static void AppTask(void *arg) {
(void)arg;
uint16_t sample = 0;
for (;;) {
if (xQueueReceive(q, &sample, portMAX_DELAY) == pdPASS) {
/* Replace with application logic (filtering, control, reporting). */
printf("sample=%u\r\n", (unsigned)sample);
}
}
}
int main(void) {
q = xQueueCreate(16, sizeof(uint16_t));
configASSERT(q != NULL);
xTaskCreate(SensorTask, "sensor", 384, NULL, tskIDLE_PRIORITY + 2, NULL);
xTaskCreate(AppTask, "app", 512, NULL, tskIDLE_PRIORITY + 1, NULL);
vTaskStartScheduler();
/* If you get here, heap/stack/config is wrong. */
for (;;) {}
}
What to check in your FreeRTOS prototype
- Enable configASSERT and stack overflow detection (catch bugs early)
- Confirm task stack sizes via high-water marks (don’t guess)
- Validate your interrupt priority configuration (MCU-specific gotcha)
- Use queues/notifications to move work out of ISRs
Step 5 — Evaluate drivers and hardware description (the “board scale” test)
The fastest way to understand ecosystem differences is to change one thing about hardware and see what breaks: move a UART pin, add an I2C sensor, change an SPI frequency, or disable a peripheral for power. If you expect multiple board revisions, this matters a lot.
Zephyr: devicetree + overlays
Zephyr often expresses hardware as devicetree nodes, then binds drivers to those nodes. You can override board defaults with an overlay file.
FreeRTOS: typically vendor HAL configuration
With FreeRTOS, hardware configuration typically lives in vendor SDK code, pinmux tools, and driver init routines. That can be great when the vendor tooling is strong—but portability depends on your own abstraction.
/* Zephyr devicetree overlay example (app.overlay)
* Goal: enable I2C0 at 400 kHz and define a sensor on address 0x76.
* (Exact node names vary by board/SoC; this shows the pattern.)
*/
/ {
aliases {
i2c-sensor = &bmp280;
};
};
&i2c0 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
bmp280: bmp280@76 {
compatible = "bosch,bmp280";
reg = <0x76>;
label = "BMP280";
};
};
Hardware revisions are inevitable: pin changes, new sensors, alternate suppliers. A clean hardware description model reduces rewrite risk. If your product roadmap includes variants, treat this as a first-class requirement.
Step 6 — Make the choice using a scored checklist
After both prototypes, pick the RTOS that wins on the constraints you wrote in Step 1. This table is designed to be scored quickly. Mark each row with “FreeRTOS wins / Zephyr wins / tie” and write a one-line reason. That one-line reason becomes your decision record.
| Decision axis | FreeRTOS tends to win when… | Zephyr tends to win when… |
|---|---|---|
| Footprint control | You need the smallest possible kernel and only the features you pick | You still need control, but accept a platform model to gain upstream reuse |
| Vendor SDK integration | Your vendor’s official path is FreeRTOS-based and well-supported | Your board is supported upstream and you want consistent workflows across vendors |
| Multi-board / variants | You’ll maintain your own HAL abstraction layer | You expect many variants and want OS-level hardware description conventions |
| Tooling & CI | You already have stable tooling/IDE and reproducible builds | You want a standardized build/config stack that works similarly everywhere |
| Networking stack needs | You rely on a vendor-provided or curated networking path | You want integrated options and consistent APIs/configuration |
| Team familiarity | Your team is already effective with FreeRTOS patterns | Your team benefits from an upstream-first OS workflow and shared conventions |
Step 7 — Lock your architecture boundaries
Once you choose, set boundaries early. This prevents the “Franken-firmware” outcome where every layer has two competing sources of truth. Keep the rules short and enforce them in code review.
If you chose FreeRTOS
- Define a minimal platform layer: clocks, GPIO, timers, comms, logging
- Keep RTOS primitives behind clear modules (no queues everywhere)
- Standardize ISR-to-task pattern (notifications/queues) across the codebase
- Make memory/stack usage visible in CI (fail on regressions)
If you chose Zephyr
- Use Kconfig and devicetree as your configuration source of truth
- Prefer upstream drivers/APIs; avoid vendor forks unless necessary
- Organize app code as modules; keep board-specific logic in overlays
- Pin versions and update deliberately (treat updates like feature work)
Common mistakes
Most “RTOS bugs” are architecture bugs: priorities, stacks, interrupts, and configuration drift. Here are the common failure modes—and the fixes that save days.
Mistake 1 — Treating ISR code like normal code
Long ISRs, blocking calls, or heavy logging inside interrupts cause latency spikes and unstable behavior.
- Fix: keep ISRs minimal; signal a thread via queue/notification and return fast.
- Fix: measure worst-case interrupt latency early (before features pile up).
Mistake 2 — Guessing task stack sizes
Stack overflows look like random corruption, not a clean crash—especially on microcontrollers without memory protection.
- Fix: enable stack overflow checks and asserts.
- Fix: track stack “high water mark” and set sizes from measurements.
Mistake 3 — Priority inversion and “always highest priority” thinking
Assigning the highest priority to everything makes the system brittle and can starve critical background work.
- Fix: define priority bands (ISR-deferred, control loop, comms, housekeeping).
- Fix: use proper mutex patterns; keep critical sections small.
Mistake 4 — Configuration drift across environments
Builds that “work on one laptop” often hide mismatched flags, missing features, or implicit vendor IDE settings.
- Fix: make builds reproducible in CI; pin toolchains and dependencies.
- Fix: keep configuration centralized (FreeRTOSConfig.h or Zephyr Kconfig/prj.conf) and reviewed.
Mistake 5 — Over-customizing too early
Forking OS code or replacing core subsystems early increases upgrade pain and makes security fixes harder.
- Fix: start with upstream defaults; only customize after you have a measured need.
- Fix: document every non-standard change with a “why” and a rollback plan.
Mistake 6 — Not testing power until the end
Power bugs appear when you combine peripherals, sleep states, and interrupts—not in hello-world.
- Fix: create a power test mode early (sleep/wake loop + logging).
- Fix: verify clocks and peripherals recover correctly after deep sleep.
RTOS systems often look stable during demos and then fail in long runs. Add a soak test early: run for hours, vary inputs, stress the network, and watch memory and queue usage.
FAQ
Is Zephyr “heavier” than FreeRTOS?
Often, yes—because Zephyr is typically used as a fuller OS platform. But “heavier” is not automatically bad. What matters is whether the included subsystems reduce your custom glue and maintenance. If your product needs drivers, networking, logging, and configuration consistency, Zephyr’s footprint may be worth it. If you need a minimal binary and you’ll build the stack yourself, FreeRTOS usually stays smaller and simpler.
Can I use vendor SDK drivers with either RTOS?
Typically yes. Many real products pair FreeRTOS with vendor SDK drivers, and Zephyr can also integrate vendor components in some cases. The important part is choosing one “primary” model for configuration and initialization. Mixing two driver worlds without boundaries is what creates hard-to-debug conflicts (clock init, interrupt priorities, power states).
Which one is better for networking and IoT features?
It depends on your vendor and constraints. Zephyr tends to offer a more integrated OS approach (consistent configuration and APIs), while FreeRTOS projects often rely on vendor or curated stacks. For IoT devices, test the exact combination you need: TLS handshake memory usage, reconnection behavior, and long-run stability under poor connectivity.
How do I avoid RTOS “random crashes” in production?
Treat observability as a feature: enable asserts, add stack usage tracking, keep ISRs short, and build a soak test. Most “random” crashes are stack overflow, race conditions, or priority problems. Catch them early with strict debug settings and by measuring stack/heap/queue usage over time.
Do I need tickless idle?
If you run on batteries or care about power, tickless idle (or equivalent low-power scheduling) is usually a must. But it only helps if your timers and wake sources are configured correctly and your peripherals truly allow deep sleep. Measure current draw and wake frequency—don’t assume.
What’s a safe way to decide if both seem fine?
Choose the one that best matches your long-term maintenance model: vendor-first (often FreeRTOS in SDKs) versus upstream-first (often Zephyr). If your team expects multiple boards and wants a consistent build/config story, Zephyr is frequently easier to scale. If your product is tightly tied to one vendor SDK and needs a minimal kernel, FreeRTOS is frequently the pragmatic choice.
Cheatsheet
Use this as a quick “RTOS selection and setup” checklist. If you can confidently check most of these boxes, you’re in a good place.
Decision checklist (pick based on reality)
- I wrote resource + power + connectivity constraints (numbers, not vibes)
- I built and flashed a minimal app on the real board (twice, on a fresh machine)
- I validated one core peripheral and one connectivity path
- I tested logging + asserts + basic debugging workflow
- I identified where drivers will come from (vendor SDK vs upstream)
- I can explain how we’ll update dependencies for 18–24 months
Architecture checklist (avoid future pain)
- ISRs are minimal; heavy work is in threads
- Priorities are planned (bands), not “everything high”
- Task stacks are measured and monitored
- Queues/notifications have bounded sizes and backpressure behavior
- Power mode is tested early with a dedicated soak test
- Config is centralized and reproducible in CI
Quick “choose this if…” summary
| If you say… | Lean toward… | Because… |
|---|---|---|
| “We’re deep in a vendor SDK and need minimal change.” | FreeRTOS | Lower integration risk and simpler core when the SDK path is stable |
| “We’ll have multiple boards/variants and want consistency.” | Zephyr | Unified build/config/hardware description reduces long-term friction |
| “We need tight footprint control and a very focused firmware.” | FreeRTOS | Kernel-first approach keeps you in control of what ships |
| “We want upstream reuse for drivers and OS services.” | Zephyr | Platform model can reduce custom glue and make upgrades more systematic |
Wrap-up
FreeRTOS vs Zephyr is ultimately about choosing a development model. If you want a small, well-known kernel that fits neatly into vendor SDKs and you’re comfortable assembling your own stack, FreeRTOS is a strong and pragmatic choice. If you want a consistent, upstream-first platform with unified configuration, board support, and a driver model that scales across variants, Zephyr often pays off as the project grows.
The best next step is simple: take your target board and run the two prototypes from Quickstart—one peripheral, one connectivity path. Then make the choice based on what you can build, debug, and update confidently.
- Write constraints as numbers (RAM/flash/power/wake)
- Pick a “must work” peripheral and connectivity feature
- Run a clean build/flash on your actual board
- Record the decision with one sentence: “We chose X because Y.”
Quiz
Quick self-check (demo). This quiz is auto-generated for hardware / iot / embedded.