ht1621b 0.1.1

Platform-agnostic embedded-hal driver for the HT1621B LCD controller (3-wire bit-bang)
Documentation
//! Low-level HT1621B protocol layer.
//!
//! [`Ht1621bBus`] implements the raw 3-wire framing and knows nothing about
//! LCD semantics.  Most users should prefer the ergonomic [`crate::Ht1621b`]
//! wrapper; use this layer directly only when you need raw command / RAM
//! access.

use arbitrary_int::{u4, u6};
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;

/// Low-level protocol layer.
///
/// Owns the three output pins plus a delay source and implements the raw
/// HT1621B 3-wire framing.  It is generic over any [`OutputPin`] and
/// [`DelayNs`] implementations, so it is not tied to a specific HAL.  It
/// knows nothing about LCD semantics — only how to push commands and RAM
/// data onto the bus.  Use [`crate::Ht1621b`] for an ergonomic,
/// command-based interface built on top of this.
pub struct Ht1621bBus<CS, WR, DA, D> {
    cs: CS,
    wr: WR,
    da: DA,
    delay: D,
}

impl<CS, WR, DA, D> Ht1621bBus<CS, WR, DA, D>
where
    CS: OutputPin,
    WR: OutputPin,
    DA: OutputPin,
    D: DelayNs,
{
    /// Create the protocol-layer instance.
    ///
    /// Idles the bus (CS=High, WR=High, DA=High).  A 1 ms power-on delay is
    /// recommended before issuing the initialisation commands.
    pub fn new(mut cs: CS, mut wr: WR, mut da: DA, delay: D) -> Self {
        let _ = cs.set_high();
        let _ = wr.set_high();
        let _ = da.set_high();
        Self { cs, wr, da, delay }
    }

    /// Send an 8-bit command.
    ///
    /// Frame: CS↓ → `100` → C7–C0 → `X`(=0) → CS↑ → 10 µs settling.
    pub fn write_command(&mut self, cmd: u8) {
        self.cs_low();
        // 3-bit command ID: 100
        self.write_bit(true);
        self.write_bit(false);
        self.write_bit(false);
        // 8-bit command, MSB first (C7 → C0)
        let mut mask: u8 = 0x80;
        for _ in 0..8 {
            self.write_bit((cmd & mask) != 0);
            mask >>= 1;
        }
        // don't-care fill bit X = 0
        self.write_bit(false);
        self.cs_high();
        self.delay.delay_us(10);
    }

    /// Burst-write multiple 4-bit words starting at `addr`.
    ///
    /// Frame: CS↓ → `101` → 6-bit addr → words … → CS↑ → 10 µs.
    /// Each `u4` writes one RAM address (4 bits, D0→D3 LSB-first); the
    /// address auto-increments so all words go out in a single CS frame.
    pub fn write_words(&mut self, addr: u6, words: &[u4]) {
        self.cs_low();
        // 3-bit write ID: 101
        self.write_bit(true);
        self.write_bit(false);
        self.write_bit(true);
        // 6-bit address, MSB first (A5 → A0)
        let mut mask: u8 = 0x20;
        for _ in 0..6 {
            self.write_bit((addr.value() & mask) != 0);
            mask >>= 1;
        }
        // words, LSB first (D0 → D3)
        for &word in words {
            let value = word.value();
            let mut mask: u8 = 0x01;
            for _ in 0..4 {
                self.write_bit((value & mask) != 0);
                mask <<= 1;
            }
        }
        self.cs_high();
        self.delay.delay_us(10);
    }

    /// Write a single 4-bit word to `addr` (wrapper around `write_words`).
    #[inline]
    pub fn write_word(&mut self, addr: u6, word: u4) {
        self.write_words(addr, &[word]);
    }

    /// Burst-write multiple bytes starting at `addr`.
    ///
    /// Each byte spans 2 RAM addresses: low nibble → `addr`, high nibble →
    /// `addr+1` (D0→D3 LSB-first).  The address auto-increments so all data
    /// is written in a single CS frame.
    pub fn write_bytes(&mut self, addr: u6, data: &[u8]) {
        self.cs_low();
        // 3-bit write ID: 101
        self.write_bit(true);
        self.write_bit(false);
        self.write_bit(true);
        // 6-bit address, MSB first (A5 → A0)
        let mut mask: u8 = 0x20;
        for _ in 0..6 {
            self.write_bit((addr.value() & mask) != 0);
            mask >>= 1;
        }
        // data bytes, LSB first (bit0 → bit7)
        for &byte in data {
            let mut mask: u8 = 0x01;
            for _ in 0..8 {
                self.write_bit((byte & mask) != 0);
                mask <<= 1;
            }
        }
        self.cs_high();
        self.delay.delay_us(10);
    }

    /// Write a single byte to `addr` (wrapper around `write_bytes`).
    ///
    /// Independent CS frame; writes exactly one byte (2 RAM addresses, one
    /// complete digit position).
    #[inline]
    pub fn write_byte(&mut self, addr: u6, data: u8) {
        self.write_bytes(addr, &[data]);
    }

    /// Send a single bit on the bus.
    ///
    /// Timing: DA set → WR↓ → 5 µs → WR↑ (data latched here) → 5 µs.
    /// HT1621B min WR pulse ~3.34 µs (VDD=3V); 5 µs gives ~50% margin.
    #[inline]
    fn write_bit(&mut self, b: bool) {
        if b {
            let _ = self.da.set_high();
        } else {
            let _ = self.da.set_low();
        }
        let _ = self.wr.set_low();
        self.delay.delay_us(5);
        let _ = self.wr.set_high();
        self.delay.delay_us(5);
    }

    #[inline]
    fn cs_low(&mut self) {
        let _ = self.cs.set_low();
    }

    #[inline]
    fn cs_high(&mut self) {
        let _ = self.cs.set_high();
    }
}