st7305-lcd 0.2.0

Platform-independent no_std driver for the Sitronix ST7305 reflective LCD controller (embedded-hal 1.0, blocking and async)
Documentation
use crate::{
    Error, InitStep, PowerMode,
    cmd::base::{
        ColumnAddressSet, DisplayInversionOff, DisplayInversionOn, DisplayOff, DisplayOn,
        HighPowerModeOn, LowPowerModeOn, RowAddressSet, SleepIn, SleepOut, TearingEffectLineOff,
        TearingEffectLineOn, TearingEffectMode,
    },
    command::WriteCommand,
    instr::Instr,
};

use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};

/// Blocking driver of the ST7305 on the SPI device `SPI`, the data/command pin `DC` and the reset
/// pin `RST`. `DC` and `RST` must share one pin error type.
///
/// Create it with [`Self::new`] and initialize the panel with [`Self::init_with`].
#[derive(Debug)]
pub struct St7305<SPI, DC, RST> {
    spi: SPI,
    dc: DC,
    rst: RST,
}

impl<SPI: SpiDevice, DC: OutputPin<Error = P>, RST: OutputPin<Error = P>, P> St7305<SPI, DC, RST> {
    /// Creates the driver. Does no I/O: the pins and the bus must already be configured.
    pub fn new(spi: SPI, dc: DC, rst: RST) -> Self {
        Self { spi, dc, rst }
    }

    /// Returns the SPI device and the pins. The LCD panel is left in its
    /// current state.
    pub fn release(self) -> (SPI, DC, RST) {
        (self.spi, self.dc, self.rst)
    }

