Hello from ZDS!

This week the Golioth team is at Zephyr Developer Summit. Previously we announced that we’ll be there and shared the talks we are presenting. We will post those shortly after the conference takes place. In the meantime, let’s recap how we got here in the first place and share a little bit more about what we’re showcasing.

Why Zephyr?

In short, because it helps our users. We are members of the Technical Steering Committee (TSC) and have been almost since the inception of the company. We built our first Device SDK on top of Zephyr because of the broad hardware support and high level integration of Golioth features into the Real Time Operating System (RTOS).

The assertion that “Zephyr helps our users” might be extra frustrating to beginners: Zephyr—and RTOSes more broadly—represents a tricky new set of skills that might be foreign to firmware or hardware engineers. For beginners coming from the hobby space, it can be an extra rude introduction into the world of command line compilation and large ecosystem. However, connecting to the internet is a difficult task, especially for custom hardware: we think that Zephyr represents a great first step towards managing those devices over time. We are committed to pushing for more user-friendly code and methods from the Zephyr foundation, and we will continue to publish best practices on our blog and our YouTube channel to help people get connected.

Showcase

One thing we’re excited about is showcasing how Golioth works to members of the community. We have been developing different “color coded” demos to make them a bit more memorable for folks that stop by our booth. Each of these demos feature a hardware (device) component and a dashboard component, in order to visualize the data that is on the Golioth Cloud.

This is the first time we have showcased the “Aludel”, which is our internal platform for prototyping ideas and switching out different development boards and plug-in sensors. We will post more about this in the future, including our talk on the subject.

Red Demo

The Red Demo is our showcase of devices running OpenThread on Zephyr; this is part of our larger interest in Thread, which we see as a very interesting way to connect a large range of sensors to the internet securely. We have been excited to show how we can use low power devices like the Nordic nRF52840 to communicate directly with the Golioth Cloud.

The devices we are using in this case are off-the-shelf multi-sensor nodes from Laird called the BT510. This hardware has additional sensors on the board which we integrated with LightDB Stream to send time-series data back to Golioth. This was fast work, thanks to Laird’s Zephyr support, it was as simple as calling out the board when we compiled the demo firmware.

We then capture the data from these on the Red Demo Dashboard, showing both historical and live data for the sensors.

 

Green Demo

The Green Demo showcases LightDB State, our real-time database that can be used to control a wide range of devices in a deployment. On the device side, it uses the Aludel platform to measure a light sensor, as would happen in a greenhouse. There is also a secondary Zephyr-based device inside a lamp, representing a grow light that might be inside a grow house. The lamp is set up to “listen” to commands from another node, in this case the Aludel.

LightDB State is used to control elements like “update rate” to control regulate flow of information. It also lets us monitor critical device variables on an ongoing basis and set up logic on the web to take actions as a result. Command and control variables can be set from multiple places, including a custom mobile app, the Golioth Console, a visualization platform, a web page, or (as is the case here) even from another device!

Our Green Demo Dashboard (below) again showcases live and historical information, as well as the current status of the connected lamp.

As an added bonus, we control some of the logic on the back end from a Node-RED instance, including control logic. That takes the light intensity sensor output and calculates how bright the lamp should be. Because this is written in Node-RED, we can include an additional input from a mobile app to control the “target intensity”. In this way, people at the booth can adjust the lamp output if the exhibition space is brighter or darker. Plus…it looks cool!

Blue Demo

The Blue Demo helps to showcase how data migrates into and out of Golioth. Using Output Streams, you can export all cloud events to 3rd party providers like AWS, Azure, and Google Cloud. Buttons on the Blue faceplate switch the output being sent back to the cloud. The sensor readings being exported to all 3 clouds can be turned on or off by changing which variables are exported from the device.

On the device side, we capture a sensor using our Aludel platform. The sensor is a BME280 (in-tree sensor in Zephyr), going through a feather form-factor dev board, talking to the network over a WizNet W5500 Ethernet external chip to the network. The Blue Demo Dashboard showcases the live data, and of course the data is being exported simultaneously to the 3 cloud platforms in real-time.

Orange Demo

Golioth is a “middleware” built on top of Zephyr RTOS, which means you can use it to implement new features on top of already-existing hardware. This demo uses the Nordic Semiconductor Thingy91 with custom firmware to send GPS data back over the cellular network to Golioth using LightDB Stream. This demo also has Golioth Logging and Device Firmware Update, which are easy to add to any project as an additional service for troubleshooting or in-field updates.

On the dashboard side, we wanted different ways to showcase this data, including “latest update”. Having access to the raw data is useful for anyone wanting to try asset tracking applications. We’re excited to be able to showcase this data as it dynamically flows into the Golioth Console and back out to the Grafana dashboard.

Future showcases

We’re excited to be showcasing our demos at the Zephyr Developer Summit, but these are moving targets! We will continue to update and pull in new feature for future events. We will be at Embedded World in two weeks (June 20-24th) and will have many of the same demos there.

The Zephyr project uses the KConfig system to select what subsystems and libraries are included in the build of your project. Sometime this is really simple, like adding CONFIG_GPIO=y when you need to use input/output pins. That’s easy enough to add manually. But things get more complex when you don’t know which configuration you need, or you want to fine tune a value like stack size.

In this post, we’re going to look at how to make changes in menuconfig, but also how to check those changes and make them permanent.

Turning on a watchdog timer

If you want to use the watchdog timer subsystem in Zephyr you need to add CONFIG_WATCHDOG=y to your build. That’s not entirely easy to figure out as the Watchdog entry in the Zephyr docs doesn’t mention it. There is some sample code called Task Watchdog and you can see the symbol is enable in the project configuration file, but there are other symbols related to watchdog timers in there as well. Which ones do we need?

I like to use menuconfig to work out these issue. It’s something we’ve talked about a few times before, and Marcin and Chris even filmed walkthrough of menuconfig system. The first step is to build your project before trying to enable the new configuration. This is necessary to generate the initial configuration for the project; if your project has a build error, you can’t load up menuconfig. Here I’m building for an nRF52840 feather board:

west build -b adafruit_feather_nrf52840 my_app_directory -p

Notice I’m using the -p directive for a “pristine” build. This makes sure that anything in the build directory is generated fresh so we are certain that we begin with a clean build.

Next, use west build -t menuconfig to launch the interactive configuration menu:

Zephyr menuconfig

Zephyr menuconfig

My most-used feature is the forward-slash ( / ) which brings up a text search. I searched for “watchdog” and two similar options came up.

I find that the options with helper text (“Watchdog Support” in this example) next to them are the best items to choose. Pressing enter on that entry takes me to the configuration item where I use the space bar to select it, indicated by an asterisk. That’s it, I’m not going any deeper right now. Pressing q brings up the exit menu, don’t forget to save your changes.

Zephyr kind of takes care of the rest, automatically pulling in a bunch of other options based on the top-level option I enabled. But those are actually why I’m writing this article. How can I see what changes other were made by “under the hood” by menuconfig?

Seeing changes made by menuconfig

Running a build command on a Zephyr project pulls in configurations from all over the place (eg: the board’s default configuration from the Zephyr tree, the project’s prj.conf file, and any .conf files in the boards directory). These coalesce into the build/zephyr/.config file, which is what menuconfig loads and saves to. Luckily, before this happens it makes a copy called .config.old which we can compare using the diff command:


(.venv) mike@krusty ~/golioth-compile/AgTech-Soil/fw $ diff build/zephyr/.config.old build/zephyr/.config
30c30
< # CONFIG_WATCHDOG is not set
---
> CONFIG_WATCHDOG=y
179c179,180
< # CONFIG_NRFX_WDT0 is not set
---
> CONFIG_NRFX_WDT=y
> CONFIG_NRFX_WDT0=y
813a815
> CONFIG_HAS_DTS_WDT=y
1046a1049,1057
> # CONFIG_WDT_DISABLE_AT_BOOT is not set
> # CONFIG_WDT_LOG_LEVEL_OFF is not set
> # CONFIG_WDT_LOG_LEVEL_ERR is not set
> # CONFIG_WDT_LOG_LEVEL_WRN is not set
> CONFIG_WDT_LOG_LEVEL_INF=y
> # CONFIG_WDT_LOG_LEVEL_DBG is not set
> CONFIG_WDT_LOG_LEVEL=3
> # CONFIG_WDT_COUNTER is not set
> CONFIG_WDT_NRFX=y

