Skip to content
Open
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
153 changes: 153 additions & 0 deletions display-driver/src/bus/gpio_te.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
use crate::{DisplayBus, DisplayError, Metadata};
use core::fmt::Debug;
use embedded_hal_async::digital::Wait;

/// Errors that can occur when using a GpioTeBus.
#[derive(Debug)]
pub enum GpioTeError<BusError, WaitError> {
/// A timeout occurred while waiting for the TE signal.
Timeout,
/// An error occurred while waiting on the TE pin.
WaitError(WaitError),
/// An error originated from the underlying bus.
BusError(BusError),
}

/// A bus wrapper that waits for a Tearing Effect (TE) signal before initiating a frame transfer.
///
/// This is used to implement Software TE (GPIO TE). When `write_pixels` is called
/// with `metadata.frame_control.first == true`, it awaits a rising edge on the `te_pin`
/// before passing the command and data to the underlying bus.
pub struct GpioTeBus<B, P> {
inner_bus: B,
te_pin: P,
}

impl<B, P> GpioTeBus<B, P> {
/// Creates a new `GpioTeBus` wrapping an existing bus and a TE pin.
pub fn new(bus: B, te_pin: P) -> Self {
Self {
inner_bus: bus,
te_pin,
}
}

/// Consumes the wrapper and returns the underlying bus and pin.
pub fn release(self) -> (B, P) {
(self.inner_bus, self.te_pin)
}
}

// Delegate the basic ErrorType to the inner bus
impl<B: DisplayBus, P: Wait> crate::bus::ErrorType for GpioTeBus<B, P>
where
P::Error: Debug,
{
type Error = GpioTeError<B::Error, P::Error>;
}

impl<B: DisplayBus, P: Wait> DisplayBus for GpioTeBus<B, P>
where
P::Error: Debug,
{
async fn write_cmd(&mut self, cmd: &[u8]) -> Result<(), Self::Error> {
self.inner_bus
.write_cmd(cmd)
.await
.map_err(GpioTeError::BusError)
}

async fn write_cmd_with_params(
&mut self,
cmd: &[u8],
params: &[u8],
) -> Result<(), Self::Error> {
self.inner_bus
.write_cmd_with_params(cmd, params)
.await
.map_err(GpioTeError::BusError)
}

async fn write_pixels(
&mut self,
cmd: &[u8],
data: &[u8],
metadata: Metadata,
) -> Result<(), DisplayError<Self::Error>> {
// If this is the start of a frame, wait for the TE signal.
if metadata.frame_control.first {
// According to MIPI DCS spec (Command 35h TEON M=0), the V-Blanking period
// active time is represented by a low signal. The start of the V-Blanking
// period corresponds to the falling edge. So we wait for the falling edge.
self.te_pin
.wait_for_falling_edge()
.await
.map_err(|e| DisplayError::BusError(GpioTeError::WaitError(e)))?;
}

self.inner_bus
.write_pixels(cmd, data, metadata)
.await
.map_err(|e| e.map_bus_error(GpioTeError::BusError))
}

fn set_reset(&mut self, reset: bool) -> Result<(), DisplayError<Self::Error>> {
self.inner_bus
.set_reset(reset)
.map_err(|e| e.map_bus_error(GpioTeError::BusError))
}
}

// Delegate other optional traits if the inner bus implements them
impl<B: crate::bus::BusHardwareFill, P: Wait> crate::bus::BusHardwareFill for GpioTeBus<B, P>
where
P::Error: Debug,
{
async fn fill_solid(
&mut self,
cmd: &[u8],
color: crate::SolidColor,
area: crate::Area,
) -> Result<(), DisplayError<Self::Error>> {
self.inner_bus
.fill_solid(cmd, color, area)
.await
.map_err(|e| e.map_bus_error(GpioTeError::BusError))
}
}

impl<B: crate::bus::BusRead, P: Wait> crate::bus::BusRead for GpioTeBus<B, P>
where
P::Error: Debug,
{
async fn read_data(
&mut self,
cmd: &[u8],
params: &[u8],
buffer: &mut [u8],
) -> Result<(), DisplayError<Self::Error>> {
self.inner_bus
.read_data(cmd, params, buffer)
.await
.map_err(|e| e.map_bus_error(GpioTeError::BusError))
}
}

impl<B: crate::bus::BusBytesIo, P: Wait> crate::bus::BusBytesIo for GpioTeBus<B, P>
where
P::Error: Debug,
{
async fn write_cmd_bytes(&mut self, cmd: &[u8]) -> Result<(), Self::Error> {
self.inner_bus
.write_cmd_bytes(cmd)
.await
.map_err(GpioTeError::BusError)
}

async fn write_data_bytes(&mut self, data: &[u8]) -> Result<(), Self::Error> {
self.inner_bus
.write_data_bytes(data)
.await
.map_err(GpioTeError::BusError)
}
}
3 changes: 3 additions & 0 deletions display-driver/src/bus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pub use qspi_flash::QspiFlashBus;
pub mod simple;
pub use simple::SimpleDisplayBus;

