Cast struct to dynamic trait object

Hey, I'm fairly new to Rust and been trying to play around with the avr-hal library in combination with other public driver crates.
I wanted to use the available Sh1106 driver by jamwaffles over SPI with the avr lib.
But I have some problems getting the respective GraphicsMode struct constructed via the provided Builder, because I just own a ChipSelectPin object (wrapper object for the real OutputPin) provided by the avr-hal crate.
But the Builder requires a OutputPin.

AVR-Hal Documentation about ChipSelectPin object

/// Wrapper for the CS pin
///
/// Used to contain the chip-select pin during operation to prevent its mode from being
/// changed from Output. This is necessary because the SPI state machine would otherwise
/// reset itself to SPI slave mode immediately. This wrapper can be used just like an
/// output pin, because it implements all the same traits from embedded-hal.
pub struct ChipSelectPin<CSPIN>(port::Pin<port::mode::Output, CSPIN>);

Sh1106 code regarding the Builder

/// Finish the builder and use SPI to communicate with the display
    ///
    /// If the Chip Select (CS) pin is not required, [`NoOutputPin`] can be used as a dummy argument
    ///
    /// [`NoOutputPin`]: ./struct.NoOutputPin.html
    pub fn connect_spi<SPI, DC, CS, CommE, PinE>(
        self,
        spi: SPI,
        dc: DC,
        cs: CS,
    ) -> DisplayMode<RawMode<SpiInterface<SPI, DC, CS>>>
    where
        SPI: hal::blocking::spi::Transfer<u8, Error = CommE>
            + hal::blocking::spi::Write<u8, Error = CommE>,
        DC: OutputPin<Error = PinE>,
        CS: OutputPin<Error = PinE>,
    {
        let properties = DisplayProperties::new(
            SpiInterface::new(spi, dc, cs),
            self.display_size,
            self.rotation,
        );
        DisplayMode::<RawMode<SpiInterface<SPI, DC, CS>>>::new(properties)
    }

Comming from the Rust Book, the normal way on how to solve this would be to use some sort of Box casting, but as I use the no_std macro, I dont have access to Box.
Also, if I try to .into() method, which is quite prominent in Code examples, I just get a exception regarding missing type annotations.

let mut display: GraphicsMode<_> = Builder::new()
   |         ----------- consider giving `display` the explicit type `GraphicsMode<SpiInterface<avr_hal_generic::spi::Spi<Atmega, SPI, PB5, PB3, PB4, PB2>, avr_hal_generic::port::Pin<Output, PB0>, CS>>`, where the type parameter `CS` is specified
46 |         .connect_spi(spi, dc, cs.into() )
   |          ^^^^^^^^^^^ cannot infer type for type parameter `CS` declared on the associated function `connect_spi`

What would be the typical solution to solve this kind of issues in Embedded Rust? I will also append my current not working source code, but its basically just copy-pasted from the different examples.
Sincerely, Fatalon

Full SourceCode

#![no_std]
#![no_main]

use embedded_hal::digital::v2::OutputPin;
use sh1106::{prelude::*, Builder, mode::GraphicsMode};
use arduino_hal::prelude::*;
use arduino_hal::spi;
use embedded_hal::spi::FullDuplex;

use embedded_graphics::{
    image::{Image, ImageRawLE},
    pixelcolor::BinaryColor,
    prelude::*,
};

use panic_halt as _;

const UART_BAUDRATE: u32 = 57600;

#[arduino_hal::entry]
fn main() -> ! {
    let dp = arduino_hal::Peripherals::take().unwrap();
    let pins = arduino_hal::pins!(dp);

    // Create SPI interface.
    let (mut spi, mut cs) = arduino_hal::Spi::new(
        dp.SPI,
        pins.d13.into_output(),
        pins.d11.into_output(),
        pins.d12.into_pull_up_input(),
        pins.d10.into_output(),
        spi::Settings::default(),
    );

    let dc = pins.d8.into_output();

    // Alternative: Use lib without a Chip Select pin
    //let cs_no_output = sh1106::builder::NoOutputPin::new();

    let mut display: GraphicsMode<_> = Builder::new()
        .connect_spi(spi, dc, cs.into())
        .into();

    display.init().unwrap();
    display.flush().unwrap();

    let im: ImageRawLE<BinaryColor> = ImageRawLE::new(include_bytes!("rust.raw"), 64);

    Image::new(&im, Point::new(32, 0))
        .draw(&mut display)
        .unwrap();

    display.flush().unwrap();
    loop {
    }

}