Check that out! Enabling CONFIG_WATCHDOG automatically pulls in the CONFIG_NRFX_WDT because of the chip used in my project. Be we also learn some interesting things here. It looks like some Nordic chips have multiple watchdog timers as CONFIG_NRFX_WDT0 is selected. If we search in menuconfig for that symbol and type ? on the entry we can get more information on what that’s all about:

Nordic watchdog timer instance info

The help screen in menuconfig for CONFIG_NRFX_WDT0

You can see here that this symbol was enabled (y-selecting) by the WDT_NRFX symbol. So if you’re cruising through menuconfig and want to know why something is turned on, this is a handy way to track down those answers. Try bringing up the help screen for the CONFIG_WDT_COUNTER which is shown above but not enabled. I certainly learned something about this chip for doing so!

Making menuconfig changes persistent

Remember way back at the top of the article when I mentioned I was using a “pristine” build with the -p flag? If you do that now, you’ll lose your menuconfig changes. So make sure you do an incremental build (just don’t use the pristine flag) to test those changes out. But eventually you will want to make them persistent.

The solution for that is easy. Use the diff trick I showed above to confirm what symbols were changed by your menuconfig selections, and add those changes to the prj.conf or board-specific .conf files. Once you commit these to your revision control, you’ll be certain to compile in the options you need for future projects.

Stop by our Discord or our Forum for more quick tips about Zephyr and your next IoT project!

Zephyr has a powerful interactive shell that you need to have in your bag of tricks. Two weeks ago I showed how to use the Zephyr shell to set device keys for authenticating with the Golioth platform. Today I’ll dive into using the same interface for live-debugging of i2c sensors and devices.

i2c shell basics

The ability to type out i2c commands, rather than writing/compiling/flashing code to test your changes will speed up the prototyping process with new i2c parts. My favorite feature is the scan command which lets me verify the part is connected correctly and at the address that I expected. Let’s begin by testing that out.

1. Starting from minimal sample

For this demo I’ll be using an ESP32 and the Zephyr basic/minimal sample. As the name suggests, this starts out with almost nothing running. We need to add a KConfig file that enables GPIO, I2C, and the related Zephyr shells:

CONFIG_GPIO=y
CONFIG_I2C=y

CONFIG_SHELL=y
CONFIG_I2C_SHELL=y

I’m using an ESP32 board with the i2c0 pins. I also have a sensor connected which is shown as a node and will be used in the next section of this demo.

&i2c0 {
	status = "okay";

	apds9960@39 {
		compatible = "avago,apds9960";
		reg = <0x39>;
		label = "APDS9960";
		int-gpios = <&gpio0 26 (GPIO_ACTIVE_LOW)>;
	};
};
west build -b esp32 samples/basic/minimal/
west flash

This can be built and flashed as normal:

west build -b esp32 samples/basic/minimal/
west flash

2. Opening a terminal connection

From there I drop into the shell by opening the device with a serial terminal program. I like to use minicom -D /dev/ttyUSB0 --color=on. As a side note, I have noticed with the ESP32 I need to turn off hardware flow control or else keystrokes don’t make it to the device.

3. Basic i2c in the shell

Shell commands often include help menus that can be activated by adding -h to your command. I use this to remind me of the syntax for the shell functions I’m using.

uart:~$ i2c -h
i2c - I2C commands
Subcommands:
  scan        :Scan I2C devices
  recover     :Recover I2C bus
  read        :Read bytes from an I2C device
  read_byte   :Read a byte from an I2C device
  write       :Write bytes to an I2C device
  write_byte  :Write a byte to an I2C device
uart:~$ device list
devices:
- clock@5000 (READY)
- gpio@842500 (READY)
- CRYPTOCELL_SW (READY)
- uart@8000 (READY)
- nrf91_socket (READY)
- i2c@9000 (READY)
- flash-controller@39000 (READY)
- bme280@76 (READY)
  requires: i2c@9000
- lis2dh@18 (READY)
  requires: gpio@842500
  requires: i2c@9000
uart:~$

Here you can see that the help menu for the i2c keyword lists the basic syntax. I also called device list which prints out the available devices, including the i2c bus. This means the actual command I want is:

uart:~$ i2c scan i2c@9000
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:             -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- 18 -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- 39 -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --
4 devices found on i2c@9000

Voila! The grid that prints out shows that I have five devices that responded on the i2c bus. Note that I also get a number of error logs, which is the naturally result of trying to talk to i2c devices that are not present.

4. Direct communication with an i2c device

I know that the device at address 0x58 is an AW9523 port expander. There is no Zephyr driver for this part, so I need to control it with my own code. Before writing the functions in Zephyr, I can try out each command to verify the behavior.

uart:~$ i2c read_byte i2c@9000 0x58 0x12
Output: 0xff
uart:~$ i2c write_byte i2c@9000 0x58 0x12 0x00
uart:~$ i2c read_byte i2c@9000 0x58 0x12
Output: 0x0
uart:~$

Here I’ve read the LED mode switch register on the device, set it to LED mode, and then verified that the new setting was received. The syntax places the device address (0x58) after the read/write command, then the register address (0x12), and for write commands you then add the value you want stored on that register (0x00).

Sensor shell

But wait, what about the i2c sensors with built in Zephyr support? There’s a shell for that too! Using it is an easy way to verify your sensors are working, and to confirm what sensor channels (the uniformed types of data used by the sensor subsystem) are available.

1. Turn on the sensor shell and sensor subsystem

You may remember that I already have an APDS9960 sensor declared as a subnode in my overlay file above. But to use it, we need to configure the sensor subsystem and the sensor shell. Here is my prj.conf file with three new entries:

CONFIG_GPIO=y
CONFIG_I2C=y

CONFIG_SHELL=y
CONFIG_I2C_SHELL=y

CONFIG_SENSOR_SHELL=y
CONFIG_SENSOR=y
CONFIG_APDS9960=y

With those changes in place, just rebuild and flash:

west build -b esp32 samples/basic/minimal/
west flash

2. Open the terminal connection

As above, I’m using minicom -D /dev/ttyUSB0 --color=on to open a serial connection to the Zephyr shell on the device.

3. Read sensor values in the shell

I will again use the built-in help to find the right syntax:

uart:~$ sensor -h
sensor - Sensor commands
Subcommands:
  get  :Get sensor data. Channel names are optional. All channels are read when
        no channels are provided. Syntax:
        <device_name> <channel name 0> .. <channel name N>
uart:~$ sensor get -h
get - Get sensor data. Channel names are optional. All channels are read when no
      channels are provided. Syntax:
      <device_name> <channel name 0> .. <channel name N>
Subcommands:
  RTC     
  GPIO_1  
  GPIO_0  
  UART_0  
  I2C_0   
  APDS9960
uart:~$ sensor get APDS9960
channel idx=15 prox =  16.000000
channel idx=17 light = 143.000000
channel idx=19 red =  77.000000
channel idx=20 green =  56.000000
channel idx=21 blue =  39.000000
uart:~$ 

Can you hear my evil laugh building to a crescendo?

The help message tells us that get is the only available sensor command, and calling help on that lists our devices. The new entry on the list is the sensor we declared in our devicetree overlay file. Calling it without specifying a channel lists out all that are available. Now compare that to the sensor_channel_get() commands in code sample for this sensor:

sensor_channel_get(dev, SENSOR_CHAN_LIGHT, &intensity);
sensor_channel_get(dev, SENSOR_CHAN_PROX, &pdata);

It’s worth mentioning that we’ve done all of this using KConfig values in Zephyr. The source code for this example is literally empty:

#include <zephyr/zephyr.h>;

void main(void)
{
}

Zephyr has so many shells!

How many shells does Zephyr have? How many stars are there in the sky? There are surely ways to answer these questions but I don’t have them in front of me right now.

Popular among our team is the Network Shell for networking diagnostics, and the OpenThread Shell was used extensively in getting our Open Thread demo up and running. I have used the Kconfig Search page on the Zephyr docs to search for other shells and that yields a lot of really interesting info.

