-
Notifications
You must be signed in to change notification settings - Fork 64
Description
In order to create a USB device in the esp32s3-hal crate, you must provide it with the correct pins, which is constrained by marker traits. Current way of doing things, is select the correct pins in the Pins struct, then invoke the USB::new() constructor, like so.
I'm working on managing pins as an HList - you start with all the pins, and as you use them to make peripherals, they move from that list, into the device struct at the type-level: esp-rs/esp-hal#748
For structs that need more than one pin, the solution from what I've worked out is an esoteric where clause. If the burden of setting up this where clause is on the user, that would make the hal-crate pretty much unusable:
here is a working example. That `where` clause is _very_ non-trivial if you don't already know what to do:
struct Blinker {
pin4: GpioPin<Output<PushPull>, 4>,
pin5: GpioPin<Output<PushPull>, 5>,
}
impl Blinker {
fn initialize<L0, L0Remaining, L1Remaining>(
io: IO<L0>,
) -> (
Self,
IO<<L0::Remainder as Plucker<GpioPin<Unknown, 5>, L1Remaining>>::Remainder>,
)
where
L0: Plucker<GpioPin<Unknown, 4>, L0Remaining>,
<L0 as Plucker<GpioPin<Unknown, 4>, L0Remaining>>::Remainder:
Plucker<GpioPin<Unknown, 5>, L1Remaining>,
{
let (pin4, io) = io.pluck_pin();
let mut pin4 = pin4.into_push_pull_output();
pin4.set_high().unwrap();
let (pin5, io) = io.pluck_pin();
let mut pin5 = pin5.into_push_pull_output();
pin5.set_high().unwrap();
(Self { pin4, pin5 }, io)
}One thing that can simplify things:
Multi-pluck constructor via the builder: The complexity is reduced, but watch the verbosity...
impl<T> BlinkerBuilder<T> {
fn new(io: IO<T>) -> Self {
Self {
io,
pin1: None,
pin2: None,
}
}
fn set_pin1<Remain>(self) -> BlinkerBuilder<T::Remainder>
where
T: Plucker<GpioPin<Unknown, 4>, Remain>,
{
let (pin, io) = self.io.pluck_pin();
let pin = pin.into_push_pull_output();
BlinkerBuilder {
io,
pin1: Some(pin),
pin2: self.pin2,
}
}
fn set_pin2<Remain>(self) -> BlinkerBuilder<T::Remainder>
where
T: Plucker<GpioPin<Unknown, 5>, Remain>,
{
let (pin, io) = self.io.pluck_pin();
let pin = pin.into_push_pull_output();
BlinkerBuilder {
io,
pin1: self.pin1,
pin2: Some(pin),
}
}
fn build(self) -> (Blinker, IO<T>) {
let bl = Blinker {
pin4: self.pin1.unwrap(),
pin5: self.pin2.unwrap(),
};
(bl, self.io)
}
}Would it be useful to write a proc-macro that could be used like so?
// expands into something like the builder pattern I showed earlier
#[frunk::hl_builder]
struct Blinker {
// So the proc macro knows to pluck the value from the hlist being used
// probably not good to have the init option to the annotion
// Instead, blinker should just call the construction
// in its `initialize`/`new` method, and run the per-pin inits itself...
#[pluck_build(init = pin4_init)]
pin4: GpioPin<Output<PushPull>, 4>,
#[pluck_build(init = pin5_init)]
pin5: GpioPin<Output<PushPull>, 5>,
}I also recognize that this is getting a bit crazy. Is it possible that I'm missing something that would simplify things? is there already something baked into this crate that constructs by moving the types from a typelist into a struct? That's essentially what I'm trying to do. Generics seem to be more about transforming between structs that only differ in name. That's close, but I need something that differ by a const-generic value only. Same name, otherwise.