· 5 min read
Why I Switched to Zephyr (After 5 Years of Reinventing Wheels)
For years, I built the same things over and over - SPI drivers, circular buffers, build systems. Every new project meant starting from scratch. Then I discovered Zephyr, and finally stopped fighting the same battles.
The Reinventing Problem
For years, I built the same things over and over - SPI drivers, circular buffers, build systems. Every new project meant starting from scratch or adapting old code that never quite fit the new requirements. Then I discovered Zephyr, and finally stopped fighting the same battles.
Here’s why Zephyr changed how I approach embedded development.
What I Used to Build (Again and Again)
Let me give you context about my previous projects. I spent years working on:
- Reusable drivers and software modules with custom schedulers
- CMake-based build systems with CI/CD integration
- Unit testing frameworks (more clicking than coding)
- Low-level drivers for SPI/UART/I2C peripherals
- Post-mortem analysis tools
- Custom bootloaders and MCUboot integrations
Sounds exciting, right? The first time you write an SPI driver, it feels like a challenge. The fifth time? It’s just sad duty. It’s like crafting a hammer every time you want to hang a frame on the wall.
What Zephyr Solves
Here’s my personal ranking of what makes Zephyr appealing for embedded developers:
Mature Build System
Remember starting green field projects with simple CMake?
add_executable(app)
target_sources(app PUBLIC main.cpp)
Then as the project grows, you face the same questions:
- How to integrate 3rd party libraries?
- How to manage dependency versions?
- Should you sacrifice DRY principles for simplicity?
- Do you need a dedicated team for build system maintenance?
After multiple projects, I got tired of making these decisions from scratch every time. That’s why I appreciate west + CMake in Zephyr. Some questions are already answered, some problems already solved. You can focus on your actual project instead of infrastructure.
Portability Through Kconfig and Device Tree
I can’t recall any non-Zephyr project where I could freely build the same code for multiple targets. With Zephyr, it’s simple:
# build for nrf
west build --board nrf52dk/nrf52832 app
# build for linux
west build --board native_sim app
The framework supports you in supporting many boards and targets. All thanks to device tree and Kconfig configuration.
Support for 300+ MCU Families
Finally, learn one framework and use it in 99% of projects. No need to adjust to vendor-specific tools and HALs every time you switch microcontrollers.
There’s Already a Wheel - No Need to Reinvent
Imagine a green field project with no internal libraries. You need a circular buffer. Your options:
- Write your own - starts buggy, takes time to mature
- Use open source library - usually not designed for constrained devices
- Generate/copy code - needs API design, integration work
Would these hand-made components be easily integrable with other projects? Would they be robust? When would they mature enough?
In Zephyr, for instance you get a well-thought ring buffer with a comprehensive API:
void ring_buf_reset(struct ring_buf *buf)
uint32_t ring_buf_space_get(const struct ring_buf *buf)
uint32_t ring_buf_item_space_get(const struct ring_buf *buf)
uint32_t ring_buf_capacity_get(const struct ring_buf *buf)
uint32_t ring_buf_put_claim(struct ring_buf *buf, uint8_t **data, uint32_t size)
int ring_buf_put_finish(struct ring_buf *buf, uint32_t size)
uint32_t ring_buf_put(struct ring_buf *buf, const uint8_t *data, uint32_t size)
// and many more...
Development Tools - Shell, Twister, Ztest
These components make the development process easier. During custom board bring-up, you’re never sure if issues come from firmware or hardware. That’s where the shell utility helps.
Need to check if an I2C device responds? Just scan the bus:
uart:~$ i2c scan i2c@209000
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- 39 -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
2 devices found on i2c@209000
All without changing firmware. There are many more such utilities built in.
The Trade-off: Steep Learning Curve
If Zephyr solves so many problems, why doesn’t everyone use it?
The learning curve is much steeper than other embedded frameworks. There’s no other framework with even close to such complexity at the beginning.
Poll Results Confirm the Challenge
I ran a LinkedIn poll asking “What’s your biggest challenge when working with Zephyr RTOS?” with 72 responses:
████████████████████████ Initial setup & learning curve (47.2%)
██████████ Device Tree configuration (20.8%)
████████ Documentation gaps (18.1%)
██████ Custom driver development (13.9%)
The results confirm what I suspected - Zephyr is hard to start! The learning curve is the biggest barrier, followed by device tree configuration that you need early in any project.
Summary
Zephyr isn’t easy to learn, but it solves real problems that I faced in every embedded project. Instead of rebuilding the same infrastructure over and over, I can focus on what makes my product unique.
Yes, the initial investment is high. But after you climb that learning curve, you stop reinventing wheels and start building products faster.
For me, that trade-off was worth it. Your mileage may vary depending on how much wheel-reinventing you’re tired of doing.