But mostly I just ask Marcin on the Golioth firmware team. He seems to already know about all of the cool ones. Like the Kernel Shell that lets you check on threads and stacks. Here’s a shell output that I’m using to tune up how I’m using RAM. Note that I’ve over-allocated stacks for my animation threads, and the system work queue probably needs a bit more stack space, just to be safe.

uart:~$ kernel stacks
0x3ffd3d60            (real size 2048): unused 1740     usage 308 / 2048 (15 %)
0x3ffd3cc0 weather_tid (real size 2560):        unused 272      usage 2288 / 2560 (89 %)
0x3ffd3c20 hello_tid  (real size 2048): unused 272      usage 1776 / 2048 (86 %)
0x3ffd4490 golioth_system (real size 3072):     unused 768      usage 2304 / 3072 (75 %)
0x3ffd3b80 connection_tid (real size 2048):     unused 304      usage 1744 / 2048 (85 %)
0x3ffd3a40 animate_sense_tid (real size 1024):  unused 524      usage 500 / 1024 (48 %)
0x3ffd3ae0 animate_ping_tid (real size 1024):   unused 524      usage 500 / 1024 (48 %)
0x3ffd47d0 rx_q[0]    (real size 1504): unused 256      usage 1248 / 1504 (82 %)
0x3ffd4718 net_mgmt   (real size 768):  unused 288      usage 480 / 768 (62 %)
0x3ffd45e0 wifi       (real size 3584): unused 1660     usage 1924 / 3584 (53 %)
0x3ffd4938 esp_event  (real size 4096): unused 3388     usage 708 / 4096 (17 %)
0x3ffd4530 esp_timer  (real size 4096): unused 3584     usage 512 / 4096 (12 %)
0x3ffd4b20 sysworkq   (real size 1024): unused 32       usage 992 / 1024 (96 %)
0x3ffd3ef8 shell_uart (real size 2048): unused 588      usage 1460 / 2048 (71 %)
0x3ffd3e20 logging    (real size 2048): unused 336      usage 1712 / 2048 (83 %)
0x3ffd49d8 idle       (real size 1024): unused 828      usage 196 / 1024 (19 %)
0x3ffd4a78 main       (real size 4096): unused 3040     usage 1056 / 4096 (25 %)
0x3ffed510 IRQ 00     (real size 2048): unused 1776     usage 272 / 2048 (13 %)
uart:~$ kernel threads
Scheduler: 2745 since last call
Threads:
 0x3ffd3d60           
        options: 0x0, priority: -1 timeout: 0
        state: pending, entry: 0x4008d72c
        stack size 2048, unused 1740, usage 308 / 2048 (15 %)

 0x3ffd3cc0 weather_tid
        options: 0x0, priority: 14 timeout: 10093
        state: suspended, entry: 0x400d16b0
        stack size 2560, unused 272, usage 2288 / 2560 (89 %)

 0x3ffd3c20 hello_tid 
        options: 0x0, priority: 14 timeout: 5843
        state: suspended, entry: 0x400d1b60
        stack size 2048, unused 272, usage 1776 / 2048 (86 %)

 0x3ffd4490 golioth_system
        options: 0x0, priority: 14 timeout: 3785
        state: pending, entry: 0x400dba6c
        stack size 3072, unused 768, usage 2304 / 3072 (75 %)

 0x3ffd3b80 connection_tid
        options: 0x0, priority: 14 timeout: 1635823
        state: suspended, entry: 0x400d1c28
        stack size 2048, unused 304, usage 1744 / 2048 (85 %)

 0x3ffd3a40 animate_sense_tid
        options: 0x0, priority: 14 timeout: 592
        state: suspended, entry: 0x400d1bc8
        stack size 1024, unused 524, usage 500 / 1024 (48 %)

 0x3ffd3ae0 animate_ping_tid
        options: 0x0, priority: 14 timeout: 287
        state: suspended, entry: 0x400d1b20
        stack size 1024, unused 524, usage 500 / 1024 (48 %)

 0x3ffd47d0 rx_q[0]   
        options: 0x0, priority: -1 timeout: 0
        state: pending, entry: 0x4008872c
        stack size 1504, unused 256, usage 1248 / 1504 (82 %)

 0x3ffd4718 net_mgmt  
        options: 0x0, priority: -1 timeout: 0
        state: pending, entry: 0x40086ff0
        stack size 768, unused 288, usage 480 / 768 (62 %)

        0x3ffd45e0 wifi      
        options: 0x8, priority: 2 timeout: 0
        state: pending, entry: 0x400949c4
        stack size 3584, unused 1660, usage 1924 / 3584 (53 %)

 0x3ffd4938 esp_event 
        options: 0x8, priority: 4 timeout: 0
        state: pending, entry: 0x400ea860
        stack size 4096, unused 3388, usage 708 / 4096 (17 %)

 0x3ffd4530 esp_timer 
        options: 0x8, priority: 3 timeout: 0
        state: pending, entry: 0x400dcf88
        stack size 4096, unused 3584, usage 512 / 4096 (12 %)

 0x3ffd4b20 sysworkq  
        options: 0x0, priority: -1 timeout: 0
        state: pending, entry: 0x4008d72c
        stack size 1024, unused 32, usage 992 / 1024 (96 %)

*0x3ffd3ef8 shell_uart
        options: 0x0, priority: 14 timeout: 0
        state: queued, entry: 0x400d5570
        stack size 2048, unused 588, usage 1460 / 2048 (71 %)

 0x3ffd3e20 logging   
        options: 0x0, priority: 14 timeout: 36
        state: pending, entry: 0x4008f538
        stack size 2048, unused 336, usage 1712 / 2048 (83 %)

 0x3ffd49d8 idle      
        options: 0x1, priority: 15 timeout: 0
        state: , entry: 0x4008d1b0
        stack size 1024, unused 828, usage 196 / 1024 (19 %)

 0x3ffd4a78 main      
        options: 0x1, priority: 0 timeout: 184000604
        state: suspended, entry: 0x4008cbd4
        stack size 4096, unused 3040, usage 1056 / 4096 (25 %)
uart:~$

Until next time, go out and explore Zephyr shells. Just make sure to pop into our Discord channel and let us know which shells you find the most useful!

One small step for debug

If you are getting started in Zephyr, you can get a lot done using the serial/terminal output. I normally refer to this as “printf debugging”. With Zephyr it would be “printk debugging” because of the difference in commands to print to the serial output (or to a remote logging service like Golioth). Honestly, this method works great for example code, including many of our tutorials.

In addition to Zephyr’s ad hoc nature as a package management platform for embedded software, it is also a Real Time Operating System (RTOS). We use Zephyr’s package management as a starting point: we want users to be able to bootstrap a solution by downloading toolchains and vendor libraries. We don’t dig into the operating system very often on this blog or in our tutorials. Much like printk debugging, the details aren’t really needed when getting started with Zephyr and Golioth. But when you begin to dig deeper, you will be kicking off your own threads and workers, and utilizing other features of the RTOS like semaphores and queues. Once you’re doing that, I can all but guarantee you’ll want more visibility into what’s happening in your system.

This article showcases SEGGER Ozone and SystemView tools, which will help you peek inside. It also adds a few pieces to getting started with these platforms that I found lacking when searching for answers on the broader internet.

Saving battery budgets

My motivator to dig deeper on these systems is getting ready for the upcoming conference season. We will be at the Zephyr Developer Summit and Embedded World representing Golioth. We want to showcase our technology, including our capabilities as a Device Management solution for Thread-based device networks. Our demo of Zephyr, OpenThread, and Golioth runs on a battery-based device, which isn’t something we normally do. Most of our demos expect you’ll be powering your platform using a USB cable. When you start to care about power draw, you start to care about where your program is spending its time. Understanding whether a device is in sleep mode and how long it spends processing a piece of data is critical to optimizing for battery life. Since I don’t want to lug along an entire suitcase of batteries with me to the conference, I started wondering where we’re hanging out in the various threads of Zephyr. This is where a debugger and a real-time process recorder come in.

Tooling up for debugging

So I know I need a debugger.

