st7305-lcd
A no_std driver for the Sitronix ST7305 monochrome reflective LCD controller,
built on embedded-hal 1.0 and independent of any HAL or board.
- Talks to the display through
embedded_hal::spi::SpiDevice: chip select belongs to the SPI device, so the bus can be shared with other peripherals. - Blocking driver (
blocking::St7305) and, with theasyncfeature, the same API onembedded-hal-async(asynch::St7305). Both can write a frame in two steps (begin_write_ram+write_ram_data); with the async driver, rendering the next frame can then overlap with the DMA transfer of the current one. - Typed register commands (
cmd) withconst fnencoders and compile-time checked defaults. - Wrappers for the common operations:
reset,sleep_in/sleep_out,display_on/display_off,set_inversion,set_power_mode,set_tearing_effect,set_address_window, andreleaseto get the SPI device and the pins back. - Optional
framebufferfeature: aFrameBufferimplementingembedded-graphicsDrawTarget<Color = BinaryColor>, with a pluggable pixel mapping and a runtimeRotation(0, 90, 180, 270 degrees). - Initialization tables are
&[InitStep]: the parameters borrow, so a table can be built at run time (from a config, a buffer) as well as be aconst. - Errors keep the SPI and pin error types and implement
Displayandcore::error::Error. - Optional
defmtfeature:defmt::Formatfor the errors and the small public types. - Host tests (mock SPI, pins and delay) cover the command framing, both drivers, the initialization table and the frame buffer.
Not supported yet
The driver only writes. Reading from the controller (RDDID, RDDST, RDDPM, RDID1..3,
RAMRD, RDMEMC, NVMRD) is not supported yet: the Instr table lists these opcodes, but
there are no methods for them, and SpiDevice is used for writes only.
Features
| Feature | Enables |
|---|---|
async |
asynch::St7305 on embedded-hal-async |
framebuffer |
framebuffer::{FrameBuffer, Landscape, PixelMap, Rotation}, embedded-graphics-core integration |
defmt |
defmt::Format for Error, PowerMode, InitStep, Rotation and Instr |
nvm-program |
cmd::nvm commands that program the panel NVM and set its ID; they need an external programming supply (VPP, about 7.5 V) and EXTB pulled to VDGND (datasheet v0.2, §6.5) |
No feature is enabled by default.
Minimum supported Rust version: 1.87 (rust-version in Cargo.toml).
Wiring
The driver needs three things from your HAL:
- an
embedded_hal::spi::SpiDevice(async:embedded_hal_async::spi::SpiDevice) in SPI mode 0, MSB first, with the chip select inside the device. The panel guarantees 33 MHz; the driver never configures the bus; - two
OutputPins with the same error type:DC(data/command) andRST(reset); - a
DelayNsfor the reset and the initialization pauses.
A bus plus a chip select pin become a SpiDevice with
embedded-hal-bus, for example
ExclusiveDevice::new_no_delay(bus, cs).
Example: blocking
use ;
use ;
// `spi` is an `embedded_hal::spi::SpiDevice`, `dc` and `rst` are `OutputPin`s.
let mut display = new;
display.init_with?;
display.set_inversion?;
// 400 x 300 pixels at 1 bit per pixel = 15 000 bytes.
let mut frame = new;
// Drawing into a `FrameBuffer` cannot fail (`Error = Infallible`).
let Ok = new
.into_styled
.draw;
display.write_ram?;
The error type of every method is st7305_lcd::Error<SpiError, PinError>.
Example: async, transfer overlapped with rendering
use join;
use ;
let mut display = new;
display.init_with.await?;
display.set_inversion.await?;
// `front` is being sent, `back` is being rendered.
let = ;
loop
write_ram_data must be called right after a successful begin_write_ram; this order is
guaranteed only by documentation. The first .await of the joined driver future has to be
the data transfer itself (not the 1-byte RAMWR command), otherwise the DMA does not start
before rendering and the overlap is lost.
Rotation
FrameBuffer draws in a logical orientation. The bytes in as_bytes() always stay in the
native panel layout and are sent as they are.
use Rotation;
let frame = new.with_rotation;
assert_eq!; // `OriginDimensions` reports the logical size
Deg90 and Deg270 turn the picture clockwise by the given angle (the logical top-left
corner goes to the native top-right corner for Deg90). Changing the rotation does not move
the pixels already drawn: redraw the frame. read_row / write_row copy whole logical rows
along a native row or column (also mirrored), which is much faster than pixel by pixel.
Sleep
sleep_in / sleep_out alone are enough; GRAM and the on/off state of the display are kept
(datasheet v0.2, §8.1.6 and §8.1.7). The methods wait the pauses the datasheet requires
(5 ms after SLPIN, 100 ms after SLPOUT).
display.sleep_in?;
// ... later
display.sleep_out?; // the previous picture is shown again
Measured on hardware
Waveshare ESP32-S3-RLCD-4.2, esp-hal 1.2.2, SPI at 80/3 MHz (about 26.7 MHz), 400x300 at
1 bpp (15 000 bytes per frame), Conway's Game of Life over the whole frame:
| Mode | Frame time | FPS |
|---|---|---|
blocking write_ram, then render |
about 9.0 ms | about 111 |
begin_write_ram + join(write_ram_data, render) |
about 4.6 ms (bus-bound) | about 218 |
Why another ST7305 crate?
Other ST7305 drivers already exist on crates.io. This table compares them with this
crate so you can pick what fits your project. It reflects st7305 0.1.2 and
st7305-driver 0.1.0 and may be out of date.
| this crate | st7305 |
st7305-driver |
|
|---|---|---|---|
| SPI abstraction | SpiDevice (CS owned by the device) |
display-interface WriteOnlyDataCommand |
SpiBus + manual CS pin |
| Shares the SPI bus with other devices | yes | depends on the interface implementation | no |
| Async | full (async feature, SPI transfers awaited) |
delays only; flush and commands are blocking |
none |
| Frame transfer can overlap with rendering | yes | no | no |
| Register commands | typed, const fn encoders, compile-time asserts |
opcode enum + raw bytes | opcode enum + raw bytes |
| Driver dependencies | embedded-hal (+ embedded-hal-async) |
display-interface, embedded-hal |
embedded-hal |
| SPI error preserved | yes | no (DisplayError) |
yes |
Display / core::error::Error for errors |
yes | no | yes |
embedded-graphics |
framebuffer feature, generic pixel map |
graphics feature, fixed 400x300 |
no |
| Framebuffer | separate from the driver, you own the buffers | inside the driver (15 KB) | none, you pass raw bytes |
| Orientations | Rotation 0/90/180/270 at runtime |
Landscape and Portrait | raw MadCtl |
| Sleep / power mode / TE / address window API | wrappers for all four | basic | wide |
| Host tests | yes (mock SPI, pins, delay) | no | no |
| Init preset | presets::WAVESHARE_RLCD_42, verified on hardware |
hardcoded in init |
hardcoded in init |
If you only need a quick, blocking, batteries-included setup with embedded-graphics,
st7305 may be simpler. If you want a thin blocking driver with a wide register API on a
dedicated SPI bus, look at st7305-driver. Choose this crate if you want SpiDevice, async
with DMA overlap, or typed commands.
Panel notes
- The GRAM window for the 400x300 panel is CASET
0x12..=0x2Aand RASET0x00..=0xC7(15 000 bytes); the limits in datasheet v0.2 (264x320) do not match this panel. - With display inversion on, bit 1 is white; with it off, bit 1 is black.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you shall be dual licensed as above, without any additional terms or conditions.