Skip to content

Parallel OutputBus trait #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
264 changes: 115 additions & 149 deletions parallel-gpio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self, DisplayError> {
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, Self::Error> {
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, P1, P2, P3, P4, P5, P6, P7, DC, WR> {
p0: P0,
p1: P1,
p2: P2,
p3: P3,
p4: P4,
p5: P5,
p6: P6,
p7: P7,
pub struct PGPIO8BitInterface<BUS, DC, WR> {
bus: BUS,
dc: DC,
wr: WR,
last: u8,
}

impl<P0, P1, P2, P3, P4, P5, P6, P7, DC, WR>
PGPIO8BitInterface<P0, P1, P2, P3, P4, P5, P6, P7, DC, WR>
impl<BUS, DC, WR> PGPIO8BitInterface<BUS, DC, WR>
where
P0: OutputPin,
P1: OutputPin,
P2: OutputPin,
P3: OutputPin,
P4: OutputPin,
P5: OutputPin,
P6: OutputPin,
P7: OutputPin,
BUS: OutputBus<Word = u8>,
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<P0, P1, P2, P3, P4, P5, P6, P7, DC, WR> WriteOnlyDataCommand
for PGPIO8BitInterface<P0, P1, P2, P3, P4, P5, P6, P7, DC, WR>
impl<BUS, DC, WR> WriteOnlyDataCommand for PGPIO8BitInterface<BUS, DC, WR>
where
P0: OutputPin,
P1: OutputPin,
P2: OutputPin,
P3: OutputPin,
P4: OutputPin,
P5: OutputPin,
P6: OutputPin,
P7: OutputPin,
BUS: OutputBus<Word = u8>,
DC: OutputPin,
WR: OutputPin,
{
Expand Down