epd_waveshare_async/
lib.rs

1//! This crate provides an `async`/`await` interface for controlling Waveshare E-Paper displays.
2//!
3//! It is built on top of `embedded-hal-async` and `embedded-graphics`, making it compatible with a
4//! wide range of embedded platforms.
5//!
6//! ## Core traits
7//!
8//! The crate is organized around two main traits:
9//!
10//! - [`Epd`]: This trait defines the core functionality for interacting with an E-Paper display,
11//!   such as initialization, refreshing, writing image data, and managing sleep states.
12//!   Implementations of this trait (e.g., [`epd2in9::Epd2In9`]) provide concrete display-specific
13//!   logic. Concrete implementations may also provide further functionality that doesn't fit in
14//!   the general `Epd` trait (e.g. modifying the border on the Epd2In9 screen).
15//!
16//! - [`EpdHw`]: This trait abstracts over the underlying hardware components required to control an
17//!   E-Paper display, including SPI communication, GPIO pins (for Data/Command, Reset, and Busy
18//!   signals), and a delay timer. You need to implement this trait for your chosen peripherals.
19//!   This trades off some set up code (implementing this trait), for simple type signatures with
20//!   only one generic parameter.
21//!
22//! Additionally, the crate provides:
23//!
24//! - `buffer` module: Contains utilities for creating and managing efficient display buffers that
25//!   implement `embedded-graphics::DrawTarget`. These are designed to be fast and compact.
26//! - `<display>` modules: each display lives in its own module, such as `epd2in9` for the 2.9"
27//!   e-paper display.
28#![no_std]
29
30use core::error::Error as CoreError;
31
32use embedded_graphics::{
33    prelude::{DrawTarget, Point},
34    primitives::Rectangle,
35};
36use embedded_hal::digital::{ErrorType as PinErrorType, InputPin, OutputPin};
37use embedded_hal_async::{
38    delay::DelayNs,
39    digital::Wait,
40    spi::{ErrorType as SpiErrorType, SpiDevice},
41};
42
43pub mod buffer;
44pub mod epd2in9;
45
46mod log;
47
48#[allow(async_fn_in_trait)]
49pub trait Epd<HW>
50where
51    HW: EpdHw,
52{
53    type RefreshMode;
54    type Buffer: DrawTarget;
55
56    /// Creates a buffer for use with this display.
57    fn new_buffer(&self) -> Self::Buffer;
58
59    fn width(&self) -> u32;
60
61    fn height(&self) -> u32;
62
63    /// Initialise the display. This must be called before any other operations.
64    async fn init(&mut self, spi: &mut HW::Spi, mode: Self::RefreshMode) -> Result<(), HW::Error>;
65
66    /// Sets the refresh mode for the display.
67    async fn set_refresh_mode(
68        &mut self,
69        spi: &mut HW::Spi,
70        mode: Self::RefreshMode,
71    ) -> Result<(), HW::Error>;
72
73    /// Hardware reset the display.
74    async fn reset(&mut self) -> Result<(), HW::Error>;
75
76    /// Puts the display to sleep.
77    async fn sleep(&mut self, spi: &mut HW::Spi) -> Result<(), HW::Error>;
78
79    /// Wakes and re-initialises the display (if necessary) if it's asleep.
80    async fn wake(&mut self, spi: &mut HW::Spi) -> Result<(), HW::Error>;
81
82    /// Writes the buffer's data to the display and displays it.
83    async fn display_buffer(
84        &mut self,
85        spi: &mut HW::Spi,
86        buffer: &Self::Buffer,
87    ) -> Result<(), HW::Error>;
88
89    /// Sets the window to write to during a call to [Epd::write_image]. This can enable partial
90    /// writes to a subsection of the display.
91    async fn set_window(&mut self, spi: &mut HW::Spi, shape: Rectangle) -> Result<(), HW::Error>;
92
93    /// Sets the cursor position for where the next byte of image data will be written.
94    async fn set_cursor(
95        &mut self,
96        spi: &mut HW::Spi,
97        position: Point,
98    ) -> Result<(), <HW as EpdHw>::Error>;
99
100    /// Writes raw image data, starting at the current cursor position and auto-incrementing x and
101    /// y within the current window. By default, x should increment first, then y (data is written
102    /// in rows).
103    async fn write_image(&mut self, spi: &mut HW::Spi, image: &[u8]) -> Result<(), HW::Error>;
104
105    /// Updates (refreshes) the display based on what has been written to RAM. Note that this can be
106    /// stateful. For example, on the Epd2in9 display, there are two RAM buffers. Calling this
107    /// function swaps the active buffer. Consider this scenario:
108    ///
109    /// 1. [Epd::write_image] is used to turn the RAM all white.
110    /// 2. [Epd::update_display] is called, which refreshes the display to be all white.
111    /// 3. [Epd::write_image] is used to turn the RAM all black.
112    /// 4. [Epd::update_display] is called, which refreshes the display to be all black.
113    /// 5. [Epd::update_display] is called again, which refreshes the display to be all white again.
114    async fn update_display(&mut self, spi: &mut HW::Spi) -> Result<(), HW::Error>;
115}
116
117/// Provides access to the hardware needed to control an EPD.
118///
119/// This greatly simplifies the generics needed by the `Epd` trait and implementing types at the cost of implementing this trait.
120///
121/// In this example, we make the EPD generic over just the SPI type, but can drop generics for the pins and delay type.
122///
123/// ```rust
124/// use core::convert::Infallible;
125///
126/// use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice;
127/// use embassy_embedded_hal::shared_bus::SpiDeviceError;
128/// use embassy_rp::gpio::{Input, Output};
129/// use embassy_rp::spi::{self, Spi};
130/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
131/// use embassy_time::Delay;
132/// use epd_waveshare_async::EpdHw;
133/// use thiserror::Error as ThisError;
134///
135/// /// Define an error type that can convert from the SPI and GPIO errors.
136/// #[derive(Debug, ThisError)]
137/// enum Error {
138///   #[error("SPI error: {0:?}")]
139///   SpiError(SpiDeviceError<spi::Error, Infallible>),
140/// }
141///
142/// impl From<Infallible> for Error {
143///     fn from(_: Infallible) -> Self {
144///         // GPIO errors are infallible, i.e. they can't occur, so this should be unreachable.
145///         unreachable!()
146///     }
147/// }
148///
149/// impl From<SpiDeviceError<spi::Error, Infallible>> for Error {
150///     fn from(e: SpiDeviceError<spi::Error, Infallible>) -> Self {
151///         Error::SpiError(e)
152///     }
153/// }
154///
155/// struct RpEpdHw<'a, SPI: spi::Instance + 'a> {
156///     dc: Output<'a>,
157///     reset: Output<'a>,
158///     busy: Input<'a>,
159///     delay: Delay,
160///     _phantom: core::marker::PhantomData<SPI>,
161/// }
162///
163/// impl <'a, SPI: spi::Instance + 'a> EpdHw for RpEpdHw<'a, SPI> {
164///     type Spi = SpiDevice<'a, CriticalSectionRawMutex, Spi<'a, SPI, spi::Async>, Output<'a>>;
165///     type Dc = Output<'a>;
166///     type Reset = Output<'a>;
167///     type Busy = Input<'a>;
168///     type Delay = Delay;
169///     type Error = Error;
170///
171///     fn dc(&mut self) -> &mut Self::Dc {
172///       &mut self.dc
173///     }
174///
175///     fn reset(&mut self) -> &mut Self::Reset {
176///       &mut self.reset
177///     }
178///
179///     fn busy(&mut self) -> &mut Self::Busy {
180///       &mut self.busy
181///     }
182///
183///     fn delay(&mut self) -> &mut Self::Delay {
184///       &mut self.delay
185///     }
186/// }
187/// ```
188pub trait EpdHw {
189    type Spi: SpiDevice;
190    type Dc: OutputPin;
191    type Reset: OutputPin;
192    type Busy: InputPin + Wait;
193    type Delay: DelayNs;
194    type Error: CoreError
195        + From<<Self::Spi as SpiErrorType>::Error>
196        + From<<Self::Dc as PinErrorType>::Error>
197        + From<<Self::Reset as PinErrorType>::Error>
198        + From<<Self::Busy as PinErrorType>::Error>;
199
200    fn dc(&mut self) -> &mut Self::Dc;
201    fn reset(&mut self) -> &mut Self::Reset;
202    fn busy(&mut self) -> &mut Self::Busy;
203    fn delay(&mut self) -> &mut Self::Delay;
204}