Skip to content

Spike: Gpio pins as a statically typed heterogeneous lists using frunk #745

Closed
@Ben-PH

Description

@Ben-PH

Using frunk, and the following macro I managed to put together...

(You could think of it as a tuple, idexable by type)

macro_rules! create_pins {
    ($head:expr, $($tail:expr),+) => {
        HCons<GpioPin<Unknown, $head>, create_pins!($($tail),*)>
    };
    ($single:expr) => {
        HCons<GpioPin<Unknown, $single>, HNil>
    };
}

macro_rules! create_pins_head {
    ($head:expr, $tail:expr) => {
        HCons<GpioPin<Unknown, $head>, $tail>
    };
}

Using the macro like so:

type HetPinList = create_pins!(0, 1, 2, 3);

...expands to:

type HetPinList = HCons<GpioPin<Unknown, 0>,
                  HCons<GpioPin<Unknown, 1>,
                  HCons<GpioPin<Unknown, 2>,
                  HCons<GpioPin<Unknown, 3>,
                  HNil>>>>;

...which provides this handy interface

...so lets use it in our IO struct:

/// General Purpose Input/Output driver
pub struct IO<T: HList>
{
    _io_mux: IO_MUX,
    pins: T
}

impl<T: HList> IO<T> {
    /// Construct the initial list of pins
    pub fn new(gpio: GPIO, io_mux: IO_MUX) -> !/*IO<create_pins!(1, 2, 3)>*/ {
        let pins = todo!(); // construct the actual pins
        let io = IO {
            _io_mux: io_mux,
            pins,
        };
        io
    }
}

impl<T: HList + Plucker<GpioPin<Unknown, { PIN_NO }>, HList>> IO<T> {
    /// Claim a pin
    pub fn pin<const PIN_NO: u8>(&mut self) -> GpioPin<Unknown, PIN_NO> {
        let (pin, rest) = self.pins.pluck();
        self.pins = rest;
        pin
    }
}

note: the pins field is now private, replaced by a special getter, and instead of let pin18 = io.gpio18, you do let pin18 = io.pin::<18>()... though this depends on IO::pin taking &mut self, which might be difficult.

another example: The ability to construct a USB like so, as the type system should be able to infer that the pins need to be pins 18, 19, and 20 (esp32s3):

    let usb = USB::new(
        peripherals.USB0,
        io.pluck(),
        io.pluck(),
        io.pluck(),
        &mut system.peripheral_clock_control,
    );

That's unlikely, though, as the pin-list is a generic, changing it's monomorphised type each time you remove a pin

Possible pros that I see so far:

  • The possibility of compile-time, integer indexing to select pins
  • "auto-indexing" where the type system can infer which pin to take, such as when picking pins for Usb::new (each pin-arg can use only one pin)
  • It almost feels like GpioPins are a perfect use-case for Heterogeneous lists: Too different to be in an array without the overhead of dynamic dispatch (array of trait objects), and yet GpioPins are a series of the same thing, just with seperate, well defined, roles.

Possible cons:

  • A very FP-based design. Those not familiar with FP style lists might have a tough time getting to grips with this
  • Performance traps: Could this cause an explosion in the code size or compile time due to the huge(?i think?) leverage of compile-time generics

I'll continue investigating this...

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions