epd_waveshare/epd2in66b/
mod.rs

1//! A driver for the Waveshare three-color E-ink Pi Pico hat 'Pico-ePaper-2.66-B' B/W/R.
2//!
3//!
4//! This driver was built and tested for this 296x152, 2.66inch three-color E-Ink display hat for the Pi Pico, it is expected to work for
5//! other boards too, but that might depend on how the OTP memory in the display is programmed by the factory.
6//!
7//! The driver embedded in the display of this board is the SSD1675B, [documented by cursedhardware](https://cursedhardware.github.io/epd-driver-ic/SSD1675B.pdf).
8//!
9//! The pin assigments are shown on the Waveshare wiki [schematic](https://www.waveshare.com/w/upload/8/8d/Pico-ePaper-2.66.pdf).
10//!
11//! Information on this display/hat can be found at the [Waveshare Wiki](https://www.waveshare.com/wiki/Pico-ePaper-2.66-B).
12//! Do read this documentation, in particular to understand how often this display both should and should not be updated.
13//!
14//! # Example for the 'Pico-ePaper-2.66-B' B/W/R Pi Pico Hat E-Ink Display
15//! This example was created in an environment using the [Knurling](https://github.com/knurling-rs) ```flip-link```, ```defmt``` and ```probe-run``` tools - you will
16//! need to adjust for your preferred setup.
17//!```ignore
18//!#![no_std]
19//!#![no_main]
20//!use epd_waveshare::{epd2in66b::*, prelude::*};
21//!
22//!use cortex_m_rt::entry;
23//!//use defmt::*;
24//!use defmt_rtt as _;
25//!use panic_probe as _;
26//!
27//!// Use embedded-graphics to create a bitmap to show
28//!fn drawing() -> Display2in66b {
29//!    use embedded_graphics::{
30//!        mono_font::{ascii::FONT_10X20, MonoTextStyle},
31//!        prelude::*,
32//!        primitives::PrimitiveStyle,
33//!        text::{Alignment, Text},
34//!    };
35//!
36//!    // Create a Display buffer to draw on, specific for this ePaper
37//!    let mut display = Display2in66b::default();
38//!
39//!    // Landscape mode, USB plug to the right
40//!    display.set_rotation(DisplayRotation::Rotate270);
41//!
42//!    // Change the background from the default black to white
43//!    let _ = display
44//!        .bounding_box()
45//!        .into_styled(PrimitiveStyle::with_fill(TriColor::White))
46//!        .draw(&mut display);
47//!
48//!    // Draw some text on the buffer
49//!    let text = "Pico-ePaper-2.66 B/W/R";
50//!    Text::with_alignment(
51//!        text,
52//!        display.bounding_box().center() + Point::new(1, 0),
53//!        MonoTextStyle::new(&FONT_10X20, TriColor::Black),
54//!        Alignment::Center,
55//!    )
56//!    .draw(&mut display)
57//!    .unwrap();
58//!    Text::with_alignment(
59//!        text,
60//!        display.bounding_box().center() + Point::new(0, 1),
61//!        MonoTextStyle::new(&FONT_10X20, TriColor::Chromatic),
62//!        Alignment::Center,
63//!    )
64//!    .draw(&mut display)
65//!    .unwrap();
66//!
67//!    display
68//!}
69//!
70//!#[entry]
71//!fn main() -> ! {
72//!    use fugit::RateExtU32;
73//!    use rp_pico::hal::{
74//!        self,
75//!        clocks::{init_clocks_and_plls, Clock},
76//!        gpio::{FunctionSpi, PinState, Pins},
77//!        pac,
78//!        sio::Sio,
79//!        watchdog::Watchdog,
80//!    };
81//!
82//!    // Boilerplate to access the peripherals
83//!    let mut pac = pac::Peripherals::take().unwrap();
84//!    let core = pac::CorePeripherals::take().unwrap();
85//!    let mut watchdog = Watchdog::new(pac.WATCHDOG);
86//!    let external_xtal_freq_hz = 12_000_000u32;
87//!    let clocks = init_clocks_and_plls(
88//!        external_xtal_freq_hz,
89//!        pac.XOSC,
90//!        pac.CLOCKS,
91//!        pac.PLL_SYS,
92//!        pac.PLL_USB,
93//!        &mut pac.RESETS,
94//!        &mut watchdog,
95//!    )
96//!    .ok()
97//!    .unwrap();
98//!    let sio = Sio::new(pac.SIO);
99//!    let pins = Pins::new(
100//!        pac.IO_BANK0,
101//!        pac.PADS_BANK0,
102//!        sio.gpio_bank0,
103//!        &mut pac.RESETS,
104//!    );
105//!
106//!    // Pin assignments of the Pi Pico-ePaper-2.66 Hat
107//!    let _ = pins.gpio10.into_mode::<FunctionSpi>();
108//!    let _ = pins.gpio11.into_mode::<FunctionSpi>();
109//!    let chip_select_pin = pins.gpio9.into_push_pull_output_in_state(PinState::High);
110//!    let is_busy_pin = pins.gpio13.into_floating_input();
111//!    let data_or_command_pin = pins.gpio8.into_push_pull_output_in_state(PinState::High);
112//!    let reset_pin = pins.gpio12.into_push_pull_output_in_state(PinState::High);
113//!
114//!    // SPI
115//!    let spi = hal::Spi::<_, _, 8>::new(pac.SPI1);
116//!    let mut spi = spi.init(
117//!        &mut pac.RESETS,
118//!        clocks.peripheral_clock.freq(),
119//!        20_000_000u32.Hz(), // The SSD1675B docs say 20MHz max
120//!        &SPI_MODE,
121//!    );
122//!
123//!    // Delay
124//!    let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
125//!
126//!    // Setup the EPD driver
127//!    let mut e_paper = Epd2in66b::new(
128//!        &mut spi,
129//!        chip_select_pin,
130//!        is_busy_pin,
131//!        data_or_command_pin,
132//!        reset_pin,
133//!        &mut delay,
134//!        None,
135//!    )
136//!    .unwrap();
137//!
138//!    // Create and fill a Display buffer
139//!    let display = drawing();
140//!
141//!    // Send the Display buffer to the ePaper RAM
142//!    e_paper
143//!        .update_color_frame(
144//!            &mut spi,
145//!            &mut delay,
146//!            &display.bw_buffer(),
147//!            &display.chromatic_buffer(),
148//!        )
149//!        .unwrap();
150//!
151//!    // Render the ePaper RAM - takes time.
152//!    e_paper.display_frame(&mut spi, &mut delay).unwrap();
153//!
154//!    // Always turn off your EPD as much as possible - ePaper wears out while powered on.
155//!    e_paper.sleep(&mut spi, &mut delay).unwrap();
156//!
157//!    delay.delay_ms(60 * 1000);
158//!
159//!    // Set the display all-white before storing your ePaper long-term.
160//!    e_paper.wake_up(&mut spi, &mut delay).unwrap();
161//!    e_paper.clear_frame(&mut spi, &mut delay).unwrap();
162//!    e_paper.display_frame(&mut spi, &mut delay).unwrap();
163//!    e_paper.sleep(&mut spi, &mut delay).unwrap();
164//!
165//!    loop {}
166//!}
167//!```
168
169use embedded_hal::{
170    delay::DelayNs,
171    digital::{InputPin, OutputPin},
172    spi::SpiDevice,
173};
174
175use crate::color::TriColor;
176use crate::interface::DisplayInterface;
177use crate::traits::{
178    InternalWiAdditions, RefreshLut, WaveshareDisplay, WaveshareThreeColorDisplay,
179};
180
181pub(crate) mod command;
182use self::command::*;
183use crate::buffer_len;
184
185/// Display height in pixels.
186pub const WIDTH: u32 = 152;
187/// Display width in pixels
188pub const HEIGHT: u32 = 296;
189
190const SINGLE_BYTE_WRITE: bool = true;
191
192/// White, display this during long-term storage
193pub const DEFAULT_BACKGROUND_COLOR: TriColor = TriColor::White;
194
195/// A Display buffer configured with our extent and color depth.
196#[cfg(feature = "graphics")]
197pub type Display2in66b = crate::graphics::Display<
198    WIDTH,
199    HEIGHT,
200    false,
201    { buffer_len(WIDTH as usize, HEIGHT as usize) * 2 },
202    TriColor,
203>;
204
205/// The EPD 2in66-B driver.
206pub struct Epd2in66b<SPI, BUSY, DC, RST, DELAY> {
207    interface: DisplayInterface<SPI, BUSY, DC, RST, DELAY, SINGLE_BYTE_WRITE>,
208    background: TriColor,
209}
210
211impl<SPI, BUSY, DC, RST, DELAY> InternalWiAdditions<SPI, BUSY, DC, RST, DELAY>
212    for Epd2in66b<SPI, BUSY, DC, RST, DELAY>
213where
214    SPI: SpiDevice,
215    BUSY: InputPin,
216    DC: OutputPin,
217    RST: OutputPin,
218    DELAY: DelayNs,
219{
220    fn init(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
221        // We follow the sequence of the Pi-Pico hat example code.
222        self.hw_reset(delay)?;
223        self.sw_reset(spi, delay)?;
224        self.data_entry_mode(spi, DataEntryRow::XMinor, DataEntrySign::IncYIncX)?;
225        self.set_display_window(spi, 0, 0, WIDTH - 1, HEIGHT - 1)?;
226        self.update_control1(
227            spi,
228            WriteMode::Normal,
229            WriteMode::Normal,
230            OutputSource::S8ToS167,
231        )?;
232        self.set_cursor(spi, 0, 0)?;
233
234        Ok(())
235    }
236}
237
238impl<SPI, BUSY, DC, RST, DELAY> WaveshareThreeColorDisplay<SPI, BUSY, DC, RST, DELAY>
239    for Epd2in66b<SPI, BUSY, DC, RST, DELAY>
240where
241    SPI: SpiDevice,
242    BUSY: InputPin,
243    DC: OutputPin,
244    RST: OutputPin,
245    DELAY: DelayNs,
246{
247    fn update_color_frame(
248        &mut self,
249        spi: &mut SPI,
250        delay: &mut DELAY,
251        black: &[u8],
252        chromatic: &[u8],
253    ) -> Result<(), SPI::Error> {
254        self.update_achromatic_frame(spi, delay, black)?;
255        self.update_chromatic_frame(spi, delay, chromatic)
256    }
257
258    fn update_achromatic_frame(
259        &mut self,
260        spi: &mut SPI,
261        _delay: &mut DELAY,
262        black: &[u8],
263    ) -> Result<(), SPI::Error> {
264        self.set_cursor(spi, 0, 0)?;
265        self.interface.cmd(spi, Command::WriteBlackWhiteRAM)?;
266        self.interface.data(spi, black)
267    }
268
269    fn update_chromatic_frame(
270        &mut self,
271        spi: &mut SPI,
272        _delay: &mut DELAY,
273        chromatic: &[u8],
274    ) -> Result<(), SPI::Error> {
275        self.set_cursor(spi, 0, 0)?;
276        self.interface.cmd(spi, Command::WriteRedRAM)?;
277        self.interface.data(spi, chromatic)
278    }
279}
280
281impl<SPI, BUSY, DC, RST, DELAY> WaveshareDisplay<SPI, BUSY, DC, RST, DELAY>
282    for Epd2in66b<SPI, BUSY, DC, RST, DELAY>
283where
284    SPI: SpiDevice,
285    BUSY: InputPin,
286    DC: OutputPin,
287    RST: OutputPin,
288    DELAY: DelayNs,
289{
290    type DisplayColor = TriColor;
291
292    fn new(
293        spi: &mut SPI,
294        busy: BUSY,
295        dc: DC,
296        rst: RST,
297        delay: &mut DELAY,
298        delay_us: Option<u32>,
299    ) -> Result<Self, SPI::Error>
300    where
301        Self: Sized,
302    {
303        let mut epd = Self {
304            interface: DisplayInterface::new(busy, dc, rst, delay_us),
305            background: DEFAULT_BACKGROUND_COLOR,
306        };
307        epd.init(spi, delay)?;
308        Ok(epd)
309    }
310
311    fn sleep(&mut self, spi: &mut SPI, _delay: &mut DELAY) -> Result<(), SPI::Error> {
312        self.interface.cmd_with_data(
313            spi,
314            Command::DeepSleepMode,
315            &[DeepSleep::SleepLosingRAM as u8],
316        )
317    }
318
319    fn wake_up(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
320        self.init(spi, delay)
321    }
322
323    fn set_background_color(&mut self, color: Self::DisplayColor) {
324        self.background = color;
325    }
326
327    fn background_color(&self) -> &Self::DisplayColor {
328        &self.background
329    }
330
331    fn width(&self) -> u32 {
332        WIDTH
333    }
334
335    fn height(&self) -> u32 {
336        HEIGHT
337    }
338
339    fn update_frame(
340        &mut self,
341        spi: &mut SPI,
342        buffer: &[u8],
343        delay: &mut DELAY,
344    ) -> Result<(), SPI::Error> {
345        self.set_cursor(spi, 0, 0)?;
346        self.update_achromatic_frame(spi, delay, buffer)?;
347        self.red_pattern(spi, delay, PatW::W160, PatH::H296, StartWith::Zero) // do NOT consider background here since red overrides other colors
348    }
349
350    fn update_partial_frame(
351        &mut self,
352        spi: &mut SPI,
353        delay: &mut DELAY,
354        buffer: &[u8],
355        x: u32,
356        y: u32,
357        width: u32,
358        height: u32,
359    ) -> Result<(), SPI::Error> {
360        self.set_display_window(spi, x, y, x + width, y + height)?;
361        self.set_cursor(spi, x, y)?;
362        self.update_achromatic_frame(spi, delay, buffer)?;
363        self.set_display_window(spi, 0, 0, WIDTH, HEIGHT)
364    }
365
366    fn display_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
367        self.interface.cmd(spi, Command::MasterActivation)?;
368        self.wait_until_idle(delay)
369    }
370
371    fn update_and_display_frame(
372        &mut self,
373        spi: &mut SPI,
374        buffer: &[u8],
375        delay: &mut DELAY,
376    ) -> Result<(), SPI::Error> {
377        self.update_frame(spi, buffer, delay)?;
378        self.display_frame(spi, delay)
379    }
380
381    fn clear_frame(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
382        let (white, red) = match self.background {
383            TriColor::Black => (StartWith::Zero, StartWith::Zero),
384            TriColor::White => (StartWith::One, StartWith::Zero),
385            TriColor::Chromatic => (StartWith::Zero, StartWith::One),
386        };
387        self.black_white_pattern(spi, delay, PatW::W160, PatH::H296, white)?;
388        self.red_pattern(spi, delay, PatW::W160, PatH::H296, red)
389    }
390
391    fn set_lut(
392        &mut self,
393        _spi: &mut SPI,
394        _delay: &mut DELAY,
395        _refresh_rate: Option<RefreshLut>,
396    ) -> Result<(), SPI::Error> {
397        Ok(())
398    }
399
400    fn wait_until_idle(&mut self, _spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
401        self.wait_until_idle(delay)
402    }
403}
404
405// Helper functions that enforce some type and value constraints. Meant to help with code readability. They caught some of my silly errors -> yay rust!.
406impl<SPI, BUSY, DC, RST, DELAY> Epd2in66b<SPI, BUSY, DC, RST, DELAY>
407where
408    SPI: SpiDevice,
409    BUSY: InputPin,
410    DC: OutputPin,
411    RST: OutputPin,
412    DELAY: DelayNs,
413{
414    fn wait_until_idle(&mut self, delay: &mut DELAY) -> Result<(), SPI::Error> {
415        self.interface.wait_until_idle(delay, false);
416        Ok(())
417    }
418    fn hw_reset(&mut self, delay: &mut DELAY) -> Result<(), SPI::Error> {
419        // The initial delay is taken from other code here, the 2 ms comes from the SSD1675B datasheet.
420        self.interface.reset(delay, 20_000, 2_000);
421        self.wait_until_idle(delay)
422    }
423    fn sw_reset(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), SPI::Error> {
424        self.interface.cmd(spi, Command::Reset)?;
425        self.wait_until_idle(delay)
426    }
427    fn data_entry_mode(
428        &mut self,
429        spi: &mut SPI,
430        row: DataEntryRow,
431        sign: DataEntrySign,
432    ) -> Result<(), SPI::Error> {
433        self.interface
434            .cmd_with_data(spi, Command::DataEntryMode, &[row as u8 | sign as u8])
435    }
436    fn set_display_window(
437        &mut self,
438        spi: &mut SPI,
439        xstart: u32,
440        ystart: u32,
441        xend: u32,
442        yend: u32,
443    ) -> Result<(), SPI::Error> {
444        self.interface.cmd_with_data(
445            spi,
446            Command::SetXAddressRange,
447            &[(((xstart >> 3) & 0x1f) as u8), (((xend >> 3) & 0x1f) as u8)],
448        )?;
449        self.interface.cmd_with_data(
450            spi,
451            Command::SetYAddressRange,
452            &[
453                ((ystart & 0xff) as u8),
454                (((ystart >> 8) & 0x01) as u8),
455                ((yend & 0xff) as u8),
456                (((yend >> 8) & 0x01) as u8),
457            ],
458        )
459    }
460    fn update_control1(
461        &mut self,
462        spi: &mut SPI,
463        red_mode: WriteMode,
464        bw_mode: WriteMode,
465        source: OutputSource,
466    ) -> Result<(), SPI::Error> {
467        self.interface.cmd_with_data(
468            spi,
469            Command::DisplayUpdateControl1,
470            &[((red_mode as u8) << 4 | bw_mode as u8), (source as u8)],
471        )
472    }
473
474    fn set_cursor(&mut self, spi: &mut SPI, x: u32, y: u32) -> Result<(), SPI::Error> {
475        self.interface.cmd_with_data(
476            spi,
477            Command::SetXAddressCounter,
478            &[((x >> 3) & 0x1f) as u8],
479        )?;
480        self.interface.cmd_with_data(
481            spi,
482            Command::SetYAddressCounter,
483            &[((y & 0xff) as u8), (((y >> 8) & 0x01) as u8)],
484        )
485    }
486
487    fn black_white_pattern(
488        &mut self,
489        spi: &mut SPI,
490        delay: &mut DELAY,
491        w: PatW,
492        h: PatH,
493        phase: StartWith,
494    ) -> Result<(), SPI::Error> {
495        self.interface.cmd_with_data(
496            spi,
497            Command::BlackWhiteRAMTestPattern,
498            &[phase as u8 | h as u8 | w as u8],
499        )?;
500        self.wait_until_idle(delay)
501    }
502    fn red_pattern(
503        &mut self,
504        spi: &mut SPI,
505        delay: &mut DELAY,
506        w: PatW,
507        h: PatH,
508        phase: StartWith,
509    ) -> Result<(), SPI::Error> {
510        self.interface.cmd_with_data(
511            spi,
512            Command::RedRAMTestPattern,
513            &[phase as u8 | h as u8 | w as u8],
514        )?;
515        self.wait_until_idle(delay)
516    }
517}