epd_waveshare_async/
epd2in9.rs

1use core::time::Duration;
2use embedded_graphics::{
3    pixelcolor::BinaryColor,
4    prelude::{Dimensions, Point, Size},
5    primitives::Rectangle,
6};
7use embedded_hal::{
8    digital::{InputPin, OutputPin},
9    spi::{Phase, Polarity},
10};
11use embedded_hal_async::{delay::DelayNs, digital::Wait, spi::SpiDevice};
12
13use crate::{
14    buffer::{binary_buffer_length, BinaryBuffer},
15    log::{debug, trace},
16    Epd, EpdHw,
17};
18
19/// LUT for a full refresh. This should be used occasionally for best display results.
20///
21/// See [RECOMMENDED_MIN_FULL_REFRESH_INTERVAL] and [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
22const LUT_FULL_UPDATE: [u8; 30] = [
23    0x50, 0xAA, 0x55, 0xAA, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
24    0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
25];
26/// LUT for a partial refresh. This should be used for frequent updates, but it's recommended to
27/// perform a full refresh occasionally.
28///
29/// See [RECOMMENDED_MIN_FULL_REFRESH_INTERVAL] and [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
30const LUT_PARTIAL_UPDATE: [u8; 30] = [
31    0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
32    0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
33];
34
35#[cfg_attr(feature = "defmt", derive(defmt::Format))]
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37/// The refresh mode for the display.
38pub enum RefreshMode {
39    /// Use the full update LUT. This is slower, but should be done occasionally to avoid ghosting.
40    ///
41    /// It's recommended to avoid full refreshes less than [RECOMMENDED_MIN_FULL_REFRESH_INTERVAL] apart,
42    /// but to do a full refresh at least every [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
43    Full,
44    /// Use the partial update LUT for fast refresh. A full refresh should be done occasionally to
45    /// avoid ghosting, see [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
46    Partial,
47}
48
49impl RefreshMode {
50    /// Returns the LUT to use for this refresh mode.
51    pub fn lut(&self) -> &[u8; 30] {
52        match self {
53            RefreshMode::Full => &LUT_FULL_UPDATE,
54            RefreshMode::Partial => &LUT_PARTIAL_UPDATE,
55        }
56    }
57}
58
59/// The height of the display (portrait orientation).
60pub const DISPLAY_HEIGHT: u16 = 296;
61/// The width of the display (portrait orientation).
62pub const DISPLAY_WIDTH: u16 = 128;
63/// It's recommended to avoid doing a full refresh more often than this (at least on a regular basis).
64pub const RECOMMENDED_MIN_FULL_REFRESH_INTERVAL: Duration = Duration::from_secs(180);
65/// It's recommended to do a full refresh at least this often.
66pub const RECOMMENDED_MAX_FULL_REFRESH_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60);
67pub const RECOMMENDED_SPI_HZ: u32 = 4_000_000; // 4 MHz
68/// Use this phase in conjunction with [RECOMMENDED_SPI_POLARITY] so that the EPD can capture data
69/// on the rising edge.
70pub const RECOMMENDED_SPI_PHASE: Phase = Phase::CaptureOnFirstTransition;
71/// Use this polarity in conjunction with [RECOMMENDED_SPI_PHASE] so that the EPD can capture data
72/// on the rising edge.
73pub const RECOMMENDED_SPI_POLARITY: Polarity = Polarity::IdleLow;
74
75#[cfg_attr(feature = "defmt", derive(defmt::Format))]
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum Command {
78    /// Used to initialise the display.
79    DriverOutputControl = 0x01,
80    /// Used to configure the on chip voltage booster and regulator.
81    BoosterSoftStartControl = 0x0C,
82    /// Used to enter deep sleep mode. Requires a hardware reset and reinitialisation to wake up.
83    DeepSleepMode = 0x10,
84    /// Changes the auto-increment behaviour of the address counter.
85    DataEntryModeSetting = 0x11,
86    /// Resets all commands and parameters to default values (except deep sleep mode).
87    SwReset = 0x12,
88    /// Writes to the temperature register.
89    TemperatureSensorControl = 0x1A,
90    /// Activates the display update sequence. This must be set beforehand using [Command::DisplayUpdateControl2].
91    /// This operation must not be interrupted.
92    MasterActivation = 0x20,
93    /// Used for a RAM "bypass" mode, though the precise meaning is unclear.
94    DisplayUpdateControl1 = 0x21,
95    /// Configures the display update sequence for use with [Command::MasterActivation].
96    DisplayUpdateControl2 = 0x22,
97    /// Writes data to RAM, auto-incrementing the address counter.
98    WriteRam = 0x24,
99    /// Writes to the VCOM register.
100    WriteVcom = 0x2C,
101    /// Writes the LUT register (30 bytes, exclude the VSH/VSL and dummy bits).
102    WriteLut = 0x32,
103    /// ? Part of magic config.
104    SetDummyLinePeriod = 0x3A,
105    /// ? Part of magic config.
106    SetGateLineWidth = 0x3B,
107    /// Register to configure the behaviour of the border.
108    BorderWaveformControl = 0x3C,
109    /// Sets the start and end positions of the X axis for the auto-incrementing address counter.
110    /// Note that the x position can only be configured as a multiple of 8.
111    SetRamXStartEnd = 0x44,
112    /// Sets the start and end positions of the Y axis for the auto-incrementing address counter.
113    SetRamYStartEnd = 0x45,
114    /// Sets the current x coordinate of the address counter.
115    /// Note that the x position can only be configured as a multiple of 8.
116    SetRamX = 0x4E,
117    /// Sets the current y coordinate of the address counter.
118    SetRamY = 0x4F,
119    /// Does nothing, but can be used to terminate other commands such as [Command::WriteRam]
120    Noop = 0xFF,
121}
122
123impl Command {
124    /// Returns the register address for this command.
125    fn register(&self) -> u8 {
126        *self as u8
127    }
128}
129
130/// This should be sent with [Command::DriverOutputControl] during initialisation.
131///
132/// From the sample code, the bytes mean the following:
133///
134/// * low byte of display long edge
135/// * high byte of display long edge
136/// * GD = 0, SM = 0, TB = 0 (unclear what this means)
137const DRIVER_OUTPUT_INIT_DATA: [u8; 3] = [0x27, 0x01, 0x00];
138/// This should be sent with [Command::BoosterSoftStartControl] during initialisation.
139/// Note that there are two versions of this command, one in the datasheet, and one in the sample code.
140const BOOSTER_SOFT_START_INIT_DATA: [u8; 3] = [0xD7, 0xD6, 0x9D];
141// Sample code: ^
142// Datasheet:
143// const BOOSTER_SOFT_START_INIT_DATA: [u8; 3] = [0xCF, 0xCE, 0x8D];
144
145/// Controls v1 of the 2.9" Waveshare e-paper display.
146///
147/// * [datasheet](https://files.waveshare.com/upload/e/e6/2.9inch_e-Paper_Datasheet.pdf)
148/// * [sample code](https://github.com/waveshareteam/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd2in9.py)
149///
150/// The display has a portrait orientation. This uses [BinaryColor], where `Off` is black and `On` is white.
151pub struct Epd2In9<HW>
152where
153    HW: EpdHw,
154{
155    hw: HW,
156}
157
158impl<HW> Epd2In9<HW>
159where
160    HW: EpdHw,
161{
162    pub fn new(hw: HW) -> Self {
163        Epd2In9 { hw }
164    }
165
166    /// Sets the border to the specified colour. You need to call [Epd::update_display] using
167    /// [RefreshMode::Full] afterwards to apply this change.
168    pub async fn set_border(
169        &mut self,
170        spi: &mut HW::Spi,
171        color: BinaryColor,
172    ) -> Result<(), HW::Error> {
173        let border_setting: u8 = match color {
174            BinaryColor::Off => 0x00,
175            BinaryColor::On => 0x01,
176        };
177        self.send(spi, Command::BorderWaveformControl, &[border_setting])
178            .await
179    }
180
181    /// Send the following command and data to the display. Waits until the display is no longer busy before sending.
182    async fn send(
183        &mut self,
184        spi: &mut HW::Spi,
185        command: Command,
186        data: &[u8],
187    ) -> Result<(), HW::Error> {
188        trace!("Sending EPD command: {:?}", command);
189        self.wait_if_busy().await?;
190
191        self.hw.dc().set_low()?;
192        spi.write(&[command.register()]).await?;
193
194        if !data.is_empty() {
195            self.hw.dc().set_high()?;
196            spi.write(data).await?;
197        }
198
199        Ok(())
200    }
201
202    /// Waits for the current operation to complete if the display is busy.
203    ///
204    /// Note that this will wait forever if the display is asleep.
205    async fn wait_if_busy(&mut self) -> Result<(), HW::Error> {
206        let busy = self.hw.busy();
207        // Note: the datasheet states that busy pin is active low, i.e. we should wait for it when
208        // it's low, but this is incorrect. The sample code treats it as active high, which works.
209        if busy.is_high().unwrap() {
210            trace!("Waiting for busy EPD");
211            busy.wait_for_low().await?;
212        }
213        Ok(())
214    }
215}
216
217impl<HW> Epd<HW> for Epd2In9<HW>
218where
219    HW: EpdHw,
220{
221    type RefreshMode = RefreshMode;
222    type Buffer = BinaryBuffer<
223        { binary_buffer_length(Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)) },
224    >;
225
226    fn new_buffer(&self) -> Self::Buffer {
227        BinaryBuffer::new(Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32))
228    }
229
230    fn width(&self) -> u32 {
231        DISPLAY_WIDTH as u32
232    }
233
234    fn height(&self) -> u32 {
235        DISPLAY_HEIGHT as u32
236    }
237
238    async fn init(&mut self, spi: &mut HW::Spi, mode: RefreshMode) -> Result<(), HW::Error> {
239        // Ensure reset is high before toggling it low.
240        self.reset().await?;
241
242        // Reset all configurations to default.
243        self.send(spi, Command::SwReset, &[]).await?;
244
245        self.send(spi, Command::DriverOutputControl, &DRIVER_OUTPUT_INIT_DATA)
246            .await?;
247        self.send(
248            spi,
249            Command::BoosterSoftStartControl,
250            &BOOSTER_SOFT_START_INIT_DATA,
251        )
252        .await?;
253        // Auto-increment X and Y, moving in the X direction first.
254        self.send(spi, Command::DataEntryModeSetting, &[0b11])
255            .await?;
256
257        // Apply more magical config settings from the sample code.
258        // Potentially: configure VCOM for 7 degrees celsius?
259        self.send(spi, Command::WriteVcom, &[0xA8]).await?;
260        // Configure 4 dummy lines per gate.
261        self.send(spi, Command::SetDummyLinePeriod, &[0x1A]).await?;
262        // 2us per line.
263        self.send(spi, Command::SetGateLineWidth, &[0x08]).await?;
264
265        self.send(spi, Command::WriteLut, mode.lut()).await?;
266
267        Ok(())
268    }
269
270    async fn set_refresh_mode(
271        &mut self,
272        spi: &mut <HW as EpdHw>::Spi,
273        mode: Self::RefreshMode,
274    ) -> Result<(), <HW as EpdHw>::Error> {
275        debug!("Changing refresh mode to {:?}", mode);
276        self.send(spi, Command::WriteLut, mode.lut()).await
277    }
278
279    async fn reset(&mut self) -> Result<(), HW::Error> {
280        debug!("Resetting EPD");
281        // Assume reset is already high.
282        self.hw.reset().set_low()?;
283        self.hw.delay().delay_ms(10).await;
284        self.hw.reset().set_high()?;
285        self.hw.delay().delay_ms(10).await;
286        Ok(())
287    }
288
289    async fn sleep(&mut self, spi: &mut HW::Spi) -> Result<(), <HW as EpdHw>::Error> {
290        debug!("Sleeping EPD");
291        self.send(spi, Command::DeepSleepMode, &[0x01]).await
292    }
293
294    async fn wake(&mut self, _spi: &mut HW::Spi) -> Result<(), <HW as EpdHw>::Error> {
295        debug!("Waking EPD");
296        self.reset().await
297
298        // Confirmed with a physical screen that init is not required after waking.
299    }
300
301    async fn display_buffer(
302        &mut self,
303        spi: &mut HW::Spi,
304        buffer: &Self::Buffer,
305    ) -> Result<(), <HW as EpdHw>::Error> {
306        debug!("Displaying buffer");
307        let buffer_bounds = buffer.bounding_box();
308        self.set_window(spi, buffer_bounds).await?;
309        self.set_cursor(spi, buffer_bounds.top_left).await?;
310        self.write_image(spi, buffer.data()).await?;
311
312        self.update_display(spi).await?;
313
314        Ok(())
315    }
316
317    /// Sets the window to which the next image data will be written.
318    ///
319    /// The x-axis only supports multiples of 8; values outside this result in a debug-mode panic,
320    /// or potentially misaligned content when debug assertions are disabled.
321    async fn set_window(
322        &mut self,
323        spi: &mut HW::Spi,
324        shape: Rectangle,
325    ) -> Result<(), <HW as EpdHw>::Error> {
326        // Use a debug assert as this is a soft failure in production; it will just lead to
327        // slightly misaligned display content.
328        let x_start = shape.top_left.x;
329        let x_end = x_start + shape.size.width as i32 - 1;
330        #[cfg(feature = "defmt")]
331        defmt::debug_assert!(
332            x_start % 8 == 0 && x_end % 8 == 7,
333            "window's top_left.x and width must be 8-bit aligned"
334        );
335        #[cfg(not(feature = "defmt"))]
336        debug_assert!(
337            x_start % 8 == 0 && x_end % 8 == 7,
338            "window's top_left.x and width must be 8-bit aligned"
339        );
340        let x_start_byte = ((x_start >> 3) & 0xFF) as u8;
341        let x_end_byte = ((x_end >> 3) & 0xFF) as u8;
342        self.send(spi, Command::SetRamXStartEnd, &[x_start_byte, x_end_byte])
343            .await?;
344
345        let (y_start_low, y_start_high) = split_low_and_high(shape.top_left.y as u16);
346        let (y_end_low, y_end_high) =
347            split_low_and_high((shape.top_left.y + shape.size.height as i32 - 1) as u16);
348        self.send(
349            spi,
350            Command::SetRamYStartEnd,
351            &[y_start_low, y_start_high, y_end_low, y_end_high],
352        )
353        .await?;
354
355        Ok(())
356    }
357
358    /// Sets the cursor position to write the next data to.
359    ///
360    /// The x-axis only supports multiples of 8; values outside this will result in a panic in
361    /// debug mode, or potentially misaligned content if debug assertions are disabled.
362    async fn set_cursor(
363        &mut self,
364        spi: &mut HW::Spi,
365        position: Point,
366    ) -> Result<(), <HW as EpdHw>::Error> {
367        // Use a debug assert as this is a soft failure in production; it will just lead to
368        // slightly misaligned display content.
369        #[cfg(feature = "defmt")]
370        defmt::debug_assert_eq!(position.x % 8, 0, "position.x must be 8-bit aligned");
371        #[cfg(not(feature = "defmt"))]
372        debug_assert_eq!(position.x % 8, 0, "position.x must be 8-bit aligned");
373
374        self.send(spi, Command::SetRamX, &[(position.x >> 3) as u8])
375            .await?;
376        let (y_low, y_high) = split_low_and_high(position.y as u16);
377        self.send(spi, Command::SetRamY, &[y_low, y_high]).await?;
378        Ok(())
379    }
380
381    async fn update_display(&mut self, spi: &mut HW::Spi) -> Result<(), <HW as EpdHw>::Error> {
382        // Enable the clock and CP (?), and then display the data from the RAM. Note that there are
383        // two RAM buffers, so this will swap the active buffer. Calling this function twice in a row
384        // without writing further to RAM therefore results in displaying the previous image.
385
386        // Experimentation:
387        // * Sending just 0x04 doesn't work, it hangs in busy state. The clocks are needed.
388        // * Sending 0xC8 (INITIAL_DISPLAY) results in a black screen.
389        // * Sending 0xCD (INITIAL_DISPLAY + PATTERN_DISPLAY) results in seemingly broken, semi-random behaviour.
390        // The INIITIAL_DISPLAY settings potentially relate to the "bypass" settings in
391        // [Command::DisplayUpdateControl1], but the precise mode is unclear.
392
393        self.send(spi, Command::DisplayUpdateControl2, &[0xC4])
394            .await?;
395        self.send(spi, Command::MasterActivation, &[]).await?;
396        self.send(spi, Command::Noop, &[]).await?;
397        Ok(())
398    }
399
400    async fn write_image(
401        &mut self,
402        spi: &mut HW::Spi,
403        image: &[u8],
404    ) -> Result<(), <HW as EpdHw>::Error> {
405        self.send(spi, Command::WriteRam, image).await
406    }
407}
408
409#[inline(always)]
410/// Splits a 16-bit value into the two 8-bit values representing the low and high bytes.
411fn split_low_and_high(value: u16) -> (u8, u8) {
412    let low = (value & 0xFF) as u8;
413    let high = ((value >> 8) & 0xFF) as u8;
414    (low, high)
415}