I have a question about the embedded_hals design. I am currently implementing a device driver based on the embedded_hal, which uses lots of GPIO peripherals. I have a hard time abstracting over InputPin + OutputPin, because my device driver is supposed to reconfigure some pins from output to input.
Here is my PIN configuration:
8 or 4 databits: IN / OUT
3 dedicated only OUT pins.
I came up with the following idea: Create an internal PinHolder which can be used to change the pin mode between in and out configurations. The actual conversion must be implemented by the driver user. For that the trait PinModeConverter is used. The driver user supplies the conversion functions (by implementing the trait on a type), and all the pins in an initial state (OUT for all).
At the top level there is Driver::new_4bit, and Driver::new_8bit, which sets up the Driver, and then there is a generic over all the CFN, and generic over DataBits protocol implementation.
pub trait PinModeConverter {
type Output;
type Input;
fn into_output(pin: Self::Input) -> Self::Output;
fn into_input(pin: Self::Output) -> Self::Input;
}
enum PinHolder<IN, OUT> {
Output(OUT),
Input(IN),
}
impl<IN, OUT, CFN> PinHolder<IN, OUT>
where
CFN: PinModeConverter<Input = IN, Output = OUT>,
{
fn is_out(&self) -> bool {...}
fn is_input(&self) -> bool {...}
fn toggle_mode(&mut self) {
let is_out = self.is_out();
let mut swapper = unsafe {mem::uninitialized()};
mem::swap(self, &mut swapper);
swapper = if is_out {
PinHolder::Input(CFN::into_input(swapper.output().unwrap()))
} else {
PinHolder::Output(CFN::into_output(swapper.input().unwrap()))
};
mem::swap(self, &mut swapper);
mem::forget(swapper);
}
fn to_input(&mut self) {...}
fn to_output(&mut self) {...}
}
// Databit trait (implements 4 / 8bit mode of the peripheral)
trait DataBits {
fn raw_cmd(&self, cmd: RawCmd);
}
struct Data4bit<RO, R1, R2, R3, W0, W1, W2, W3>(PinHolder<R0, W0>, PinHolder<R1, W1>, ...);
struct Data8bit<...>(...);
impl<...> DataBits for Data4bit<...> {...}
// Private internal type to hold pins
struct OutPins<EN, RW, RS> {
en: EN,
rw: RW,
rs: RS,
}
pub struct Driver<EN, RW, RS, DATA> {
out_pins: OutPins<EN, RW, RS>,
data_pins: DATA,
}
impl<EN, RW, RS, RO, R1, R2, R3, W0, W1, W2, W3> Driver<EN, RW, RS, Data4bit<RO, R1, R2, R3, W0, W1, W2, W3>>
where
EN: OutputPin,
RW: OutputPin,
RS: OutputPin,
{
pub fn new_4bit() -> Self {
}
}
As you can see, this gets messy because of all the generics + constraints. I think the root of the problem is, that in embedded_hal each and every pin may be its own type. Is there some kind of generic solution to this problem? I am currently experimenting with macros to get rid of all the boilerplate.