How can I create an array of pins?

Hi,

after a while learning and reading about Rust, I decided that it's now time for me to contribute to Embedded Rust with a crate of my own :slight_smile:

Unfortunately I hit a roadblock with the type system.

I want to create a struct that holds the pins for a 8-bit parallel data bus:

pub struct DataBus<T> {
    pins: [T; 8],
}

impl<T> DataBus<T> 
where T: embedded_hal::digital::v2::OutputPin {
    pub fn new(pins: [T; 8]) -> DataBus<T> {
        DataBus {
            pins
        }
    }
}

since every pin has its own type, I made the array generic over T.

Now, when I want to instantiate a DataBus like this:

let d0 = gpiob.pb0.into_open_drain_output(&mut gpiob.crl);
let d1 = gpiob.pb1.into_open_drain_output(&mut gpiob.crl);
let d2 = gpiob.pb2.into_open_drain_output(&mut gpiob.crl);
let d3 = gpiob.pb5.into_open_drain_output(&mut gpiob.crl);
let d4 = gpiob.pb6.into_open_drain_output(&mut gpiob.crl);
let d5 = gpiob.pb7.into_open_drain_output(&mut gpiob.crl);
let d6 = gpiob.pb8.into_open_drain_output(&mut gpiob.crh);
let d7 = gpiob.pb9.into_open_drain_output(&mut gpiob.crh);

let dbus: [embedded_hal::digital::v2::OutputPin; 8] = [d0, d1, d2, d3, d4, d5, d6, d7];

let data_bus = DataBus::new(dbus);

I get the following error:

error[E0277]: the size for values of type `dyn embedded_hal::digital::v2::OutputPin` cannot be known at compilation time
  --> src/main.rs:64:20
   |
64 |     let data_bus = DataBus::new(dbus);
   |                    ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   | 
  ::: src/ili9486/gpio.rs:61:20
   |
61 | pub struct DataBus<T> {
   |                    - required by this bound in `ili9486::gpio::DataBus`
   |
   = help: the trait `core::marker::Sized` is not implemented for `dyn embedded_hal::digital::v2::OutputPin`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>

I did read the referenced chapter in the book, but I'm still lost with this. How can I create an array of pins? I would not like to have a struct like this:

pub struct DataBus<D0, D1, D2, D3, D4, D5, D6, D7> {
    d0: D0,
    d1: D1,
    d2: D2,
    d3: D3,
    d4: D4,
    d5: D5,
    d6: D6,
    d7: D7,
}

impl<D0, D1, D2, D3, D4, D5, D6, D7> DataBus<D0, D1, D2, D3, D4, D5, D6, D7>
where
    D0: embedded_hal::digital::v2::OutputPin,
    D1: embedded_hal::digital::v2::OutputPin,
    D2: embedded_hal::digital::v2::OutputPin,
    D3: embedded_hal::digital::v2::OutputPin,
    D4: embedded_hal::digital::v2::OutputPin,
    D5: embedded_hal::digital::v2::OutputPin,
    D6: embedded_hal::digital::v2::OutputPin,
    D7: embedded_hal::digital::v2::OutputPin,
{
    pub fn new(
        d0: D0,
        d1: D1,
        d2: D2,
        d3: D3,
        d4: D4,
        d5: D5,
        d6: D6,
        d7: D7,
    ) -> DataBus<D0, D1, D2, D3, D4, D5, D6, D7> {
        DataBus {
            d0,
            d1,
            d2,
            d3,
            d4,
            d5,
            d6,
            d7,
        }
    }
}

Remove this annotation

let dbus: [embedded_hal::digital::v2::OutputPin; 8]

OutputPin is a trait, so this doesn't do what you expect it to. It tries to create an array of trait objects, but that is invalid because trait objects are unsized.

That doesn't work because each pin has a different type and I want to have an array of 8 different pins.

error[E0308]: mismatched types
  --> src/main.rs:62:21
   |
62 |     let dbus = [d0, d1, d2, d3, d4, d5, d6, d7];
   |                     ^^ expected struct `stm32f1xx_hal::gpio::gpiob::PB0`, found struct `stm32f1xx_hal::gpio::gpiob::PB1`
   |
   = note: expected type `stm32f1xx_hal::gpio::gpiob::PB0<stm32f1xx_hal::gpio::Output<stm32f1xx_hal::gpio::OpenDrain>>`
            found struct `stm32f1xx_hal::gpio::gpiob::PB1<stm32f1xx_hal::gpio::Output<stm32f1xx_hal::gpio::OpenDrain>>`

error: aborting due to previous error; 2 warnings emitted

Hi there,

well as far as I'm aware you are not able to directly store trait object within an array. The structure that is implementing the same trait might be different and therefor have a different size (memory footprint). So the compiler is not able to deduce the size the array should occupy on the stack to store all possible variations of the trait implementations (trait objects). To solve this you would typically Box the trait objects to unify them according to their size and thus enable to stack them in an array like this:

pub struct DataBus {
    pins: [Box<dyn embedded_hal::digital::v2::OutputPin>; 8],
}

The draw back in regards to embedded development is that this approach requires an allocator to be present as the Box stores the trait objects on the heap memory and provides a "pointer" to your trait objects where the size of this "pointer" is known and therefor the size of the array itself (which remains on the stack) could be calculated.

There are some no_std minimalistic allocators available on crates.io if I remember correct, maybe there is one that fit's your needs. If you are not able to use an allocator for whatever reason I guess your only option is to use struct with as many generics as pins you'd like to store.

For memory allocation you can use https://github.com/rust-embedded/alloc-cortex-m which is developed by the Cortex-M team, but it requires Rust Nightly.
Or you can use the default allocator, see here:
https://doc.rust-lang.org/stable/embedded-book/collections/index.html
https://doc.rust-lang.org/alloc/index.html

I would 100% not like to have to add an allocator to my project just to deal with hardware initialization. I guess in the end I will have to use the option I didn't want to use.

You could also downgrade the pin, see https://docs.rs/stm32f1xx-hal/0.6.0/stm32f1xx_hal/gpio/gpioa/struct.PA1.html#method.downgrade

3 Likes

An example is here:

Another downside of box+dyn is obviously the impact on performance (indirection).

Perfect :smiley: I think downgrade() is the solution to my problem.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.