riscv-rust / e310x-hal

Implementation of the `embedded-hal` traits for E310x microcontrollers
17 stars 18 forks source link

Traits for GPIO pin / SPI device / etc #13

Open laanwj opened 5 years ago

laanwj commented 5 years ago

The macros for the various peripherals create a new type for each instance of the same hardware. These types share the general embedded_hal traits, but I need something that provides the hardware-specific methods. I'd like to write some code that is general and doesn't care, say, what SPI device (for example) is passed in.

I have code like this, for example:

use hifive1::hal::spi::{Spi, Mode, Polarity, Phase};
...

// SPI backend trait for display interface
pub trait SPI {
    fn send(&mut self, data: u8);
    fn complete(&mut self);
    fn mode_data(&mut self);
    fn mode_cmd(&mut self);
}

pub struct Hifive1SPI<X, Y, DC> where
    DC: OutputPin {
    hwspi:  Spi<X, Y>,
    dc:    DC,
}

impl <Y, DC> Hifive1SPI<QSPI1, Y, DC> where
    Y: hifive1::hal::spi::Pins<QSPI1>,
    DC: OutputPin {
    pub fn new(hwspi: Spi<QSPI1, Y>, dc: DC) -> Hifive1SPI<QSPI1, Y, DC> {
        Hifive1SPI {
            hwspi,
            dc,
        }
    }
}

impl <Y, DC> SPI for Hifive1SPI<QSPI1, Y, DC> where
    Y: hifive1::hal::spi::Pins<QSPI1>,
    DC: OutputPin {
    fn complete(&mut self)
    {
        // Wait for send FIFO to drain
        while !self.hwspi.tx_wm_is_pending() {
            // IDLE
        }
    }

    fn send(&mut self, data: u8)
    {
        while let Err(_) = self.hwspi.send(data) {
            // IDLE
        }
    }

    fn mode_data(&mut self)
    {
        self.complete();
        self.dc.set_high();
    }

    fn mode_cmd(&mut self)
    {
        self.complete();
        self.dc.set_low();
    }
}

Currently this specializes on QSPI1 because I don't see a way to generalize it over all SPI devices, as tx_wm_is_pending is not on a trait.

(I had a similar situation with GPIO pins in the board setup, where I wanted to make it possible to pass in arbitrary pins to configure, but there is no trait that provides into_output into_inverted_output. There's OutputPin of course, but that's only useful when the pin is already set up)

I'm fairly new to Rust, entirely new to using Rust for embedded programming, so I may be missing something.

Disasm commented 5 years ago

Currently this specializes on QSPI1 because I don't see a way to generalize it over all SPI devices, as tx_wm_is_pending is not on a trait.

I thought about moving tx_wm_is_pending into separate trait, but abandoned this idea, because it leads to changes of visibility for such methods in documentation. However, if you need this, feel free to file a PR!

(I had a similar situation with GPIO pins in the board setup, where I wanted to make it possible to pass in arbitrary pins to configure, but there is no trait that provides into_output into_inverted_output. There's OutputPin of course, but that's only useful when the pin is already set up)

It's a common way to handle pins that need to be transformed prior passing them to the device peripheral. I have no idea about rationality behind this, but it seems to be ok in Rust to handle such transformations explicitly.

Disasm commented 5 years ago

I refactored the SPI driver, now it has spi::SpiX trait implemented for all the SPI peripheral instances. You can use this trait to access device-dependent functions in your code. It doesn't provide any methods on its own, but all device-dependent functions are implemented for any peripheral with this trait.