An open source template for creating Zephyr projects for cellular dev boards

Every time I start a new project, I feel like I’m starting from scratch. But with modern systems, I’m rarely starting from the registers and working my way up. Instead, I’m starting from a bunch of pre-built examples in an SDK or in a vendor IDE. These are often tied to the specific hardware that a silicon vendor offers and are tied to the peripherals on a dev board. It works great for evaluation, but then I need to start layering things together to build a bigger system. This is why I was so excited about learning Zephyr; it’s a cross platform Real Time Operating System and Ecosystem. I can target the same base code at a bunch of different connectivity methods and processing chipsets. I also can easily retarget for custom hardware I want to make later.

So what if I start from a project base that already has a bunch of examples built into it where I can slot in my business logic and peripheral-specific functions? That’s where a good template comes in!

Target design requirements

Most of the time in my job, I’m creating application-specific designs to solve a problem in an industry. One example might be an asset tracker that can also plug into an ODB-II port on a car and read out an available diagnostic, such as vehicle speed. Insurers want to do this sort of thing to reduce insurance rates for subscribers. At the heart of the design, it’s reading a bunch of sensors, and then publishing those readings to the internet. If I want to control the device I’ll also want to push data from the cloud back down to the device.


Open Source Reference Design Template

The way that I get started these days is from an Open Source Reference Design Template built on top of Zephyr. In fact, this template was just recently open sourced. It has all of the device-to-cloud, cloud-to-device, and bi-directional APIs built in so I can just start developing the part that I care about. The most important feature that is ready-to-go inside this template is Over-the-Air updates, which means I have access to remote firmware updates on day 1 of the design. If there’s one thing that’s certain on my firmware designs, it’s that I won’t get things right on the very first rev while the device is still on my bench :smiley:

My normal flow is to start from the app_work.c file. I often use the Zephyr Sensor Subsystem to talk to devices that are connected to my target processor. In something like the Golioth Air Quality Monitor (AQM) Reference Design, this includes a range of environmental sensors. Notable ones are the Sensirion SPS30, the Bosch BME280, and the Sensirion SCD41


If those sensor are in-tree for Zephyr, then the exercise gets even easier. It becomes a couple-of-line API call within Zephyr to extract data from the sensor. The RTOS actually returns a formatted data type from a list of available characteristics. The Zephyr ecosystem starts to impose some standardization, even across vendors in the sensor space, which is awesome. All I want the sensor to do is give me atmospheric pressure or temperature anyway.

From there, I take that reading and format it to send to LightDB Stream, a time-series database that synchronizes between device and cloud. Zephyr helps abstract much of the connection to the cloud, in my case I’m often using a cellular connection on the nRF9160. Then the Golioth Zephyr SDK takes care of the connection and negotiation to the endpoints on the cloud. From my perspective, that same temperature or pressure data “shows up” on the cloud. The data can be extracted from the REST API to be displayed on a dashboard or sent to a bespoke app. Basically it helps me to get the reading over to my software team, who wants to do something else with that data. This is what a dashboard looks like for that Air Quality example mentioned above

Once my sensor data is transmitting, I start diving into other features that might be relevant for my design. I can push settings down to the field using the Golioth Settings Service and setting up handling code on the device in app_settings.c. Or I can create bespoke functions (a Remote Procedure Call) on the device that runs some subroutine in app_rpc.c and then trigger those functions (including passing variables) on the cloud. Anytime I need to update the device-side code to have these new capabilities, I can do so using the Over-the-Air update service.

Targeting different hardware

Zephyr’s strength is targeting different hardware; we utilized this in the open source template by targeting the Aludel platform (a custom platform we created) and also the nRF9160-DK. This is done in the boards directory of Zephyr projects.

We target our own custom platform because we want to pass data up to an eInk display on our Ostentus cover board. But we also want users to be able to try out the template and subsequent designs. This creates a great starting point for a cellular IoT application, even if you want to start a project “from scratch”: pick out your sensors, figure out what kind of functions you need to have controlled from the cloud, and get started. I find that engineers who want to stand up a demo in a day instead of a month really benefit from this method.

Maintenance over time

One real benefit is that as the template moves forward, you can also pull your application specific design forward. As a new feature is available on the Golioth platform or specific to the Zephyr-based template, I can roll those changes into my design. For example, we added a standard way to report the firmware version of the device up to a standard screen that we use. Once that change was available on the template, I updated my specific design to pull that feature in.

It’s not without some challenges. Working from a template, as opposed to say cobbling together subsystems, means that there may be merge conflicts when upgrading an older design. I had previously written about the Thingy91 and how we have a project that creates binaries to try out cellular IoT quickly. That is also based on this template and needs to be pulled forward. However, the benefits of using manifest in Zephyr is that as long as the cloud side APIs are the same, the code will continue to build indefinitely. We speak about this topic often and think it’s important to call out versions of Zephyr and subsystems so that future developers (even “future you”) is set up for success when developing on top of a project.

Help with Zephyr

If you need help with Zephyr, Golioth offers free training with Zephyr. We go through how to get started and the basics of devicetree, RTOS usage, KConfig, and more. You’ll also see how devices can connect directly (and easily) to the cloud with Zephyr and Golioth.