Converting an Embedded HAL impl from digital::v1 to digital::v2 traits

A while ago people had the idea to introduce fallible traits for GPIO pin to allow the implementation of virtual pins and port expanders. In this blog I'm demonstrating how to convert an existing implementation to the v2 version of the traits.

Some background

Embedded HAL (embedded-hal on crates.io) contains a number of traits specifying the interface to implement and use peripherals usually found on embedded devices. The digital traits in particular define functions/methods to be used to both read the electrical state of an input pin as well as change the electrical state of an output pin.

Now a pin might not be necessarily a thing which directly corresponds to some local memory at the device the program itself is running on but might be managed by a remote chip. The big difference is that usually with a local pin there's really nothing that can go wrong when reading or changing state as long as you can access the memory location related to it[1]. This all changes if the pin is not under direct control, e.g. because you're using something like my embedded-bridge or a GPIO exander chip like the PCF857x, in which case there are number of reasons which a seemingly operation like reading the state might actually fail and you want to convey that information to the user of the HAL implementation.

This is why people decided to introduce a new version of the digital traits to convey errors along. The new traits were introduced in the version 0.2.3 of embedded-hal which came with quite a bit of controversy due to the patch level only bump together with a rather prominent (and thus potentially annoying) deprecation warning.

But since digital::v2 is here to stay, let's see how we can actually make the switchover.

Switching versions

The following steps are excertps from the nrf51-hal conversion, so if you'd like to follow along you are invited you to check out this commit.

So let's get started...

Use the right version of the traits

In order to use digital::v2 rather than digital::v1 you either need to spell out the version explicitely all the time or you may want to change a use statement to import the correct version into the namespace.

So before I used:

use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin};

and I switched that over to:

use embedded_hal::digital::v2::{InputPin, OutputPin, StatefulOutputPin};

Changing function signatures

Since the new versions always return a Result and the Err part of the Result may vary, there's now an associated Error type which needs to be specified.

Since (as mentioned before) for direct MCU pins the operation usually infallible I'm specifying the type to be core::convert::Infallible, so we're changing from:

impl<MODE> OutputPin for $PXx<Output<MODE>> {

to:

impl<MODE> OutputPin for $PXx<Output<MODE>> {
    type Error = Infallible;

and then of course we need to change the functions to return a result, too, so we go from:

fn set_high(&mut self) {
    ...
}

fn set_low(&mut self) {
    ...
}

to:

fn set_high(&mut self) -> Result<(), Self::Error> {
    Ok(...)
}

fn set_low(&mut self) -> Result<(), Self::Error> {
    Ok(...)
}

Of course in some cases it's slightly more complex because the is_* methods are often implemented based on the other implementation and now we need to take into account that we're not just inverting a truth value but working with a Result so we might have to go from:

fn is_set_high(&self) -> bool {
    !self.is_set_low()

to something like this:

fn is_set_high(&self) -> Result<bool, Self::Error> {
    self.is_set_low().map(|v| !v)
}

Prelude

An additional thing you might want to do is to make sure that you add the used digital traits to the prelude of the HAL impl so users can simply use the new impls without much fuzz.

So here we add the following to src/prelude.rs:

pub use embedded_hal::digital::v2::InputPin as __nrf51_hal_rng_InputPin;
pub use embedded_hal::digital::v2::OutputPin as __nrf51_hal_rng_OutputPin;
pub use embedded_hal::digital::v2::StatefulOutputPin as __nrf51_hal_rng_StatefulOutputPin;
pub use embedded_hal::digital::v2::ToggleableOutputPin as __nrf51_hal_rng_ToggleableOutputPin;
pub use embedded_hal::digital::v2::toggleable as __nrf51_hal_rng_toggleable;

Bump the minor version

This these changes will be breaking for your users you definitely want to well document the change, at least by bumping the minor version so people will not simply run nasty and unexpected troubles when compiling their code.

And with this you are done and I hope to see you again on my blog soon.

[1]: There might be some security features thwarting that direct acces but in the vast majority of cases this not a problem