kywy/
display.rs

1// SPDX-FileCopyrightText: 2025 KOINSLOT Inc.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5
6//! Kywy Display Driver
7
8use core::ops::Not;
9use embassy_rp::gpio::Output;
10use embedded_graphics::{
11    Pixel,
12    draw_target::DrawTarget,
13    pixelcolor::BinaryColor,
14    prelude::{OriginDimensions, Size},
15};
16use embedded_hal_async::spi::SpiDevice;
17
18const WIDTH: usize = 144;
19const HEIGHT: usize = 168;
20const TOTAL_BUFFER_SIZE: usize = WIDTH * HEIGHT / 8;
21const BYTES_PER_LINE: usize = WIDTH / 8;
22const LINE_PACKET_SIZE: usize = 2 + BYTES_PER_LINE + 2;
23
24#[derive(Clone, Copy, PartialEq, Eq)]
25enum Vcom {
26    Lo = 0x00,
27    Hi = 0x40,
28}
29
30impl Not for Vcom {
31    type Output = Self;
32    fn not(self) -> Self::Output {
33        match self {
34            Vcom::Lo => Vcom::Hi,
35            Vcom::Hi => Vcom::Lo,
36        }
37    }
38}
39
40#[derive(Clone, Copy, PartialEq, Eq)]
41enum Command {
42    Nop = 0x00,
43    ClearMemory = 0x20,
44    WriteLine = 0x80,
45}
46
47pub struct KywyDisplay<'a, SPI> {
48    spi: SPI,
49    disp: Output<'a>,
50    buffer: [u8; TOTAL_BUFFER_SIZE],
51    line_buf: [u8; LINE_PACKET_SIZE],
52    vcom: Vcom,
53    auto_vcom: bool,
54}
55
56impl<'a, SPI> KywyDisplay<'a, SPI>
57where
58    SPI: SpiDevice,
59{
60    pub fn new(spi: SPI, disp: Output<'a>) -> Self {
61        Self {
62            spi,
63            disp,
64            buffer: [0x00; TOTAL_BUFFER_SIZE],
65            line_buf: [0x00; LINE_PACKET_SIZE],
66            vcom: Vcom::Hi,
67            auto_vcom: true, //defaults to toggling vcom every display update
68        }
69    }
70
71    pub async fn initialize(&mut self) {
72        self.disable();
73        self.vcom = Vcom::Hi;
74        self.clear_display().await;
75        self.enable();
76    }
77
78    pub fn enable(&mut self) {
79        self.disp.set_high();
80    }
81
82    pub fn disable(&mut self) {
83        self.disp.set_low();
84    }
85
86    pub async fn write_spi(&mut self, data: &[u8]) {
87        self.spi.transfer(&mut [], data).await.unwrap();
88    }
89
90    pub async fn write_display(&mut self) {
91        if self.auto_vcom {
92            self.vcom = !self.vcom;
93        }
94
95        for line in 0..HEIGHT {
96            self.line_buf[0] = Command::WriteLine as u8 | self.vcom as u8 | 0x80;
97            self.line_buf[1] = (line as u8 + 1).reverse_bits();
98
99            let buf_start = line * BYTES_PER_LINE;
100            for i in 0..BYTES_PER_LINE {
101                self.line_buf[2 + i] = self.buffer[buf_start + i].reverse_bits();
102            }
103
104            self.line_buf[2 + BYTES_PER_LINE] = 0x00;
105            self.line_buf[3 + BYTES_PER_LINE] = 0x00;
106
107            self.spi.write(&self.line_buf).await.unwrap();
108        }
109    }
110
111    pub async fn toggle_vcom(&mut self) {
112        self.vcom = !self.vcom;
113        self.write_spi(&[Command::Nop as u8 | self.vcom as u8, 0x00])
114            .await;
115    }
116
117    pub fn set_auto_vcom(&mut self, enable: bool) {
118        self.auto_vcom = enable;
119    }
120
121    pub fn is_auto_vcom(&self) -> bool {
122        self.auto_vcom
123    }
124
125    pub async fn clear_display(&mut self) {
126        if self.auto_vcom {
127            self.vcom = !self.vcom;
128        }
129        self.write_spi(&[Command::ClearMemory as u8 | self.vcom as u8, 0x00])
130            .await;
131    }
132
133    pub fn clear_buffer(&mut self, color: BinaryColor) {
134        let fill_value = if color.is_on() { 0xFF } else { 0x00 };
135        self.buffer.fill(fill_value);
136    }
137
138    pub fn set_pixel(&mut self, x: usize, y: usize, color: BinaryColor) {
139        if x >= WIDTH || y >= HEIGHT {
140            return;
141        }
142
143        let index = (y * BYTES_PER_LINE) + (x / 8);
144        let bit = x % 8;
145
146        if color.is_on() {
147            self.buffer[index] |= 1 << bit;
148        } else {
149            self.buffer[index] &= !(1 << bit);
150        }
151    }
152    pub fn height(&self) -> usize {
153        HEIGHT
154    }
155    pub fn width(&self) -> usize {
156        WIDTH
157    }
158}
159
160impl<'a, SPI> OriginDimensions for KywyDisplay<'a, SPI> {
161    fn size(&self) -> Size {
162        Size::new(WIDTH as u32, HEIGHT as u32)
163    }
164}
165
166impl<'a, SPI> DrawTarget for KywyDisplay<'a, SPI>
167where
168    SPI: SpiDevice,
169{
170    type Color = BinaryColor;
171    type Error = ();
172
173    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
174    where
175        I: IntoIterator<Item = Pixel<Self::Color>>,
176    {
177        for Pixel(coord, color) in pixels {
178            if coord.x >= 0 && coord.y >= 0 {
179                let x = coord.x as usize;
180                let y = coord.y as usize;
181                if x < WIDTH && y < HEIGHT {
182                    self.set_pixel(x, y, color);
183                }
184            }
185        }
186        Ok(())
187    }
188}