My experience as a hardware engineer is that silicon vendors normally have a dedicated path for their code examples, Real Time Operating Systems (RTOSes), and Integrated Development Environments (IDEs). If I’m being honest, that was the path I took in the past: it was a low friction way to get something blinking or talking back to the network. Going outside of that path to use Zephyr means the tooling is more DIY. Even some vendors that provide support for Zephyr as their primary or secondary solution don’t have a “one way” of doing things. The fact that Zephyr is flexible is both a blessing and a curse. I can implement anything I’d like! But I need to go figure it out.

SEGGER Ozone

SEGGER are the makers of the popular J-Link programmer, in addition to a wide suite of software tools for embedded developers. I was interested in Ozone because of the open nature of their debugger, completely decoupled from any IDE or vendor toolchain.

I was excited to see a webinar from our friends at NXP talking about using SEGGER Ozone with Zephyr (registration required). The webinar showcases using a Zephyr sample called “Synchronize”, which is available at <zephyr_install_directory>/zephyr/samples/synchronize.

The basic idea of the sample is you are sharing a semaphore between two threads. It’s like passing a ball back and forth. Once the loop for one thread runs, it release the semaphore and the other thread can pick it up and use it. Each thread is effectively running the same code, it just only does so when the thread and semaphore line up properly.

the main function of main.c on the synchronize sample (with a small modification)

The net result is that you can see the threads ping-pong-ing back and forth on a debugger. See the NXP link above for a video example of this in action.

Using Ozone with Zephyr

One thing that wasn’t clear to me from the NXP webinar is getting everything set up. This was the genesis of this article. I wanted to put the required steps in one place.

Requirements:

  • J-Link programmer
  • Development board with SWD or JTAG access
  • Compatible chipset/board in the Zephyr ecosysttem (we will be showing the mimxrt1060_evkb below)
  • SEGGER Ozone installed on your machine. You can download and install the program from this page.

Step 1: Compile the program

The first step is to compile the project at <zephyr_install_directory>/zephyr/samples/synchronize with some added settings in the prj.conf file.

You will need to have the Zephyr toolchains installed. For our example, I will compiling for the NXP RT1060 EVKB board, which means I need to include the NXP Hardware Abstraction Layer (HAL). If you’re a regular Golioth user, this is not installed by default (but will be soon). Instead, I recommend you install Zephyr directly from the tip of main or start from a “vanilla” Zephyr install already on your machine. Start a virtual environment if you have one (or prefer one) and then run the following:

mkdir ~/RTOS_test
cd ~/RTOS_test
west init
west update

This will be an entire Zephyr default install and will take a bit to download/install. We’re showing this for the RT1060 board but this should work on almost any board in the main Zephyr tree, including virtual devices.

cd ~/RTOS_test/zephyr/
nano samples/synchronize/prj.conf

Add the following to the sample code, if it’s not already there. This will allow Ozone to understand some of the threads in the program.

# enable to use thread names
CONFIG_THREAD_NAME=y
CONFIG_SCHED_CPU_MASK=y
CONFIG_THREAD_ANALYZER=y

Finally, build the code:

west build -b mimxrt1060_evk samples/synchronization/ -p    #you can swap this out for another board
west flash

This loads the binary file (zephyr.bin) onto your board.

Step 2: Load the ELF into SEGGER Ozone

Normally it’s the “binary” version of your program that is loaded onto the board. To use a debugger, we want something called an ELF File instead, which stands for “Executable and Linkable Format”. I think of it as an annotated version of your binary, because it includes the source files and all of the references as you go through your program.

Start a new project using the New Project Wizard, walking through the various dialogs:

If it’s not already selected, choose your processor (in the case of the mimxrt1060_evkb, the part is actually the rt1062)

Choose your J-Link (required for SEGGER Ozone). On my board it uses Single Wire Debug (SWD) but some boards might use JTAG.

Load the ELF file from your build directory. This will be located at  <zephyr_install_directory>/zephyr/build/zephyr, using the instructions above.

The most critical piece!

I wanted to call this out because it took me so long to find how to enable the “thread aware” debugging part of Ozone. You need to run the following command in the Console:

Project.SetOSPlugin ("ZephyrPlugin")

This tells SEGGER to run a built-in script and enable a new window in the “View” menu. Select the newly available “Zephyr” option or hit Alt + Shift + O to enable it. You should now see a new window pop up on your screen.

This window shows the two threads that are available in the “Synchronize” program.

I set a breakpoint on the printk command that is writing to the terminal (click the gray button next to the line where you want to set a breakpoint). Then I start debugging from the menu Debug -> Start Debug Session -> Attach to Running Program. This should start the debugger and then halt where you set a breakpoint:

Click the Resume button or hit F5 and you will see the Zephyr window switching between Thread A and Thread B.

SEGGER SystemView

SystemView is something I first became aware of in Brian Amos’s book “Hands-On RTOS with Microcontrollers”. I was reading it to learn more about the pieces of Real Time Operating Systems and he uses SystemView to help analyze where an RTOS is spending the majority of time. This is critical because operating systems rely on the concept of a “scheduler”, which relinquishes control over precisely what is happening when in a program.

SystemView is a separate piece of software from Ozone and is licensed differently. It is free to use as a trial, but extended usage by commercial operations will need to purchase a license. You can download the software from SEGGER for trial usage.

Using SystemView with Zephyr

There are some additional steps required to get a program working with SystemView on Zephyr.

The most critical piece(s)!

There are two critical pieces to get a Zephyr program running with SystemView:

  1. You must be doing your logging using RTT.
    • Using only UART logging of messages will not work. SystemView requires an “RTT Control Block” in your code and if it’s not there, SystemView will timeout while trying to capture events.
    • The message I kept receiving was “Could not find SystemView Buffer”.
  2. You must log traces using RAM instead of UART (default)
    • This allows the debugger to extract trace information from memory. Some other OSes can pull in UART trace messages but this is not enabled on Zephyr yet.

You can enable RTT and other required settings in the prj.conf file (these can also be set through Zephyr’s menuconfig):

CONFIG_THREAD_NAME=y
CONFIG_SEGGER_SYSTEMVIEW=y
CONFIG_USE_SEGGER_RTT=y  #see point 1 above
CONFIG_TRACING=y
CONFIG_TRACING_BACKEND_RAM=y  # see point 2 above

Recompile the program and flash to your board. You should now be able to open SystemView and get started. Upon opening the program, you’ll need to configure for your J-Link:

And your board settings:

Finally when you hit the “Play” button (green arrow) or hit F5, it should start to capture events on your device.

As you can see below in the “Timeline” window, control is bouncing back and forth between “Thread A” and “Thread B”.

Using Ozone and SystemView together

These are two different tools using the same interface. The cool thing is that you can use them together at the same time. This is especially useful because SystemView will capture all events, which can quickly become overwhelming. You might instead only want to see a small subset of events. You can set a breakpoint in Ozone, start recording in SystemView, and then get a targeted look at the program execution right where the breakpoint is happening. You can target smaller subsections of your code to really pinpoint and optimize your functions.

Giant Leaps in Debugging

These are just some of the tools that will help to give you more insight into your Zephyr programs as you dig deeper into the ecosystem and the Golioth Zephyr SDK. Once you start adding more capabilities, you will be able to visualize the finest details of what is happening and develop better software for your customers.

If you need help getting started with the tools described here, you can always join us on the Golioth Discord or check out the Golioth Forums for assistance. Happy debugging!

When it comes to the Internet of Things, wireless tech like celluar and WiFi get all the flashy press coverage. But wired devices aren’t second class citizens, they’re the connectivity-of-choice for tons of industrial applications. Zephyr makes it really easy to add an Ethernet connection to any project.

Here at Golioth we’re getting ready for a couple of conferences: the Zephyr Developer’s Summit and the Embedded World Conference, both in June. We’ll have hardware demos on site, and the thought of competing for RF spectrum with tens of thousands of other radios gives me the demo blues. So we will include a wired-network demo at the Golioth kiosk to be on the safe side.

What did it take to get our hardware up and running with Ethernet? Not much. It’s a quick and easy process, so let’s dive in!

Wire up an Ethernet module

We’ve chosen the WIZnet W5500 Ethernet chip to handle the Ethernet PHY. It connects to a microcontroller using SPI and there is a handy ETH WIZ Click module available that includes the jack and magnetics for easy prototyping. This chip has great driver support in Zephyr, which we’ll get to in the next section.

Wiring it up will be familiar to anyone who has worked with Serial Peripheral Interface (SPI) devices. I’m using an nRF52840 microcontroller in this demo. The bindings page for nrf-spi lists sck-pin, mosi-pin, miso-pin, and cs-gpios which connect to the ETH WIZ Click’s SCK, SDI, SDO, and CS pins. The w5500 bindings page also details int-gpios and reset-gpios which connect to the INT and RST pins on the Ethernet module.

We need to tell Zephyr that this module is present by adding it to an overlay file.

&spi1 {
	compatible = "nordic,nrf-spi";
	status = "okay";
	cs-gpios = <&gpio0 3 GPIO_ACTIVE_LOW>;
	test_spi_w5500: w5500@0 {
		compatible = "wiznet,w5500";
		label = "w5500";
		reg = <0x0>;
		spi-max-frequency = <10000000>;
		int-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>;
		reset-gpios = <&gpio0 30 GPIO_ACTIVE_LOW>;
	};
};

This overlay file uses the SPI pin definitions already present in the board DTS file. The node for the W5500 chip correctly assigns CS (chip select), INT, and RST pins.

Configure the Ethernet library and IP handling

The node shown above needs to be added to a board overlay file in your project. For your convenience, we have hello-ethernet sample code that includes board files for several different microcontrollers.

In addition to telling Zephyr how the Ethernet chip is connected, we need to tell Zephyr to build in the proper libraries.

CONFIG_SPI=y
CONFIG_NET_L2_ETHERNET=y
CONFIG_ETH_W5500=y
CONFIG_ETH_W5500_TIMEOUT=1000
CONFIG_NET_DHCPV4=y
CONFIG_NET_MGMT=y

These KConfig symbols tell the build tools that we need the SPI peripherals, we’ll be using Ethernet–specifically the W5500 chip, and that we’re going to need some tools to manage the DHCP process for acquiring and using an IP address. I’ve added these settings to a board-specific conf file, but they could be added to the prj.conf if you prefer.

That last part requires just a bit of work in the main.c file. We need to tell Zephyr that if Ethernet is enabled, we want to make an API call to acquire and use an IP address assigned by the wired network’s DHCP server.

#if IS_ENABLED(CONFIG_NET_L2_ETHERNET)
#include <net/net_if.h>
#endif

/* This next part goes in main() before the loop */
	if (IS_ENABLED(CONFIG_NET_L2_ETHERNET))
	{
		LOG_INF("Connecting to Ethernet");
		struct net_if *iface;
		iface = net_if_get_default();
		net_dhcpv4_start(iface);
	}

Don’t forget to plug it in

This sounds silly, but I have spent fives-of-minutes wondering why I wasn’t able to get an IP address. I’m so used to working with WiFi and cellular, sometimes I forget to plug in the Ethernet cable. Don’t be me.

*** Booting Zephyr OS build zephyr-v3.0.0-3806-g05cc2e1ac388  ***

[00:00:00.259,429]  golioth_system: Initializing
[00:00:00.259,796]  golioth_hello: main: Start Hello sample
[00:00:00.259,826]  golioth_hello: Connecting to Ethernet
[00:00:00.259,887]  golioth_hello: Sending hello! 0
[00:00:00.259,948]  golioth_system: Starting connect
[00:00:00.260,131]  golioth_hello: Failed to send hello!
[00:00:00.260,467]  golioth: Fail to get address (coap.golioth.io 5684) -11
[00:00:00.260,467]  golioth_system: Failed to connect: -11
[00:00:00.260,498]  golioth_system: Failed to connect: -11
[00:00:05.260,223]  golioth_hello: Sending hello! 1
[00:00:05.260,437]  golioth_hello: Failed to send hello!
[00:00:05.260,589]  golioth_system: Starting connect
[00:00:05.260,925]  golioth: Fail to get address (coap.golioth.io 5684) -11
[00:00:05.260,955]  golioth_system: Failed to connect: -11
[00:00:05.260,955]  golioth_system: Failed to connect: -11
[00:00:05.300,750]  net_dhcpv4: Received: 192.168.1.106
[00:00:10.260,498]  golioth_hello: Sending hello! 2
[00:00:10.260,711]  golioth_hello: Failed to send hello!
[00:00:10.261,047]  golioth_system: Starting connect
[00:00:10.315,734]  golioth_system: Client connected!
[00:00:15.260,803]  golioth_hello: Sending hello! 3
[00:00:20.263,305]  golioth_hello: Sending hello! 4

That warning aside, the display above is a typical run of the hello-ethernet sample. You can see that the Ethernet connection is initialized shortly after power-on. The Golioth client immediately starts trying to send log messages to the cloud but fails until an IP address is secured about 5.3 seconds into runtime.

All Internet connections act the same in Zephyr

Zephyr abstracts the details of your Internet connection. Once it’s set up, your app has access to sockets for whatever operation it needs. For the most part your code doesn’t need to know how it’s actually getting to the network.

Of course there are exceptions. Here we needed to explicitly sort out an IP address. But considering the complexity that actually goes into operating a network stack, Zephyr sure has taken the degree of difficulty down to a minimum.

The Golioth Zephyr SDK has a new name, a new recommended install method, and a new recommended install directory name.

If you installed our SDK prior to May 2022, now is a great time to make one change to your manifest file and pull the newest version. We’ll walk you through that in the next section, but first let’s discuss what changed, and why we’re excited about it!

Last week we changed the name of our SDK from zephyr-sdk to golioth-zephyr-sdkto make it clear this code is for using Golioth device management features with Zephyr. We also updated our recommended install directory names to golioth-zephyr-workspace and golioth-ncs-workspace.

This second change differentiates the “vanilla” version of Zephyr from the specialized “nRF Connect SDK” (NCS) version of Zephyr that Nordic Semiconductor maintains for chips like the nRF52 and the nRF9160. It also prepares the way for Golioth to expand our platform support beyond Zephyr, which is extremely exciting for us.

For new installs, our getting started guide for ESP32 or for nRF9160 have already been updated and you won’t notice the difference. For existing installs, read on for simple steps to keep your local copy in sync with this new development.

Existing Golioth Zephyr SDK installs: How to update

A small manual update needs to be made to any Golioth SDK that was installed prior to May of 2022. If you previously followed our getting started docs, you have a folder called ~/zephyrproject for the Zephyr version of our SDK, or ~/zephyr-nrf for the NCS (nRF Connect SDK) version of our SDK.

Begin in that directory:

1. Edit the .west/config file

If you have the Golioth Zephyr SDK installed, change the manifest section of ~/zephyrproject/.west/config to match the following:

[manifest]
path = modules/lib/golioth
file = west-zephyr.yml

If you have the NCS version of the Golioth Zephyr SDK installed, change the manifest section of ~/zephyr-nrf/.west/config to match the follow:

[manifest]
path = modules/lib/golioth
file = west-ncs.yml

2. Update your Golioth remote, then pull and update the SDK

cd modules/lib/golioth
git checkout main
git remote set-url origin https://github.com/golioth/golioth-zephyr-sdk.git
git pull
west update

That’s it, your SDK is now up to date!

What changed for new installs: west init option and directory names

The install instructions for the Golioth SDK are very similar to what they were before this change. The most obvious difference is that we’ve moved away from using west.yml as the manifest file and instead use west-zephyr.yml for vanilla Zephyr, or west-ncs.yml for the Nordic “flavor” of Zephyr. When calling west init, we use a flag to chose one of these manifest files:

#Installing the Golioth Zephyr SDK:
west init -m https://github.com/golioth/golioth-zephyr-sdk.git --mf west-zephyr.yml ~/golioth-zephyr-workspace
cd golioth-zephyr-workspace
west update
#Installing the Golioth NCS SDK:
west init -m https://github.com/golioth/golioth-zephyr-sdk.git --mf west-ncs.yml ~/golioth-ncs-workspace
cd golioth-ncs-workspace
west update

