From ccc57e9ba3653007e0eef0caaa239382d268bd92 Mon Sep 17 00:00:00 2001 From: decaday Date: Wed, 17 Jun 2026 18:50:46 +0800 Subject: [PATCH 1/3] Impl tearing effect control --- display-driver/src/bus/gpio_te.rs | 153 ++++++++++++++++++++++++++++++ display-driver/src/bus/mod.rs | 3 + display-driver/src/lib.rs | 14 +++ display-driver/src/panel/mod.rs | 16 ++++ 4 files changed, 186 insertions(+) create mode 100644 display-driver/src/bus/gpio_te.rs diff --git a/display-driver/src/bus/gpio_te.rs b/display-driver/src/bus/gpio_te.rs new file mode 100644 index 0000000..8670bad --- /dev/null +++ b/display-driver/src/bus/gpio_te.rs @@ -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 { + /// 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 { + inner_bus: B, + te_pin: P, +} + +impl GpioTeBus { + /// 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 crate::bus::ErrorType for GpioTeBus +where + P::Error: Debug, +{ + type Error = GpioTeError; +} + +impl DisplayBus for GpioTeBus +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> { + // 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.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 crate::bus::BusHardwareFill for GpioTeBus +where + P::Error: Debug, +{ + async fn fill_solid( + &mut self, + cmd: &[u8], + color: crate::SolidColor, + area: crate::Area, + ) -> Result<(), DisplayError> { + self.inner_bus + .fill_solid(cmd, color, area) + .await + .map_err(|e| e.map_bus_error(GpioTeError::BusError)) + } +} + +impl crate::bus::BusRead for GpioTeBus +where + P::Error: Debug, +{ + async fn read_data( + &mut self, + cmd: &[u8], + params: &[u8], + buffer: &mut [u8], + ) -> Result<(), DisplayError> { + self.inner_bus + .read_data(cmd, params, buffer) + .await + .map_err(|e| e.map_bus_error(GpioTeError::BusError)) + } +} + +impl crate::bus::BusBytesIo for GpioTeBus +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) + } +} diff --git a/display-driver/src/bus/mod.rs b/display-driver/src/bus/mod.rs index d506ad9..901c078 100644 --- a/display-driver/src/bus/mod.rs +++ b/display-driver/src/bus/mod.rs @@ -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. diff --git a/display-driver/src/lib.rs b/display-driver/src/lib.rs index 25edb61..dad8730 100644 --- a/display-driver/src/lib.rs +++ b/display-driver/src/lib.rs @@ -38,6 +38,20 @@ impl From for DisplayError { } } +impl DisplayError { + /// Maps a `DisplayError` to `DisplayError` by applying a function to a contained `BusError` value, + /// leaving all other variants untouched. + pub fn map_bus_error O>(self, op: F) -> DisplayError { + 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 diff --git a/display-driver/src/panel/mod.rs b/display-driver/src/panel/mod.rs index ada7a22..2bc64f7 100644 --- a/display-driver/src/panel/mod.rs +++ b/display-driver/src/panel/mod.rs @@ -134,3 +134,19 @@ pub trait PanelSetBrightness: Panel { brightness: u8, ) -> Result<(), DisplayError>; } + +/// An optional trait for panels that support Tearing Effect (TE) output configuration. +#[allow(async_fn_in_trait)] +pub trait PanelTeControl: Panel { + /// 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>; +} From 233d11f36b00760c11c4070b44da33f6c0350ffd Mon Sep 17 00:00:00 2001 From: decaday Date: Wed, 17 Jun 2026 18:52:11 +0800 Subject: [PATCH 2/3] Impl `PanelTeControl` for panels` --- mipidcs/src/display_bus.rs | 28 +++++++++++++++++++++++++++- panels/co5300/src/lib.rs | 17 ++++++++++++++++- panels/gc9a01/src/lib.rs | 17 ++++++++++++++++- panels/st7735/src/lib.rs | 17 ++++++++++++++++- panels/st7789/src/lib.rs | 17 ++++++++++++++++- 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/mipidcs/src/display_bus.rs b/mipidcs/src/display_bus.rs index 4730bd6..ac50d10 100644 --- a/mipidcs/src/display_bus.rs +++ b/mipidcs/src/display_bus.rs @@ -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; @@ -102,3 +104,27 @@ where .map_err(DisplayError::BusError) } } + +impl PanelTeControl for GenericMipidcs +where + B: DisplayBus, + S: PanelSpec, + RST: OutputPin, +{ + async fn set_tearing_effect( + &mut self, + bus: &mut B, + enable: bool, + ) -> Result<(), DisplayError> { + 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) + } + } +} diff --git a/panels/co5300/src/lib.rs b/panels/co5300/src/lib.rs index 5d5454e..726585f 100644 --- a/panels/co5300/src/lib.rs +++ b/panels/co5300/src/lib.rs @@ -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}; @@ -163,6 +163,21 @@ where } } +impl PanelTeControl for Co5300 +where + Spec: Co5300Spec, + RST: OutputPin, + B: DisplayBus, +{ + async fn set_tearing_effect( + &mut self, + bus: &mut B, + enable: bool, + ) -> Result<(), DisplayError> { + self.inner.set_tearing_effect(bus, enable).await + } +} + impl PanelSetBrightness for Co5300 where Spec: Co5300Spec, diff --git a/panels/gc9a01/src/lib.rs b/panels/gc9a01/src/lib.rs index 1b26609..af7df9a 100644 --- a/panels/gc9a01/src/lib.rs +++ b/panels/gc9a01/src/lib.rs @@ -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}; @@ -215,6 +215,21 @@ where } } +impl PanelTeControl for Gc9a01 +where + Spec: Gc9a01Spec, + RST: OutputPin, + B: DisplayBus, +{ + async fn set_tearing_effect( + &mut self, + bus: &mut B, + enable: bool, + ) -> Result<(), DisplayError> { + self.inner.set_tearing_effect(bus, enable).await + } +} + impl PanelSetBrightness for Gc9a01 where Spec: Gc9a01Spec, diff --git a/panels/st7735/src/lib.rs b/panels/st7735/src/lib.rs index 3f38686..9e6ed08 100644 --- a/panels/st7735/src/lib.rs +++ b/panels/st7735/src/lib.rs @@ -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}; @@ -160,3 +160,18 @@ where } } } + +impl PanelTeControl for St7735 +where + Spec: St7735Spec, + RST: OutputPin, + B: DisplayBus, +{ + async fn set_tearing_effect( + &mut self, + bus: &mut B, + enable: bool, + ) -> Result<(), DisplayError> { + self.inner.set_tearing_effect(bus, enable).await + } +} diff --git a/panels/st7789/src/lib.rs b/panels/st7789/src/lib.rs index cbaa293..d88fd7b 100644 --- a/panels/st7789/src/lib.rs +++ b/panels/st7789/src/lib.rs @@ -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}; @@ -177,6 +177,21 @@ where } } +impl PanelTeControl for St7789 +where + Spec: St7789Spec, + RST: OutputPin, + B: DisplayBus, +{ + async fn set_tearing_effect( + &mut self, + bus: &mut B, + enable: bool, + ) -> Result<(), DisplayError> { + self.inner.set_tearing_effect(bus, enable).await + } +} + impl PanelSetBrightness for St7789 where Spec: St7789Spec, From 034861f1ee9cebd07299daa02e68cc51f806dcb8 Mon Sep 17 00:00:00 2001 From: decaday Date: Wed, 17 Jun 2026 19:00:00 +0800 Subject: [PATCH 3/3] Expose set_tearing_effect top-level API --- display-driver/src/eg/framebuffered.rs | 17 ++++++++++++++++- display-driver/src/lib.rs | 9 ++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/display-driver/src/eg/framebuffered.rs b/display-driver/src/eg/framebuffered.rs index ff3c45f..8cdb396 100644 --- a/display-driver/src/eg/framebuffered.rs +++ b/display-driver/src/eg/framebuffered.rs @@ -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; @@ -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 + PanelTeControl, + C: PixelColor, +{ + 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>; + } + } +} + 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 diff --git a/display-driver/src/lib.rs b/display-driver/src/lib.rs index dad8730..4b5c87f 100644 --- a/display-driver/src/lib.rs +++ b/display-driver/src/lib.rs @@ -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; @@ -246,6 +246,13 @@ impl + PanelSetBrightness> DisplayDriver { } } +impl + PanelTeControl> DisplayDriver { + /// Sets the tearing effect (TE) output mode (if supported by the panel). + pub async fn set_tearing_effect(&mut self, enable: bool) -> Result<(), DisplayError> { + self.panel.set_tearing_effect(&mut self.bus, enable).await + } +} + impl> DisplayDriver { /// Fills the area with a solid color using bus auto-fill. pub async fn fill_solid_via_bus(