1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//! Interface factory
//!
//! This is the easiest way to create a driver instance. You can set various parameters of the
//! driver and give it an interface to use. The builder will return a
//! [`mode::RawMode`](../mode/raw/struct.RawMode.html) object which you should coerce to a richer
//! display mode, like [mode::Graphics](../mode/graphics/struct.GraphicsMode.html) for drawing
//! primitives and text.
//!
//! # Examples
//!
//! Connect over SPI with default rotation (0 deg) and size (128x64):
//!
//! ```rust,ignore
//! let spi = /* SPI interface from your HAL of choice */;
//! let dc = /* GPIO data/command select pin */;
//!
//! Builder::new().connect_spi(spi, dc);
//! ```
//!
//! Connect over I2C, changing lots of options
//!
//! ```rust,ignore
//! let i2c = /* I2C interface from your HAL of choice */;
//!
//! Builder::new()
//!     .with_rotation(DisplayRotation::Rotate180)
//!     .with_i2c_addr(0x3D)
//!     .with_size(DisplaySize::Display128x32)
//!     .connect_i2c(i2c);
//! ```
//!
//! The above examples will produce a [RawMode](../mode/raw/struct.RawMode.html) instance
//! by default. You need to coerce them into a mode by specifying a type on assignment. For
//! example, to use [`GraphicsMode` mode](../mode/graphics/struct.GraphicsMode.html):
//!
//! ```rust,ignore
//! let spi = /* SPI interface from your HAL of choice */;
//! let dc = /* GPIO data/command select pin */;
//!
//! let display: GraphicsMode<_> = Builder::new().connect_spi(spi, dc).into();
//! ```

use core::marker::PhantomData;
use hal::{self, digital::v2::OutputPin};

use crate::{
    displayrotation::DisplayRotation,
    displaysize::DisplaySize,
    interface::{I2cInterface, SpiInterface},
    mode::{displaymode::DisplayMode, raw::RawMode},
    properties::DisplayProperties,
};

/// Builder struct. Driver options and interface are set using its methods.
#[derive(Clone, Copy)]
pub struct Builder {
    display_size: DisplaySize,
    rotation: DisplayRotation,
    i2c_addr: u8,
}

impl Default for Builder {
    fn default() -> Self {
        Self::new()
    }
}

impl Builder {
    /// Create new builder with a default size of 128 x 64 pixels and no rotation.
    pub fn new() -> Builder {
        Builder {
            display_size: DisplaySize::Display128x64,
            rotation: DisplayRotation::Rotate0,
            i2c_addr: 0x3c,
        }
    }
}

impl Builder {
    /// Set the size of the display. Supported sizes are defined by [DisplaySize].
    pub fn with_size(self, display_size: DisplaySize) -> Self {
        Self {
            display_size,
            ..self
        }
    }

    /// Set the I2C address to use. Defaults to 0x3C which is the most common address.
    /// The other address specified in the datasheet is 0x3D. Ignored when using SPI interface.
    pub fn with_i2c_addr(self, i2c_addr: u8) -> Self {
        Self { i2c_addr, ..self }
    }

    /// Set the rotation of the display to one of four values. Defaults to no rotation.
    pub fn with_rotation(self, rotation: DisplayRotation) -> Self {
        Self { rotation, ..self }
    }

    /// Finish the builder and use I2C to communicate with the display
    pub fn connect_i2c<I2C, CommE>(self, i2c: I2C) -> DisplayMode<RawMode<I2cInterface<I2C>>>
    where
        I2C: hal::blocking::i2c::Write<Error = CommE>,
    {
        let properties = DisplayProperties::new(
            I2cInterface::new(i2c, self.i2c_addr),
            self.display_size,
            self.rotation,
        );
        DisplayMode::<RawMode<I2cInterface<I2C>>>::new(properties)
    }

    /// 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)
    }
}

/// Represents an unused output pin.
#[derive(Clone, Copy)]
pub struct NoOutputPin<PinE = ()> {
    _m: PhantomData<PinE>,
}

impl<PinE> NoOutputPin<PinE> {
    /// Create a new instance of `NoOutputPin`
    pub fn new() -> Self {
        Self { _m: PhantomData }
    }
}

impl<PinE> OutputPin for NoOutputPin<PinE> {
    type Error = PinE;
    fn set_low(&mut self) -> Result<(), PinE> {
        Ok(())
    }
    fn set_high(&mut self) -> Result<(), PinE> {
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::NoOutputPin;
    use embedded_hal::digital::v2::OutputPin;

    enum SomeError {}

    struct SomeDriver<P: OutputPin<Error = SomeError>> {
        #[allow(dead_code)]
        p: P,
    }

    #[test]
    fn test_output_pin() {
        let p = NoOutputPin::new();
        let _d = SomeDriver { p };

        assert!(true);
    }
}