Mastering the Zephyr Perimeter: DeviceTree, Driver Model, and Production on Custom Hardware
Development teams typically begin Zephyr evaluation on vendor-supplied boards. These platforms function immediately because the silicon vendor has already mapped the MCU pins and validated the drivers for that specific hardware layout. However, for companies building commercial products, such as wearables, industrial tags, or medical devices, the target is always a custom printed circuit board (PCB).
On a custom PCB, Zephyr does more than simply manage the processor; it must manage how that processor interacts with a unique electrical neighborhood. This interface, where the Zephyr kernel, the MCU, and the custom board layout meet, is the Perimeter.
The perimeter represents one of the highest technical risks in a Zephyr project. Because a custom board features unique power rails, specific low-power requirements, and sensors connected to non-standard buses, any misconfiguration at this level leads to system hangs, excessive power consumption, and boot failures.
Mastering the perimeter requires a formal hardware-software contract. Zephyr enforces this contract through the Driver Model and DeviceTree. These integrated tools ensure that the kernel knows exactly how to interact with the custom PCB without requiring modifications to the high-level application logic.
The Zephyr Driver Model
The Driver Model is the primary mechanism for managing perimeter risk. It keeps application code independent from the low-level electrical details of the board. In this architecture, the application calls a generic subsystem function, while the driver manages the MCU registers and the DeviceTree defines the board-level wiring.
This structure allows an engineering team to change hardware, such as moving a peripheral to different pins on the PCB, without rewriting the application. Every device is represented as a structure:
const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(i2c0)); |
The application uses this device instance to find the hardware location and configuration for a specific board. A native Zephyr driver consists of five parts:
- The API: The public interface the application uses to call functions.
- The Implementation: The C code that manages the MCU hardware.
- The Bindings: YAML files that define hardware requirements for the build system.
- Kconfig: Configuration flags used to enable or tune the driver.
- Initialization: The logic that registers the driver with the kernel during boot.
When building a driver for a proprietary sensor, a YAML binding is required to act as a rulebook. If the DeviceTree definition does not match the binding, the build fails. This is a safety mechanism that catches configuration errors before they reach the physical hardware.
The Complexity of Initialization Levels
One of the hardest parts of building for custom hardware is managing how the system starts up. Hardware components often depend on each other. For example, a temperature sensor might sit on an I2C bus. If the kernel tries to talk to the sensor before the I2C bus is ready, the system will fail to communicate.
Zephyr manages this by using initialization levels. These levels tell the kernel the exact order in which drivers should start:
PRE_KERNEL_1: For the most basic hardware, like the clock and power management.
PRE_KERNEL_2: For drivers that need some kernel features but don’t need multi-threading.
POST_KERNEL: Where most drivers start. The kernel is active, and threads can run.
APPLICATION: For the highest level of code that runs after everything else is stable.
Consider a real-world dependency chain: an external SPI Flash chip used for a filesystem. To work, the GPIO power-pin for the chip must be toggled, then the SPI controller must start, then the Flash driver, and finally the filesystem middleware. If the Flash driver is set to PRE_KERNEL_2 but the GPIO power-pin isn’t toggled until POST_KERNEL, the mount will fail.
On custom hardware, you must be very careful. If your custom PCB has a power-on sequence where one chip must wait for another to stabilize, you must reflect that in these levels. Many teams struggle here because they put everything in the same level, which leads to race conditions where the software is faster than the hardware’s electrical startup.
DeviceTree and Overlays
The DeviceTree is a file that describes every part of your hardware. Think of it as a map. It lists every pin, every memory address, and every interrupt. In Zephyr, you don’t hard-code these values in your C code. Instead, you put them in the DeviceTree. When you build your project, Zephyr looks at this map and creates a set of macros. Your driver code uses these macros to find the hardware:
const struct device *uart = DEVICE_DT_GET(DT_CHOSEN(zephyr_console)); |
This is useful when you have multiple versions of a board. If “Version 1” uses Pin 5 for an LED, but “Version 2” uses Pin 10, you only change the DeviceTree. The C code stays the same. However, managing DeviceTrees on custom hardware is difficult because of overlays. An overlay is a small file that “patches” the main hardware map.
A common issue with overlays is that they apply silently. If you override a node but the label does not match, the build succeeds, but your change is ignored. Another mistake is forgetting status = “okay”;. If this is missing, the device exists in the tree but is never initialized. If you are not careful, you end up with a hardware map that does not match your physical board, which can lead to pins being driven incorrectly, potentially damaging the hardware.
Power Management at the Perimeter
Production-ready devices usually need to save power, especially if they run on batteries. Zephyr has a power management subsystem, but it only works if the drivers at the perimeter support it. Every driver should know how to suspend and resume.
When the system goes to sleep, the kernel tells every driver to enter a low-power state. On custom hardware, this is where most bugs are found. It isn’t enough to just turn off the CPU. If your sensor driver leaves the I2C bus high, you might leak 500µA of current. In a battery device, that is the difference between lasting a year and lasting a month.
To solve this, you must write drivers that are power-aware. This means the driver tracks its own state and knows exactly how much current the hardware is drawing at any time. This level of detail is necessary for devices that must run for long periods without a charge.
Middleware and System Integration
A product needs middleware like file systems, network stacks, and logging. Zephyr makes it easy to add these, but they must be tied to the hardware correctly. Zephyr uses chosen nodes in the DeviceTree to do this.
For example, you can choose which UART port the system uses for logging by setting a single line in the DeviceTree. This is much cleaner than the old way of manually passing pointers around. It keeps the system organized and makes it easier for different engineers to work on the same project without stepping on each other’s code.
Security and the 2026 Mandate
Starting in September 2026, the EU Cyber Resilience Act will require every connected device to be secure by design. Security cannot be an afterthought; it must be built into the perimeter. For a Zephyr project, this means you must use a secure bootloader like MCUboot and generate a Software Bill of Materials (SBOM) for every build.
An SBOM is a verified list of every piece of code in your device. If you use hacky or non-native drivers that don’t follow the Zephyr model, they won’t show up correctly in the SBOM, making your compliance audit much harder. Setting up this framework on custom hardware is a major task. You have to partition your flash memory correctly and ensure that the bootloader can talk to the hardware even if the main application is corrupted. Delve deeper by catching up on our previous post – Zephyr RTOS and the EU Cyber Resilience Act.
The Problem with the Porting Approach
Many teams try to port their old code into Zephyr by wrapping existing drivers in Zephyr functions. This rarely works. Zephyr is an ecosystem with its own rules. If you wrap old code, you cannot use Zephyr’s DEVICE_DT_GET macros. This means if the hardware changes, you have to go back into your wrapped C code and manually change addresses, which defeats the whole purpose of using Zephyr.
The right way is to rewrite drivers to be native, using the DeviceTree for configuration and standard APIs for communication. It takes more work upfront, but it makes the system much more stable.
Why the Perimeter is High-Risk
If your team is stuck on hardware issues, your time-to-market will slip. Small mistakes at the perimeter, like a misconfigured interrupt or a slow clock, can take weeks to find. These issues are often hard to reproduce and can show up only in the field.
This is why the perimeter is a business risk. It is the place where the most money is lost during development. Read all about it in our previous post – The Cost of Zephyr RTOS. A stable perimeter allows your developers to focus on the features that your customers actually care about.
How embedUR Can Help
At embedUR, we have more than 20 years of experience in embedded systems. We have seen every possible problem that can happen at the interface between software and hardware. We work with major silicon partners like Synaptics, NXP, ST, Infineon, and Alif to build stable foundations for Zephyr projects.
Our engineers don’t just write code; we solve hardware problems. We help teams by:
i) Auditing DeviceTrees: We make sure your hardware map matches your PCB.
ii) Building Custom Drivers: We write native Zephyr drivers for your specific sensors and peripherals.
iii) Optimizing Power: We tune your drivers to ensure your device meets its battery life targets.
iv) Ensuring Security: We set up secure boot and SBOM generation to meet the 2026 legal requirements.
We have delivered code for millions of devices in the medical, industrial, and consumer markets. We know what it takes to get a product from a prototype to a shipping box.
Conclusion
The Zephyr kernel is a powerful tool, but it is not a magic solution. The success of your product depends on how well you build the perimeter around that kernel. By using the Driver Model correctly, mastering the DeviceTree, and planning for security, you can build a system that is stable, secure, and ready for production.
If your team is facing challenges with custom hardware or needs help moving to Zephyr, let us help you. Talk to a Zephyr engineer at embedUR today for a technical review. We will look at your current setup and provide a clear plan to make your product production-ready.