pub mod gpio_te;
pub use gpio_te::GpioTeBus;

use crate::{Area, DisplayError, SolidColor};

/// Error type trait.
Expand Down
17 changes: 16 additions & 1 deletion display-driver/src/eg/framebuffered.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
bus::DisplayBus,
color::ColorFormat,
panel::{Orientation, Panel, PanelSetBrightness},
panel::{Orientation, Panel, PanelSetBrightness, PanelTeControl},
Area, DisplayDriver, DisplayError, FrameControl,
};
use delegate::delegate;
Expand Down Expand Up @@ -213,6 +213,21 @@ where
}
}

impl<'a, B, P, C, R, BO, const W: usize, const H: usize, const N: usize>
FrameBufferedDisplayDriver<'a, B, P, C, R, BO, W, H, N>
where
B: DisplayBus,
P: Panel<B> + PanelTeControl<B>,
C: PixelColor<Raw = R>,
{
delegate! {
to self.driver {
/// Sets the tearing effect (TE) output mode (if supported by the panel).
pub async fn set_tearing_effect(&mut self, enable: bool) -> Result<(), DisplayError<B::Error>>;
}
}
}

impl<'a, B, P, C, R, BO, const W: usize, const H: usize, const N: usize> OriginDimensions
for FrameBufferedDisplayDriver<'a, B, P, C, R, BO, W, H, N>
where
Expand Down
23 changes: 22 additions & 1 deletion display-driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub use crate::bus::{
BusBytesIo, BusHardwareFill, DisplayBus, FrameControl, Metadata, SimpleDisplayBus,
};
pub use color::{ColorFormat, ColorType, SolidColor};
pub use panel::{reset::LCDResetOption, Orientation, Panel, PanelSetBrightness};
pub use panel::{reset::LCDResetOption, Orientation, Panel, PanelSetBrightness, PanelTeControl};

use embedded_hal_async::delay::DelayNs;

Expand All @@ -38,6 +38,20 @@ impl<E> From<E> for DisplayError<E> {
}
}

impl<E> DisplayError<E> {
/// Maps a `DisplayError<E>` to `DisplayError<O>` by applying a function to a contained `BusError` value,
/// leaving all other variants untouched.
pub fn map_bus_error<O, F: FnOnce(E) -> O>(self, op: F) -> DisplayError<O> {
match self {
DisplayError::BusError(e) => DisplayError::BusError(op(e)),
DisplayError::Unsupported => DisplayError::Unsupported,
DisplayError::OutOfRange => DisplayError::OutOfRange,
DisplayError::InvalidArgs => DisplayError::InvalidArgs,
DisplayError::UnalignedArea => DisplayError::UnalignedArea,
}
}
}

/// A builder for configuring and initializing a [`DisplayDriver`].
///
/// Use [`DisplayDriver::builder`] to create a builder, then chain configuration methods
Expand Down Expand Up @@ -232,6 +246,13 @@ impl<B: DisplayBus, P: Panel<B> + PanelSetBrightness<B>> DisplayDriver<B, P> {
}
}

impl<B: DisplayBus, P: Panel<B> + PanelTeControl<B>> DisplayDriver<B, P> {
/// Sets the tearing effect (TE) output mode (if supported by the panel).
pub async fn set_tearing_effect(&mut self, enable: bool) -> Result<(), DisplayError<B::Error>> {
self.panel.set_tearing_effect(&mut self.bus, enable).await
}
}

impl<B: DisplayBus + BusHardwareFill, P: Panel<B>> DisplayDriver<B, P> {
/// Fills the area with a solid color using bus auto-fill.
pub async fn fill_solid_via_bus(
Expand Down
16 changes: 16 additions & 0 deletions display-driver/src/panel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,19 @@ pub trait PanelSetBrightness<B: DisplayBus>: Panel<B> {
brightness: u8,
) -> Result<(), DisplayError<B::Error>>;
}

/// An optional trait for panels that support Tearing Effect (TE) output configuration.
#[allow(async_fn_in_trait)]
pub trait PanelTeControl<B: DisplayBus>: Panel<B> {
/// Sets the tearing effect output mode.
///
/// - `true`: Enables TE output.
/// - `false`: Disables TE output.
///
/// TODO: Support more fine-grained control like V-Blanking only vs V-Blanking and H-Blanking.
async fn set_tearing_effect(
&mut self,
bus: &mut B,
enable: bool,
) -> Result<(), DisplayError<B::Error>>;
}
28 changes: 27 additions & 1 deletion mipidcs/src/display_bus.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use display_driver::bus::DisplayBus;
use display_driver::panel::{initseq::sequenced_init, reset::LCDResetHandler, Orientation, Panel};
use display_driver::panel::{
initseq::sequenced_init, reset::LCDResetHandler, Orientation, Panel, PanelTeControl,
};

use display_driver::{ColorFormat, DisplayError};
use embedded_hal::digital::OutputPin;
Expand Down Expand Up @@ -102,3 +104,27 @@ where
.map_err(DisplayError::BusError)
}
}

