Including a Device Tree Overlay in an Elixir Nerves Project

I’ve been working on a Raspberry Pi 3 and Elixir Nerves project that utilizes an additional piece of hardware attached through an SPI controller. In order to get this hardware recognizable in Raspbian, I had to include an additional device tree overlay with my Nerves firmware. Raspbian uses a device tree to manage resource allocation and module loading.[1] In order to make the kernel aware of our external SPI device, a device tree blob (DTB) that describes the hardware must be provided to the kernel on boot. While the process was pretty straight forward for Raspbian, I was left a little lost when it came to translating that over to a Nerves RPI3 system configuration. This was my first Nerves project, and I found the documentation was a little light in this area. (I have since updated the Nerves Advanced Configuration documentation to include information about device tree overlays.)

There are two steps required to enable the SPI interface. First the device tree blob must be built into the Nerves firmware. Once this is done, the overlay must be loaded and configured when the device boots. The example here will be focused on Raspberry Pi 3, but it should provide enough of a foundation that It will also be helpful for configuring other similar Nerves systems. Note that in addition to what is described here for loading the DTB, the kernel must also be configured to load any drivers that are required by the connected hardware. Another quick note before we jump in: There is a good chance of finding the required DTB in the Raspberry Pi firmware, so take a look at the firmware overlays README for the list of available overlays. If not listed there, one may be available from the hardware supplier.

Adding an additional DTB to the Nerves firmware

In order to add additional files to my firmware image, I first had to configure Nerves to use my own custom fwup.conf file. This file is found in the repository for the desired nerves system, so in this case, I copied it from nerves_system_rpi3 and placed into my project at config/fwup.conf. I add an additional configuration line to config/config.exs so Nerves loads my new one instead of the default one:

# config/config.exs
config :nerves, :firmware,
  fwup_conf: "config/fwup.conf"

I can now use this custom config to tell Nerves to include my additional device tree blobs by adding additional file resources to the firmware build. For my project I’m using a Raspberry Pi3 with a PiCAN2 interface attached to it. The PiCAN2 uses an MCP2515 CAN controller, and the hardware’s documentation states I’ll need to configure the mcp2515 device tree overlay on boot. Thankfully for me, this DTB is included in the Raspbian image, so I just needed to direct Nerves to pull it in when it builds the firmware, then configure it on boot. Inside my new fwup.conf I defined a new file-resource for my dtbo, then I pointed it at the one that already existed in the RPI system image:

# fwup.conf

file-resource mcp2515-can0.dtbo {
    host-path = "${NERVES_SYSTEM}/images/rpi-firmware/overlays/mcp2515-can0.dtbo"
}

Next I needed to make sure the dtbo file is written to the destination media on build and update of my firmware. I added a new on-resource declaration for each of the three firmware tasks:

# fwup.conf

task complete{
    # ... look for where `on-resource` directives are already defined and add:
    on-resource mcp2515-can0.dtbo {
        fat_write(${BOOT_A_PART_OFFSET}, "overlays/mcp2515-can0.dtbo")
    }
}

task upgrade.a {
    # ...
    on-resource mcp2515-can0.dtbo {
        fat_write(${BOOT_A_PART_OFFSET}, "overlays/mcp2515-can0.dtbo")
    }
}

task upgrade.b {
    # ...
    on-resource i2c-sensor.dtbo {
        fat_write(${BOOT_B_PART_OFFSET}, "overlays/mcp2515-can0.dtbo")
    }
}

*Note that the BOOT_x_PART_OFFSET variable for each task matches the partition being written.

Loading and configuring the new DTB

There’s one last change I needed to make in my fwup.conf. I need to configure my device tree overlay inside the Raspbian config.txt, which means I needed to provide my own config.txt file to be loaded instead of the default. I copied the file from nerves_system_rpi3 like I did for fwup.conf and I placed it in my project at config/config.txt. Inside my fwup.conf there is already a file-resource for config.txt, so I simply edited it to point at my newly created one:

# fwup.conf

file-resource config.txt {
    host-path = "${NERVES_APP}/config/config.txt"
}

The last remaining piece is to add the configuration line to my new config.txt

At this point the DTB overlay will be available to load inside config/config.txt during boot. Follow the documentation for the new hardware here. For my MCP2515 I add

# config.txt

dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=25

A Quick Recap

At this point I’ve configured fwup.conf to include my additional DTB file when the firmware is burned. Since Nerves uses an A<->B boot rotation, I updated all build tasks to make sure the file is loaded to all boot partitions. I then configured my device to load when my firmware is booted. Now I can pop into an IEX session on the device and double check that everything was loaded.

Interactive Elixir (1.5.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Nerves.Runtime.Shell.start
$ dmesg | grep mcp
[    5.887757] mcp251x spi0.0 can0: MCP2515 successfully initialized.

My hardware device is now loaded and configured and ready to be used in my Nerves project. As mentioned before, this example was specific to Device Tree Blobs on Raspbian, but the general idea is the same for other Nerves systems.

 

We'd love to hear how you are using Elixir and Nerves on a hardware project. Leave a comment below to let us know about your experience with it, or click the button to get in touch with us.


  1. https://www.raspberrypi.org/documentation/configuration/device-tree.md  ↩

Continue the conversation.

Lab Zero is a San Francisco-based product team helping startups and Fortune 100 companies build flexible, modern, and secure solutions.