This makes the installation process, and the update process for both approaches the same which it wasn’t before.

The old install directory (zephyrproject and zephry-nrf) have been updated to golioth-zephyr-workspace and golioth-ncs-workspace. This change does two things to better describe the contents of these directories. First, they are much more specific about the intended purpose of the folder contents. Second, calling these directories a workspace helps with understanding that you will find multiple repositories inside of each directory (the Golioth SDK, the Zephyr Project RTOS, and the nRF Connect SDK will also be there for NCS installs).

Golioth is a Rolling Stone

We are constantly improving how Golioth empowers you to manage your IoT infrastructure. These changes to the Golioth Zephyr SDK deliver a better workflow, and prepare Golioth to add SDKs for additional platforms in the future. We are dedicated to updating our users about changes that might impact their setup and tooling. If you have any questions on these changes, or want help getting up to speed with our tools, we’d love to hear from you on the Golioth Discord server!

Custom Kconfig symbols

Last week I programmed 15 consecutive boards with unique firmware images. I needed to build multiple versions of the same Zephyr firmware, supplying unique values to each process during build time. The Zephyr Kconfig system provides built-in support for passing values during a build. The trick is that you can’t just dynamically declare symbols, you need to tell Zephyr that you are expecting a value to be set for a new symbol. Today I will walk through how to do this, and why you might want to.

Fifteen Devices, Fifteen Names

Golioth device names

In my case I was pre-provisioning devices to use in a training workshop. I supplied each build them with credentials so they would be able to authenticate with the Golioth Cloud platform. There is already built-in Kconfig support for these values. But at the same time, I wanted the devices to have a human-readable name that matches up with the device displayed on our cloud console. During the training, the device prints its name on a screen as an easy reference.

The solution to both of these needs is to set the values at build time using the -D<SYMBOLNAME>=flag format on the command line. Any Kconfig value that you would normally set in a prj.conf file can also be set this way. So if you wanted to enable the Zephyr Logging subsystem for just one build, you could turn it on by adding -DCONFIG_LOG=y to your west build command.

I used a script that called the goliothctl command line tool to create each device and to make the credentials for each on our cloud platform. The script then called the west tool to build the firmware, supplying the device name and the credentials as arguments.

The gotcha is that if you try to make up your own symbols on the fly (like -DGOLIOTH_IS_AWESOME=y) you will be met with errors. Zephyr needs to know what symbols to expect–anything else is assumed to be a typo.

Add a Custom Symbol to Zephyr Kconfig

The easiest way to add a symbol is to declare it in the Kconfig file in your project directory. The syntax for this is pretty simple:

  • config SYMBOLNAME
    • bool “Description”

According to the Linux docs, the following types are valid: bool, tristate, string, hex, int. The description string is important if you want the value to appear in menuconfig. Consider the following code:

config GOLIOTH_IS_AWESOME
	bool "Confirm that Golioth is awesome"

config MAGTAG_NAME
	string "MagTag Name"
	default "MagTag-One"
	help
		Used during automatic provisioning for workshops

This creates two new symbols, one accepts a boolean value, the other a string value. These can be viewed and set using the menuconfig interface. After building your project, type west build -t menuconfig.

Custom Kconfig symbols shown in menuconfig

Both of the new symbols appear in the menu, and you can see the strings we used when declaring the type are what is shown as labels in the menu interface. Of course, you can now set the values in a Kconfig file as you would any other interface. But for me, the goal was to do so from the command line. Here is an example build command used by my provisioning script:

west build -b esp32s2_saola magtag-demo -d /tmp/magtag-build -DCONFIG_MAGTAG_NAME=\"azure-camellia\" -DCONFIG_GOLIOTH_SYSTEM_CLIENT_PSK_ID=\"azure-camellia-id@developer-training\" -DCONFIG_GOLIOTH_SYSTEM_CLIENT_PSK=\"b00a0fef769d65d9021d747c8d710af5\" -DCONFIG_ESP32_WIFI_SSID=\"Golioth\" -DCONFIG_ESP32_WIFI_PASSWORD=\"training\"

The symbol will now be available to your c code:

LOG_INF("Device name: %s", CONFIG_MAGTAG_NAME);

And of course you can view the state of all Kconfig symbols processed during the build process. Just open up the build/zephyr/.config file that was generated. Below you will see the first baker’s-dozen lines, including my custom symbols along with some specified by the Golioth SDK, and others that are standard to Zephyr:

CONFIG_ESP32_WIFI_SSID="Golioth"
CONFIG_ESP32_WIFI_PASSWORD="training"
CONFIG_DNS_SERVER_IP_ADDRESSES=y
CONFIG_DNS_SERVER1="1.1.1.1"
CONFIG_GOLIOTH_IS_AWESOME=y
CONFIG_MAGTAG_NAME="azure-camellia"
CONFIG_GPIO=y
CONFIG_SPI=y
CONFIG_I2C=y
# CONFIG_KSCAN is not set
CONFIG_LV_Z_POINTER_KSCAN_DEV_NAME="KSCAN"
# CONFIG_WIFI_WINC1500 is not set
CONFIG_WIFI=y

Learn more about Kconfig

To dive deeper into Kconfig options, your first stop should be the Zephry Docs page on Setting Kconfig values. I also found the Kconfig Tips and Best Practices to be useful, as well as the Linux Kconfig Language reference. Ten minutes of reading the docs, and a little bit of trial and error, and you’ll have a good grasp of what Kconfig is all about.

The Zephyr Developer Summit (ZDS) is coming up June 7th-9th 2022 in Mountain View California at the Computer History Museum. Golioth will be there and we’re very excited to interact with fellow users, developers, and stakeholders in the open source real-time operating system (RTOS) known as Zephyr!

We love Zephyr

People reading this blog will not be surprised to know that we love Zephyr. We write about it quite often and it is the basis of our Zephyr-based SDK. As a result, many of our samples and demos are built using Zephyr. We often talk about Zephyr being an indicator that a hardware device will work with Golioth; all you need is a network connection, a board running Zephyr, and little bit of storage overhead to hold the Golioth code. It’s the hardware interoperability of Zephyr that allows Golioth users to target a wide range of platforms, including microcontrollers from Espressif, Infineon, Intel, Microchip, NXP, Nordic Semiconductor…and more being added every day!

Our plans at ZDS

We’re excited to be returning to ZDS. Last year we officially announced Golioth to the world at ZDS, and talked about how our platform works within the Zephyr ecosystem. We hope to have another year of connection, this time in person and online. Let’s look at how we’ll be participating.

Sponsoring/Showcase

We are helping to sponsor ZDS this year. We believe in the mission of the project and the conference and wanted to be part of it. We will also be showcasing Golioth at a vendor table at the conference. If you would like to see Golioth in action, you can stop by at any time to ask questions and see demos. You can, of course, also try out Golioth at any time using our Dev Tier plan, which gives anyone up to 50 free devices on the platform.

Giving Talks

We will be presenting a range of talks at ZDS:

  • What chip shortage? How we use Zephyr for truly modular hardware
    • Chris and Mike from Developer Relations will highlight the Aludel, an internal hardware platform we’ve built as a customizable solution that can switch out hardware pieces without major redesign. This modular hardware showcases a path for hardware and firmware teams to unify their codebase using Zephyr while targeting a wide range of hardware. Being able to swap out a sensor, microcontroller, or radio but keep the main board, or go from outdoor air monitoring to indoor monitoring is really powerful. Zephyr makes it much easier to create alternate builds and manage firmware pipelines to hardware variants.
  • Connecting Zephyr Logging to the Cloud over Constrained Channels
    • Our resident Zephyr expert Marcin will cover an approach to preparing Zephyr logging messages for transmission through a constrained networking layer, such as a cellular connection. This includes CBOR compression on all logging messages, including special handling around binary payloads. There is also an interface to a CoAP library to take advantage of smaller payloads and standardized format to a cloud backend. Additional tooling is included for selectable acknowledgement of messages, to handle high priority and high traffic scenarios.
  • Zephyr <3 Internet: How Zephyr speeds implementation for new IoT devices
    • I (Jonathan, CEO) will make a case to people outside of the Zephyr ecosystem on why they should adopt the platform and contrast the difficulties to other RTOS solutions. These networking concepts are so baked-in that it fundamentally changes the cost for anyone buying into the ecosystem. From vendors adding modems to developers building apps, the underlying framework saves time and engineering complexity.
  • End-to-end IoT development with Zephyr
    • Founding engineer Alvaro will cover the options for getting a Zephyr app connected (WiFi, Ethernet, Cellular), selecting the right data encoding (JSON/CBOR), securing the data transfer (DTLS/TLS), and choosing a protocol (HTTP/MQTT/COAP). But that’s not the end of the story, the cloud needs to manage devices allowed to connect, consume the data being received, open up options for using that data, and be aware of the continued state of the hardware. And once you have the data you need to build a user-facing application on top of it.

