How to Build Drivers for Zephyr | Interrupt

If you’ve heard the news about the nRF9160 then you know it’s a game-changer. ARM Cortex M33 + LTE Cat-M + NB-IoT and GPS all included under one System in Package. Drool-worthy, right?


This is a companion discussion topic for the original entry at https://interrupt.memfault.com/blog/building-drivers-on-zephyr

Great article! This is something that Zephyr community differently need more, hands on examples how to do grate stuff!

1 Like

Thanks @voja! More examples and code coming soon. I don’t want people to go through the muck I did trying to get things to work. That was paaaainful. :upside_down_face:

@jaredwolff Thank you Jared for this great post! May I ask you a question about Kconfig? Apart from the conditional compilation in CMakeList, it seems to me that according to the boolean value of Kconfig symbols, some driver API functions will be compiled and some won’t (e.g. in /zephyr/include/uart.h). So to see which API functions my application code is able to use, is going into corresponding driver API source and header file and looking for selected Kconfig symbols the only way?

Hi @jleng

Essentially yes. You can also run a west build -t menuconfig and this will open up the configuration screen which allows you to search for specific names. It also makes it a little easier to view the notes for those KConfig values. Otherwise, yep, your other option is to look manually at the driver code to see what those KConfig entries actually do.

Another thing I found useful is to look at the example code scattered throughout the Zephyr repo and NCS (if you’re using Nordic chips). The prj.conf files give you a great insight on what’s needed where.

Hope that helps!

1 Like

Hello @jaredwolff

I am currently using nRF52840 DK. On that board I2S peripheral is not available by default in device tree, but Zephyr has provided drivers for I2S. So i added I2S peripheral in dtsi and dts file. Also configured CONFIG_I2S=y in project configuration file. And after building the object i can see it’s giving a status Okay in Zephy.dts file.

Here is the snippet of the zephyr.dts

		i2s: i2s@40025000 {
		#address-cells = < 0x1 >;
		#size-cells = < 0x0 >;
		compatible = "nordic,nrf-i2s";
		reg = < 0x40025000 0x1000 >;
		interrupts = < 0x25 0x1 >;
		status = "okay";
		label = "I2S";
		sck-pin = < 0x11 >;
		lrck-pin = < 0x12 >;
		sdin-pin = < 0x13 >;
		my_i2s_device: mp34dt05@0 {
			compatible = "st,mpxxdtyy";
			reg = < 0x0 >;
			label = "MP34DT05";
		};
	};

Now after getting the device label, when i call device get binding function on it, result is a NULL pointer. Most probably the DEVICE_DEFINE() is not getting executed for I2S peripheral otherwise it would have found the I2S peripheral config pointers from ROM.

Below is my code for reference :

#include <zephyr.h> 
#include <sys/printk.h>
#include <sys/util.h>
#include <string.h>
#include <usb/usb_device.h>
#include <drivers/uart.h>
#include <stdio.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <drivers/i2s.h>


//Device structures
 const struct device *usb_dev;
const struct device *i2s_dev;


 / * The devicetree node identifier for the I2S */
 #define MY_I2S DT_NODELABEL(i2s)



void main(void)
{

   uint32_t dtr = 0;

//get usb device
usb_dev = device_get_binding(CONFIG_UART_CONSOLE_ON_DEV_NAME);

if (usb_enable(NULL)) {
        return;
}

while (!dtr) {
        uart_line_ctrl_get(usb_dev, UART_LINE_CTRL_DTR, &dtr);
}

if (strlen(CONFIG_UART_CONSOLE_ON_DEV_NAME) !=
    strlen("CDC_ACM_0") ||
    strncmp(CONFIG_UART_CONSOLE_ON_DEV_NAME, "CDC_ACM_0",
            strlen(CONFIG_UART_CONSOLE_ON_DEV_NAME))) {
        printk("Error: Console device name is not USB ACM\n");

        return;
}

k_msleep(100);

    //Checking status of I2S in device tree
  #if DT_NODE_HAS_STATUS(MY_I2S, okay)
        printk("I2S enabled in Zephyr DeviceTree \r\n");
 #else 
       printk("I2S is disabled in DeviceTree\r\n");
 #endif

  //Get I2S device 
     i2s_dev = device_get_binding(DT_LABEL(MY_I2S));
     if(i2s_dev == NULL){
      printk("I2S not found\r\n");    
    }else{
     printk("I2S found\r\n");    
  }


 while (1) 
   {

   }        
 }

Do you have any idea how to solve this issue ?

Pulling the correct device from the .dts can be tricky. Generally you’ll get a compile error if you’re using the macros incorrectly. You’ll have to pinpoint in your code where it’s NULL where it shouldn’t be.

I’m not sure why your i2s device is inside another label though. Generally those devices are outside (no indentation). For example here’s my i2c definition for the nRF9160 Feather:

&i2c1 {
	compatible = "nordic,nrf-twim";
	status = "okay";
	sda-pin = <26>;
	scl-pin = <27>;

	pcf85063a@51 {
			compatible = "nxp,pcf85063a";
			label = "PCF85063A";
			reg = <0x51>;
	};

};

I’m actually getting the device by simply using:

device_get_binding("PCF85063A");

But I think you should be able to do something like

