ht1621b 0.2.0

Platform-agnostic embedded-hal driver for the HT1621B LCD controller (3-wire bit-bang)
Documentation

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:

ht1621b = { version = "0.2", features = ["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:

[profile.release]
opt-level = "z"     # optimise for size
lto = true
codegen-units = 1
panic = "abort"     # drop unwind tables / landing pads
strip = 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 thumbv6m instruction set.
  • When constructing RAM addresses, prefer arbitrary_int::u6::new_masked(x) over u6::new(x): new() panics on out-of-range input, which pulls the core::fmt panic-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:

cargo size --example size_m0 --release --target thumbv6m-none-eabi -- -A

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.