# ht1621b
Platform-agnostic [`embedded-hal`] 1.0 driver for the **HT1621B** LCD
controller, driven over its custom 3‑ or 4‑wire serial interface.
| `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
| `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:
```toml
ht1621b = { version = "0.2", features = ["read"] }
```
## Example
```ignore
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`:
```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:
```sh
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:
| 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.
[`embedded-hal`]: https://crates.io/crates/embedded-hal
[`embedded-hal-async`]: https://crates.io/crates/embedded-hal-async