Skip to main content

st7305_lcd/
lib.rs

1//! Platform-independent `no_std` driver for the Sitronix ST7305 reflective LCD
2//! controller.
3//!
4//! The driver talks 4-wire SPI through [`embedded_hal::spi::SpiDevice`] (chip
5//! select belongs to the device, not to the driver) and drives the `DC` and
6//! `RST` pins itself. It never configures the bus.
7//!
8//! - [`blocking::St7305`]: blocking driver on `embedded-hal`.
9//! - [`asynch::St7305`] (feature `async`): the same API on
10//!   `embedded-hal-async`.
11//! - [`framebuffer`] (feature `framebuffer`): 1 bpp frame buffer in the GRAM
12//!   layout of the panel with runtime [`Rotation`](framebuffer::Rotation) and
13//!   an `embedded-graphics` `DrawTarget`, and
14//!   [`Window`](framebuffer::Window) for partial updates
15//!   ([`write_window`](blocking::St7305::write_window)).
16//! - [`cmd`]: typed commands for
17//!   [`write_command`](blocking::St7305::write_command), and [`presets`]:
18//!   ready-made initialization tables for
19//!   [`init_with`](blocking::St7305::init_with).
20//!
21//! Both drivers can send a frame in two steps,
22//! [`begin_write_ram`](blocking::St7305::begin_write_ram) (the `RAMWR` command)
23//! and [`write_ram_data`](blocking::St7305::write_ram_data) (the pixel bytes).
24//! With the async driver the transfer can then be overlapped with rendering of
25//! the next frame.
26//!
27//! # Where is `St7305`?
28//!
29//! The driver type is not re-exported at the crate root, because the blocking
30//! and the async driver share its name. Import it from the module you need:
31//! `st7305_lcd::blocking::St7305` or `st7305_lcd::asynch::St7305`.
32//!
33//! # Example
34//!
35//! ```ignore
36//! use st7305_lcd::{blocking::St7305, presets::WAVESHARE_RLCD_42};
37//!
38//! // `spi` is an `embedded_hal::spi::SpiDevice`, `dc` and `rst` are
39//! // `OutputPin`s, `delay` is a `DelayNs`.
40//! let mut display = St7305::new(spi, dc, rst);
41//! display.init_with(WAVESHARE_RLCD_42, &mut delay)?;
42//! display.write_ram(&frame_bytes)?; // 15 000 bytes of GRAM data
43//! ```
44#![deny(missing_docs)]
45#![no_std]
46#![cfg_attr(docsrs, feature(doc_cfg))]
47// The crate docs link to `asynch` and `framebuffer`; docs.rs builds with all
48// features, a local `cargo doc` without them must not fail on those links.
49#![cfg_attr(
50    not(all(feature = "async", feature = "framebuffer")),
51    allow(rustdoc::broken_intra_doc_links)
52)]
53
54#[cfg(feature = "async")]
55/// Async driver on `embedded-hal-async`.
56pub mod asynch;
57/// Blocking driver on `embedded-hal`.
58pub mod blocking;
59pub mod cmd;
60mod command;
61#[cfg(feature = "framebuffer")]
62pub mod framebuffer;
63/// Command opcode table.
64pub mod instr;
65/// Initialization tables for `init_with`.
66pub mod presets;
67
68pub use command::WriteCommand;
69
70use core::fmt;
71
72/// One step of an initialization table, see `init_with` of the drivers and
73/// [`presets`].
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75#[cfg_attr(feature = "defmt", derive(defmt::Format))]
76#[non_exhaustive]
77pub enum InitStep<'a> {
78    /// Command: opcode and its parameters (may be empty)
79    Cmd(u8, &'a [u8]),
80    /// Pause in milliseconds
81    DelayMs(u32),
82}
83
84/// Power mode of the panel, selected with `set_power_mode`.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86#[cfg_attr(feature = "defmt", derive(defmt::Format))]
87pub enum PowerMode {
88    /// `HPM`: high power mode, frame rate set by `HpmFrameRate`
89    High,
90    /// `LPM`: low power mode, frame rate set by `LpmFrameRate`
91    Low,
92}
93
94/// Driver error. `S` is the SPI error type, `P` is the error type of the `DC`
95/// and `RST` pins.
96///
97/// The state of the `DC` line after an error is not guaranteed, every command
98/// sets it again.
99#[derive(Debug, Clone, PartialEq, Eq)]
100#[non_exhaustive]
101pub enum Error<S, P> {
102    /// SPI transaction failed
103    Spi(S),
104    /// DC or RST pin could not be driven
105    Pin(P),
106}
107
108impl<S: fmt::Debug, P: fmt::Debug> fmt::Display for Error<S, P> {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        match self {
111            Error::Spi(e) => write!(f, "SPI error: {e:?}"),
112            Error::Pin(e) => write!(f, "GPIO pin error: {e:?}"),
113        }
114    }
115}
116
117impl<S: fmt::Debug, P: fmt::Debug> core::error::Error for Error<S, P> {}
118
119/// Implemented by hand instead of derived: a derive would require `S` and `P` to
120/// be `defmt::Format`, which HAL error types usually are not. `Debug` is always
121/// there, because `embedded_hal::spi::Error` and `digital::Error` require it.
122#[cfg(feature = "defmt")]
123impl<S: fmt::Debug, P: fmt::Debug> defmt::Format for Error<S, P> {
124    fn format(&self, f: defmt::Formatter) {
125        match self {
126            Error::Spi(e) => defmt::write!(f, "SPI error: {}", defmt::Debug2Format(e)),
127            Error::Pin(e) => defmt::write!(f, "GPIO pin error: {}", defmt::Debug2Format(e)),
128        }
129    }
130}