    /// Hardware reset: `RST` low for 2 ms, then high and a 120 ms wait for the panel.
    ///
    /// Does not use the SPI device, so the only possible error is [`Error::Pin`].
    pub fn reset(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<SPI::Error, P>> {
        self.rst.set_low().map_err(Error::Pin)?;
        delay.delay_ms(2); // >= 1 ms
        self.rst.set_high().map_err(Error::Pin)?;
        delay.delay_ms(120); // ready after < 120
        Ok(())
    }

    /// Turns the display output on (`DISPON`). Does not leave sleep mode.
    pub fn display_on(&mut self) -> Result<(), Error<SPI::Error, P>> {
        self.write_command(&DisplayOn)
    }

    /// Turns the display output off (`DISPOFF`). GRAM content is kept.
    pub fn display_off(&mut self) -> Result<(), Error<SPI::Error, P>> {
        self.write_command(&DisplayOff)
    }

    /// Switches display inversion (`INVON` / `INVOFF`).
    /// With inversion on, bit 1 is white; with inversion off, bit 1 is black.
    pub fn set_inversion(&mut self, on: bool) -> Result<(), Error<SPI::Error, P>> {
        if on {
            self.write_command(&DisplayInversionOn)
        } else {
            self.write_command(&DisplayInversionOff)
        }
    }

    /// Selects the power mode (`HPM` / `LPM`). The frame rate of each mode is
    /// set with `FrameRateControl`.
    pub fn set_power_mode(&mut self, mode: PowerMode) -> Result<(), Error<SPI::Error, P>> {
        match mode {
            PowerMode::High => self.write_command(&HighPowerModeOn),
            PowerMode::Low => self.write_command(&LowPowerModeOn),
        }
    }

    /// Configures the tearing effect output line (TE): `Some(mode)` sends
    /// `TEON` with that mode, `None` sends `TEOFF`. The driver does not own
    /// the TE pin, wait for it yourself.
    pub fn set_tearing_effect(
        &mut self,
        mode: Option<TearingEffectMode>,
    ) -> Result<(), Error<SPI::Error, P>> {
        match mode {
            Some(mode) => self.write_command(&TearingEffectLineOn::new(mode)),
            None => self.write_command(&TearingEffectLineOff),
        }
    }

    /// Sets the GRAM window for the next `RAMWR` (`CASET` + `RASET`), in
    /// controller units: a column is 3 bytes of GRAM, a row is one GRAM
    /// page line. The following frame data must contain exactly `columns *
    /// 3 * rows` bytes, and the window stays set until the next call. The
    /// coordinate ranges are validated by the command constructors (they
    /// panic).
    pub fn set_address_window(
        &mut self,
        columns: ColumnAddressSet,
        rows: RowAddressSet,
    ) -> Result<(), Error<SPI::Error, P>> {
        self.write_command(&columns)?;
        self.write_command(&rows)
    }

    /// Enters sleep mode (`SLPIN`) and waits 5 ms, as the datasheet requires
    /// before the next command. The display on/off state and GRAM are kept, so
    /// no `display_off` is needed. Leave it with [`Self::sleep_out`].
    pub fn sleep_in(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<SPI::Error, P>> {
        self.write_command(&SleepIn)?;
        delay.delay_ms(5);
        Ok(())
    }

    /// Leaves sleep mode (`SLPOUT`) and waits 100 ms (datasheet: >= 100 ms
    /// before the next `SLPIN`). Does not change the display on/off state:
    /// if the display was on before [`Self::sleep_in`], it is on again. After
    /// `init_with` use [`Self::display_on`] if needed.
    pub fn sleep_out(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<SPI::Error, P>> {
        self.write_command(&SleepOut)?;
        delay.delay_ms(100);
        Ok(())
    }

    fn write_cmd(&mut self, cmd: u8, params: &[u8]) -> Result<(), Error<SPI::Error, P>> {
        self.dc.set_low().map_err(Error::Pin)?;
        self.spi.write(&[cmd]).map_err(Error::Spi)?;
        if !params.is_empty() {
            self.dc.set_high().map_err(Error::Pin)?;
            self.spi.write(params).map_err(Error::Spi)?;
        }
        Ok(())
    }

    /// Sends a typed command (see [`cmd`](crate::cmd)): the opcode with `DC`
    /// low, then the parameter bytes, if any, with `DC` high, each in its own
    /// SPI transaction.
    ///
    /// The `DC` level after an error is not guaranteed; the next command sets
    /// it again.
    pub fn write_command<T: WriteCommand>(
        &mut self,
        command: &T,
    ) -> Result<(), Error<SPI::Error, P>> {
        self.write_cmd(T::OPCODE as u8, command.params().as_ref())
    }

    /// Writes `data` into the GRAM window `columns` x `rows`:
    /// [`Self::set_address_window`], then [`Self::write_ram`]. `data` must
    /// contain exactly `columns * 3 * rows` bytes (see
    /// [`FrameBuffer::copy_window`](crate::framebuffer::FrameBuffer::copy_window)).
    ///
    /// The window stays set afterwards. Before the next full frame restore the
    /// whole GRAM window of the panel with [`Self::set_address_window`], e.g.
    /// from [`Window::full`](crate::framebuffer::Window::full).
    pub fn write_window(
        &mut self,
        columns: ColumnAddressSet,
        rows: RowAddressSet,
        data: &[u8],
    ) -> Result<(), Error<SPI::Error, P>> {
        self.set_address_window(columns, rows)?;
        self.write_ram(data)
    }

    /// Hardware [`reset`](Self::reset), then runs the initialization table
    /// `seq` (see [`presets`](crate::presets)) step by step. Stops at the
    /// first error.
    pub fn init_with(
        &mut self,
        seq: &[InitStep<'_>],
        delay: &mut impl DelayNs,
    ) -> Result<(), Error<SPI::Error, P>> {
        self.reset(delay)?;
        for step in seq {
            match step {
                InitStep::Cmd(opcode, params) => self.write_cmd(*opcode, params)?,
                InitStep::DelayMs(ms) => delay.delay_ms(*ms),
            }
        }
        Ok(())
    }

    /// Sends the `RAMWR` command without data. Send the pixel bytes with
    /// [`Self::write_ram_data`] right after it.
    ///
    /// The split form lets a frame go out in several pieces, e.g. from a
    /// buffer smaller than the frame. [`Self::write_ram`] does both steps.
    pub fn begin_write_ram(&mut self) -> Result<(), Error<SPI::Error, P>> {
        self.write_cmd(Instr::RAMWR as u8, &[])
    }

    /// Sends pixel bytes with `DC` high. Call it only after a successful
    /// [`Self::begin_write_ram`] (or another `write_ram_data`); the order is
    /// not checked. Empty `data` sends nothing.
    pub fn write_ram_data(&mut self, data: &[u8]) -> Result<(), Error<SPI::Error, P>> {
        if !data.is_empty() {
            self.dc.set_high().map_err(Error::Pin)?;
            self.spi.write(data).map_err(Error::Spi)?;
        }
        Ok(())
    }

    /// Writes a frame into the GRAM window set by
    /// [`Self::set_address_window`] (or by the initialization table): `RAMWR`,
    /// then all of `data` in one SPI transaction.
    ///
    /// The `DC` level after an error is not guaranteed; the next command sets
    /// it again.
    pub fn write_ram(&mut self, data: &[u8]) -> Result<(), Error<SPI::Error, P>> {
        self.begin_write_ram()?;
        self.write_ram_data(data)
    }
}