st7305-lcd 0.2.0

Platform-independent no_std driver for the Sitronix ST7305 reflective LCD controller (embedded-hal 1.0, blocking and async)
Documentation
/// Mapping between pixel coordinates and the native GRAM layout of the panel.
///
/// Coordinates are native (not rotated); rotation is applied by [`FrameBuffer`](super::FrameBuffer).
pub trait PixelMap {
    /// Width in pixels
    const WIDTH: u32;
    /// Height in pixels
    const HEIGHT: u32;
    /// Size of the buffer in bytes
    const BUFFER_LEN: usize;
    /// First `CASET` column of the panel's GRAM window. One column is a band of 12 pixels.
    const CASET_FIRST: u8;
    /// First `RASET` row of the panel's GRAM window. One row is a pair of pixel columns.
    const RASET_FIRST: u8;

    /// Byte index and bit number of the pixel `(x, y)`, `None` outside the panel.
    fn locate(x: u32, y: u32) -> Option<(usize, u8)>;

    /// Reads a row `y` into `out` (out.len() == WIDTH)
    /// Returns `true` if at least one active pixel in the row
    fn read_row(buf: &[u8], y: u32, out: &mut [bool]) -> bool;

    /// Writes a row `y` from `row` (row.len() == WIDTH)
    ///
    /// Only sets pixels (ORs the bits in), it never clears them: write into a cleared row
    /// (e.g. after `FrameBuffer::fill(false)`), otherwise old pixels stay on.
    fn write_row(buf: &mut [u8], y: u32, row: &[bool]);

    /// Like [`Self::write_row`], but mirrored: `row[i]` goes to the pixel `(WIDTH - 1 - i, y)`.
    fn write_row_rev(buf: &mut [u8], y: u32, row: &[bool]);

    /// Reads a column `x` into `out` (out.len() == HEIGHT, `out[y]` is the pixel `(x, y)`)
    /// Returns `true` if at least one active pixel in the column
    fn read_col(buf: &[u8], x: u32, out: &mut [bool]) -> bool;

    /// Writes a column `x` from `col` (col.len() == HEIGHT)
    ///
    /// Only sets pixels (ORs the bits in), like [`Self::write_row`].
    fn write_col(buf: &mut [u8], x: u32, col: &[bool]);

    /// Like [`Self::write_col`], but mirrored: `col[i]` goes to the pixel `(x, HEIGHT - 1 - i)`.
    fn write_col_rev(buf: &mut [u8], x: u32, col: &[bool]);
}

/// Layout of the ST7305 GRAM for a `W` x `H` landscape panel: a byte holds a block of 2 x 4
/// pixels, bytes go along the columns of pixel pairs, bottom to top. `W` must be even and `H` a
/// multiple of 4.
///
/// `CASET0` and `RASET0` are the first `CASET` column and `RASET` row of the panel's GRAM
/// window; they depend on how the panel is wired and have no default. The ready-made values
/// of a verified panel are in [`presets`](crate::presets).
#[derive(Debug)]
pub enum Landscape<const W: u32, const H: u32, const CASET0: u8, const RASET0: u8> {}

impl<const W: u32, const H: u32, const CASET0: u8, const RASET0: u8>
    Landscape<W, H, CASET0, RASET0>
{
    const fn len() -> usize {
        ((W / 2) * (H / 4)) as usize
    }

    const fn locate(x: u32, y: u32) -> Option<(usize, u8)> {
        if x >= W || y >= H {
            return None;
        }

        let inv_y = H - 1 - y;
        Some((
            ((x / 2) * (H / 4) + inv_y / 4) as usize,
            (7 - inv_y % 4 * 2 - x % 2) as u8,
        ))
    }
}

const _: () = assert!(matches!(
    Landscape::<400, 300, 0x12, 0>::locate(0, 299),
    Some((0, 7))
));
const _: () = assert!(matches!(
    Landscape::<400, 300, 0x12, 0>::locate(0, 0),
    Some((74, 1))
));
const _: () = assert!(matches!(
    Landscape::<400, 300, 0x12, 0>::locate(399, 0),
    Some((14999, 0))
));
const _: () = assert!(matches!(
    Landscape::<400, 300, 0x12, 0>::locate(399, 299),
    Some((14925, 6))
));
const _: () = assert!(matches!(
    Landscape::<400, 300, 0x12, 0>::locate(1, 299),
    Some((0, 6))
));
const _: () = assert!(matches!(
    Landscape::<400, 300, 0x12, 0>::locate(2, 299),
    Some((75, 7))
));
const _: () = assert!(matches!(
    Landscape::<400, 300, 0x12, 0>::locate(5, 100),
    Some((199, 0))
));
const _: () = assert!(Landscape::<400, 300, 0x12, 0>::locate(400, 0).is_none());
const _: () = assert!(Landscape::<400, 300, 0x12, 0>::locate(0, 300).is_none());
const _: () = assert!(matches!(Landscape::<400, 300, 0x12, 0>::len(), 15000));