uart = device_get_binding(DT_LABEL(DT_NODELABEL(i2s)));

I hope that helps!

Hello,
Thanks for the suggestion, it solved the issue. And my I2S device is under SOC label like all other peripherals of the board in Zephyr Dts file.

@jaredwolff thanks for the great guide. I tried following it to implement my own driver for an accelerometer in a Zephyr nRF project. The devicetree seems to be ok and I can see the c file for the driver getting compiled but it seems it is not getting linked to the main app. I fail to get a device_get_binding result. I am getting “undefined reference to” errors if I try to directly reference functions from the driver in the main app. Also I can’t find any strings from the driver in the .elf file.
Is there anything special that is responsible for linking the “zephyr_library” that I could have missed?
Any tips on how to troubleshoot?
Is there a way to download the code you have used in the guide?

Appreciate any help, Arik.

Good question Arik.

In general when you’re creating a device driver you’re going to be putting it within an “external” module. That means outside your project code. You can see that in a few repositories that I’ve worked on in the past:

There are a few important things you need to make sure you do:

  • Point your west.yml to the module with the driver in it
  • Make sure your module has a zephyr/module.yml defined
  • Make sure that your module has a Kconfig and CMakeLists.txt chain that point to your driver.

Modules always get called and compiled before your application. So that all has to be in place before you start building.

One of the users on my forum has been working though getting an accelerometer driver going as well. You can read through our dialogue and it may help your process.

https://community.jaredwolff.com/u/tedhavelka66

Let me know if you have any other questions!

Jared

Ok. Thanks, I got it. Once I split the driver and the client into separate parts and used the driver as a Zephyr module it all clicked.

1 Like

Awesome! Glad that helped!

This is an excellent walkthrough of the complex driver model and really filled in a lot of my understanding. Thank you!

Thanks for this tutorial @jaredwolff

I wanted to use it to write a driver for my display with an SSD7317.
As a template, I took the display driver of the SSD1306 which is already available in the device tree of Zephyr.

I have packed the shield definition into my overlay file, as I have connected it directly to my board.

	chosen {
		nordic,pm-ext-flash = &mx25r64;
		zephyr,display = &ssd7317;
	};
};

&arduino_i2c {
	status = "okay";

	ssd7317: ssd7317@3c {
		compatible = "solomon,ssd7317";
		reg = <0x3c>;
		width = <128>;
		height = <32>;
		segment-offset = <0>;
		page-offset = <0>;
		display-offset = <0>;
		multiplex-ratio = <31>;
		segment-remap;
		com-invdir;
		com-sequential;
		prechargep = <0x22>;
	};
};

Now when I want to use it with LVGL I have the problem that it doesn’t seem to find the device in the DTS.

I get a No SOURCES given to Zephyr library: drivers__display as a warning when compiling and at the end the linker fails with undefined reference to __device_dts_ord_128`'.

In zephyr.dts my driver is listed as expected.

However, if I place the same driver under zephyr/drivers/display and adjust the Kconfig and CMakeList.txt the compilation runs through without any errors.
It also works if I omit the zephyr_library() in the CMakeLists.txt of the driver.

Do you have any hints what I am doing wrong with the driver in my project or what I could still check?

It seems like maybe you’re copying the driver source to a different location? Is there a reason behind this?

My guess from the initial error is that you aren’t enabling the display driver in your config.

# Display
CONFIG_DISPLAY=y

Here is a full working sample that uses the Air Quality Wing on Zephyr with different board targets:

I hope that helps you!

Thanks for your reply @jaredwolff, but my problem isn’t the usage of different boards.

I wanted to create a new display driver within my project dir as mentioned in your blog post. But if I use this with lvgl it doesn’t compile (" undefined reference to __device_dts_ord_128").

That’s why I copied the unchanged driver files in my zephyr directory (zephyr/drivers/display) and edited manually the CMakeList and Kconfig of the zephyr driver directory and then it works and compiles correctly (without any change to my app code or project config)

Snippet from my prj.conf:

#new display
CONFIG_SSD7317=y
CONFIG_DISPLAY=y

CONFIG_LVGL=y
CONFIG_LV_MEM_CUSTOM=y
CONFIG_LV_USE_LOG=n
CONFIG_LV_USE_LABEL=y
CONFIG_LV_USE_BTN=y
CONFIG_LV_USE_IMG=y
CONFIG_LV_FONT_MONTSERRAT_14=y

I think he has the same problem:

Ahh ok.

So your most likely problem is you’re trying to change or update a driver with the same name as it exists elsewhere in the Zephyr tree. In my experience I had to rename my updated driver to something else so I could define it in the code. For example I added FIFO to the LIS2DH driver and had to rename it with an _ENHANCED suffix.

menuconfig LIS2DH_ENHANCED
	bool "LIS2DH Three Axis Accelerometer (Enhanced)"
	depends on I2C || SPI
	help
	  Enable SPI/I2C-based driver for LIS2DH, LIS3DH, LSM303DLHC,
	  LIS2DH12, LSM303AGR triaxial accelerometer sensors.

Whereas the original was simply:

menuconfig LIS2DH

A good clue to see what’s happening is to take a look at your build/zephyr/.config file to see whether things are enabled correctly or not.

Hope that helps!