Giving a workshop

Hands-on demos are a critical part of understanding a new system. This is true of both Zephyr and of Golioth. We wanted to showcase how Golioth works to Zephyr users, while also helping people get a real piece of hardware talking to the cloud. We’re giving a workshop called “Hands-on with Zephyr-based IoT Hardware – Data Goes in, Data Comes Out, Data Goes Up“.  This is a hands-on developer training showing how to get a finished piece of hardware utilizing the various features that Zephyr has to offer. The main thrust of the training is getting up and running with the Zephyr toolchain, implementing examples on a piece of hardware (provided), and interacting with cloud services. The user will learn about various abstraction layers around things like CoAP and CBOR, and experience a real world example of a smart device talking back to the Golioth Cloud. This will also expose the user to web-side technologies and how they can export data to external commercial services like AWS, Azure, and GCP.

Meeting with users and partners

We love our community and are always looking to meet new people within it. Interested in setting up a time to discuss something? Email [email protected]

Should you attend?

If you’re a someone already developing for Zephyr and pushing code upstream, this is the best opportunity to meet with others from the community and continue to build your skills. We think this is a perfect event for you!

If you’re new to Zephyr, the content can seem a bit intimidating…but fear not! The first half day of the conference (June 7th starting at 1 pm) is the “Intro to Zephyr” day, and this is a great introduction to the platform and how you can build your skills using Zephyr. There are also reduced cost tickets for students, if you’re still learning. We think if you’re looking to build a product with Zephyr in the future, or already are building with Zephyr, it’s a worthwhile experience to be there.

See you there!

We’re excited to meet more people and hear the other great talks that will be happening at the 2022 Zephyr Developer Summit. While we definitely plan to share the talks after the fact, and you can also participate in the virtual conference, we still hope to see you there!

Embedded firmware development almost always involves an interaction between an MCU and sensors used for detecting environmental factors. The Zephyr OS has a very particular way of interacting with sensors that can be challenging to learn and duplicate.

I needed to incorporate a sensor driver without it being part of the Zephyr tree. I won’t cover all of the detail necessary to build a driver from scratch, but I certainly learned a lot about how the driver model works in Zephyr. I’ll be sharing that with you today.

New to Golioth? Sign up for our newsletter to keep learning more about IoT development or create your free Golioth account to start building now.

In the tree, or out of the tree?

There are two methods of adding a driver to Zephyr. The first is to add the relevant files to the OS directory internally, such as in the sensors folder you see on the main Zephyr repo. The second is to add the driver into a directory structure outside of Zephyr, known as Out-of-Tree.

Working “In-Tree” (IT) is the most straightforward: the sensor driver lives in the official Zephyr repository as if it was native to Zephyr the day the project started. Any hardware vendor hoping to get their device driver In-Tree would need to submit a Pull Request (PR) to the Zephyr project to be included on every computer compiling Zephyr in the future. This is also a benefit for my learning: there are many examples of how to do this in the main repository from all the PRs. I can go to GitHub and track down the change that incorporated any particular sensor. This serves as a guide, with relevant file additions and existing file modifications.

Working in an “Out-of-Tree” (OOT) context means we will develop driver code independent of the central Zephyr repository, so no upstream changes are required. I think this helps to clarify the driver binding process and hierarchy. There are use cases for retaining the driver code alongside application code and not incorporating the driver within the OS, especially for projects that have customization or need to keep some aspect of driver code out of the public repositories.

We’re going to move an IT driver to an OOT context. The benefit of this exercise is that we will see how Zephyr locates and binds the driver to the application target device. All of the relevant files and file changes are contained to a single driver folder rather than spread out over the OS in various include, src, and specialized header files.

Understanding how Zephyr drivers are structured

There are three categorical topics to understand when adding and interacting with a driver. These are:

  1. Get the build system to find the driver
  2. Have the target-specific overlay file in place that aligns with the sensor yaml file
  3. Understand the Zephyr generalized sensor interaction functions

Getting the build system to find your driver and incorporate it involves one manifest file and a series of CMakeLists.txt and Kconfig files. Configuration of the driver could be accomplished with a single CMakeLists.txt file and a single Kconfig file, but using many files allows for the content of the files to be brief. It also establishes a directory build hierarchy that becomes intuitive after some study. This hierarchy will be explained in more detail later.

Some of the most cryptic errors will be generated when the target-specific overlay file that matches the sensor yaml file is either missing or contains errors. This file pair should be studied carefully within existing sensor examples.

Overlay and yaml files

To start, let’s find a pair of overlay and yaml files that we can study. Navigate to zephyr/samples/sensor and open any of the sensor folders. There will be a boards folder containing overlay files. Open one of them. Next navigate to zephyr/dts/bindings/sensor and open the sensor yaml file corresponding the the sensor example.

The board overlay file will typically declare and describe the communication method (i2c,SPI, etc). It will declare relevant pins used and communication bus speeds. The sensor yaml file will declare properties which may or may not need to be initialized by the target-specific overlay file for correct driver operation. The properties declared within the yaml file will have a key called required which may be true or false. If property:required: is false then it does not need to be specified in the overlay file. If it is true then it must be specified in the overlay file or the project will not build.

The sensor_driver_api struct

The most generalized functions for interacting with a sensor in Zephyr are defined by the sensor_driver_api struct. This can be found by searching for it within the sensor.h file found in /zephyr/include/drivers/ folder. These generalized functions include a getter and setter for sensor ‘attributes’, a sample fetching function, a ‘channel’ getter, and a ‘trigger’ setter. These generalized functions allow for ease of interaction with the sensor from the perspective of application code.

The most important methods are the sample_fetch and channel_get functions. The sample_fetch function can be executed from the application simply by passing the sensor device object. Sensor data for all defined channels will be obtained and stored in the ‘data’ portion of the ‘device’ struct. This struct can be found in /zephyr/include/zephyr.h. Sensor data specific to a channel can then be obtained with the channel_get function by passing this getter the device object, the relevant channel #define, and the address of a sensor_value struct into which we will store the sensor data value integer and decimal components.

Adding the out-of-tree sensor driver

Adding a sensor driver to project in the OOT context is most easily achieved by modifying the Example Application provided by the Zephyr Project . Lets add the hx711 load sensor driver to this project.

I mentioned before that it’s helpful to review the pull-request that added the driver to Zephyr in the first place. Here’s the source PR for the hx711. This driver adds source files, configuration files, and also modifies internal zephyr files. Fortunately, there is a method to avoid modifying the internal zephyr files.

  1. Add a folder called hx711 inside of example-application/drivers/sensor/
  2. Add the hx711.c and hx711.h files from the PR inside the hx711 folder
  3. Add the drivers/sensor/hx711/CMakeLists.txt and drivers/sensor/hx711/Kconfig files from the PR to this folder
  4. Add the avia,hx711.yaml file from the PR to the dts/bindings/sensor directory in the example-application project
  5. Add the nrf52dk_nrf52832.overlay file to example-application/app/boards directory
  6. Add samples/sensor/hx711/prj.conf from the PR to example-application/app/
  7. Overwrite the main.c source code from the PR to the main.c file in the example-application/app/src folder.

That’s it for file addition. The remaining steps are to modify the configuration files to locate and build the driver, and finally modify the driver header file to incorporate the changes made to internal zephyr files into the driver directly instead.

West manifest, CMakeList, and Kconfig (oh my!)