impl<B, S, RST> PanelTeControl<B> for GenericMipidcs<B, S, RST>
where
B: DisplayBus,
S: PanelSpec,
RST: OutputPin,
{
async fn set_tearing_effect(
&mut self,
bus: &mut B,
enable: bool,
) -> Result<(), DisplayError<B::Error>> {
if enable {
// V-Blanking mode (0x00)
bus.write_cmd_with_params(&[SET_TEAR_ON], &[0x00])
.await
.map_err(DisplayError::BusError)
} else {
bus.write_cmd(&[SET_TEAR_OFF])
.await
.map_err(DisplayError::BusError)
}
}
}
17 changes: 16 additions & 1 deletion panels/co5300/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use embedded_hal_async::delay::DelayNs;
use display_driver::bus::DisplayBus;
use display_driver::panel::initseq::{sequenced_init, InitStep};
use display_driver::panel::reset::{LCDResetOption, LCDResetHandler};
use display_driver::panel::{Orientation, Panel, PanelSetBrightness};
use display_driver::panel::{Orientation, Panel, PanelTeControl, PanelSetBrightness};

use display_driver::{ColorFormat, DisplayError};

Expand Down Expand Up @@ -163,6 +163,21 @@ where
}
}

impl<Spec, RST, B> PanelTeControl<B> for Co5300<Spec, RST, B>
where
Spec: Co5300Spec,
RST: OutputPin,
B: DisplayBus,
{
async fn set_tearing_effect(
&mut self,
bus: &mut B,
enable: bool,
) -> Result<(), DisplayError<B::Error>> {
self.inner.set_tearing_effect(bus, enable).await
}
}

impl<Spec, RST, B> PanelSetBrightness<B> for Co5300<Spec, RST, B>
where
Spec: Co5300Spec,
Expand Down
17 changes: 16 additions & 1 deletion panels/gc9a01/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use embedded_hal_async::delay::DelayNs;
use display_driver::bus::DisplayBus;
use display_driver::panel::initseq::{sequenced_init, InitStep};
use display_driver::panel::reset::{LCDResetHandler, LCDResetOption};
use display_driver::panel::{Orientation, Panel, PanelSetBrightness};
use display_driver::panel::{Orientation, Panel, PanelSetBrightness, PanelTeControl};

use display_driver::{ColorFormat, DisplayError};

Expand Down Expand Up @@ -215,6 +215,21 @@ where
}
}

impl<Spec, RST, B> PanelTeControl<B> for Gc9a01<Spec, RST, B>
where
Spec: Gc9a01Spec,
RST: OutputPin,
B: DisplayBus,
{
async fn set_tearing_effect(
&mut self,
bus: &mut B,
enable: bool,
) -> Result<(), DisplayError<B::Error>> {
self.inner.set_tearing_effect(bus, enable).await
}
}

impl<Spec, RST, B> PanelSetBrightness<B> for Gc9a01<Spec, RST, B>
where
Spec: Gc9a01Spec,
Expand Down
17 changes: 16 additions & 1 deletion panels/st7735/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use embedded_hal_async::delay::DelayNs;
use display_driver::bus::DisplayBus;
use display_driver::panel::initseq::{sequenced_init, InitStep};
use display_driver::panel::reset::{LCDResetOption, LCDResetHandler};
use display_driver::panel::{Orientation, Panel};
use display_driver::panel::{Orientation, Panel, PanelTeControl};

use display_driver::{ColorFormat, DisplayError};

Expand Down Expand Up @@ -160,3 +160,18 @@ where
}
}
}

impl<Spec, RST, B> PanelTeControl<B> for St7735<Spec, RST, B>
where
Spec: St7735Spec,
RST: OutputPin,
B: DisplayBus,
{
async fn set_tearing_effect(
&mut self,
bus: &mut B,
enable: bool,
) -> Result<(), DisplayError<B::Error>> {
self.inner.set_tearing_effect(bus, enable).await
}
}
Loading