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}