ht1621b
Platform-agnostic embedded-hal 1.0 driver for the HT1621B LCD
controller, driven over its custom 3‑ or 4‑wire serial interface.
| Type | Pins | Capabilities |
|---|---|---|
Ht1621b |
CS, WR, DA | Write commands + RAM data |
Ht1621bRW |
CS, WR, RD, DA | Write + read + read‑modify‑write |
Both are aliases for the generic Driver<B>, which wraps a bus
implementation (Ht1621bBus / Ht1621bBusRW). Generic over any
embedded-hal 1.0 OutputPin (+ FlexPin for DA on the 4‑wire variant)
and DelayNs; the power-on initialisation (Ht1621b::new) is async and
borrows an embedded-hal-async DelayNs for its millisecond-scale waits.
Features
| feature | default | effect |
|---|---|---|
read |
off | Enables Ht1621bRW / Ht1621bBusRW (RAM read + read‑modify‑write) and pulls in the embedded-flex-pin dependency for the bidirectional DA pin. |
The default build is write-only (3-wire) and does not depend on
embedded-flex-pin. Enable read support with:
= { = "0.2", = ["read"] }
Example
use ht1621b::{Config, Ht1621b};
let mut lcd = Ht1621b::new(cs, wr, da, delay, &mut async_delay, Config::default()).await;
lcd.write_byte(arbitrary_int::u6::new(0x00), 0xEF); // display a digit
Resource-constrained MCUs (e.g. Cortex-M0+)
This driver targets extremely small parts (tested against budgets like
3 KB SRAM / 20 KB flash). It uses no heap, no static buffers, and the
struct is only the moved pin/delay fields, so its RAM footprint is a handful of
bytes. The dominant cost is flash, which is overwhelmingly decided by your
application's release profile.
Add this to your binary crate's Cargo.toml:
[]
= "z" # optimise for size
= true
= 1
= "abort" # drop unwind tables / landing pads
= true
panic = "abort" is usually the single biggest flash win at this level, as it
removes the unwinding machinery entirely.
Cortex-M0+ notes (thumbv6m-none-eabi)
- The M0+ has no hardware divide and no atomic compare-and-swap. This
driver contains neither division nor atomics, so it maps cleanly onto the
thumbv6minstruction set. - When constructing RAM addresses, prefer
arbitrary_int::u6::new_masked(x)overu6::new(x):new()panics on out-of-range input, which pulls thecore::fmtpanic-formatting machinery into flash.new_masked()truncates instead and keeps that code path out of your binary.
Measuring size
An example harness is included that instantiates the full driver (including the
async new) on bare metal so you can profile it:
Measured on thumbv6m-none-eabi (driver + minimal cortex-m-rt runtime; the
size_m0 harness exercises new, clear, write_byte, and set_config).
Two profiles compared — stock release (opt-level = 3, no LTO) vs. the size
profile above:
| profile | .text |
.rodata / .data / .bss |
|---|---|---|
stock release |
124 B | 0 / 0 / 0 |
| recommended size opts | 384 B | 0 / 0 / 0 |
(.text here excludes the 192 B .vector_table, which is Cortex-M runtime, not
the driver. Stock release inlines the no-op harness pins away more
aggressively, hence the smaller number; both are tiny.)
Key takeaways: .rodata/.data/.bss are all 0 — no core::fmt
panic-formatting machinery, libcalls, or static buffers are linked, and the
driver's static RAM footprint is zero. Unused features cost nothing: methods are
generic over the pins/delay, so any command you never call (e.g. tone or
time-base/WDT) is dropped entirely by dead-code elimination.