st77916
A no_std Rust driver for the Sitronix ST77916 TFT-LCD display controller
(360x390, 262K color).
The ST77916 is found in round and square LCD modules driven over QSPI, SPI, or parallel interfaces. It is used in boards such as the Waveshare ESP32-S3 Knob Touch LCD 1.8" (360x360, QSPI).
Features
- Generic over communication interface — implement
ControllerInterfacefor QSPI, SPI, parallel, or any other bus. - Generic over reset pin — GPIO, I2C expander, or any other mechanism via
ResetInterface. - Three buffering modes selected at compile-time via a generic parameter
(requires the
embedded-graphicsfeature):- Unbuffered DrawTarget — zero RAM, each draw is a hardware transaction.
- Single-buffered — internal framebuffer with
DrawTargetsupport and automatic dirty band tracking (only changed rows are flushed). - Double-buffered — two framebuffers for overlapping render and flush on hardware with separate CPU/DMA memory buses.
- Builder pattern with
with_init_commands()for vendor-specific panel init sequences. - Static or heap-allocated framebuffer.
- RGB565, RGB888, and Gray8 color formats.
- Full ST77916 command set from the datasheet (V1.0, Sitronix, 2022/08).
Cargo features
| Feature | Default | Description |
|---|---|---|
embedded-graphics |
yes | DrawTarget support, buffering modes, color trait |
Without the feature the crate provides the core driver (commands, set_window,
send_pixels, sleep, MADCTL, brightness) with zero dependency on
embedded-graphics-core.
# Default — includes embedded-graphics integration
[]
= "0.1"
# Without embedded-graphics (raw driver only)
[]
= { = "0.1", = false }
Usage
Unbuffered (no DrawTarget)
The caller owns the pixel data and streams it directly.
use ;
const SIZE: DisplaySize = new;
let mut display = builder
.with_init_commands
.build?;
display.set_window?;
display.send_pixels?;
Single-buffered (recommended)
Internal framebuffer with embedded-graphics DrawTarget. Draw operations
write to RAM; flush() sends only the changed rows to the display.
use ;
use Rgb565;
use *;
use ;
const SIZE: DisplaySize = new;
const FB_SIZE: usize = framebuffer_size;
let mut display = builder
.with_init_commands
.
.build?;
new
.into_styled
.draw?;
// Flush only the dirty rows to the display
display.flush?;
// Or force a full refresh (e.g. after wake from sleep)
display.full_flush?;
Double-buffered
Two framebuffers allow overlapping render and flush on hardware with separate
CPU and DMA memory buses. Draw operations target the back buffer;
swap_buffers() exchanges front and back, then flush_front() sends the
completed frame.
use ;
use Rgb565;
const SIZE: DisplaySize = new;
const FB_SIZE: usize = framebuffer_size;
let mut display = builder
.with_init_commands
.
.build?;
loop
Direct framebuffer access
For advanced use cases, the framebuffer can be accessed directly. After writing
pixels manually, call mark_dirty() so the next flush() includes the
modified rows.
let fb = display.framebuffer_mut;
fb.fill; // Write to first row (360 pixels x 2 bytes)
display.mark_dirty; // Mark row 0 as dirty
display.flush?;
Dirty band tracking
The single-buffered mode automatically tracks which rows have been modified by
DrawTarget operations (draw_iter, fill_solid, fill_contiguous, clear).
When flush() is called:
- If nothing changed since the last flush, it returns immediately (no SPI traffic).
- Otherwise, only the contiguous band of modified rows is sent — a single
set_window+send_pixelscall with no allocation.
This is transparent to all callers — the DrawTarget API is unchanged. For UIs
where only a small region updates each frame (e.g. a clock or status bar), this
can reduce flush time from ~12ms (full screen) to <1ms.
Use full_flush() to bypass dirty tracking when needed (e.g. after wake from
sleep or direct framebuffer writes without mark_dirty()).
QSPI protocol
The ST77916 QSPI interface (datasheet Section 8.8.5) uses:
| Opcode | Mode | Description |
|---|---|---|
0x02 |
PP | Single-line cmd + addr + data (register writes) |
0x32 |
PP4O | Single-line cmd + addr, quad-line data (pixel writes) |
0x0B |
FAST READ | Single-line read with dummy byte |
Address format: [0x00, command_byte, 0x00] (24-bit).
Datasheet
The ST77916 datasheet (V1.0, 264 pages) is hosted by Espressif:
https://dl.espressif.com/AE/esp-iot-solution/ST77916_SPEC_V1.0.pdf
Acknowledgements
This crate is heavily inspired by
theembeddedrustacean/sh8601-rs.
The trait-based architecture (ControllerInterface, ResetInterface), builder
pattern, framebuffer design, and embedded-graphics integration all follow the
patterns established in that crate.
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.