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::digital::{InputPin, OutputPin};
8use embedded_hal_async::{delay::DelayNs, digital::Wait, spi::SpiBus};
9
10use crate::{
11    buffer::{binary_buffer_length, BinaryBuffer},
12    Epd, EpdHw, Error,
13};
14
15/// LUT for a full refresh. This should be used occasionally for best display results.
16///
17/// See [RECOMMENDED_MIN_FULL_REFRESH_INTERVAL] and [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
18pub const LUT_FULL_UPDATE: [u8; 30] = [
19    0x50, 0xAA, 0x55, 0xAA, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
20    0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
21];
22/// LUT for a partial refresh. This should be used for frequent updates, but it's recommended to
23/// perform a full refresh occasionally.
24///
25/// See [RECOMMENDED_MIN_FULL_REFRESH_INTERVAL] and [RECOMMENDED_MAX_FULL_REFRESH_INTERVAL].
26pub const LUT_PARTIAL_UPDATE: [u8; 30] = [
27    0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
28    0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
29];
30
31/// The height of the display (portrait orientation).
32pub const DISPLAY_HEIGHT: u16 = 296;
33/// The width of the display (portrait orientation).
34pub const DISPLAY_WIDTH: u16 = 128;
35/// It's recommended to avoid doing a full refresh more often than this (at least on a regular basis).
36pub const RECOMMENDED_MIN_FULL_REFRESH_INTERVAL: Duration = Duration::from_secs(180);
37/// It's recommended to do a full refresh at least this often.
38pub const RECOMMENDED_MAX_FULL_REFRESH_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60);
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum Command {
42    /// Used to initialise the display.
43    DriverOutputControl = 0x01,
44    /// Used to configure the on chip voltage booster and regulator.
45    BoosterSoftStartControl = 0x0C,
46    /// Used to enter deep sleep mode. Requires a hardware reset and reinitialisation to wake up.
47    DeepSleepMode = 0x10,
48    /// Changes the auto-increment behaviour of the address counter.
49    DataEntryModeSetting = 0x11,
50    /// Resets all commands and parameters to default values (except deep sleep mode).
51    SwReset = 0x12,
52    /// Writes to the temperature register.
53    TemperatureSensorControl = 0x1A,
54    /// Activates the display update sequence. This must be set beforehand using [DisplayUpdateControl2].
55    /// This operation must not be interrupted.
56    MasterActivation = 0x20,
57    /// ? bypass ?
58    DisplayUpdateControl1 = 0x21,
59    /// Configures the display update sequence for use with [MasterActivation].
60    DisplayUpdateControl2 = 0x22,
61    /// Writes data to RAM, autoincrementing the address counter.
62    WriteRam = 0x24,
63    /// Writes to the VCOM register.
64    WriteVcom = 0x2C,
65    /// Writes the LUT register (30 bytes, exclude the VSH/VSL and dummy bits).
66    WriteLut = 0x32,
67    /// ? Part of magic config.
68    SetDummyLinePeriod = 0x3A,
69    /// ? Part of magic config.
70    SetGateLineWidth = 0x3B,
71    /// Register to configure the behaviour of the border.
72    /// This can be set directly to a fixed colour, or managed via the VCOM register.
73    BorderWaveformControl = 0x3C,
74    /// Sets the start and end positions of the X axis.
75    SetRamXStartEnd = 0x44,
76    /// Sets the start and end positions of the Y axis.
77    SetRamYStartEnd = 0x45,
78    /// Sets the current x coordinate of the address counter.
79    SetRamX = 0x4E,
80    /// Sets the current y coordinate of the address counter.
81    SetRamY = 0x4F,
82    /// Does nothing, but can be used to terminate other commands such as [WriteRam]
83    Noop = 0xFF,
84}
85
86/// This should be sent with [Command::DriverOutputControl] during initialisation.
87///
88/// From the sample code, the bytes mean the following:
89///
90/// * low byte of display long edge
91/// * high byte of display long edge
92/// * GD = 0, SM = 0, TB = 0 (unclear what this means)
93const DRIVER_OUTPUT_INIT_DATA: [u8; 3] = [0x27, 0x01, 0x00];
94/// This should be sent with [Command::BoosterSoftStartControl] during initialisation.
95/// Note: this comes from the datasheet, but doesn't match the booster data sent in the sample code.
96/// That uses [0xD7, 0xD6, 0x9D]
97const BOOSTER_SOFT_START_INIT_DATA: [u8; 3] = [0xCF, 0xCE, 0x8D];
98
99impl Command {
100    fn register(&self) -> u8 {
101        *self as u8
102    }
103}
104
105/// Controls v1 of the 2.9" Waveshare e-paper display ([datasheet](https://files.waveshare.com/upload/e/e6/2.9inch_e-Paper_Datasheet.pdf)).
106///
107/// Initialise the display with either [LUT_FULL_UPDATE] or [LUT_PARTIAL_UPDATE].
108///
109/// Defaults to a portrait orientation. Uses [BinaryColor], where `Off` is black and `On` is white.
110pub struct Epd2in9<HW>
111where
112    HW: EpdHw,
113{
114    hw: HW,
115}
116
117impl<HW> Epd2in9<HW>
118where
119    HW: EpdHw,
120{
121    pub fn new(hw: HW) -> Self {
122        Epd2in9 { hw }
123    }
124
125    /// Sets the border to the specified colour.
126    pub async fn set_border(&mut self, color: BinaryColor) -> Result<(), HW::Error> {
127        let border_setting: u8 = match color {
128            BinaryColor::Off => 0x40, // Ground for black
129            BinaryColor::On => 0x50,  // Set high for white
130        };
131        self.send(Command::BorderWaveformControl, &[border_setting])
132            .await
133    }
134}
135
136impl<HW> Epd<HW> for Epd2in9<HW>
137where
138    HW: EpdHw,
139{
140    type Command = Command;
141    type Buffer = BinaryBuffer<
142        { binary_buffer_length(Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)) },
143    >;
144
145    async fn init(&mut self, lut: &[u8]) -> Result<(), HW::Error> {
146        if lut.len() != 30 {
147            Err(Error::InvalidArgument)?
148        }
149
150        // Ensure reset is high.
151        self.hw.reset().set_high()?;
152
153        // Reset everything to defaults.
154        self.send(Command::SwReset, &[]).await?;
155
156        self.send(Command::DriverOutputControl, &DRIVER_OUTPUT_INIT_DATA)
157            .await?;
158        self.send(
159            Command::BoosterSoftStartControl,
160            &BOOSTER_SOFT_START_INIT_DATA,
161        )
162        .await?;
163        // Auto-increment X and Y, moving in the X direction first.
164        self.send(Command::DataEntryModeSetting, &[0b11]).await?;
165
166        // Apply more magical config settings from the sample code.
167        self.send(Command::WriteVcom, &[0xA8]).await?;
168        // Configure 4 dummy lines per gate.
169        self.send(Command::SetDummyLinePeriod, &[0x1A]).await?;
170        // 2us per line.
171        self.send(Command::SetGateLineWidth, &[0x08]).await?;
172
173        self.send(Command::WriteLut, lut).await?;
174
175        Ok(())
176    }
177
178    async fn clear(&mut self) -> Result<(), HW::Error> {
179        // Bypass the RAM to read 1 (white) for all values. This should be faster than re-writing
180        // all the display data.
181        self.send(Command::DisplayUpdateControl1, &[0x90]).await?;
182        self.update_display().await?;
183        // Disable bypass for future commands.
184        self.send(Command::DisplayUpdateControl1, &[0x01]).await?;
185
186        Ok(())
187    }
188
189    async fn reset(&mut self) -> Result<(), HW::Error> {
190        // Assume reset is already high.
191        self.hw.reset().set_low()?;
192        self.hw.delay().delay_ms(10).await;
193        self.hw.reset().set_high()?;
194        self.hw.delay().delay_ms(10).await;
195        Ok(())
196    }
197
198    async fn sleep(&mut self) -> Result<(), <HW as EpdHw>::Error> {
199        self.send(Command::DeepSleepMode, &[0x01]).await
200    }
201
202    async fn wake(&mut self) -> Result<(), <HW as EpdHw>::Error> {
203        self.reset().await
204
205        // TODO: is init needed?
206    }
207
208    async fn display_buffer(&mut self, buffer: &Self::Buffer) -> Result<(), <HW as EpdHw>::Error> {
209        let buffer_bounds = buffer.bounding_box();
210        self.set_window(buffer_bounds).await?;
211        self.set_cursor(buffer_bounds.top_left).await?;
212        self.write_image(buffer.data()).await?;
213
214        Ok(())
215    }
216
217    /// Sets the window to which the next image data will be written.
218    ///
219    /// The x-axis only supports multiples of 8; values outside this result in an [Error::InvalidArgument] error.
220    async fn set_window(&mut self, shape: Rectangle) -> Result<(), <HW as EpdHw>::Error> {
221        let x_start = shape.top_left.x;
222        let x_end = x_start + shape.size.width as i32;
223        if x_start % 8 != 0 || x_end % 8 != 0 {
224            Err(Error::InvalidArgument)?
225        }
226        let x_start_byte = (x_start >> 3) as u8;
227        let x_end_byte = (x_end >> 8) as u8;
228        self.send(Command::SetRamXStartEnd, &[x_start_byte, x_end_byte])
229            .await?;
230
231        let y_start = shape.top_left.y;
232        let y_end = y_start + shape.size.height as i32;
233        let y_start_low = (y_start & 0xFF) as u8;
234        let y_start_high = ((y_start >> 8) & 0xFF) as u8;
235        let y_end_low = (y_end & 0xFF) as u8;
236        let y_end_high = ((y_end >> 8) & 0xFF) as u8;
237        self.send(
238            Command::SetRamYStartEnd,
239            &[y_start_low, y_start_high, y_end_low, y_end_high],
240        )
241        .await?;
242
243        Ok(())
244    }
245
246    /// Sets the cursor position to write the next data to.
247    ///
248    /// The x-axis only supports multiples of 8; values outside this will result in [Error::InvalidArgument].
249    async fn set_cursor(&mut self, position: Point) -> Result<(), <HW as EpdHw>::Error> {
250        if position.x % 8 != 0 {
251            Err(Error::InvalidArgument)?
252        }
253        self.send(Command::SetRamX, &[(position.x >> 3) as u8])
254            .await?;
255        let y_low = (position.y & 0xFF) as u8;
256        let y_high = ((position.y >> 8) & 0xFF) as u8;
257        self.send(Command::SetRamY, &[y_low, y_high]).await?;
258        Ok(())
259    }
260
261    async fn update_display(&mut self) -> Result<(), <HW as EpdHw>::Error> {
262        // Enable the clock and CP (?), and then display the latest data.
263        // self.send(Command::DisplayUpdateControl2, &[0xC4]).await?;
264        // To try: just display the pattern
265        self.send(Command::DisplayUpdateControl2, &[0x04]).await?;
266        self.send(Command::MasterActivation, &[]).await?;
267        self.send(Command::Noop, &[]).await?;
268        Ok(())
269    }
270
271    async fn write_image(&mut self, image: &[u8]) -> Result<(), <HW as EpdHw>::Error> {
272        self.send(Command::WriteRam, image).await
273    }
274
275    async fn send(&mut self, command: Command, data: &[u8]) -> Result<(), HW::Error> {
276        self.wait_if_busy().await?;
277
278        self.hw.cs().set_low()?;
279        self.hw.dc().set_low()?;
280        self.hw.spi().write(&[command.register()]).await?;
281
282        if !data.is_empty() {
283            self.hw.dc().set_high()?;
284            self.hw.spi().write(data).await?;
285        }
286
287        self.hw.cs().set_high()?;
288        Ok(())
289    }
290
291    async fn wait_if_busy(&mut self) -> Result<(), HW::Error> {
292        let busy = self.hw.busy();
293        if busy.is_low().unwrap() {
294            busy.wait_for_high().await?;
295        }
296        Ok(())
297    }
298}
299
300// Notes:
301
302// Pull CS low to communicate.
303// Pull D/C low for command, then high for data.
304// Reset is active low.
305// Busy is active low, only do things when it's high.
306//