Pin mapping in Zephyr lets you move pin functions with ease

Pin mapping has long been a headache for embedded development. Those cold, hard pins on the microcontroller are connected to actual hardware, so a number of chip makers have figured out how to reroute them internally using code. Historically that code can be complex, and spawn a rash of gui-based configuration tools with varying success. But Zephyr does a pretty good job of making pin assignments painless using devicetree overlay files. Let’s take look!

Real life portability

The ah-ha moment for me was when I switch from one architecture to another during a project. We’ve been developing some demo hardware here at Golioth, and as the hardware engineer on the project, Chris Gammell chose to use the Feather footprint for swapability. I started working with the Circuit Dojo nRF9160 Feather during development but later wanted to test out the Adafruit ESP32 Huzzah Feather. Same PCB footprint, same features enabled, should be fine, right? Not so fast.

Zephyr sells itself on portability and I was able to compile the code for both devices without any hiccups. The problem is that the Feather board specification designates where the i2c pins will be on the PCB layout. When I made the switch from the compiling the project with the nRF91 to the ESP32, those signals ended up on different physical pins of the board I was using. This kind of ruined my day… but only for like 5 minutes until I figured out how easy pin remapping is. I literally just created an overlay file that says: “hey, put these signals on these other pins (please).”

&i2c0 {
	sda-pin = < 23 >
	scl-pin = < 22 >
};

This devicetree file is pretty easy to read. I’ve told the build system that I need i2c0 pins to be on GPIO22 and GPIO23. Note that these are GPIO numbers and not pin numbers. ESP32 can be a bit confusing, just stick with the GPIO number and checkout my other post on the quirks of specifying ports for those pins when you need them.

Check for pin collision

The good news is that moving pins just works, at least on devices that have a pinmux. The bad news is that sometimes it can cause other problems and you don’t get a warning about it. In Zephyr it is up each of us to make sure we don’t assign multiple signals to the same pin.

When working with devicetree you should get used to reviewing the generated build/zephyr/zephyr.dts file. This is created by the build tools which combine the board-specific .dts from the Zephyr tree with any overlay files you have in the boards directory of your project tree. Looking at the output allows you to check the expected pins and features were as you expected.

zephyr devicetree entry for ESP32 i2c0

Left: Remapped pins        Right: Stock pin assignments

Here you can see the pin remapping as the final two lines of the i2c0 peripheral entry. On the right are the original pin assignments, and the left are the updated assignments thanks to my overlay file (Note that our decimal values from the overlay file have been converted into hexadecimal).

To avoid pin collision, search this zephyr.dts file for 0x16 and 0x17. In my case, spi3 is also using one of these pins: mosi-pin = < 0x17 >;. If you’re not going to enable that peripheral in your build this is no big deal. But if one of your active peripherals is already mapping a pin you need, the solution is pretty simple. Just remap the pin on that peripheral in the same way you did the i2c pins.

I have been searching for a way to map unused pins to a null value but haven’t yet found a way. If you have the answer, we’d love to hear about it over on the Golioth Discord channel.

Using variant overlay files

If you’re going to build your code base for several different hardware variations, it’s a good idea to keep a set of specialized overlay files. In my case I made one called esp32-feather.overlay. I still have an overlay file for the ESP32, but when I build for the Feather variant I specify the specialized overlay in the build commands:

west build -b esp32 . -D DTC_OVERLAY_FILE=boards/esp32-feather.overlay -p

Other architectures

What’s that you say? You’re not using an ESP32. Well, it’s really no different. For example, when building for the nrf52dk_nrf52832, you can move the mosi-pin of spi2 to P0.20 pretty easily.

&spi2 {
        mosi-pin = < 20 >
};

But beware if you do. The nRF52 Development Kit already has an LED mapped to that pin.

The devicetree makes moving peripheral pins quite painless. There’s even more power under the hood, as Zephyr provides access to use the pinmux, making it possible to move pins at runtime. We’ll discuss that and other Zephyr tips in future posts.