1use 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, }
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}