epd_waveshare_async/
epd2in9.rs

1use core::time::Duration;
2use embedded_graphics::{
3    pixelcolor::BinaryColor,
4    prelude::{Point, Size},
5    primitives::Rectangle,
6};
7use embedded_hal::{
8    digital::{OutputPin, PinState},
9    spi::{Phase, Polarity},
10};
11use embedded_hal_async::delay::DelayNs;
12
13use crate::{
14    buffer::{binary_buffer_length, split_low_and_high, BinaryBuffer, BufferView},
15    hw::{BusyHw, DcHw, DelayHw, ErrorHw, ResetHw, SpiHw},
16    log::{debug, debug_assert},
17    DisplayPartial, DisplaySimple, Displayable, Reset, Sleep, Wake,
18};
19
20/// LUT for a full refresh. This should be used occasionally for best display results.
21///
22/// See [RECOMMENDED_MIN_FULL_REFRESH_INTERVAL] and [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
23const LUT_FULL_UPDATE: [u8; 30] = [
24    0x50, 0xAA, 0x55, 0xAA, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
25    0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
26];
27/// LUT for a partial refresh. This should be used for frequent updates, but it's recommended to
28/// perform a full refresh occasionally.
29///
30/// See [RECOMMENDED_MIN_FULL_REFRESH_INTERVAL] and [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
31const LUT_PARTIAL_UPDATE: [u8; 30] = [
32    0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
33    0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
34];
35
36#[cfg_attr(feature = "defmt", derive(defmt::Format))]
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38/// The refresh mode for the display.
39pub enum RefreshMode {
40    /// Use the full update LUT. This is slower, but should be done occasionally to avoid ghosting.
41    ///
42    /// It's recommended to avoid full refreshes less than [RECOMMENDED_MIN_FULL_REFRESH_INTERVAL] apart,
43    /// but to do a full refresh at least every [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
44    Full,
45    /// Uses the partial update LUT for fast refresh. A full refresh should be done occasionally to
46    /// avoid ghosting, see [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
47    ///
48    /// This is the standard "fast" update. It diffs the current framebuffer against the
49    /// previous framebuffer, and just updates the pixels that differ.
50    Partial,
51    /// Uses the partial update LUT for a fast refresh, but only updates black (`BinaryColor::Off`)
52    /// pixels from the current framebuffer. The previous framebuffer is ignored.
53    PartialBlackBypass,
54    /// Uses the partial update LUT for a fast refresh, but only updates white (`BinaryColor::On`)
55    /// pixels from the current framebuffer. The previous framebuffer is ignored.
56    PartialWhiteBypass,
57}
58
59impl RefreshMode {
60    /// Returns the LUT to use for this refresh mode.
61    pub fn lut(&self) -> &[u8; 30] {
62        match self {
63            RefreshMode::Full => &LUT_FULL_UPDATE,
64            _ => &LUT_PARTIAL_UPDATE,
65        }
66    }
67}
68
69/// The height of the display (portrait orientation).
70pub const DISPLAY_HEIGHT: u16 = 296;
71/// The width of the display (portrait orientation).
72pub const DISPLAY_WIDTH: u16 = 128;
73/// It's recommended to avoid doing a full refresh more often than this (at least on a regular basis).
74pub const RECOMMENDED_MIN_FULL_REFRESH_INTERVAL: Duration = Duration::from_secs(180);
75/// It's recommended to do a full refresh at least this often.
76pub const RECOMMENDED_MAX_FULL_REFRESH_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60);
77pub const RECOMMENDED_SPI_HZ: u32 = 4_000_000; // 4 MHz
78/// Use this phase in conjunction with [RECOMMENDED_SPI_POLARITY] so that the EPD can capture data
79/// on the rising edge.
80pub const RECOMMENDED_SPI_PHASE: Phase = Phase::CaptureOnFirstTransition;
81/// Use this polarity in conjunction with [RECOMMENDED_SPI_PHASE] so that the EPD can capture data
82/// on the rising edge.
83pub const RECOMMENDED_SPI_POLARITY: Polarity = Polarity::IdleLow;
84/// The default pin state that indicates the display is busy.
85///
86/// Note: the datasheet states that busy pin is active low, i.e. we should wait for it when
87/// it's low, but this is incorrect. The sample code treats it as active high, which works.
88pub const DEFAULT_BUSY_WHEN: PinState = PinState::High;
89
90/// Low-level commands for the Epd2In9. You probably want to use the other methods exposed on the
91/// [Epd2In9] for most operations, but can send commands directly with [Epd2In9::send] for low-level
92/// control or experimentation.
93#[cfg_attr(feature = "defmt", derive(defmt::Format))]
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum Command {
96    /// Used to initialise the display.
97    DriverOutputControl = 0x01,
98    /// Used to configure the on chip voltage booster and regulator.
99    BoosterSoftStartControl = 0x0C,
100    /// Used to enter deep sleep mode. Requires a hardware reset and reinitialisation to wake up.
101    DeepSleepMode = 0x10,
102    /// Changes the auto-increment behaviour of the address counter.
103    DataEntryModeSetting = 0x11,
104    /// Resets all commands and parameters to default values (except deep sleep mode).
105    SwReset = 0x12,
106    /// Writes to the temperature register.
107    TemperatureSensorControl = 0x1A,
108    /// Activates the display update sequence. This must be set beforehand using [Command::DisplayUpdateControl2].
109    /// This operation must not be interrupted.
110    MasterActivation = 0x20,
111    /// Used for a RAM "bypass" mode when using [RefreshMode::Partial]. This is poorly explained in the docs,
112    /// but essentially we have three options:
113    ///
114    /// 1. `0x00` (default): just update the pixels that have changed **between the two internal
115    ///    frame buffers**. This normally does what you expect. You can hack it a bit to do
116    ///    interesting things by writing to both the old and new frame buffers.
117    /// 2. `0x80`: just update the white (`BinaryColor::On`) pixels in the current frame buffer. It
118    ///    doesn't matter what is in the old frame buffer.
119    /// 3. `0x90`: just update the black (`BinaryColor::Off`) pixels in the current frame buffer.
120    ///    It doesn't matter what is in the old frame buffer.
121    ///
122    /// Options 2 and 3 are what the datasheet calls "bypass" mode.
123    DisplayUpdateControl1 = 0x21,
124    /// Configures the display update sequence for use with [Command::MasterActivation].
125    DisplayUpdateControl2 = 0x22,
126    /// Writes data to the current frame buffer, auto-incrementing the address counter.
127    WriteRam = 0x24,
128    /// Writes data to the old frame buffer, auto-incrementing the address counter.
129    WriteOldRam = 0x26,
130    /// Writes to the VCOM register.
131    WriteVcom = 0x2C,
132    /// Writes the LUT register (30 bytes, exclude the VSH/VSL and dummy bits).
133    WriteLut = 0x32,
134    /// ? Part of magic config.
135    SetDummyLinePeriod = 0x3A,
136    /// ? Part of magic config.
137    SetGateLineWidth = 0x3B,
138    /// Register to configure the behaviour of the border.
139    BorderWaveformControl = 0x3C,
140    /// Sets the start and end positions of the X axis for the auto-incrementing address counter.
141    /// Start and end are inclusive.
142    ///
143    /// Note that the x position can only be written on a whole byte basis (8 bits at once). The
144    /// start and end positions are therefore sent right shifted 3 bits to indicate the byte number
145    /// being written. For example, to write the first 32 x positions, you would send 0 (0 >> 3 =
146    /// 0), and 3 (31 >> 3 = 3). If you tried to write just the first 25 x positions, you would end
147    /// up sending the same values and actually writing all 32.
148    SetRamXStartEnd = 0x44,
149    /// Sets the start and end positions of the Y axis for the auto-incrementing address counter.
150    /// Start and end are inclusive.
151    SetRamYStartEnd = 0x45,
152    /// Sets the current x coordinate of the address counter.
153    /// Note that the x position can only be configured as a multiple of 8.
154    SetRamX = 0x4E,
155    /// Sets the current y coordinate of the address counter.
156    SetRamY = 0x4F,
157    /// Does nothing, but can be used to terminate other commands such as [Command::WriteRam]
158    Noop = 0xFF,
159}
160
161impl Command {
162    /// Returns the register address for this command.
163    fn register(&self) -> u8 {
164        *self as u8
165    }
166}
167
168/// The length of the underlying buffer used by [Epd2In9].
169pub const BINARY_BUFFER_LENGTH: usize =
170    binary_buffer_length(Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32));
171/// The buffer type used by [Epd2In9].
172pub type Epd2In9Buffer =
173    BinaryBuffer<{ binary_buffer_length(Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)) }>;
174/// Constructs a new buffer for use with the [Epd2In9] display.
175pub fn new_buffer() -> Epd2In9Buffer {
176    Epd2In9Buffer::new(Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32))
177}
178
179/// This should be sent with [Command::DriverOutputControl] during initialisation.
180///
181/// From the sample code, the bytes mean the following:
182///
183/// * low byte of display long edge
184/// * high byte of display long edge
185/// * GD = 0, SM = 0, TB = 0 (unclear what this means)
186const DRIVER_OUTPUT_INIT_DATA: [u8; 3] = [0x27, 0x01, 0x00];
187/// This should be sent with [Command::BoosterSoftStartControl] during initialisation.
188/// Note that there are two versions of this command, one in the datasheet, and one in the sample code.
189const BOOSTER_SOFT_START_INIT_DATA: [u8; 3] = [0xD7, 0xD6, 0x9D];
190// Sample code: ^
191// Datasheet:
192// const BOOSTER_SOFT_START_INIT_DATA: [u8; 3] = [0xCF, 0xCE, 0x8D];
193
194trait StateInternal {}
195#[allow(private_bounds)]
196pub trait State: StateInternal {}
197pub trait StateAwake: State {}
198
199macro_rules! impl_base_state {
200    ($state:ident) => {
201        impl StateInternal for $state {}
202        impl State for $state {}
203    };
204}
205
206#[cfg_attr(feature = "defmt", derive(defmt::Format))]
207#[derive(Debug, Clone, Copy, PartialEq)]
208pub struct StateUninitialized();
209impl_base_state!(StateUninitialized);
210impl StateAwake for StateUninitialized {}
211
212#[cfg_attr(feature = "defmt", derive(defmt::Format))]
213#[derive(Debug, Clone, Copy, PartialEq)]
214pub struct StateReady {
215    mode: RefreshMode,
216}
217impl_base_state!(StateReady);
218impl StateAwake for StateReady {}
219
220#[cfg_attr(feature = "defmt", derive(defmt::Format))]
221#[derive(Debug, Clone, Copy, PartialEq)]
222pub struct StateAsleep<W: StateAwake> {
223    wake_state: W,
224}
225impl<W: StateAwake> StateInternal for StateAsleep<W> {}
226impl<W: StateAwake> State for StateAsleep<W> {}
227
228/// Controls v1 of the 2.9" Waveshare e-paper display.
229///
230/// * [datasheet](https://files.waveshare.com/upload/e/e6/2.9inch_e-Paper_Datasheet.pdf)
231/// * [sample code](https://github.com/waveshareteam/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd2in9.py)
232///
233/// The display has a portrait orientation. This uses [BinaryColor], where `Off` is black and `On` is white.
234///
235/// HW should implement [ResetHw], [BusyHw], [DcHw], [SpiHw], [DelayHw], and [ErrorHw].
236pub struct Epd2In9<HW, STATE> {
237    hw: HW,
238    state: STATE,
239}
240
241impl<HW> Epd2In9<HW, StateUninitialized>
242where
243    HW: DcHw + ResetHw + BusyHw + DelayHw + ErrorHw + SpiHw,
244    HW::Error: From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
245        + From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>
246        + From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
247        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
248{
249    pub fn new(hw: HW) -> Self {
250        Epd2In9 {
251            hw,
252            state: StateUninitialized(),
253        }
254    }
255}
256
257impl<HW, STATE> Epd2In9<HW, STATE>
258where
259    HW: DcHw + ResetHw + BusyHw + DelayHw + ErrorHw + SpiHw,
260    STATE: StateAwake,
261    HW::Error: From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
262        + From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>
263        + From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
264        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
265{
266    /// Initialise the display. This should be called before any other operations.
267    pub async fn init(
268        mut self,
269        spi: &mut HW::Spi,
270        mode: RefreshMode,
271    ) -> Result<Epd2In9<HW, StateReady>, HW::Error> {
272        debug!("Initialising display");
273        self = self.reset().await?;
274
275        // Reset all configurations to default.
276        self.send(spi, Command::SwReset, &[]).await?;
277
278        self.send(spi, Command::DriverOutputControl, &DRIVER_OUTPUT_INIT_DATA)
279            .await?;
280        self.send(
281            spi,
282            Command::BoosterSoftStartControl,
283            &BOOSTER_SOFT_START_INIT_DATA,
284        )
285        .await?;
286        // Auto-increment X and Y, moving in the X direction first.
287        self.send(spi, Command::DataEntryModeSetting, &[0b11])
288            .await?;
289
290        // Apply more magical config settings from the sample code.
291        // Potentially: configure VCOM for 7 degrees celsius?
292        self.send(spi, Command::WriteVcom, &[0xA8]).await?;
293        // Configure 4 dummy lines per gate.
294        self.send(spi, Command::SetDummyLinePeriod, &[0x1A]).await?;
295        // 2us per line.
296        self.send(spi, Command::SetGateLineWidth, &[0x08]).await?;
297
298        let mut epd = Epd2In9 {
299            hw: self.hw,
300            state: StateReady { mode },
301        };
302        epd.set_refresh_mode_impl(spi, mode).await?;
303        Ok(epd)
304    }
305}
306
307impl<HW, STATE> Epd2In9<HW, STATE>
308where
309    HW: DcHw + BusyHw + ErrorHw + SpiHw,
310    STATE: StateAwake,
311    HW::Error: From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
312        + From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
313        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
314{
315    /// Sets the border to the specified colour. You need to call [Displayable::update_display]
316    /// using [RefreshMode::Full] afterwards to apply this change.
317    ///
318    /// Note: on my board, the white setting fades to grey fairly quickly. I have not found a way
319    /// to avoid this.
320    pub async fn set_border(
321        &mut self,
322        spi: &mut HW::Spi,
323        color: BinaryColor,
324    ) -> Result<(), HW::Error> {
325        let border_setting: u8 = match color {
326            BinaryColor::Off => 0x00,
327            BinaryColor::On => 0x01,
328        };
329        self.send(spi, Command::BorderWaveformControl, &[border_setting])
330            .await
331    }
332
333    /// Send the following command and data to the display. Waits until the display is no longer busy before sending.
334    pub async fn send(
335        &mut self,
336        spi: &mut HW::Spi,
337        command: Command,
338        data: &[u8],
339    ) -> Result<(), HW::Error> {
340        use crate::hw::CommandDataSend;
341        self.hw.send(spi, command.register(), data).await
342    }
343}
344
345impl<HW> Epd2In9<HW, StateReady>
346where
347    HW: DcHw + BusyHw + DelayHw + ErrorHw + SpiHw,
348    HW::Error: From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
349        + From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
350        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
351{
352    /// Sets the refresh mode.
353    pub async fn set_refresh_mode(
354        &mut self,
355        spi: &mut HW::Spi,
356        mode: RefreshMode,
357    ) -> Result<(), HW::Error> {
358        if self.state.mode == mode {
359            Ok(())
360        } else {
361            debug!("Changing refresh mode to {:?}", mode);
362            self.set_refresh_mode_impl(spi, mode).await?;
363            Ok(())
364        }
365    }
366
367    /// Sets the window to which the next image data will be written.
368    ///
369    /// The x-axis only supports multiples of 8; values outside this result in a debug-mode panic,
370    /// or potentially misaligned content when debug assertions are disabled.
371    pub async fn set_window(
372        &mut self,
373        spi: &mut HW::Spi,
374        shape: Rectangle,
375    ) -> Result<(), HW::Error> {
376        // Use a debug assert as this is a soft failure in production; it will just lead to
377        // slightly misaligned display content.
378        let x_start = shape.top_left.x;
379        let x_end = x_start + shape.size.width as i32 - 1;
380        debug_assert!(
381            x_start % 8 == 0 && x_end % 8 == 7,
382            "window's top_left.x and width must be 8-bit aligned"
383        );
384        let x_start_byte = ((x_start >> 3) & 0xFF) as u8;
385        let x_end_byte = ((x_end >> 3) & 0xFF) as u8;
386        self.send(spi, Command::SetRamXStartEnd, &[x_start_byte, x_end_byte])
387            .await?;
388
389        let (y_start_low, y_start_high) = split_low_and_high(shape.top_left.y as u16);
390        let (y_end_low, y_end_high) =
391            split_low_and_high((shape.top_left.y + shape.size.height as i32 - 1) as u16);
392        self.send(
393            spi,
394            Command::SetRamYStartEnd,
395            &[y_start_low, y_start_high, y_end_low, y_end_high],
396        )
397        .await?;
398
399        Ok(())
400    }
401
402    /// Sets the cursor position to write the next data to.
403    ///
404    /// The x-axis only supports multiples of 8; values outside this will result in a panic in
405    /// debug mode, or potentially misaligned content if debug assertions are disabled.
406    pub async fn set_cursor(
407        &mut self,
408        spi: &mut HW::Spi,
409        position: Point,
410    ) -> Result<(), HW::Error> {
411        // Use a debug assert as this is a soft failure in production; it will just lead to
412        // slightly misaligned display content.
413        debug_assert_eq!(position.x % 8, 0, "position.x must be 8-bit aligned");
414
415        self.send(spi, Command::SetRamX, &[(position.x >> 3) as u8])
416            .await?;
417        let (y_low, y_high) = split_low_and_high(position.y as u16);
418        self.send(spi, Command::SetRamY, &[y_low, y_high]).await?;
419        Ok(())
420    }
421
422    async fn set_refresh_mode_impl(
423        &mut self,
424        spi: &mut HW::Spi,
425        mode: RefreshMode,
426    ) -> Result<(), HW::Error> {
427        self.send(spi, Command::WriteLut, mode.lut()).await?;
428        self.state.mode = mode;
429
430        // Update bypass if needed.
431        match mode {
432            RefreshMode::Partial => {
433                self.send(spi, Command::DisplayUpdateControl1, &[0x00])
434                    .await
435            }
436            RefreshMode::PartialBlackBypass => {
437                self.send(spi, Command::DisplayUpdateControl1, &[0x90])
438                    .await
439            }
440            RefreshMode::PartialWhiteBypass => {
441                self.send(spi, Command::DisplayUpdateControl1, &[0x80])
442                    .await
443            }
444            _ => Ok(()),
445        }
446    }
447}
448
449impl<HW> Displayable<HW::Spi, HW::Error> for Epd2In9<HW, StateReady>
450where
451    HW: DcHw + BusyHw + DelayHw + ErrorHw + SpiHw,
452    HW::Error: From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
453        + From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
454        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
455{
456    async fn update_display(&mut self, spi: &mut HW::Spi) -> Result<(), HW::Error> {
457        // Enable the clock and CP (?), and then display the data from the RAM. Note that there are
458        // two RAM buffers, so this will swap the active buffer. Calling this function twice in a row
459        // without writing further to RAM therefore results in displaying the previous image.
460
461        // Experimentation:
462        // * Sending just 0x04 doesn't work, it hangs in busy state. The clocks are needed.
463        // * Sending 0xC8 (INITIAL_DISPLAY) results in a black screen.
464        // * Sending 0xCD (INITIAL_DISPLAY + PATTERN_DISPLAY) results in seemingly broken, semi-random behaviour.
465        // The INIITIAL_DISPLAY settings potentially relate to the "bypass" settings in
466        // [Command::DisplayUpdateControl1], but the precise mode is unclear.
467        debug!("Updating display");
468
469        self.send(spi, Command::DisplayUpdateControl2, &[0xC4])
470            .await?;
471        self.send(spi, Command::MasterActivation, &[]).await?;
472        self.send(spi, Command::Noop, &[]).await?;
473        Ok(())
474    }
475}
476
477impl<HW> DisplaySimple<1, 1, HW::Spi, HW::Error> for Epd2In9<HW, StateReady>
478where
479    HW: DcHw + BusyHw + DelayHw + ErrorHw + SpiHw,
480    HW::Error: From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
481        + From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
482        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
483{
484    async fn display_framebuffer(
485        &mut self,
486        spi: &mut HW::Spi,
487        buf: &dyn BufferView<1, 1>,
488    ) -> Result<(), HW::Error> {
489        self.write_framebuffer(spi, buf).await?;
490        self.update_display(spi).await
491    }
492
493    async fn write_framebuffer(
494        &mut self,
495        spi: &mut HW::Spi,
496        buf: &dyn BufferView<1, 1>,
497    ) -> Result<(), HW::Error> {
498        let buffer_bounds = buf.window();
499        self.set_window(spi, buffer_bounds).await?;
500        self.set_cursor(spi, buffer_bounds.top_left).await?;
501        self.send(spi, Command::WriteRam, buf.data()[0]).await
502    }
503}
504
505impl<HW> DisplayPartial<1, 1, HW::Spi, HW::Error> for Epd2In9<HW, StateReady>
506where
507    HW: DcHw + BusyHw + DelayHw + ErrorHw + SpiHw,
508    HW::Error: From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
509        + From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
510        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
511{
512    /// Writes buffer data into the old internal framebuffer. This can be useful either:
513    ///
514    /// * to prep the next frame before the current one has been displayed (since the old buffer
515    ///   becomes the current buffer after the next call to [Self::update_display()]).
516    /// * to modify the "diff base" if in [RefreshMode::Partial]. Also see [Command::DisplayUpdateControl1].
517    async fn write_base_framebuffer(
518        &mut self,
519        spi: &mut HW::Spi,
520        buf: &dyn BufferView<1, 1>,
521    ) -> Result<(), HW::Error> {
522        let buffer_bounds = buf.window();
523        self.set_window(spi, buffer_bounds).await?;
524        self.set_cursor(spi, buffer_bounds.top_left).await?;
525        self.send(spi, Command::WriteOldRam, buf.data()[0]).await
526    }
527}
528
529async fn reset_impl<HW>(hw: &mut HW) -> Result<(), HW::Error>
530where
531    HW: ResetHw + DelayHw + ErrorHw,
532    HW::Error: From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>,
533{
534    debug!("Resetting EPD");
535    // Assume reset is already high.
536    hw.reset().set_low()?;
537    hw.delay().delay_ms(10).await;
538    hw.reset().set_high()?;
539    hw.delay().delay_ms(10).await;
540    Ok(())
541}
542
543impl<HW, STATE> Reset<HW::Error> for Epd2In9<HW, STATE>
544where
545    HW: ResetHw + DelayHw + ErrorHw,
546    HW::Error: From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>,
547    STATE: StateAwake,
548{
549    type DisplayOut = Epd2In9<HW, STATE>;
550
551    async fn reset(mut self) -> Result<Self::DisplayOut, HW::Error> {
552        reset_impl(&mut self.hw).await?;
553        Ok(self)
554    }
555}
556
557impl<HW, W> Reset<HW::Error> for Epd2In9<HW, StateAsleep<W>>
558where
559    HW: ResetHw + DelayHw + ErrorHw,
560    HW::Error: From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>,
561    W: StateAwake,
562{
563    type DisplayOut = Epd2In9<HW, W>;
564
565    async fn reset(mut self) -> Result<Self::DisplayOut, HW::Error> {
566        reset_impl(&mut self.hw).await?;
567        Ok(Epd2In9 {
568            hw: self.hw,
569            state: self.state.wake_state,
570        })
571    }
572}
573
574impl<HW, STATE> Sleep<HW::Spi, HW::Error> for Epd2In9<HW, STATE>
575where
576    HW: DcHw + BusyHw + ErrorHw + SpiHw,
577    HW::Error: From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
578        + From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
579        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
580    STATE: StateAwake,
581{
582    type DisplayOut = Epd2In9<HW, StateAsleep<STATE>>;
583
584    async fn sleep(mut self, spi: &mut HW::Spi) -> Result<Self::DisplayOut, HW::Error>
585where {
586        debug!("Sleeping EPD");
587        self.send(spi, Command::DeepSleepMode, &[0x01]).await?;
588        Ok(Epd2In9 {
589            hw: self.hw,
590            state: StateAsleep {
591                wake_state: self.state,
592            },
593        })
594    }
595}
596
597impl<HW, W> Wake<HW::Spi, HW::Error> for Epd2In9<HW, StateAsleep<W>>
598where
599    HW: ResetHw + BusyHw + DelayHw + ErrorHw + SpiHw,
600    HW::Error: From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>
601        + From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
602        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
603    W: StateAwake,
604{
605    type DisplayOut = Epd2In9<HW, W>;
606
607    async fn wake(self, _spi: &mut HW::Spi) -> Result<Self::DisplayOut, HW::Error> {
608        debug!("Waking EPD");
609        self.reset().await
610        // Confirmed with a physical screen that init is not required after waking.
611    }
612}