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.
-
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.