At Team Spatzenhirn, the Carolo-Cup (1:10 scale autonomous driving competition) team at Ulm University, we use Zephyr for the software on our interface board. Recently, we had the need to connect an PMW3389 sensor to the board, for which we didn’t find any already existing Zephyr drivers. This is not the first Zephyr driver we built, but the previous drivers have all been built in-tree, inside our fork of Zephyr. While it would be cool to upstream those eventually, the main goal would be to make the drivers easily usable by other Zephyr users, even if the driver is not included in Zephyr by default.

This post intends to give an overview of the layout of an out-of-tree sensor driver for zephyr, following our PMW3389 driver as an example:

West

Zephyr makes use of the west tool to load external components, we even use west to download zephyr itself in our application workspace. To make the driver usable as a west module, a file zephyr/module.yml is required in the repository. It defines how the module is built, where to find additional Kconfig files and devicetree sources:

build:
  cmake: .
  kconfig: Kconfig
  settings:
    dts_root: .

Now, an application wishing to use the driver can add the following to its own west manifest. Our manifest looks something like this (the parts downloading zephyr itself omitted):

manifest:
  remotes:
    - name: teamspatzenhirn
      url-base: https://github.com/teamspatzenhirn
  projects:
    - name: pmw3389_zephyr_driver
      remote: teamspatzenhirn
      revision: zephyr
      path: modules/pmw3389
  self:
    path: manifest

DTS

As with an in-tree driver, the DTS binding is defined at dts/bindings/sensor/vendor,device.yaml, in our case that would be dts/bindings/sensor/pixart,pmw3389.yaml . Here is the DTS binding for our sensor, which is just the same as every other sensor DTS binding:

description: |
  Motion Sensor  

compatible: "pixart,pmw3389"

include: spi-device.yaml

properties:
  resolution:
    type: int
    required: true
    description: |
      Resolution in counts per inch, multiples of 50, from 50 to 16000      

Note that the vendor must be in the list of known vendor prefixes, so if the vendor is not known yet in upstream zephyr, you can just provide dts/bindings/vendor-prefixes.txt, which will be merged with the upstream file at build time:

pixart	PixArt Imaging Inc.

(Note that the separator between the vendor prefix and the description is a tab…)

Kconfig

The main Kconfig file just includes the sensor-specific Kconfig file next to the sources:

rsource "drivers/sensor/pmw3389/Kconfig"

Driver Sources

The driver sources at drivers/sensor/pmw3389/ are just the same as any other in-tree driver. Take a look at our driver sources or any other sensor driver as an example.

Headers

As far as I can tell, the in-tree drivers don’t handle the headers in a special way, since the driver directory is in the include path anyway. For many drivers, the header is not needed by the application anyway, since the application just uses the generic sensor API. We did however need a header to make custom sensor channels and an additional raw-data readout function available, see the header at include/pmw3389.h.

CMake

The only addition we had to make to the usual add_subdirectory_ifdef(CONFIG_PMW3389 drivers/sensor/pmw3389) was to make the header mentioned above available. See the full CMake file at CMakeLists.txt.

References

These blog posts and samples have been helpful in figuring this out: