,

Zephyr for Hardware Engineers: Changing Boot Configurations

As a hardware engineer, Zephyr can be a bit confusing at first. I am used to tweaking registers, not layering file upon file. Today we’ll talk about how those layers and overlays can help you to configure your device differently than the default board startup sequence.

Devicetree (Re)Introduction

There are two pieces to the configuration of any board in Zephyr: KConfig and Devicetree. The former I think of like a control panel, toggling switches on and off to tell the West build system whether or not to include something. I recently did this to enable the i2c shell on a project, which pulled in code that might be unnecessary for production but helped me troubleshoot a problem. The latter (Devicetree) describes the various elements that make up your board, SoC, and architecture. As I said above, these are really layered on top of one another, which is a source of confusion for new Zephyr developers.

The topmost level of the devicetree is the board setting, and this is where I find myself making changes most often. If you include a boards/<boardname>.overlay file in your application, the build system will pull in those changes and give priority to them during the build. We often write about how we use this for swapping out a sensor, say on the i2c bus, and then add commands to control that sensor in the application code, like in main.c or app_sensors.c. Super useful!

Another key piece of knowledge for newcomers is that Zephyr has many development boards and products included in the overall repository. I find this to be critical for quickly trying out a new board and having sensible defaults for the built in Zephyr samples. Without these, I would be required to build a sample up from scratch, and the last thing I want to be doing is configuring clock trees and digging through register sheets just to blink an LED for the first time.

But with the good, comes the bad. The defaults of the board, aren’t always what we want. And in today’s example, those defaults were put there by…us!

Why not just change the board file?

When you’re in control of the entire board definition, this can make sense. But it’s not the default I would go for. Much like we won’t dive down into the SDK and change some fundamental aspect of the RTOS or a plugin, I wouldn’t normally go and change the board file. It changes the behavior on your computer for your future self and also it might not be tracked in your application’s repo, which could cause issues when building on different systems.

An Example in Action

For this example we’re looking at the Aludel Elixir board running the Reference Design Template. For the RD Template we employ a separate golioth zephyr boards repository called out in the west manifest of the Template. What this means in practice is we control an ‘official’ copy of the Zephyr board files for this hardware we make (a useful tip for your organization’s needs). In the aludel_elixir_common.dtsi file you can see that we call out the behavior of the 3v3 and 5v regulators onboard. This was the subject of the post about Enabling Power Regulators at Boot.

Well, guess who changed his mind? (me)

Now that I want to not enable the regulators at boot. Normally, this would be a simple overlay for a characteristic, say the delay times:

&reg_mikrobus_3v3 {
    startup-delay-us = <5000>;    /* 5ms - adjust as needed */
    off-on-delay-us = <5000>;     /* 5ms - adjust as needed */
};

But in the case of regulator-boot-on, it’s simply a property that is included or not, so there’s no way to set it to true or false. Instead, I need to delete it entirely. In my overlay, it would look like this:

&reg_mikrobus_3v3 {
    /delete-property/ regulator-boot-on;
};

Now when I build with west, the board won’t enable that regulator automatically, but I’ll still be able to configure it during runtime. This is exactly what we did using the Golioth Settings service in our recent Joulescope demo, which happened to be the genesis of this article/topic.

Deletion can also happen at the node level. This occurs when you want to change the behavior or the existence of some element of your configuration. For instance, say I wanted to change the button configuration for the mode_button in the Aludel Elixir board file:

/* Replace a button with different GPIO configuration */
/delete-node/ &mode_button;
mode_button: mode-button {
    gpios = <&gpio0 12 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
    /* additional properties */
};

This would allow me to keep the tooling in the rest of my code but potentially target a different behavior of that particular button. Say some irresponsible hardware engineer ripped a trace off the board and the only setup that would work after repairing it requires a new pin configuration; that would never happen with me, though, right?

Learning to climb the (Device)tree

Despite my trepidation about configuring the devicetree for a project, it really enables a lot of configuration tweaks, which suits the needs of hardware engineers everywhere. Learning how to modify your devicetree overlays to enable new capabilities will unlock the ability to prototype new parts at a moment’s notice. When you’re ready to send that data to the cloud, simply pop on the Golioth Firmware SDK and send some data through friendly device-side APIs. If you get stuck, we’re always happy to help over on the Golioth Forum. Happy hacking!

Talk with an Expert

Implementing an IoT project takes a team of people, and we want to help out as part of your team. If you want to troubleshoot a current problem or talk through a new project idea, we're here for you.

Start the discussion at forum.golioth.io