Skip to main content

epd_waveshare_async/
epd7in5_v2.rs

1use core::time::Duration;
2use embedded_graphics::{geometry::Point, prelude::Size, primitives::Rectangle};
3use embedded_hal::{
4    digital::{OutputPin, PinState},
5    spi::{Phase, Polarity},
6};
7use embedded_hal_async::delay::DelayNs;
8
9use crate::{
10    buffer::{binary_buffer_length, BinaryBuffer, BufferView, Gray2SplitBuffer},
11    hw::{BusyHw, BusyWait, CommandDataSend, DcHw, DelayHw, ErrorHw, ResetHw, SpiHw},
12    log::debug,
13    Clear, DisplayPartial, DisplaySimple, Displayable, Reset, Sleep,
14};
15
16#[cfg_attr(feature = "defmt", derive(defmt::Format))]
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18/// The refresh mode for the display.
19pub enum RefreshMode {
20    /// Refreshes the entire display. This is slower than [RefreshMode::Partial], but should be done
21    /// occasionally to avoid ghosting. If ghosting persists, try [RefreshMode::Full].
22    ///
23    /// It's recommended to avoid full refreshes less than [RECOMMENDED_MIN_FULL_REFRESH_INTERVAL] apart,
24    /// but to do a full refresh at least every [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
25    Fast,
26    /// A slower full update that gives a cleaner final image.
27    ///
28    /// It's recommended to avoid full refreshes less than [RECOMMENDED_MIN_FULL_REFRESH_INTERVAL] apart,
29    /// but to do a full refresh at least every [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
30    Full,
31    /// Changes only specific areas of the screen with no flickering.
32    /// A fast/full refresh should be done occasionally to avoid ghosting,
33    /// see [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
34    ///
35    /// It diffs the current framebuffer against the
36    /// previous framebuffer, and just updates the pixels that differ.
37    Partial,
38    /// A refresh mode that supports 2-bit grayscale. Note that Waveshare calls this "Gray4", but
39    /// we use `Gray2` to align with the embedded-graphics color [embedded_graphics::pixelcolor::Gray2].
40    ///
41    /// There is no partial update version for Gray2. All updates require writing to both on-device framebuffers.
42    Gray2,
43}
44
45impl RefreshMode {
46    /// If this refresh mode is black and white only.
47    pub fn is_black_and_white(&self) -> bool {
48        *self != RefreshMode::Gray2
49    }
50}
51
52/// The width of the display (landscape orientation).
53pub const DISPLAY_WIDTH: u32 = 800;
54/// The height of the display (landscape orientation).
55pub const DISPLAY_HEIGHT: u32 = 480;
56/// It's recommended to avoid doing a full refresh more often than this (at least on a regular basis).
57pub const RECOMMENDED_MIN_FULL_REFRESH_INTERVAL: Duration = Duration::from_secs(180);
58/// It's recommended to do a full refresh at least this often.
59pub const RECOMMENDED_MAX_FULL_REFRESH_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60);
60pub const RECOMMENDED_SPI_HZ: u32 = 4_000_000; // 4 MHz
61/// Use this phase in conjunction with [RECOMMENDED_SPI_POLARITY] so that the EPD can capture data
62/// on the rising edge.
63pub const RECOMMENDED_SPI_PHASE: Phase = Phase::CaptureOnFirstTransition;
64/// Use this polarity in conjunction with [RECOMMENDED_SPI_PHASE] so that the EPD can capture data
65/// on the rising edge.
66pub const RECOMMENDED_SPI_POLARITY: Polarity = Polarity::IdleLow;
67/// The default pin state that indicates the display is busy.
68pub const DEFAULT_BUSY_WHEN: PinState = PinState::Low;
69
70/// Low-level commands for the Epd7in5 v2 display. You probably want to use the other methods
71/// exposed on the [Epd7in5] for most operations, but can send commands directly with [Epd7in5::send] for low-level
72/// control or experimentation.
73#[cfg_attr(feature = "defmt", derive(defmt::Format))]
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum Command {
76    PanelSet = 0x00,
77    PowerSet = 0x01,
78    PowerOff = 0x02,
79    PowerOffSequenceSet = 0x03,
80    PowerOn = 0x04,
81    PowerOnMeasure = 0x05,
82    BoosterSoftStart = 0x06,
83    DeepSleep = 0x07,
84    DisplayStartTrans1 = 0x10, // Front buffer
85    DataStop = 0x11,
86    DisplayRefresh = 0x12,
87    DisplayStartTrans2 = 0x13, // Back buffer
88    DualSPI = 0x15,
89    AutoSequence = 0x17,
90    KWLUTOption = 0x2B,
91    PLLControl = 0x30,
92    TempSensorCalibration = 0x40,
93    TempSensorSelect = 0x41,
94    TempSensorWrite = 0x42,
95    TempSensorRead = 0x43,
96    PanelBreakCheck = 0x44,
97    VCOMDataInterval = 0x50,
98    LowerPowerDetection = 0x51,
99    EndVoltageSet = 0x52,
100    TCONSet = 0x60,
101    ResolutionSet = 0x61,
102    GateSourceStartSet = 0x65,
103    Revision = 0x70,
104    GetStatus = 0x71,
105    AutoMeasurementVCOM = 0x80,
106    ReadVCOM = 0x81,
107    VCOMDCSetting = 0x82,
108    PartialWindow = 0x90,
109    PartialIn = 0x91,
110    PartialOut = 0x92,
111    ProgramMode = 0xA0,
112    ActiveProgramming = 0xA1,
113    ReadOTP = 0xA2,
114    CascadeSet = 0xE0,
115    PowerSaving = 0xE3,
116    LVDVoltageSelect = 0xE4,
117    ForceTemperature = 0xE5,
118    TempBoundaryPhaseC2 = 0xE7,
119}
120
121impl Command {
122    /// Returns the register address for this command.
123    fn register(&self) -> u8 {
124        *self as u8
125    }
126}
127
128/// The length of the underlying buffer used by [Epd7in5].
129pub const BINARY_BUFFER_LENGTH: usize =
130    binary_buffer_length(Size::new(DISPLAY_WIDTH, DISPLAY_HEIGHT));
131/// The buffer type used by [Epd7in5].
132pub type Epd7In5BinaryBuffer = BinaryBuffer<BINARY_BUFFER_LENGTH>;
133/// Constructs a new binary buffer for use with the [Epd7in5] display.
134pub const fn new_binary_buffer() -> Epd7In5BinaryBuffer {
135    Epd7In5BinaryBuffer::new(Size::new(DISPLAY_WIDTH, DISPLAY_HEIGHT))
136}
137pub type Epd7In5Gray2Buffer = Gray2SplitBuffer<BINARY_BUFFER_LENGTH>;
138pub const fn new_gray2_buffer() -> Epd7In5Gray2Buffer {
139    Epd7In5Gray2Buffer::new(Size::new(DISPLAY_WIDTH, DISPLAY_HEIGHT))
140}
141
142/// Controls v2 of the 7.5" Waveshare e-paper display.
143///
144/// * [datasheet](https://files.waveshare.com/upload/6/60/7.5inch_e-Paper_V2_Specification.pdf)
145/// * [sample code](https://github.com/waveshareteam/e-Paper/blob/master/Arduino_R4/src/e-Paper/EPD_7in5_V2.cpp)
146///
147/// The display has a landscape orientation. This display supports either
148/// [embedded_graphics::pixelcolor::BinaryColor] or [embedded_graphics::pixelcolor::Gray2],
149/// depending on the display mode.
150///
151/// When using `BinaryColor`, `Off` is black and `On` is white.
152///
153/// HW should implement [ResetHw], [BusyHw], [DcHw], [SpiHw], [DelayHw], and [ErrorHw].
154pub struct Epd7In5V2<HW, STATE> {
155    hw: HW,
156    state: STATE,
157}
158
159trait StateInternal {}
160#[allow(private_bounds)]
161pub trait State: StateInternal {}
162pub trait StateAwake: State {}
163
164macro_rules! impl_base_state {
165    ($state:ident) => {
166        impl StateInternal for $state {}
167        impl State for $state {}
168    };
169}
170
171#[cfg_attr(feature = "defmt", derive(defmt::Format))]
172#[derive(Debug, Clone, Copy, PartialEq)]
173pub struct StateUninitialized();
174impl_base_state!(StateUninitialized);
175impl StateAwake for StateUninitialized {}
176
177#[cfg_attr(feature = "defmt", derive(defmt::Format))]
178#[derive(Debug, Clone, Copy, PartialEq)]
179pub struct StateReady {
180    mode: RefreshMode,
181}
182impl_base_state!(StateReady);
183impl StateAwake for StateReady {}
184
185#[cfg_attr(feature = "defmt", derive(defmt::Format))]
186#[derive(Debug, Clone, Copy, PartialEq)]
187pub struct StateAsleep<W: StateAwake> {
188    wake_state: W,
189}
190impl<W: StateAwake> StateInternal for StateAsleep<W> {}
191impl<W: StateAwake> State for StateAsleep<W> {}
192
193impl<HW> Epd7In5V2<HW, StateUninitialized>
194where
195    HW: BusyHw + DcHw + ResetHw + DelayHw + SpiHw + ErrorHw,
196    HW::Error: From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
197        + From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
198        + From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>
199        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
200{
201    pub fn new(hw: HW) -> Self {
202        Epd7In5V2 {
203            hw,
204            state: StateUninitialized(),
205        }
206    }
207}
208
209impl<HW, STATE> Epd7In5V2<HW, STATE>
210where
211    HW: BusyHw + DcHw + ResetHw + DelayHw + SpiHw + ErrorHw,
212    HW::Error: From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
213        + From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
214        + From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>
215        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
216    STATE: StateAwake,
217{
218    /// Initialises the display.
219    pub async fn init(
220        mut self,
221        spi: &mut HW::Spi,
222        mode: RefreshMode,
223    ) -> Result<Epd7In5V2<HW, StateReady>, HW::Error> {
224        debug!("Initializing display to {}", mode);
225        self = self.reset().await?;
226
227        let mut epd = Epd7In5V2 {
228            hw: self.hw,
229            state: StateReady { mode },
230        };
231
232        epd.set_refresh_mode_impl(spi, mode).await?;
233        Ok(epd)
234    }
235}
236
237impl<HW, STATE> Epd7In5V2<HW, STATE>
238where
239    HW: BusyHw + DcHw + SpiHw + ErrorHw,
240    HW::Error: From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
241        + From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
242        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
243    STATE: StateAwake,
244{
245    /// Send the following command and data to the display. Waits until the display is no longer busy before sending.
246    pub async fn send(
247        &mut self,
248        spi: &mut HW::Spi,
249        command: Command,
250        data: &[u8],
251    ) -> Result<(), HW::Error> {
252        self.hw.send(spi, command.register(), data).await
253    }
254}
255
256impl<HW> Epd7In5V2<HW, StateReady>
257where
258    HW: BusyHw + DcHw + SpiHw + ErrorHw + DelayHw + ResetHw,
259    HW::Error: From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
260        + From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
261        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>
262        + From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>,
263{
264    /// Sets the refresh mode.
265    pub async fn set_refresh_mode(
266        &mut self,
267        spi: &mut HW::Spi,
268        mode: RefreshMode,
269    ) -> Result<(), HW::Error> {
270        if self.state.mode == mode {
271            Ok(())
272        } else {
273            debug!("Changing refresh mode to {:?}", mode);
274            reset_impl(&mut self.hw).await?;
275            self.set_refresh_mode_impl(spi, mode).await?;
276            Ok(())
277        }
278    }
279
280    async fn set_refresh_mode_impl(
281        &mut self,
282        spi: &mut HW::Spi,
283        mode: RefreshMode,
284    ) -> Result<(), HW::Error> {
285        match mode {
286            RefreshMode::Fast => {
287                // PANEL SETTING
288                self.send(spi, Command::PanelSet, &[0x1F]).await?;
289
290                // VCOM DATA INTERVAL
291                self.send(spi, Command::VCOMDataInterval, &[0x10, 0x07])
292                    .await?;
293
294                // If the screen appears gray, use the annotated initialization command
295
296                // POWER ON
297                self.send(spi, Command::PowerOn, &[]).await?;
298                self.hw.delay().delay_ms(100).await;
299                self.hw.wait_if_busy().await?;
300
301                // BOOSTER + Cascade + Force Temp
302                self.send(spi, Command::BoosterSoftStart, &[0x27, 0x27, 0x18, 0x17])
303                    .await?;
304                self.send(spi, Command::CascadeSet, &[0x02]).await?;
305                self.send(spi, Command::ForceTemperature, &[0x5A]).await?;
306            }
307            RefreshMode::Full => {
308                // POWER SETTING
309                self.send(spi, Command::PowerSet, &[0x07, 0x07, 0x3F, 0x3F])
310                    .await?;
311
312                // BOOSTER SOFT START
313                self.send(spi, Command::BoosterSoftStart, &[0x17, 0x17, 0x28, 0x17])
314                    .await?;
315
316                // POWER ON
317                self.send(spi, Command::PowerOn, &[]).await?;
318                self.hw.delay().delay_ms(100).await;
319                self.hw.wait_if_busy().await?;
320
321                // PANEL SETTING
322                self.send(spi, Command::PanelSet, &[0x1F]).await?;
323
324                // RESOLUTION SETTING (TRES)
325                self.send(spi, Command::ResolutionSet, &[0x03, 0x20, 0x01, 0xE0])
326                    .await?;
327
328                // DUAL SPI
329                self.send(spi, Command::DualSPI, &[0x00]).await?;
330
331                // VCOM DATA INTERVAL
332                self.send(spi, Command::VCOMDataInterval, &[0x10, 0x07])
333                    .await?;
334
335                // If the screen appears gray, use the annotated initialization command
336
337                // TCON SETTING
338                self.send(spi, Command::TCONSet, &[0x22]).await?;
339            }
340            RefreshMode::Partial => {
341                // PANEL SETTING
342                self.send(spi, Command::PanelSet, &[0x1F]).await?;
343
344                // POWER ON
345                self.send(spi, Command::PowerOn, &[]).await?;
346                self.hw.delay().delay_ms(100).await;
347                self.hw.wait_if_busy().await?;
348
349                // Cascade + Force Temp
350                self.send(spi, Command::CascadeSet, &[0x02]).await?;
351                self.send(spi, Command::ForceTemperature, &[0x6E]).await?;
352            }
353            RefreshMode::Gray2 => {
354                // PANEL SETTING
355                self.send(spi, Command::PanelSet, &[0x1F]).await?;
356
357                // VCOM DATA INTERVAL
358                self.send(spi, Command::VCOMDataInterval, &[0x10, 0x07])
359                    .await?;
360
361                // POWER ON
362                self.send(spi, Command::PowerOn, &[]).await?;
363                self.hw.delay().delay_ms(100).await;
364                self.hw.wait_if_busy().await?;
365
366                // BOOSTER + Cascade + Force Temp
367                self.send(spi, Command::BoosterSoftStart, &[0x27, 0x27, 0x18, 0x17])
368                    .await?;
369                self.send(spi, Command::CascadeSet, &[0x02]).await?;
370                self.send(spi, Command::ForceTemperature, &[0x5F]).await?;
371            }
372        }
373
374        self.state.mode = mode;
375
376        Ok(())
377    }
378
379    /// Sets the window to which the next image data will be written.
380    pub async fn set_window(
381        &mut self,
382        spi: &mut HW::Spi,
383        shape: Rectangle,
384    ) -> Result<(), HW::Error> {
385        let Point {
386            x: x_start,
387            y: y_start,
388        } = shape.top_left;
389        let Point { x: x_end, y: y_end } = shape.bottom_right().unwrap();
390
391        self.send(spi, Command::PartialIn, &[]).await?;
392
393        let window: [u8; _] = [
394            (x_start / 256) as u8,
395            (x_start % 256) as u8,
396            (x_end / 256) as u8,
397            (x_end % 256) as u8 - 1,
398            (y_start / 256) as u8,
399            (y_start % 256) as u8,
400            (y_end / 256) as u8,
401            (y_end % 256) as u8 - 1,
402            0x01,
403        ];
404
405        self.send(spi, Command::PartialWindow, &window).await?;
406
407        Ok(())
408    }
409}
410
411async fn reset_impl<HW>(hw: &mut HW) -> Result<(), HW::Error>
412where
413    HW: ResetHw + DelayHw + ErrorHw,
414    HW::Error: From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>,
415{
416    debug!("Resetting EPD");
417    hw.reset().set_high()?;
418    hw.delay().delay_ms(20).await;
419    hw.reset().set_low()?;
420    hw.delay().delay_ms(2).await;
421    hw.reset().set_high()?;
422    hw.delay().delay_ms(200).await;
423    Ok(())
424}
425
426impl<HW, STATE: StateAwake> Reset<HW::Error> for Epd7In5V2<HW, STATE>
427where
428    HW: ResetHw + DelayHw + ErrorHw,
429    HW::Error: From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>,
430{
431    type DisplayOut = Epd7In5V2<HW, STATE>;
432
433    async fn reset(mut self) -> Result<Self::DisplayOut, HW::Error> {
434        reset_impl(&mut self.hw).await?;
435        Ok(self)
436    }
437}
438
439impl<HW, W: StateAwake> Reset<HW::Error> for Epd7In5V2<HW, StateAsleep<W>>
440where
441    HW: ResetHw + DelayHw + ErrorHw,
442    HW::Error: From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>,
443{
444    type DisplayOut = Epd7In5V2<HW, W>;
445
446    async fn reset(self) -> Result<Self::DisplayOut, HW::Error> {
447        // will do reset inside init()
448        Ok(Epd7In5V2 {
449            hw: self.hw,
450            state: self.state.wake_state,
451        })
452    }
453}
454
455impl<HW, STATE: StateAwake> Sleep<HW::Spi, HW::Error> for Epd7In5V2<HW, STATE>
456where
457    HW: BusyHw + DcHw + SpiHw + ErrorHw,
458    HW::Error: From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
459        + From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
460        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
461{
462    type DisplayOut = Epd7In5V2<HW, StateAsleep<StateUninitialized>>;
463
464    async fn sleep(mut self, spi: &mut HW::Spi) -> Result<Self::DisplayOut, HW::Error> {
465        debug!("Sleeping EPD");
466        self.send(spi, Command::VCOMDataInterval, &[0xF7]).await?;
467        self.send(spi, Command::PowerOff, &[]).await?;
468        self.send(spi, Command::DeepSleep, &[0xA5]).await?;
469        Ok(Epd7In5V2 {
470            hw: self.hw,
471            state: StateAsleep {
472                wake_state: StateUninitialized(),
473            },
474        })
475    }
476}
477
478impl<HW> Displayable<HW::Spi, HW::Error> for Epd7In5V2<HW, StateReady>
479where
480    HW: BusyHw + DcHw + SpiHw + ErrorHw + DelayHw,
481    HW::Error: From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
482        + From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
483        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
484{
485    async fn update_display(&mut self, spi: &mut HW::Spi) -> Result<(), HW::Error> {
486        debug!("Updating display");
487
488        self.send(spi, Command::DisplayRefresh, &[]).await?;
489        self.hw.delay().delay_ms(100).await;
490        self.hw.wait_if_busy().await?;
491        Ok(())
492    }
493}
494
495impl<HW> Clear<HW::Spi, HW::Error> for Epd7In5V2<HW, StateReady>
496where
497    HW: BusyHw + DcHw + SpiHw + ErrorHw + DelayHw,
498    HW::Error: From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
499        + From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
500        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
501{
502    async fn clear(&mut self, spi: &mut HW::Spi) -> Result<(), HW::Error> {
503        let buf1_value;
504        let buf2_value;
505        match self.state.mode {
506            RefreshMode::Fast | RefreshMode::Full | RefreshMode::Partial => {
507                buf1_value = 0xFF;
508                buf2_value = 0x00;
509            }
510            RefreshMode::Gray2 => {
511                buf1_value = 0x00;
512                buf2_value = 0x00;
513            }
514        };
515
516        self.hw
517            .send_iter(
518                spi,
519                Command::DisplayStartTrans1 as u8,
520                Some(core::iter::repeat_n(buf1_value, BINARY_BUFFER_LENGTH)),
521            )
522            .await?;
523        self.hw
524            .send_iter(
525                spi,
526                Command::DisplayStartTrans2 as u8,
527                Some(core::iter::repeat_n(buf2_value, BINARY_BUFFER_LENGTH)),
528            )
529            .await?;
530
531        self.update_display(spi).await?;
532        Ok(())
533    }
534}
535
536impl<HW> DisplaySimple<1, 1, HW::Spi, HW::Error> for Epd7In5V2<HW, StateReady>
537where
538    HW: BusyHw + DcHw + SpiHw + ErrorHw + DelayHw,
539    HW::Error: From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
540        + From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
541        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
542{
543    async fn display_framebuffer(
544        &mut self,
545        spi: &mut HW::Spi,
546        buf: &dyn BufferView<1, 1>,
547    ) -> Result<(), HW::Error> {
548        self.write_framebuffer(spi, buf).await?;
549
550        self.update_display(spi).await
551    }
552
553    async fn write_framebuffer(
554        &mut self,
555        spi: &mut HW::Spi,
556        buf: &dyn BufferView<1, 1>,
557    ) -> Result<(), HW::Error> {
558        let data = buf.data()[0];
559        self.send(spi, Command::DisplayStartTrans1, data).await?;
560        self.hw
561            .send_iter(
562                spi,
563                Command::DisplayStartTrans2 as u8,
564                Some(data.iter().map(|px| !px)),
565            )
566            .await?;
567        Ok(())
568    }
569}
570
571impl<HW> DisplaySimple<1, 2, HW::Spi, HW::Error> for Epd7In5V2<HW, StateReady>
572where
573    HW: BusyHw + DcHw + SpiHw + ErrorHw + DelayHw,
574    HW::Error: From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
575        + From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
576        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>,
577{
578    async fn display_framebuffer(
579        &mut self,
580        spi: &mut HW::Spi,
581        buf: &dyn BufferView<1, 2>,
582    ) -> Result<(), HW::Error> {
583        self.write_framebuffer(spi, buf).await?;
584
585        self.update_display(spi).await
586    }
587
588    async fn write_framebuffer(
589        &mut self,
590        spi: &mut HW::Spi,
591        buf: &dyn BufferView<1, 2>,
592    ) -> Result<(), HW::Error> {
593        let data = buf.data();
594        self.send(spi, Command::DisplayStartTrans1, data[0]).await?;
595        self.send(spi, Command::DisplayStartTrans2, data[1]).await?;
596        Ok(())
597    }
598}
599
600impl<HW> DisplayPartial<1, 1, HW::Spi, HW::Error> for Epd7In5V2<HW, StateReady>
601where
602    HW: BusyHw + DcHw + SpiHw + ErrorHw + DelayHw + ResetHw,
603    HW::Error: From<<HW::Busy as embedded_hal::digital::ErrorType>::Error>
604        + From<<HW::Dc as embedded_hal::digital::ErrorType>::Error>
605        + From<<HW::Spi as embedded_hal_async::spi::ErrorType>::Error>
606        + From<<HW::Reset as embedded_hal::digital::ErrorType>::Error>,
607{
608    async fn write_base_framebuffer(
609        &mut self,
610        spi: &mut HW::Spi,
611        buf: &dyn BufferView<1, 1>,
612    ) -> Result<(), HW::Error> {
613        let buffer_bounds = buf.window();
614
615        let data = buf.data()[0];
616
617        self.send(spi, Command::VCOMDataInterval, &[0xA9, 0x07])
618            .await?;
619
620        self.set_window(spi, buffer_bounds).await?;
621
622        self.send(spi, Command::DisplayStartTrans2, data).await?;
623
624        Ok(())
625    }
626}