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//! ### Hardware
9//!
10//! The use must implement the [`EpdHw`] trait for their hardware. This trait abstracts over the
11//! underlying hardware components required to control an E-Paper display, including SPI
12//! communication, GPIO pins (for Data/Command, Reset, and Busy and a delay timer. You need to
13//! implement this trait for your chosen peripherals. This trades off some set up code (implementing
14//! this trait), for simple type signatures with fewer generic parameters.
15//!
16//! ### Functionality
17//!
18//! Functionality is split into composable traits, to enable granular support per display, and
19//! stateful functionality that can be checked at compilation time.
20//!
21//! * [Reset]: basic hardware reset support
22//! * [Sleep]: displays that can be put to sleep
23//! * [Wake]: displays that can be woken from sleep
24//! * [DisplaySimple]: basic support for writing and displaying a single framebuffer
25//! * [DisplayPartial]: support for partial refresh using a diff
26//!
27//! Additionally, the crate provides:
28//!
29//! - [`buffer`] module: Contains utilities for creating and managing efficient display buffers that
30//! implement `embedded-graphics::DrawTarget`. These are designed to be fast and compact.
31//! - various `<display>` modules: each display lives in its own module, such as `epd2in9` for the 2.9"
32//! e-paper display.
33#![no_std]
34#![allow(async_fn_in_trait)]
35
36use embedded_hal_async::spi::SpiDevice;
37
38pub mod buffer;
39pub mod epd2in9;
40pub mod epd2in9_v2;
41
42mod hw;
43mod log;
44
45use crate::buffer::BufferView;
46pub use crate::hw::EpdHw;
47
48/// Indicates usage errors due to incorrect states.
49///
50/// These errors are allowed to occur as runtime errors instead of being prevented at compile time
51/// through stateful types. The alternative was tried, but was awkward to use in practice since
52/// async traits are not dyn compatible.
53#[cfg_attr(feature = "defmt", derive(defmt::Format))]
54#[derive(Debug, Clone)]
55pub enum Error {
56 Uninitialized,
57 Sleeping,
58 WrongRefreshMode,
59}
60
61/// Displays that have a hardware reset.
62pub trait Reset<ERROR> {
63 type DisplayOut;
64
65 /// Hardware resets the display.
66 async fn reset(self) -> Result<Self::DisplayOut, ERROR>;
67}
68
69/// Displays that can sleep to save power.
70pub trait Sleep<SPI: SpiDevice, ERROR> {
71 type DisplayOut;
72
73 /// Puts the display to sleep.
74 async fn sleep(self, spi: &mut SPI) -> Result<Self::DisplayOut, ERROR>;
75}
76
77/// Displays that can be woken from a sleep state.
78pub trait Wake<SPI: SpiDevice, ERROR> {
79 type DisplayOut;
80
81 /// Wakes and re-initialises the display (if necessary) if it's asleep.
82 async fn wake(self, spi: &mut SPI) -> Result<Self::DisplayOut, ERROR>;
83}
84
85/// Base trait for any display where the display can be updated separate from its framebuffer data.
86pub trait Displayable<SPI: SpiDevice, ERROR> {
87 /// Updates (refreshes) the display based on what has been written to RAM. Note that this can be
88 /// stateful, for example displays in [DisplayPartial] mode often swap two underlying
89 /// framebuffers on calls to this method, resulting in the following behaviour:
90 ///
91 /// 1. [DisplayPartial::write_diff_framebuffer] is used to turn the RAM all white.
92 /// 2. [Displayable::update_display] is called, which refreshes the display to be all white.
93 /// 3. [DisplayPartial::write_diff_framebuffer] is used to turn the RAM all black.
94 /// 4. [Displayable::update_display] is called, which refreshes the display to be all black.
95 /// 5. [Displayable::update_display] is called again, which refreshes the display to be all white again.
96 async fn update_display(&mut self, spi: &mut SPI) -> Result<(), ERROR>;
97}
98
99/// Simple displays that support writing and displaying framebuffers of a certain bit configuration.
100///
101/// `BITS` indicates the colour depth of each frame, and `FRAMES` indicates the total number of frames that
102/// represent a complete image. For example, some 4-colour greyscale displays accept data as two
103/// separate 1-bit frames instead of one frame of 2-bit pixels. This distinction is exposed so that
104/// framebuffers can be written directly to displays without temp copies or transformations.
105pub trait DisplaySimple<const BITS: usize, const FRAMES: usize, SPI: SpiDevice, ERROR>:
106 Displayable<SPI, ERROR>
107{
108 /// Writes the given buffer's data into the main framebuffer to be displayed on the next call to [Displayable::update_display].
109 async fn write_framebuffer(
110 &mut self,
111 spi: &mut SPI,
112 buf: &dyn BufferView<BITS, FRAMES>,
113 ) -> Result<(), ERROR>;
114
115 /// A shortcut for calling [DisplaySimple::write_framebuffer] followed by [Displayable::update_display].
116 async fn display_framebuffer(
117 &mut self,
118 spi: &mut SPI,
119 buf: &dyn BufferView<BITS, FRAMES>,
120 ) -> Result<(), ERROR>;
121}
122
123/// Displays that support a partial update, where a "diff" framebuffer is diffed against a base
124/// framebuffer, and only the changed pixels from the diff are actually updated.
125pub trait DisplayPartial<const BITS: usize, const FRAMES: usize, SPI: SpiDevice, ERROR>:
126 DisplaySimple<BITS, FRAMES, SPI, ERROR>
127{
128 /// Writes the buffer to the base framebuffer that the main framebuffer layer (written with
129 /// [DisplaySimple::write_framebuffer]) will be diffed against.
130 /// Only pixels that differ will be updated.
131 ///
132 /// For standard use, you probably only need to call this once before the first partial display,
133 /// as the main framebuffer becomes the diff base after a call to [Displayable::update_display].
134 async fn write_base_framebuffer(
135 &mut self,
136 spi: &mut SPI,
137 buf: &dyn BufferView<BITS, FRAMES>,
138 ) -> Result<(), ERROR>;
139}