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
//! Platform-independent `no_std` driver for the Sitronix ST7305 reflective LCD
//! controller.
//!
//! The driver talks 4-wire SPI through [`embedded_hal::spi::SpiDevice`] (chip
//! select belongs to the device, not to the driver) and drives the `DC` and
//! `RST` pins itself. It never configures the bus.
//!
//! - [`blocking::St7305`]: blocking driver on `embedded-hal`.
//! - [`asynch::St7305`] (feature `async`): the same API on
//!   `embedded-hal-async`.
//! - [`framebuffer`] (feature `framebuffer`): 1 bpp frame buffer in the GRAM
//!   layout of the panel with runtime [`Rotation`](framebuffer::Rotation) and
//!   an `embedded-graphics` `DrawTarget`, and
//!   [`Window`](framebuffer::Window) for partial updates
//!   ([`write_window`](blocking::St7305::write_window)).
//! - [`cmd`]: typed commands for
//!   [`write_command`](blocking::St7305::write_command), and [`presets`]:
//!   ready-made initialization tables for
//!   [`init_with`](blocking::St7305::init_with).
//!
//! Both drivers can send a frame in two steps,
//! [`begin_write_ram`](blocking::St7305::begin_write_ram) (the `RAMWR` command)
//! and [`write_ram_data`](blocking::St7305::write_ram_data) (the pixel bytes).
//! With the async driver the transfer can then be overlapped with rendering of
//! the next frame.
//!
//! # Where is `St7305`?
//!
//! The driver type is not re-exported at the crate root, because the blocking
//! and the async driver share its name. Import it from the module you need:
//! `st7305_lcd::blocking::St7305` or `st7305_lcd::asynch::St7305`.
//!
//! # Example
//!
//! ```ignore
//! use st7305_lcd::{blocking::St7305, presets::WAVESHARE_RLCD_42};
//!
//! // `spi` is an `embedded_hal::spi::SpiDevice`, `dc` and `rst` are
//! // `OutputPin`s, `delay` is a `DelayNs`.
//! let mut display = St7305::new(spi, dc, rst);
//! display.init_with(WAVESHARE_RLCD_42, &mut delay)?;
//! display.write_ram(&frame_bytes)?; // 15 000 bytes of GRAM data
//! ```
#![deny(missing_docs)]
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
// The crate docs link to `asynch` and `framebuffer`; docs.rs builds with all
// features, a local `cargo doc` without them must not fail on those links.
#![cfg_attr(
    not(all(feature = "async", feature = "framebuffer")),
    allow(rustdoc::broken_intra_doc_links)
)]

#[cfg(feature = "async")]
/// Async driver on `embedded-hal-async`.
pub mod asynch;
/// Blocking driver on `embedded-hal`.
pub mod blocking;
pub mod cmd;
mod command;
#[cfg(feature = "framebuffer")]
pub mod framebuffer;
/// Command opcode table.
pub mod instr;
/// Initialization tables for `init_with`.
pub mod presets;

pub use command::WriteCommand;

use core::fmt;

/// One step of an initialization table, see `init_with` of the drivers and
/// [`presets`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum InitStep<'a> {
    /// Command: opcode and its parameters (may be empty)
    Cmd(u8, &'a [u8]),
    /// Pause in milliseconds
    DelayMs(u32),
}

/// Power mode of the panel, selected with `set_power_mode`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PowerMode {
    /// `HPM`: high power mode, frame rate set by `HpmFrameRate`
    High,
    /// `LPM`: low power mode, frame rate set by `LpmFrameRate`
    Low,
}

/// Driver error. `S` is the SPI error type, `P` is the error type of the `DC`
/// and `RST` pins.
///
/// The state of the `DC` line after an error is not guaranteed, every command
/// sets it again.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error<S, P> {
    /// SPI transaction failed
    Spi(S),
    /// DC or RST pin could not be driven
    Pin(P),
}

impl<S: fmt::Debug, P: fmt::Debug> fmt::Display for Error<S, P> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::Spi(e) => write!(f, "SPI error: {e:?}"),
            Error::Pin(e) => write!(f, "GPIO pin error: {e:?}"),
        }
    }
}

impl<S: fmt::Debug, P: fmt::Debug> core::error::Error for Error<S, P> {}

/// Implemented by hand instead of derived: a derive would require `S` and `P` to
/// be `defmt::Format`, which HAL error types usually are not. `Debug` is always
/// there, because `embedded_hal::spi::Error` and `digital::Error` require it.
#[cfg(feature = "defmt")]
impl<S: fmt::Debug, P: fmt::Debug> defmt::Format for Error<S, P> {
    fn format(&self, f: defmt::Formatter) {
        match self {
            Error::Spi(e) => defmt::write!(f, "SPI error: {}", defmt::Debug2Format(e)),
            Error::Pin(e) => defmt::write!(f, "GPIO pin error: {}", defmt::Debug2Format(e)),
        }
    }
}