Skip to main content

epd_datafuri/displays/
adafruit_thinkink_290_mfgn.rs

1//! Driver and graphics buffers for Adafruit 2.9" e-ink displays using the SSD1680 controller.
2//!
3//! This module targets the 2025 revision of the Adafruit ThinkInk 2.9" and MagTag boards,
4//! which use the SSD1680 e-paper controller. The original MagTag revision uses an IL0373
5//! instead; see [`adafruit_thinkink_290_t5`](self::adafruit_thinkink_290_t5).
6//!
7//! ## Key differences from the IL0373 variant
8//!
9//! | Property | SSD1680 | IL0373 |
10//! |----------|---------|--------|
11//! | Busy pin polarity | Active-high (HIGH = busy) | Active-low (LOW = busy) |
12//! | LUT registers | Single register (0x32) | Five registers (0x20–0x24) |
13//! | RAM address counters | Required | Not used |
14//! | Gray2 encoding | Identical two-plane scheme | Identical two-plane scheme |
15//!
16//! Two driver structs are provided:
17//! - [`adafruit_thinkink_290_mfgn::ThinkInk2in9Mono`](self::adafruit_thinkink_290_mfgn::ThinkInk2in9Mono): black/white rendering using the mono full LUT
18//! - [`adafruit_thinkink_290_mfgn::ThinkInk2in9Gray2`](self::adafruit_thinkink_290_mfgn::ThinkInk2in9Gray2): 4-level grayscale rendering using the Gray2 LUT
19
20use crate::color::Color;
21use crate::driver::ssd1680::{Cmd, Flag};
22use crate::driver::EpdDriver;
23use crate::interface::SpiDisplayInterface;
24use display_interface::DisplayError;
25use embedded_hal::delay::DelayNs;
26use embedded_hal::digital::{InputPin, OutputPin};
27use embedded_hal::spi::SpiDevice;
28use log::debug;
29
30pub use crate::graphics::display290_gray4_mfgn::Display2in9Gray2;
31pub use crate::graphics::display290_mono::Display2in9Mono;
32
33/// Display width for 2.9in display
34pub const WIDTH: u16 = 128;
35/// Display height for 2.9in display
36pub const HEIGHT: u16 = 296;
37
38#[rustfmt::skip]
39/// Adafruit ThinkInk 290 EA4MFGN MONO FULL LUT CODE Cmd: 0x32 Size: 0x99,
40pub const TI_290_MONOFULL_LUT_CODE: [u8; 153] = [
41  0x80,	0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, //VS L0
42  0x10, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, //VS L1
43  0x80, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, //VS L2
44  0x10, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, //VS L3
45  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //VS L4
46  0x14, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01,                               //TP, SR, RP of Group0
47  0x0A, 0x0A, 0x00, 0x0A, 0x0A, 0x00, 0x01,                               //TP, SR, RP of Group1
48  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group2
49  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group3
50  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group4
51  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group5
52  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group6
53  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group7
54  0x14, 0x08, 0x00, 0x01, 0x00, 0x00, 0x01,                               //TP, SR, RP of Group8
55  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,                               //TP, SR, RP of Group9
56  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group10
57  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group11
58  0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00                    //FR, XON
59];
60
61#[rustfmt::skip]
62/// Adafruit ThinkInk 290 EA4MFGN GRAY2 LUT CODE Cmd: 0x32 Size: 0x99,
63pub const TI_290MFGN_GRAY2_LUT_CODE: [u8; 153] = [
64  0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //VS L0	 //2.28s
65  0x20, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //VS L1
66  0x28, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //VS L2
67  0x2A, 0x60, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //VS L3
68  0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //VS L4
69  0x00, 0x02, 0x00, 0x05, 0x14, 0x00, 0x00,                               //TP, SR, RP of Group0
70  0x1E, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x01,                               //TP, SR, RP of Group1
71  0x00, 0x02, 0x00, 0x05, 0x14, 0x00, 0x00,                               //TP, SR, RP of Group2
72  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group3
73  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group4
74  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group5
75  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group6
76  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group7
77  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group8
78  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group9
79  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group10
80  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,                               //TP, SR, RP of Group11
81  0x24, 0x22, 0x22, 0x22, 0x23, 0x32, 0x00, 0x00, 0x00,                   //FR, XON
82];
83
84/// Driver for the Adafruit ThinkInk 2.9" monochrome e-ink display (SSD1680 controller).
85///
86/// The SSD1680 busy pin is active-high (HIGH = busy, LOW = ready).
87///
88/// Use [`Display2in9Mono`] as the graphics buffer for black/white rendering, or
89/// [`ThinkInk2in9Gray2`] with [`Display2in9Gray2`] for 4-level grayscale.
90pub struct ThinkInk2in9Mono<SPI, BSY, DC, RST>
91where
92    SPI: SpiDevice,
93    BSY: InputPin,
94    DC: OutputPin,
95    RST: OutputPin,
96{
97    interface: SpiDisplayInterface<SPI, BSY, DC, RST>,
98}
99
100impl<SPI, BSY, DC, RST> ThinkInk2in9Mono<SPI, BSY, DC, RST>
101where
102    SPI: SpiDevice,
103    BSY: InputPin,
104    DC: OutputPin,
105    RST: OutputPin,
106{
107    /// Create a new ThinkInk 2.9" display
108    pub fn new(spi: SPI, busy: BSY, dc: DC, rst: RST) -> Result<Self, DisplayError> {
109        let interface = SpiDisplayInterface::new(spi, busy, dc, rst);
110        Ok(Self { interface })
111    }
112
113    /// Update the whole BW buffer on the display driver
114    fn write_bw_ram(&mut self, buffer: &[u8]) -> Result<(), DisplayError> {
115        self.set_ram_counter(0, 0)?;
116        self.interface.cmd_with_data(Cmd::WRITE_BW_DATA, buffer)
117    }
118
119    /// Update the whole Red buffer on the display driver
120    fn write_red_ram(&mut self, buffer: &[u8]) -> Result<(), DisplayError> {
121        self.set_ram_counter(0, 0)?;
122        self.interface.cmd_with_data(Cmd::WRITE_RED_DATA, buffer)
123    }
124
125    fn display(&mut self, delay: &mut impl DelayNs) -> Result<(), DisplayError> {
126        self.interface
127            .cmd_with_data(Cmd::DISPLAY_UPDATE_CTRL2, &[Flag::DISPLAY_MODE_1])?;
128        self.interface.cmd(Cmd::MASTER_ACTIVATE)?;
129        self.interface.wait_until_idle(delay);
130        Ok(())
131    }
132
133    /// Fill both RAM planes with white and trigger two successive refreshes.
134    ///
135    /// A double refresh is used to fully clear any image ghosting from the panel.
136    pub fn clear_display(&mut self, delay: &mut impl DelayNs) -> Result<(), DisplayError> {
137        self.clear_bw_ram()?;
138        self.clear_red_ram()?;
139        self.display(delay)?;
140        delay.delay_ms(100);
141        self.display(delay)
142    }
143
144    /// Write the black/white buffer to the display and trigger a full refresh.
145    ///
146    /// This method calls [`EpdDriver::init`] internally, so there is no need
147    /// to call it separately before invoking this method.
148    pub fn update_and_display(
149        &mut self,
150        bw_buffer: &[u8],
151        delay: &mut impl DelayNs,
152    ) -> Result<(), DisplayError> {
153        self.init(delay)?;
154        self.update_bw(bw_buffer, delay)?;
155        self.display(delay)?;
156        self.sleep(delay)
157    }
158
159    /// Write both Gray2 framebuffer planes to the display and trigger a full refresh.
160    ///
161    /// `bw_buffer` is written to the black/white RAM plane and `red_buffer` to the
162    /// red RAM plane. Pass [`Display2in9Gray2::high_buffer`] and
163    /// [`Display2in9Gray2::low_buffer`] respectively.
164    ///
165    /// This method calls [`EpdDriver::init`] internally, so there is no need
166    /// to call it separately before invoking this method.
167    pub fn update_gray2_and_display(
168        &mut self,
169        bw_buffer: &[u8],
170        red_buffer: &[u8],
171        delay: &mut impl DelayNs,
172    ) -> Result<(), DisplayError> {
173        self.init(delay)?;
174        self.update_bw(bw_buffer, delay)?;
175        self.update_red(red_buffer, delay)?;
176        self.display(delay)?;
177        self.sleep(delay)
178    }
179
180    fn set_ram_counter(&mut self, x: u32, y: u32) -> Result<(), DisplayError> {
181        self.interface
182            .cmd_with_data(Cmd::SET_RAMX_COUNTER, &[(x >> 3) as u8])?;
183
184        self.interface.cmd_with_data(
185            Cmd::SET_RAMY_COUNTER,
186            &[(y & 0xFF) as u8, ((y >> 8) & 0x01) as u8],
187        )?;
188        Ok(())
189    }
190}
191
192impl<SPI, BSY, DC, RST> EpdDriver for ThinkInk2in9Mono<SPI, BSY, DC, RST>
193where
194    SPI: SpiDevice,
195    BSY: InputPin,
196    DC: OutputPin,
197    RST: OutputPin,
198{
199    /// Reset and fully initialize the display for monochrome operation.
200    ///
201    /// Performs a hardware reset, sends the panel configuration, loads the mono
202    /// full LUT ([`TI_290_MONOFULL_LUT_CODE`]), and waits for the busy pin to
203    /// go low before returning.
204    fn init(&mut self, delay: &mut impl DelayNs) -> Result<(), DisplayError> {
205        debug!("powering up ThinkInk 2.9in mono display");
206        // hard reset
207        self.interface.hard_reset(delay)?;
208        // Set Initialial Configuration
209        self.interface.cmd(Cmd::SW_RESET)?;
210        delay.delay_ms(10);
211        // Send Initialization Code 0x01, 0x11, 0x44, 0x45, 0x3C
212        self.interface
213            .cmd_with_data(Cmd::DRIVER_OUTPUT_CTRL, &[0x27, 0x01, 0x00])?;
214        self.interface
215            .cmd_with_data(Cmd::DATA_ENTRY_MODE, &[Flag::DATA_ENTRY_INCRY_INCRX])?;
216        self.interface
217            .cmd_with_data(Cmd::SET_RAMXPOS, &[0x00, 0x0F])?;
218        self.interface
219            .cmd_with_data(Cmd::SET_RAMYPOS, &[0x00, 0x00, 0x27, 0x01])?;
220        self.interface.cmd_with_data(
221            Cmd::BORDER_WAVEFORM_CTRL,
222            &[Flag::BORDER_WAVEFORM_FOLLOW_LUT | Flag::BORDER_WAVEFORM_LUT1],
223        )?;
224        // Load Waveform LUT
225        self.interface
226            .cmd_with_data(Cmd::TEMP_CONTROL, &[Flag::INTERNAL_TEMP_SENSOR])?;
227        self.interface
228            .cmd_with_data(Cmd::DISPLAY_UPDATE_CTRL1, &[0x00, 0x80])?;
229        self.interface
230            .cmd_with_data(Cmd::END_OPTION, &[Flag::END_OPTION_NORMAL])?;
231        self.interface
232            .cmd_with_data(Cmd::GATE_VOLTAGE_CTRL, &[0x17])?;
233        self.interface
234            .cmd_with_data(Cmd::SOURCE_VOLTAGE_CTRL, &[0x41, 0x00, 0x32])?;
235        self.interface.cmd_with_data(Cmd::WRITE_VCOM_REG, &[0x36])?;
236        // load LUT into memory
237        self.interface
238            .cmd_with_data(Cmd::WRITE_LUT_REG, &TI_290_MONOFULL_LUT_CODE)?;
239        self.interface.wait_until_idle(delay);
240        Ok(())
241    }
242
243    /// Power down the display controller into deep sleep mode.
244    fn sleep(&mut self, delay: &mut impl DelayNs) -> Result<(), DisplayError> {
245        debug!("powering down ThinkInk 2.9\" display");
246        self.interface.cmd_with_data(Cmd::DEEP_SLEEP, &[0x01])?;
247        delay.delay_ms(1);
248        Ok(())
249    }
250
251    /// Write `buffer` to the black/white RAM plane and wait until ready.
252    fn update_bw(&mut self, buffer: &[u8], delay: &mut impl DelayNs) -> Result<(), DisplayError> {
253        self.write_bw_ram(buffer)?;
254        self.interface.wait_until_idle(delay);
255        Ok(())
256    }
257
258    /// Write `buffer` to the red RAM plane and wait until ready.
259    fn update_red(&mut self, buffer: &[u8], delay: &mut impl DelayNs) -> Result<(), DisplayError> {
260        self.write_red_ram(buffer)?;
261        self.interface.wait_until_idle(delay);
262        Ok(())
263    }
264
265    /// Write both RAM planes and wait until ready.
266    ///
267    /// Does not trigger a display refresh; call [`Self::update_and_display`] or
268    /// [`Self::update_gray2_and_display`] for a complete write-and-refresh cycle.
269    fn update(
270        &mut self,
271        low_buffer: &[u8],
272        high_buffer: &[u8],
273        delay: &mut impl DelayNs,
274    ) -> Result<(), DisplayError> {
275        self.write_bw_ram(low_buffer)?;
276        self.write_red_ram(high_buffer)?;
277        self.interface.wait_until_idle(delay);
278        Ok(())
279    }
280
281    /// Fill the black/white RAM plane with white, clearing it.
282    fn clear_bw_ram(&mut self) -> Result<(), DisplayError> {
283        let color = Color::White.get_byte_value();
284        self.interface.cmd(Cmd::WRITE_BW_DATA)?;
285        self.interface
286            .data_x_times(color, u32::from(WIDTH).div_ceil(8) * u32::from(HEIGHT))?;
287        Ok(())
288    }
289
290    /// Fill the red RAM plane with its cleared state.
291    fn clear_red_ram(&mut self) -> Result<(), DisplayError> {
292        let color = Color::White.inverse().get_byte_value();
293        self.interface.cmd(Cmd::WRITE_RED_DATA)?;
294        self.interface
295            .data_x_times(color, u32::from(WIDTH).div_ceil(8) * u32::from(HEIGHT))?;
296        Ok(())
297    }
298
299    /// Hardware-reset the panel and put it into a low-power sleep state.
300    ///
301    /// Use this to prepare the display before the first [`Self::init`] call.
302    fn begin(&mut self, delay: &mut impl DelayNs) -> Result<(), DisplayError> {
303        self.interface.hard_reset(delay)?;
304        self.sleep(delay)
305    }
306}
307
308/// Driver for the Adafruit ThinkInk 2.9" 4-level grayscale e-ink display (SSD1680 controller).
309///
310/// The SSD1680 busy pin is active-high (HIGH = busy, LOW = ready).
311///
312/// Use [`Display2in9Gray2::new`] to create a correctly configured
313/// graphics buffer for this driver.
314pub struct ThinkInk2in9Gray2<SPI, BSY, DC, RST>
315where
316    SPI: SpiDevice,
317    BSY: InputPin,
318    DC: OutputPin,
319    RST: OutputPin,
320{
321    interface: SpiDisplayInterface<SPI, BSY, DC, RST>,
322}
323
324impl<SPI, BSY, DC, RST> ThinkInk2in9Gray2<SPI, BSY, DC, RST>
325where
326    SPI: SpiDevice,
327    BSY: InputPin,
328    DC: OutputPin,
329    RST: OutputPin,
330{
331    /// Create a new ThinkInk 2.9" grayscale display
332    pub fn new(spi: SPI, busy: BSY, dc: DC, rst: RST) -> Result<Self, DisplayError> {
333        let interface = SpiDisplayInterface::new(spi, busy, dc, rst);
334        Ok(Self { interface })
335    }
336
337    /// Update the whole BW buffer on the display driver
338    fn write_bw_ram(&mut self, buffer: &[u8]) -> Result<(), DisplayError> {
339        self.set_ram_counter(0, 0)?;
340        self.interface.cmd_with_data(Cmd::WRITE_BW_DATA, buffer)
341    }
342
343    /// Update the whole Red buffer on the display driver
344    fn write_red_ram(&mut self, buffer: &[u8]) -> Result<(), DisplayError> {
345        self.set_ram_counter(0, 0)?;
346        self.interface.cmd_with_data(Cmd::WRITE_RED_DATA, buffer)
347    }
348
349    fn display(&mut self, delay: &mut impl DelayNs) -> Result<(), DisplayError> {
350        self.interface
351            .cmd_with_data(Cmd::DISPLAY_UPDATE_CTRL2, &[Flag::DISPLAY_MODE_1])?;
352        self.interface.cmd(Cmd::MASTER_ACTIVATE)?;
353        self.interface.wait_until_idle(delay);
354        Ok(())
355    }
356
357    /// Fill both RAM planes with white and trigger two successive refreshes.
358    ///
359    /// A double refresh is used to fully clear any image ghosting from the panel.
360    pub fn clear_display(&mut self, delay: &mut impl DelayNs) -> Result<(), DisplayError> {
361        self.clear_bw_ram()?;
362        self.clear_red_ram()?;
363        self.display(delay)?;
364        delay.delay_ms(100);
365        self.display(delay)
366    }
367
368    /// Write the black/white buffer to the display and trigger a full refresh.
369    ///
370    /// This method calls [`EpdDriver::init`] internally, so there is no need
371    /// to call it separately before invoking this method.
372    pub fn update_and_display(
373        &mut self,
374        bw_buffer: &[u8],
375        delay: &mut impl DelayNs,
376    ) -> Result<(), DisplayError> {
377        self.init(delay)?;
378        self.update_bw(bw_buffer, delay)?;
379        self.display(delay)?;
380        self.sleep(delay)
381    }
382
383    /// Write both Gray2 framebuffer planes to the display and trigger a full refresh.
384    ///
385    /// `high_buffer` is written to the black/white RAM plane and `low_buffer` to the
386    /// red RAM plane. Pass [`Display2in9Gray2::high_buffer`] and
387    /// [`Display2in9Gray2::low_buffer`] respectively.
388    ///
389    /// This method calls [`EpdDriver::init`] internally, so there is no need
390    /// to call it separately before invoking this method.
391    pub fn update_gray2_and_display(
392        &mut self,
393        high_buffer: &[u8],
394        low_buffer: &[u8],
395        delay: &mut impl DelayNs,
396    ) -> Result<(), DisplayError> {
397        self.init(delay)?;
398        self.update_bw(high_buffer, delay)?;
399        self.update_red(low_buffer, delay)?;
400        self.display(delay)?;
401        self.sleep(delay)
402    }
403
404    fn set_ram_counter(&mut self, x: u32, y: u32) -> Result<(), DisplayError> {
405        self.interface
406            .cmd_with_data(Cmd::SET_RAMX_COUNTER, &[(x >> 3) as u8])?;
407
408        self.interface.cmd_with_data(
409            Cmd::SET_RAMY_COUNTER,
410            &[(y & 0xFF) as u8, ((y >> 8) & 0x01) as u8],
411        )?;
412        Ok(())
413    }
414}
415
416impl<SPI, BSY, DC, RST> EpdDriver for ThinkInk2in9Gray2<SPI, BSY, DC, RST>
417where
418    SPI: SpiDevice,
419    BSY: InputPin,
420    DC: OutputPin,
421    RST: OutputPin,
422{
423    /// Reset and fully initialize the display for 4-level grayscale operation.
424    ///
425    /// Performs a hardware reset, sends the panel configuration, loads the Gray2
426    /// LUT ([`TI_290MFGN_GRAY2_LUT_CODE`]), and waits for the busy pin to go
427    /// low before returning.
428    fn init(&mut self, delay: &mut impl DelayNs) -> Result<(), DisplayError> {
429        debug!("powering up ThinkInk 2.9in grayscale display");
430        // hard reset
431        self.interface.hard_reset(delay)?;
432        self.interface.cmd(Cmd::SW_RESET)?;
433        delay.delay_ms(10);
434        // Send Initialization Code 0x01, 0x11, 0x44, 0x45, 0x3C
435        self.interface
436            .cmd_with_data(Cmd::DRIVER_OUTPUT_CTRL, &[0x27, 0x01, 0x00])?;
437        self.interface
438            .cmd_with_data(Cmd::DATA_ENTRY_MODE, &[Flag::DATA_ENTRY_INCRY_INCRX])?;
439        self.interface
440            .cmd_with_data(Cmd::SET_RAMXPOS, &[0x00, 0x0F])?;
441        self.interface
442            .cmd_with_data(Cmd::SET_RAMYPOS, &[0x00, 0x00, 0x27, 0x01])?;
443        self.interface.cmd_with_data(
444            Cmd::BORDER_WAVEFORM_CTRL,
445            &[Flag::BORDER_WAVEFORM_FOLLOW_LUT | Flag::BORDER_WAVEFORM_LUT1],
446        )?;
447        // Load Waveform LUT
448        self.interface
449            .cmd_with_data(Cmd::TEMP_CONTROL, &[Flag::INTERNAL_TEMP_SENSOR])?;
450        self.interface
451            .cmd_with_data(Cmd::DISPLAY_UPDATE_CTRL1, &[0x00, 0x80])?;
452        self.interface
453            .cmd_with_data(Cmd::END_OPTION, &[Flag::END_OPTION_NORMAL])?;
454        self.interface
455            .cmd_with_data(Cmd::GATE_VOLTAGE_CTRL, &[0x17])?;
456        self.interface
457            .cmd_with_data(Cmd::SOURCE_VOLTAGE_CTRL, &[0x41, 0x00, 0x32])?;
458        self.interface.cmd_with_data(Cmd::WRITE_VCOM_REG, &[0x36])?;
459        // write LUT into memory
460        self.interface
461            .cmd_with_data(Cmd::WRITE_LUT_REG, &TI_290MFGN_GRAY2_LUT_CODE)?;
462        self.interface.wait_until_idle(delay);
463        Ok(())
464    }
465
466    /// Power down the display controller into deep sleep mode.
467    fn sleep(&mut self, delay: &mut impl DelayNs) -> Result<(), DisplayError> {
468        debug!("powering down ThinkInk 2.9\" grayscale display");
469        self.interface.cmd_with_data(Cmd::DEEP_SLEEP, &[0x01])?;
470        delay.delay_ms(1);
471        Ok(())
472    }
473
474    /// Write `buffer` to the black/white RAM plane and wait until ready.
475    fn update_bw(&mut self, buffer: &[u8], delay: &mut impl DelayNs) -> Result<(), DisplayError> {
476        self.write_bw_ram(buffer)?;
477        self.interface.wait_until_idle(delay);
478        Ok(())
479    }
480
481    /// Write `buffer` to the red RAM plane and wait until ready.
482    fn update_red(&mut self, buffer: &[u8], delay: &mut impl DelayNs) -> Result<(), DisplayError> {
483        self.write_red_ram(buffer)?;
484        self.interface.wait_until_idle(delay);
485        Ok(())
486    }
487
488    /// Write both RAM planes and wait until ready.
489    ///
490    /// Does not trigger a display refresh; call [`Self::update_gray2_and_display`]
491    /// for a complete write-and-refresh cycle.
492    fn update(
493        &mut self,
494        bw_buffer: &[u8],
495        red_buffer: &[u8],
496        delay: &mut impl DelayNs,
497    ) -> Result<(), DisplayError> {
498        self.write_bw_ram(bw_buffer)?;
499        self.write_red_ram(red_buffer)?;
500        self.interface.wait_until_idle(delay);
501        Ok(())
502    }
503
504    /// Fill the black/white RAM plane with white, clearing it.
505    fn clear_bw_ram(&mut self) -> Result<(), DisplayError> {
506        let color = Color::White.get_byte_value();
507        self.interface.cmd(Cmd::WRITE_BW_DATA)?;
508        self.interface
509            .data_x_times(color, u32::from(WIDTH).div_ceil(8) * u32::from(HEIGHT))?;
510        Ok(())
511    }
512
513    /// Fill the red RAM plane with its cleared state.
514    fn clear_red_ram(&mut self) -> Result<(), DisplayError> {
515        let color = Color::White.inverse().get_byte_value();
516        self.interface.cmd(Cmd::WRITE_RED_DATA)?;
517        self.interface
518            .data_x_times(color, u32::from(WIDTH).div_ceil(8) * u32::from(HEIGHT))?;
519        Ok(())
520    }
521
522    /// Hardware-reset the panel and put it into a low-power sleep state.
523    ///
524    /// Use this to prepare the display before the first [`Self::init`] call.
525    fn begin(&mut self, delay: &mut impl DelayNs) -> Result<(), DisplayError> {
526        self.interface.hard_reset(delay)?;
527        self.sleep(delay)
528    }
529}