impl<const W: u32, const H: u32, const CASET0: u8, const RASET0: u8> PixelMap
    for Landscape<W, H, CASET0, RASET0>
{
    const WIDTH: u32 = W;
    const HEIGHT: u32 = H;
    const BUFFER_LEN: usize = {
        assert!(
            W.is_multiple_of(2) && H.is_multiple_of(4),
            "W must be multiple of 2, H must be multiple of 4"
        );
        Self::len()
    };
    const CASET_FIRST: u8 = CASET0;
    const RASET_FIRST: u8 = RASET0;

    fn locate(x: u32, y: u32) -> Option<(usize, u8)> {
        Self::locate(x, y)
    }

    fn read_row(buf: &[u8], y: u32, out: &mut [bool]) -> bool {
        assert!(y < H && out.len() == W as usize && buf.len() == Self::len());

        let inv_y = H - 1 - y;
        let base = (inv_y / 4) as usize; // offset of the row
        let hi = 7 - 2 * (inv_y % 4); // odd x bit
        let mask = 0b11u8 << (hi - 1); // both bits

        let mut acc = 0u8;
        let bytes = buf[base..].iter().step_by((H / 4) as usize);
        for (pair, &b) in out.chunks_exact_mut(2).zip(bytes) {
            pair[0] = (b >> hi) & 1 != 0;
            pair[1] = (b >> (hi - 1)) & 1 != 0;
            acc |= b & mask;
        }
        acc != 0
    }

    fn write_row(buf: &mut [u8], y: u32, row: &[bool]) {
        assert!(y < H && row.len() == W as usize && buf.len() == Self::len());

        let inv_y = H - 1 - y;
        let base = (inv_y / 4) as usize;
        let hi = 7 - 2 * (inv_y % 4);

        let bytes = buf[base..].iter_mut().step_by((H / 4) as usize);
        for (pair, b) in row.chunks_exact(2).zip(bytes) {
            *b |= (u8::from(pair[0]) << hi) | (u8::from(pair[1]) << (hi - 1));
        }
    }

    // The pairs of `row` are taken from the end and swapped inside: `row[W - 1]` is the pixel 0.
    fn write_row_rev(buf: &mut [u8], y: u32, row: &[bool]) {
        assert!(y < H && row.len() == W as usize && buf.len() == Self::len());

        let inv_y = H - 1 - y;
        let base = (inv_y / 4) as usize;
        let hi = 7 - 2 * (inv_y % 4);

        let bytes = buf[base..].iter_mut().step_by((H / 4) as usize);
        for (pair, b) in row.rchunks_exact(2).zip(bytes) {
            *b |= (u8::from(pair[1]) << hi) | (u8::from(pair[0]) << (hi - 1));
        }
    }

    // A column is `H / 4` consecutive bytes. Each byte holds 4 pixels of the column, they sit in
    // the bits `7 - lane`, `5 - lane`, `3 - lane`, `1 - lane`, where `lane` is `x % 2`. The bytes
    // go from the bottom of the screen to the top, so `rchunks_exact` pairs them with `out`.
    fn read_col(buf: &[u8], x: u32, out: &mut [bool]) -> bool {
        assert!(x < W && out.len() == H as usize && buf.len() == Self::len());

        let lane = (x % 2) as u8;
        let base = (x / 2 * (H / 4)) as usize;
        let mask = 0xAAu8 >> lane; // the 4 bits of this column in a byte

        let mut acc = 0u8;
        let bytes = &buf[base..base + (H / 4) as usize];
        for (quad, &b) in out.rchunks_exact_mut(4).zip(bytes) {
            quad[3] = (b >> (7 - lane)) & 1 != 0;
            quad[2] = (b >> (5 - lane)) & 1 != 0;
            quad[1] = (b >> (3 - lane)) & 1 != 0;
            quad[0] = (b >> (1 - lane)) & 1 != 0;
            acc |= b & mask;
        }
        acc != 0
    }

    fn write_col(buf: &mut [u8], x: u32, col: &[bool]) {
        assert!(x < W && col.len() == H as usize && buf.len() == Self::len());

        let lane = (x % 2) as u8;
        let base = (x / 2 * (H / 4)) as usize;

        let bytes = buf[base..base + (H / 4) as usize].iter_mut();
        for (quad, b) in col.rchunks_exact(4).zip(bytes) {
            *b |= (u8::from(quad[3]) << (7 - lane))
                | (u8::from(quad[2]) << (5 - lane))
                | (u8::from(quad[1]) << (3 - lane))
                | (u8::from(quad[0]) << (1 - lane));
        }
    }

    // The bytes go from the bottom to the top, so the mirrored `col` is read from its start.
    fn write_col_rev(buf: &mut [u8], x: u32, col: &[bool]) {
        assert!(x < W && col.len() == H as usize && buf.len() == Self::len());

        let lane = (x % 2) as u8;
        let base = (x / 2 * (H / 4)) as usize;

        let bytes = buf[base..base + (H / 4) as usize].iter_mut();
        for (quad, b) in col.chunks_exact(4).zip(bytes) {
            *b |= (u8::from(quad[0]) << (7 - lane))
                | (u8::from(quad[1]) << (5 - lane))
                | (u8::from(quad[2]) << (3 - lane))
                | (u8::from(quad[3]) << (1 - lane));
        }
    }
}

const _: () = assert!(<Landscape<400, 300, 0x12, 0> as PixelMap>::BUFFER_LEN == 15000);