epd_spectra/
driver.rs

1//! Generic SPI driver for all EPDs
2
3use core::marker::PhantomData;
4use embedded_hal::{delay::DelayNs, digital::InputPin, digital::OutputPin, spi::SpiDevice};
5
6use crate::DisplayBuffer;
7
8enum Command {
9    Psr = 0x00,
10    PowerOff = 0x02,
11    PowerOn = 0x04,
12    BufferBlack = 0x10,
13    Refresh = 0x12,
14    BufferRed = 0x13,
15    ActiveTemperature = 0xe0,
16    InputTemperature = 0xe5,
17}
18
19/// Config register data for sizes other than 4.2"
20const REG_DATA_SOFT_RESET: &[u8] = &[0x0e];
21const REG_DATA_INPUT_TEMP: &[u8] = &[0x19];
22const REG_DATA_ACTIVE_TEMP: &[u8] = &[0x02];
23const REG_DATA_PSR: &[u8] = &[0xcf, 0x8d];
24
25/// Timeout value when waiting for busy signal
26const TIMEOUT_MS: i32 = 60_000;
27
28// Sadly we cannot use #[from] more than once.
29// See here for similiar problem: https://stackoverflow.com/questions/37347311/how-is-there-a-conflicting-implementation-of-from-when-using-a-generic-type
30#[cfg(feature = "std")]
31#[derive(thiserror::Error, Debug)]
32pub enum Error<SpiError, DcError, RstError> {
33    #[error("SPI error: {0}")]
34    Spi(#[source] SpiError),
35    #[error("Error with GPIO 'DC': {0}")]
36    GpioDc(#[source] DcError),
37    #[error("Error with GPIO 'RESET': {0}")]
38    GpioRst(#[source] RstError),
39    #[error("Timeout while waiting for busy signal")]
40    Timeout,
41}
42
43#[cfg(not(feature = "std"))]
44#[derive(Debug)]
45pub enum Error<SpiError, DcError, RstError> {
46    Spi(SpiError),
47    GpioDc(DcError),
48    GpioRst(RstError),
49    Timeout,
50}
51
52type EpdError<SPI, DC, RST> = Error<
53    <SPI as embedded_hal::spi::ErrorType>::Error,
54    <DC as embedded_hal::digital::ErrorType>::Error,
55    <RST as embedded_hal::digital::ErrorType>::Error,
56>;
57
58type EpdResult<STATE, SPI, BUSY, DC, RST, DELAY> =
59    Result<Epd<STATE, SPI, BUSY, DC, RST, DELAY>, EpdError<SPI, DC, RST>>;
60
61/// Actual driver for e-paper display
62pub struct Epd<STATE: EpdState, SPI, BUSY, DC, RST, DELAY> {
63    /// busy pin, active low
64    busy: BUSY,
65    /// Data/Command control pin (data: high, command: low)
66    dc: DC,
67    /// reset pin, active low
68    rst: RST,
69    /// chunk size used for SPI writes (0: no chunks)
70    spi_chunk_size: usize,
71    spi: PhantomData<SPI>,
72    delay: PhantomData<DELAY>,
73    state: PhantomData<STATE>,
74}
75
76// Typestates for epd states (thanks to https://yoric.github.io/post/rust-typestate/ and https://cliffle.com/blog/rust-typestate/)
77pub struct Active; // e-paper is ready to draw something
78pub struct Inactive; // e-paper is powered off
79pub trait EpdState {}
80impl EpdState for Active {}
81impl EpdState for Inactive {}
82
83impl<SPI, BUSY, DC, RST, DELAY> Epd<Inactive, SPI, BUSY, DC, RST, DELAY>
84where
85    SPI: SpiDevice,
86    BUSY: InputPin,
87    DC: OutputPin,
88    RST: OutputPin,
89    DELAY: DelayNs,
90{
91    /// Create a new e-paper driver. You have to call `init` before sending pages to the e-paper via `update`.
92    /// `spi_chunk_size` determines the data chunk size for SPI writes, 0 means no chunks.
93    /// E.g. Linux has a default buffer size of 4096. So `spi_chunk_size` must be equal to or smaller than 4096.
94    pub fn new(
95        _spi: &mut SPI,
96        busy: BUSY,
97        dc: DC,
98        rst: RST,
99        _delay: &mut DELAY,
100        spi_chunk_size: usize,
101    ) -> Self {
102        Self {
103            busy,
104            dc,
105            rst,
106            spi_chunk_size,
107            spi: PhantomData,
108            delay: PhantomData,
109            state: PhantomData::<Inactive>,
110        }
111    }
112
113    /// Initialize the e-paper and set it to the active state. The return
114    /// value is an e-paper driver in the active state. This function
115    /// is blocking until initialisation is complete.
116    ///
117    /// # Errors
118    ///
119    /// This function will return an error if there is an error
120    /// with the GPIOs or the SPI device.
121    pub fn init(
122        mut self,
123        spi: &mut SPI,
124        delay: &mut DELAY,
125    ) -> EpdResult<Active, SPI, BUSY, DC, RST, DELAY> {
126        self.dc.set_high().map_err(Error::GpioDc)?;
127        self.reset(delay)?;
128        self.soft_reset(spi, delay)?;
129        self.send_data(spi, Command::InputTemperature, REG_DATA_INPUT_TEMP)?;
130        self.send_data(spi, Command::ActiveTemperature, REG_DATA_ACTIVE_TEMP)?;
131        self.send_data(spi, Command::Psr, REG_DATA_PSR)?;
132        Ok(Epd {
133            busy: self.busy,
134            dc: self.dc,
135            rst: self.rst,
136            spi_chunk_size: self.spi_chunk_size,
137            spi: PhantomData,
138            delay: PhantomData,
139            state: PhantomData::<Active>,
140        })
141    }
142}
143
144impl<SPI, BUSY, DC, RST, DELAY> Epd<Active, SPI, BUSY, DC, RST, DELAY>
145where
146    SPI: SpiDevice,
147    BUSY: InputPin,
148    DC: OutputPin,
149    RST: OutputPin,
150    DELAY: DelayNs,
151{
152    /// Show display on e-paper. This function is blocking until the update
153    /// process is complete.
154    ///
155    /// # Errors
156    ///
157    /// This function will return an error if there is an error
158    /// with the GPIOs or the SPI device.
159    pub fn update(
160        &mut self,
161        display: &impl DisplayBuffer,
162        spi: &mut SPI,
163        delay: &mut DELAY,
164    ) -> Result<(), EpdError<SPI, DC, RST>> {
165        self.send_data(spi, Command::BufferBlack, display.get_buffer_black())?;
166        self.send_data(spi, Command::BufferRed, display.get_buffer_red())?;
167        self.power_on(spi, delay)?;
168        self.display_refresh(spi, delay)?;
169        Ok(())
170    }
171
172    /// Power off the e-paper. This function is blocking until the e-paper
173    /// is powered off. The return value is an e-paper driver in
174    /// the inactive state. You have to call `init` again before
175    /// sending pages to the e-paper via `update`.
176    ///
177    /// # Errors
178    ///
179    /// This function will return an error if there is an error
180    /// with the GPIOs or the SPI device.
181    pub fn power_off(
182        mut self,
183        spi: &mut SPI,
184        delay: &mut DELAY,
185    ) -> EpdResult<Inactive, SPI, BUSY, DC, RST, DELAY> {
186        self.send_data(spi, Command::PowerOff, &[0x0])?;
187        self.wait_busy(delay)?;
188        self.dc.set_low().map_err(Error::GpioDc)?;
189        delay.delay_ms(150);
190        self.rst.set_low().map_err(Error::GpioRst)?;
191        Ok(Epd {
192            busy: self.busy,
193            dc: self.dc,
194            rst: self.rst,
195            spi_chunk_size: self.spi_chunk_size,
196            spi: PhantomData,
197            delay: PhantomData,
198            state: PhantomData::<Inactive>,
199        })
200    }
201}
202
203impl<STATE, SPI, BUSY, DC, RST, DELAY> Epd<STATE, SPI, BUSY, DC, RST, DELAY>
204where
205    STATE: EpdState,
206    SPI: SpiDevice,
207    BUSY: InputPin,
208    DC: OutputPin,
209    RST: OutputPin,
210    DELAY: DelayNs,
211{
212    fn reset(&mut self, delay: &mut DELAY) -> Result<(), EpdError<SPI, DC, RST>> {
213        delay.delay_ms(1);
214        self.rst.set_high().map_err(Error::GpioRst)?;
215        delay.delay_ms(5);
216        self.rst.set_low().map_err(Error::GpioRst)?;
217        delay.delay_ms(10);
218        self.rst.set_high().map_err(Error::GpioRst)?;
219        delay.delay_ms(5);
220        Ok(())
221    }
222
223    fn power_on(&mut self, spi: &mut SPI, delay: &mut DELAY) -> Result<(), EpdError<SPI, DC, RST>> {
224        self.send_data(spi, Command::PowerOn, &[0x0])?;
225        self.wait_busy(delay)?;
226        Ok(())
227    }
228
229    fn send_data(
230        &mut self,
231        spi: &mut SPI,
232        cmd: Command,
233        data: &[u8],
234    ) -> Result<(), EpdError<SPI, DC, RST>> {
235        self.dc.set_low().map_err(Error::GpioDc)?;
236        self.write(spi, &[cmd as u8])?;
237        self.dc.set_high().map_err(Error::GpioDc)?;
238        self.write(spi, data)?;
239        Ok(())
240    }
241
242    fn write(&mut self, spi: &mut SPI, data: &[u8]) -> Result<(), EpdError<SPI, DC, RST>> {
243        if self.spi_chunk_size > 0 {
244            for chunk in data.chunks(self.spi_chunk_size) {
245                spi.write(chunk).map_err(Error::Spi)?;
246            }
247        } else {
248            spi.write(data).map_err(Error::Spi)?;
249        }
250        Ok(())
251    }
252
253    fn soft_reset(
254        &mut self,
255        spi: &mut SPI,
256        delay: &mut DELAY,
257    ) -> Result<(), EpdError<SPI, DC, RST>> {
258        self.send_data(spi, Command::Psr, REG_DATA_SOFT_RESET)?;
259        self.wait_busy(delay)?;
260        Ok(())
261    }
262
263    fn display_refresh(
264        &mut self,
265        spi: &mut SPI,
266        delay: &mut DELAY,
267    ) -> Result<(), EpdError<SPI, DC, RST>> {
268        self.send_data(spi, Command::Refresh, &[0x0])?;
269        self.wait_busy(delay)?;
270        Ok(())
271    }
272
273    fn wait_busy(&mut self, delay: &mut DELAY) -> Result<(), EpdError<SPI, DC, RST>> {
274        let delay_ms = 1;
275        let mut timeout = TIMEOUT_MS;
276        while self.busy.is_low().unwrap() && timeout > 0 {
277            delay.delay_ms(delay_ms);
278            timeout -= i32::try_from(delay_ms).unwrap();
279        }
280        if timeout <= 0 {
281            Err(Error::Timeout)
282        } else {
283            Ok(())
284        }
285    }
286}
287
288/// SPI mode needed for EPD driver
289/// Mode0: CPOL 0, CPHA 0
290pub const SPI_MODE: embedded_hal::spi::Mode = embedded_hal::spi::Mode {
291    phase: embedded_hal::spi::Phase::CaptureOnFirstTransition,
292    polarity: embedded_hal::spi::Polarity::IdleLow,
293};