GPIO pins, types, and traits

This is intended to be a follow-on to the discussions here and here.

I’d like to open a little discussion and show some of the work I’ve been doing to implement GPIO on a few devices. Currently there are two pin trait families, InputPin and OutputPin. As already mentioned, many chips expose more complex pin configurations, and the type system is an excellent tool for encoding them.

A simple case, for the STM32L0x1 family, has a few structs:

pub struct Output<MODE, PUMODE> { ... }
pub struct Input<MODE> { ... }
pub struct Analog(());
pub struct $af; // macro for AF1, AF2, ...

When we turn a Pin into an output using,

pub fn into_output<OMode: OutputMode, PUMode: InputMode>( ... ) -> $PXi<Output<OMode, PUMode>> {

Each of the generic marker types consists of a family of structs that impl related types. For Output, the most complicated,

// for OMode...
pub trait OutputMode { ... }
pub struct PushPull;
impl OutputMode for PushPull { ... }
pub struct OpenDrain;
impl OutputMode for OpenDrain { ... }

// for PUMode...
pub trait InputMode { ... }
pub struct Floating;
impl InputMode for Floating { ... }
pub struct PullDown;
impl InputMode for PullDown { ... }
pub struct PullUp;
impl InputMode for PullUp { ... }
// ...

My code ends up looking like…

let vcp_rx = gpioa
    .into_output::<PushPull, Floating>(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.pupdr)
    .into_alt_fun::<AF4>(&mut gpioa.moder, &mut gpioa.afrh);
vcp_rx.set_pin_speed(PinSpeed::VeryHigh, &mut gpioa.ospeedr);

(Much!) more complex cases arise in my implementation for the LPC177x/8x family (no link, sorry, forum limitation). There, pins are grouped into “types” (type D, type W, type A, …), each having their own combination of dimensions, and configurable as inputs, outputs, analogs, etc:

// "Input" types across pin types
pub struct TypeDInput<PUPDMODE, HYS, INV> { ... }
pub struct TypeWInput<PUPDMODE, HYS, INV, FILTER> { ... }
pub struct TypeAAnalogInput;
pub struct TypeADigitalInput<PUPDMODE, INV, FILTER> { ... }
pub struct TypeIInput<INV, HS, HIDRIVE> { ... }
pub struct TypeUInput<NONCE> { ... }

with each of the various marker generics here combining in ways specific to the pins. Ultimately, the methods to change pin configurations look very similar…

let mut led_3_red = pins
    .into_output::<hal::gpio::Inactive, hal::gpio::StdSlew, hal::gpio::PushPull>();

Not bad. A bit hairy to implement, though.

Given the plethora of mode types (pullup/down, pushpull/od, hysteresis, inversion, drive strength, input filtering, …), I’m curious to see what other people are doing, and whether the community thinks any of these should end up in embedded-hal.