diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a3086c..b4779e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - New `DisplayError` variant `RSError` to use with problems with the display's reset signal - New `DisplayError` variant `OutOfBoundsError` to use when writing to a non-existing pixel outside the display's bounds +- parallel-gpio: New `OutputBus` trait +- parallel-gpio: Added `Generic8BitBus`, an implementation of `OutputBus` ### Changed - Return `DCError` instead of `BusWriteError` on errors (de-)asserting the DC signal in 8-bit GPIO interfaces +- **Breaking** parallel-gpio: `PGPIO8BitInterface` now uses any 8-bit impementation of `OutputBus` instead of 8 individual pins ## [v0.4.0] - 2020-05-25 diff --git a/parallel-gpio/src/lib.rs b/parallel-gpio/src/lib.rs index 82a4d3e..cda64ea 100644 --- a/parallel-gpio/src/lib.rs +++ b/parallel-gpio/src/lib.rs @@ -6,182 +6,148 @@ use embedded_hal::digital::v2::OutputPin; pub use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand}; +/// This trait represents the data pins of a parallel bus. +/// +/// See [Generic8BitBus] for a generic implementation. +pub trait OutputBus { + /// [u8] for 8-bit busses, [u16] for 16-bit busses, etc. + type Word: Copy; + + fn set_value(&mut self, value: Self::Word) -> Result<(), DisplayError>; +} + +macro_rules! generic_bus { + ($GenericxBitBus:ident { type Word = $Word:ident; Pins {$($PX:ident => $x:tt,)*}}) => { + /// A generic implementation of [OutputBus] using [OutputPin]s + pub struct $GenericxBitBus<$($PX, )*> { + pins: ($($PX, )*), + last: $Word, + } + + impl<$($PX, )*> $GenericxBitBus<$($PX, )*> + where + $($PX: OutputPin, )* + { + /// Creates a new instance and initializes the bus to `0`. + /// + /// The first pin in the tuple is the least significant bit. + pub fn new(pins: ($($PX, )*)) -> Result { + let mut bus = Self { pins, last: $Word::MAX }; + + // By setting `last` to all ones, we ensure that this will update all the pins + bus.set_value(0)?; + + Ok(bus) + } + + /// Consumes the bus and returns the pins. This does not change the state of the pins. + pub fn release(self) -> ($($PX, )*) { + self.pins + } + } + + impl<$($PX, )*> OutputBus + for $GenericxBitBus<$($PX, )*> + where + $($PX: OutputPin, )* + { + type Word = $Word; + + fn set_value(&mut self, value: Self::Word) -> Result<(), DisplayError> { + let changed = value ^ self.last; + + // It's quite common for multiple consecutive values to be identical, e.g. when filling or + // clearing the screen, so let's optimize for that case + if changed != 0 { + $( + let mask = 1 << $x; + if changed & mask != 0 { + if value & mask != 0 { + self.pins.$x.set_high() + } else { + self.pins.$x.set_low() + } + .map_err(|_| DisplayError::BusWriteError)?; + } + )* + + self.last = value; + } + + Ok(()) + } + } + + impl<$($PX, )*> core::convert::TryFrom<($($PX, )*)> + for $GenericxBitBus<$($PX, )*> + where + $($PX: OutputPin, )* + { + type Error = DisplayError; + + fn try_from(pins: ($($PX, )*)) -> Result { + Self::new(pins) + } + } + }; +} + +generic_bus! { + Generic8BitBus { + type Word = u8; + Pins { + P0 => 0, + P1 => 1, + P2 => 2, + P3 => 3, + P4 => 4, + P5 => 5, + P6 => 6, + P7 => 7, + } + } +} + /// Parallel 8 Bit communication interface /// /// This interface implements an 8-Bit "8080" style write-only display interface using any -/// `embedded_hal` `digital::v2::OutputPin` implementation. -/// -/// For the 8-Bit implementation you need to provide 8 types implementing `OutputPin` which -/// ressemble the bits 0 through 7 (which bit 0 being the LSB and 7 the MSB) as well as one +/// 8-bit [OutputBus] implementation as well as one /// `OutputPin` for the data/command selection and one `OutputPin` for the write-enable flag. /// /// All pins are supposed to be high-active, high for the D/C pin meaning "data" and the /// write-enable being pulled low before the setting of the bits and supposed to be sampled at a /// low to high edge. -pub struct PGPIO8BitInterface { - p0: P0, - p1: P1, - p2: P2, - p3: P3, - p4: P4, - p5: P5, - p6: P6, - p7: P7, +pub struct PGPIO8BitInterface { + bus: BUS, dc: DC, wr: WR, - last: u8, } -impl - PGPIO8BitInterface +impl PGPIO8BitInterface where - P0: OutputPin, - P1: OutputPin, - P2: OutputPin, - P3: OutputPin, - P4: OutputPin, - P5: OutputPin, - P6: OutputPin, - P7: OutputPin, + BUS: OutputBus, DC: OutputPin, WR: OutputPin, { /// Create new parallel GPIO interface for communication with a display driver - #[allow(clippy::too_many_arguments)] - pub fn new( - p0: P0, - p1: P1, - p2: P2, - p3: P3, - p4: P4, - p5: P5, - p6: P6, - p7: P7, - dc: DC, - wr: WR, - ) -> Self { - Self { - p0, - p1, - p2, - p3, - p4, - p5, - p6, - p7, - dc, - wr, - last: 0, - } + pub fn new(bus: BUS, dc: DC, wr: WR) -> Self { + Self { bus, dc, wr } } /// Consume the display interface and return - /// the GPIO pins used by it - pub fn release(self) -> (P0, P1, P2, P3, P4, P5, P6, P7, DC, WR) { - ( - self.p0, self.p1, self.p2, self.p3, self.p4, self.p5, self.p6, self.p7, self.dc, - self.wr, - ) + /// the bus and GPIO pins used by it + pub fn release(self) -> (BUS, DC, WR) { + (self.bus, self.dc, self.wr) } fn set_value(self: &mut Self, value: u8) -> Result<(), DisplayError> { - let changed = value ^ self.last; - - // It's quite common for multiple consecutive values to be identical, e.g. when filling or - // clearing the screen, so let's optimize for that case - if changed == 0 { - return Ok(()); - } - - self.last = value; - - if changed & 1 != 0 { - if value & 1 != 0 { - self.p0.set_high() - } else { - self.p0.set_low() - } - .map_err(|_| DisplayError::BusWriteError)? - }; - - if changed & 2 != 0 { - if value & 2 != 0 { - self.p1.set_high() - } else { - self.p1.set_low() - } - .map_err(|_| DisplayError::BusWriteError)? - }; - - if changed & 4 != 0 { - if value & 4 != 0 { - self.p2.set_high() - } else { - self.p2.set_low() - } - .map_err(|_| DisplayError::BusWriteError)? - }; - - if changed & 8 != 0 { - if value & 8 != 0 { - self.p3.set_high() - } else { - self.p3.set_low() - } - .map_err(|_| DisplayError::BusWriteError)? - }; - - if changed & 16 != 0 { - if value & 16 != 0 { - self.p4.set_high() - } else { - self.p4.set_low() - } - .map_err(|_| DisplayError::BusWriteError)? - }; - - if changed & 32 != 0 { - if value & 32 != 0 { - self.p5.set_high() - } else { - self.p5.set_low() - } - .map_err(|_| DisplayError::BusWriteError)? - }; - - if changed & 64 != 0 { - if value & 64 != 0 { - self.p6.set_high() - } else { - self.p6.set_low() - } - .map_err(|_| DisplayError::BusWriteError)? - }; - - if changed & 128 != 0 { - if value & 128 != 0 { - self.p7.set_high() - } else { - self.p7.set_low() - } - .map_err(|_| DisplayError::BusWriteError)? - }; - - Ok(()) + self.bus.set_value(value) } } -impl WriteOnlyDataCommand - for PGPIO8BitInterface +impl WriteOnlyDataCommand for PGPIO8BitInterface where - P0: OutputPin, - P1: OutputPin, - P2: OutputPin, - P3: OutputPin, - P4: OutputPin, - P5: OutputPin, - P6: OutputPin, - P7: OutputPin, + BUS: OutputBus, DC: OutputPin, WR: OutputPin, {