Look at the config file in the .west folder in the project root directory. It specifies the path to, and name of the manifest file (west.yml) used by this project. For the Example Application the west manifest is found in the example-application directory. It is also in this directory that the outer-most CMakeLists.txt and Kconfig files are found.

Multiple CMakeLists.txt and Kconfig files exist as pairs beginning at the example-application/ directory level. In this example, only the innermost CMakelists.txt and Kconfig file pairs contain actual build or configuration instructions. Working from the innermost directories back to the example-application/ directory, these file pairs act as sign posts, directing the build tools to find the innermost build and configuration instructions.

Open the Kconfig file in example-application. It has only one line: rsource "drivers/Kconfig". This directs the build system to look in the ‘drivers’ folder for more Kconfig information. The Kconfig file in the ‘drivers’ folder will further direct to the sensor folder. To the Kconfig file in the sensor folder which adds rsource "hx711/Kconfig". This will complete the Kconfig navigation to the hx711 folder where you earlier placed the Kconfig file containing the actual Kconfig build instructions.

The CMakeLists.txt files work in a similar manner directing the build system to inner directories. In the Example Application project the first CMakeLists.txt file that will need modification is in the ‘sensor’ folder. Here you will add add_subdirectory_ifdef(CONFIG_HX711 hx711) following the existing example for the example sensor. This is the configuration file that defines the incorporation of the driver when CONFIG_HX711=y is defined in the proj.conf file. The innermost CMakeLists.txt file that was already added will define the source files for the driver.

Adding attributes and channels

The last item to discuss is the addition of attributes and channels to the driver header file. In the PR, notice that changes were added to two enumerated lists in include/drivers/sensor.h. The second-to-last item in each of these lists is a ‘private start’ item. We can use this to create custom extensions of the enumerated lists for attributes and channels.

Add the following code to the top of the hx711.h file in your project to accommodate the attribute and channel additions in the driver file instead of the internal sensor.h file:

/** @brief Sensor specific attributes of hx711. */
enum hx711_attribute {

	/**
	 * The sensor value returned will be altered by the amount indicated by
	 * slope: final_value = sensor_value * slope.
	 */
	SENSOR_ATTR_SLOPE = SENSOR_ATTR_PRIV_START,

	/**
	 * The sensor gain.
	 */
	SENSOR_ATTR_GAIN,
};

/** @brief Sensor specific channels of hx711. */
enum hx711_channel{

	/** Weight in grams */
	SENSOR_CHAN_WEIGHT = SENSOR_CHAN_PRIV_START,


};

The project can now be built with the following line:

west build -p -b nrf52dk_nrf52832 app/.

I hope this article helped sort out some of the confusion surrounding the Zephyr methodology of adding and interacting with a sensor driver. All drivers must satisfy the three components of build direction/inclusion files, the sensor yaml and target overlay file pair, and the incorporation of the generalized sensor access functions. Seeing all of these components come together in the Out-of-Tree driver context should provide valuable insight into how they function within the drivers that are internal to Zephyr.

At Golioth, we talk about 3 things that make for likely hardware/firmware compatibility with our Cloud:

  • Running Zephyr RTOS
  • Have sufficient overhead to run the Golioth code in conjunction with other Zephyr code (about 2K extra code space)
  • A network interface in Zephyr

(this is not the only way to connect, just a good formula for getting started)

It’s that last point that disqualifies a bunch of boards in Zephyr. Maybe you love the STM32 feature set, but your board doesn’t have a modem to get to the internet. What then?

The great thing about Zephyr is that network interfaces are often abstracted to the point that you can add one after the fact to your board, say with a wire harness to a different PCB. If you’re at the design phase, you could also add the ESP32 as a co-processor to add connectivity. We have shown this in the past with Ethernet and with WiFi, and we’re working on a sample that adds a non-native cellular modem.

This article will show how to add WiFi to your Zephyr project in a cheap and efficient manner, using a $5 ESP32 board put into ESP-AT mode. Your project instantly has network connectivity (and a few other tricks too!).

AT commands? Like on my brick phone?

We’ll talk about the hardware in a bit, but the software part of this hinges on communication between processors using the ESP-AT command set.

AT Commands?? Like from the 80s?

Actually, exactly like that. And not just from your brick phone, the Hayes Command Set was created in 1981 for a 300 baud modem. It has survived 40 years later due to the easy connection over a serial interface (UART), which makes boards-to-board or chip-to-chip connectivity well understood and almost universally available. In fact, many of the cellular modems on the market if not using AT command sets directly (it has an ETSI standard), at least have an “AT mode” for setting up communications with cellular towers and troubleshooting.

The benefit is that the ESP32 acting as a secondary processor means a wide range of parts can talk over the UART interface. Though we’re talking about Zephyr in this post, a previous example showed a Cortex-m0+ running our Arduino SDK in conjunction with the ESP32 modem. On the Zephyr side of things, you can view the wide range of boards that are supported on our hardware catalog, including boards as powerful as the Altera Max 10 FPGA board and as small as the Seeeduino XIAO.

Set up the modem

The ESP32 AT command firmware is just a binary. If you find the proper module and chipset, you should be able to download it directly onto your board. The board the ESP32 module is mounted on doesn’t really matter, as long as you have access to the pins and can tell which pin on the PCB routes back to which pin on the module.

In this example, we are working with the ESP32-WROOM-32. This is one of the most common modules on the market today. You can find which module you have by looking at the laser etching on the metal can on the module itself.

I downloaded the latest binaries (V2.2.0.0 as of this writing) from the Espressif site. I will also show the command below using that version number, though you should use the newest version that is available. There is also a page that lists the different type of binaries and the associated pin numbers you’ll need to connect to when testing below.

esptool.py write_flash --verify 0x0 ~/Downloads/ESP32-WROOM-32-V2.2.0.0/factory/factory_WROOM-32.bin

Testing the modem

Once you have successfully programmed the modem, you’ll want to test it. This will involve manually typing in AT commands to a serial interface / terminal. While that might seem like an inefficient way to work with a modem, it’s a good skill set to have if you need to troubleshoot your setup at a later time.

You will need a USB to serial converter, or some other way to communicate with a UART. These are available on Amazon for $5 or less. You do not need any fancy features on this device.

If you’re using the ESP32-WROOM32 like me, you’ll have a setup like above. Hook up your USB to serial converter TX pin to pin 16 (ESP32 RX) and the converter’s RX pin to pin 17 (ESP32 TX). Note that there are pins labeled TX and RX on the dev kit, but those are the console output for the processor. The easy way to test is if you hit the Reset button (labeled “EN” on this board), you will see all of the boot sequence scrolling across the screen if hooked into TX/RX. If you are connected to the proper output (16/17), you will only see a ready prompt when the board is booted. Reminder to check the pin numbers if you’re using a different module than above.

In terms of the program to connect you to the USB to serial and communicate with the ESP32, a small warning about line endings. After initially using screen on Linux, I found that the line endings were not compatible with the ESP-AT family. I could see the ready prompt, but I could not enter any data. After some digging I found that you need to be able to send a Carriage Return / CR (\r) and a Line Feed / LF (\n). I followed this advice and downloaded and installed picocom and used the following command on the command line to launch a more interactive terminal: picocom /dev/ttyUSB0 --baud 115200 --omap crcrlf

This enabled me to try out various commands in the ESP-AT Command Set. Two in particular stood out to me as interesting, even though they are not implemented below:

  • AT+SLEEPWKCFG – Allows you to set the “light sleep” command for the modem and tell the modem which pin will be used for waking the modem.
  • AT+BLEGATTSSETATTR – This sets the GATT profile for the modem in Bluetooth LE mode. The command is actually just one of many commands…I didn’t realize that it was also possible to use the modem as a Bluetooth LE gateway as well!

Use the modem with samples

One hardware combination that is well supported in Golioth samples is the nRF52840 and the ESP32. Our “Hello” sample shows how you can configure the device and compile firmware for the nRF52840 while still taking advantage of the ESP-AT modem connected to it.

If you don’t have the nRF52840DK (Developer Kit), there are a range of other boards that will work. When you start actually running the demo, it will be very similar to our getting started using the ESP32 (natively), or the nRF9160. Our goal is to make a seamless experience once you have a network connection. We always love discussing projects in our forum, our Discord, and on Twitter.