I'm not sure I understand the issue you're having. From what you've explained, I understand you have a ChipSelectPin and you want to call Builder::connect_spi(), which needs something that implements OutputPin, but ChipSelectPin already implements OutputPin, so you should be able to just pass it right in. There's no need for casting or trait objects.

This is because you are asking for a conversion to some type, but not specifying what type to convert to, and since it's in a generic context Rust can't figure it out for itself (since there may be many types that would be valid there). You shouldn't need to use into() though as ChipSelectPin should work as-is.

Could you give more information on the sorts of errors you're getting trying to use ChipSelectPin directly?

2 Likes

Hi jameseb7, thanks for your fast reply. You are indeed correct with the description of my goal.
As I said, I am also quite a beginner to Rust in general, so it could also be, that I miss-interpreted my error-message. Sorry about the confusion.

If I run cargo-check without the nested cs.into() call, I get the following error:

error[E0271]: type mismatch resolving `<ChipSelectPin<PB2> as embedded_hal::digital::v2::OutputPin>::Error == Infallible`

which would escalate into further errors:

= note: required because of the requirements on the impl of `DisplayInterface` for `SpiInterface<avr_hal_generic::spi::Spi<Atmega, SPI, PB5, PB3, PB4, PB2>, avr_hal_generic::port::Pin<Output, PB0>, ChipSelectPin<PB2>>`
   = note: required because of the requirements on the impl of `DisplayModeTrait<SpiInterface<avr_hal_generic::spi::Spi<Atmega, SPI, PB5, PB3, PB4, PB2>, avr_hal_generic::port::Pin<Output, PB0>, ChipSelectPin<PB2>>>` for `RawMode<SpiInterface<avr_hal_generic::spi::Spi<Atmega, SPI, PB5, PB3, PB4, PB2>, avr_hal_generic::port::Pin<Output, PB0>, ChipSelectPin<PB2>>>`

Perhaps the issue is not regarding the OutputPin object, but regarding the generic <Error = PinE> Type nested inside the OutputPin?

Code to produce this error messages:

// Create SPI interface.
    let (mut spi, mut cs) = arduino_hal::Spi::new(
        dp.SPI,
        pins.d13.into_output(),
        pins.d11.into_output(),
        pins.d12.into_pull_up_input(),
        pins.d10.into_output(),
        spi::Settings::default(),
    );
    let dc = pins.d8.into_output();
    let mut display: GraphicsMode<_> = Builder::new()
        .connect_spi(spi, dc, cs)
        .into();

That does look like it may be an issue with the error type, since connect_spi() requires the pin error types to be the same. The error type for the ChipSelectPin implementation of OutputPin has Void as its error type, whereas avr_hal_generic::port::Pin uses Infallible in its implementation of OutputPin. That's a little unfortunate, since Infallible and Void have the same definition. It might be work contacting the maintainer of that crate to see if the uses of Void can be changed to Infallible.

In the meantime, you could make a newtype wrapper and give it an OutputPin implementation with Infallible:

    struct WrappedChipSelectPin<CSPIN>(ChipSelectPin<CSPIN>);

    impl<CSPIN : PinOps> OutputPin for WrappedChipSelectPin<CSPIN> {
         type Error = Infallible;

         fn set_low(&mut self) -> Result<(), Self::Error> {
        self.0.set_low();
        Ok(())
    }
    fn set_high(&mut self) -> Result<(), Self::Error> {
        self.0.set_high();
        Ok(())
    }
    }

(Note that I haven't really tested this code, and some of the implementation is copied from the ChipSelectPin implementation)

That should work to patch the pin in to the function, unless there is a good reason why they should have different error types.

1 Like

I got in contact with the maintainer and the Void type is actually legacy and will be altered to Infallible. Thanks @jameseb7 for your help and explanations.
Regarding your solution: I tried it out, but I think the PinOps trait is currently not reachable from outside the library. Still thanks for the suggestions, as it might come handy, if I have a similar problem in the future.
Edit: It is reachable, you just need to add avr-hal-generic as an additional dependency. Afterwards use avr_hal_generic::port::PinOps
If someone later stumbles upon this thread, you can follow the Issue and PR progress here: DummyOutputPin for Chipselect SPI or adjust existing Chipselect Error Type · Issue #241 · Rahix/avr-hal · GitHub

2 Likes

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.