1use crate::gpio::gpio_error_to_io_error;
27use crate::lcd::{to_xy_size, BufferedLcd, Lcd, LcdSize, LcdXY, RGB565Pixel};
28use async_channel::Sender;
29use async_trait::async_trait;
30use endbasic_core::exec::Signal;
31use endbasic_std::console::graphics::InputOps;
32use endbasic_std::console::{
33 CharsXY, ClearType, Console, GraphicsConsole, Key, PixelsXY, SizeInPixels, RGB,
34};
35use endbasic_terminal::TerminalConsole;
36use rppal::gpio::{Gpio, InputPin, Level, OutputPin};
37use rppal::spi::{self, Bus, SlaveSelect, Spi};
38use std::path::Path;
39use std::time::Duration;
40use std::{fs, io};
41
42const SPIDEV_BUFSIZ_PATH: &str = "/sys/module/spidev/parameters/bufsiz";
44
45fn spi_error_to_io_error(e: spi::Error) -> io::Error {
47 match e {
48 spi::Error::Io(e) => e,
49 e => io::Error::new(io::ErrorKind::InvalidInput, e.to_string()),
50 }
51}
52
53fn query_spi_bufsiz(path: Option<&Path>) -> io::Result<usize> {
56 let path = path.unwrap_or(Path::new(SPIDEV_BUFSIZ_PATH));
57
58 let content = match fs::read_to_string(path) {
59 Ok(content) => content,
60 Err(e) => {
61 return Err(io::Error::new(
62 e.kind(),
63 format!("Failed to read {}: {}", path.display(), e),
64 ));
65 }
66 };
67
68 let content = content.trim_end();
69
70 match content.parse::<usize>() {
71 Ok(i) => Ok(i),
72 Err(e) => Err(io::Error::new(
73 io::ErrorKind::InvalidData,
74 format!("Failed to read {}: invalid content: {}", path.display(), e),
75 )),
76 }
77}
78
79struct ST7735SInput {
84 terminal: TerminalConsole,
85}
86
87impl ST7735SInput {
88 fn new(gpio: &mut Gpio, signals_tx: Sender<Signal>) -> io::Result<Self> {
89 let (terminal, on_key_tx) = TerminalConsole::from_stdio_with_injector(signals_tx)?;
90
91 let key_up = gpio.get(6).map_err(gpio_error_to_io_error)?.into_input_pullup();
92 let key_down = gpio.get(19).map_err(gpio_error_to_io_error)?.into_input_pullup();
93 let key_left = gpio.get(5).map_err(gpio_error_to_io_error)?.into_input_pullup();
94 let key_right = gpio.get(26).map_err(gpio_error_to_io_error)?.into_input_pullup();
95 let key_press = gpio.get(13).map_err(gpio_error_to_io_error)?.into_input_pullup();
96 let key_1 = gpio.get(21).map_err(gpio_error_to_io_error)?.into_input_pullup();
97 let key_2 = gpio.get(20).map_err(gpio_error_to_io_error)?.into_input_pullup();
98 let key_3 = gpio.get(16).map_err(gpio_error_to_io_error)?.into_input_pullup();
99
100 tokio::task::spawn(async move {
101 async fn read_button(pin: &InputPin, key: Key, tx: &Sender<Key>) {
102 if pin.read() == Level::Low {
103 if let Err(e) = tx.send(key.clone()).await {
104 eprintln!("Ignoring button {:?} due to error: {}", key, e);
105 }
106 }
107 }
108
109 loop {
110 read_button(&key_up, Key::ArrowUp, &on_key_tx).await;
111 read_button(&key_down, Key::ArrowDown, &on_key_tx).await;
112 read_button(&key_left, Key::ArrowLeft, &on_key_tx).await;
113 read_button(&key_right, Key::ArrowRight, &on_key_tx).await;
114 read_button(&key_press, Key::NewLine, &on_key_tx).await;
115 read_button(&key_1, Key::Char('1'), &on_key_tx).await;
116 read_button(&key_2, Key::Char('2'), &on_key_tx).await;
117 read_button(&key_3, Key::Char('3'), &on_key_tx).await;
118
119 tokio::time::sleep(Duration::from_millis(50)).await;
120 }
121 });
122
123 Ok(Self { terminal })
124 }
125}
126
127#[async_trait(?Send)]
128impl InputOps for ST7735SInput {
129 async fn poll_key(&mut self) -> io::Result<Option<Key>> {
130 self.terminal.poll_key().await
131 }
132
133 async fn read_key(&mut self) -> io::Result<Key> {
134 self.terminal.read_key().await
135 }
136}
137
138struct ST7735SLcd {
140 spi: Spi,
141 spi_bufsiz: usize,
142
143 lcd_rst: OutputPin,
144 lcd_dc: OutputPin,
145 lcd_bl: OutputPin,
146
147 size_pixels: LcdSize,
148}
149
150impl ST7735SLcd {
151 pub fn new(gpio: &mut Gpio) -> io::Result<Self> {
153 let mut lcd_cs = gpio.get(8).map_err(gpio_error_to_io_error)?.into_output();
154 let lcd_rst = gpio.get(27).map_err(gpio_error_to_io_error)?.into_output();
155 let lcd_dc = gpio.get(25).map_err(gpio_error_to_io_error)?.into_output();
156 let mut lcd_bl = gpio.get(24).map_err(gpio_error_to_io_error)?.into_output();
157
158 lcd_cs.write(Level::High);
159 lcd_bl.write(Level::High);
160
161 let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 9000000, spi::Mode::Mode0)
162 .map_err(spi_error_to_io_error)?;
163 spi.set_ss_polarity(spi::Polarity::ActiveLow).map_err(spi_error_to_io_error)?;
164
165 let spi_bufsiz = query_spi_bufsiz(None)?;
166
167 let size_pixels = LcdSize { width: 128, height: 128 };
168
169 let mut device = Self { spi, spi_bufsiz, lcd_rst, lcd_dc, lcd_bl, size_pixels };
170
171 device.lcd_init()?;
172
173 Ok(device)
174 }
175
176 fn lcd_write(&mut self, data: &[u8]) -> io::Result<()> {
180 for chunk in data.chunks(self.spi_bufsiz) {
181 let mut i = 0;
182 loop {
183 let n = self.spi.write(&chunk[i..]).map_err(spi_error_to_io_error)?;
184 if n == 0 {
185 break;
186 }
187 i += n;
188 }
189 }
190 Ok(())
191 }
192
193 fn lcd_write_reg(&mut self, regs: &[u8]) -> io::Result<()> {
195 self.lcd_dc.write(Level::Low);
196 self.lcd_write(regs)
197 }
198
199 fn lcd_write_data(&mut self, data: &[u8]) -> io::Result<()> {
201 self.lcd_dc.write(Level::High);
202 self.lcd_write(data)
203 }
204
205 fn lcd_reset(&mut self) {
207 self.lcd_rst.write(Level::High);
208 std::thread::sleep(Duration::from_millis(100));
209 self.lcd_rst.write(Level::Low);
210 std::thread::sleep(Duration::from_millis(100));
211 self.lcd_rst.write(Level::High);
212 std::thread::sleep(Duration::from_millis(100));
213 }
214
215 fn lcd_init_reg(&mut self) -> io::Result<()> {
217 self.lcd_write_reg(&[0xb1])?;
219 self.lcd_write_data(&[0x01, 0x2c, 0x2d])?;
220
221 self.lcd_write_reg(&[0xb2])?;
222 self.lcd_write_data(&[0x01, 0x2c, 0x2d])?;
223
224 self.lcd_write_reg(&[0xb3])?;
225 self.lcd_write_data(&[0x01, 0x2c, 0x2d, 0x01, 0x2c, 0x2d])?;
226
227 self.lcd_write_reg(&[0xb4])?;
229 self.lcd_write_data(&[0x07])?;
230
231 self.lcd_write_reg(&[0xc0])?;
233 self.lcd_write_data(&[0xa2, 0x02, 0x84])?;
234 self.lcd_write_reg(&[0xc1])?;
235 self.lcd_write_data(&[0xc5])?;
236
237 self.lcd_write_reg(&[0xc2])?;
238 self.lcd_write_data(&[0x0a, 0x00])?;
239
240 self.lcd_write_reg(&[0xc3])?;
241 self.lcd_write_data(&[0x8a, 0x2a])?;
242 self.lcd_write_reg(&[0xc4])?;
243 self.lcd_write_data(&[0x8a, 0xee])?;
244
245 self.lcd_write_reg(&[0xc5])?;
247 self.lcd_write_data(&[0x0e])?;
248
249 self.lcd_write_reg(&[0xe0])?;
251 self.lcd_write_data(&[
252 0x0f, 0x1a, 0x0f, 0x18, 0x2f, 0x28, 0x20, 0x22, 0x1f, 0x1b, 0x23, 0x37, 0x00, 0x07,
253 0x02, 0x10,
254 ])?;
255
256 self.lcd_write_reg(&[0xe1])?;
257 self.lcd_write_data(&[
258 0x0f, 0x1b, 0x0f, 0x17, 0x33, 0x2c, 0x29, 0x2e, 0x30, 0x30, 0x39, 0x3f, 0x00, 0x07,
259 0x03, 0x10,
260 ])?;
261
262 self.lcd_write_reg(&[0xf0])?;
264 self.lcd_write_data(&[0x01])?;
265
266 self.lcd_write_reg(&[0xf6])?;
268 self.lcd_write_data(&[0x00])?;
269
270 self.lcd_write_reg(&[0x3a])?;
272 self.lcd_write_data(&[0x05])?;
273
274 Ok(())
275 }
276
277 fn lcd_set_gram_scan_way(&mut self) -> io::Result<()> {
279 self.lcd_write_reg(&[0x36])?; let scan_dir = 0x40 | 0x20; let rgb_mode = 0x08; self.lcd_write_data(&[scan_dir | rgb_mode])?;
283 Ok(())
284 }
285
286 fn lcd_init(&mut self) -> io::Result<()> {
288 self.lcd_bl.write(Level::High);
289
290 self.lcd_reset();
291 self.lcd_init_reg()?;
292
293 self.lcd_set_gram_scan_way()?;
294 std::thread::sleep(Duration::from_millis(200));
295
296 self.lcd_write_reg(&[0x11])?;
297 std::thread::sleep(Duration::from_millis(200));
298
299 self.lcd_write_reg(&[0x29])?;
301
302 Ok(())
303 }
304
305 fn lcd_set_window(&mut self, xy: LcdXY, size: LcdSize) -> io::Result<()> {
308 let adjust_x = 1;
309 let adjust_y = 2;
310
311 let x1 = ((xy.x & 0xff) + adjust_x) as u8;
312 let x2 = (((xy.x + size.width) + adjust_x - 1) & 0xff) as u8;
313 let y1 = ((xy.y & 0xff) + adjust_y) as u8;
314 let y2 = (((xy.y + size.height) + adjust_y - 1) & 0xff) as u8;
315
316 self.lcd_write_reg(&[0x2a])?;
317 self.lcd_write_data(&[0x00, x1, 0x00, x2])?;
318
319 self.lcd_write_reg(&[0x2b])?;
320 self.lcd_write_data(&[0x00, y1, 0x00, y2])?;
321
322 self.lcd_write_reg(&[0x2c])?;
323
324 Ok(())
325 }
326}
327
328impl Drop for ST7735SLcd {
329 fn drop(&mut self) {
330 self.lcd_bl.write(Level::Low);
331 }
332}
333
334impl Lcd for ST7735SLcd {
335 type Pixel = RGB565Pixel;
336
337 fn info(&self) -> (LcdSize, usize) {
338 (self.size_pixels, 2)
339 }
340
341 fn encode(&self, rgb: RGB) -> Self::Pixel {
342 let rgb = (u16::from(rgb.0), u16::from(rgb.1), u16::from(rgb.2));
343
344 let pixel: u16 = ((rgb.0 >> 3) << 11) | ((rgb.1 >> 2) << 5) | (rgb.2 >> 3);
345
346 let high = (pixel >> 8) as u8;
347 let low = (pixel & 0xff) as u8;
348 RGB565Pixel([high, low])
349 }
350
351 fn set_data(&mut self, x1y1: LcdXY, x2y2: LcdXY, data: &[u8]) -> io::Result<()> {
352 let (xy, size) = to_xy_size(x1y1, x2y2);
353 self.lcd_set_window(xy, size)?;
354 self.lcd_write_data(data)
355 }
356}
357
358pub struct ST7735SConsole {
360 _gpio: Gpio,
363
364 inner: GraphicsConsole<ST7735SInput, BufferedLcd<ST7735SLcd>>,
367}
368
369#[async_trait(?Send)]
370impl Console for ST7735SConsole {
371 fn clear(&mut self, how: ClearType) -> io::Result<()> {
372 self.inner.clear(how)
373 }
374
375 fn color(&self) -> (Option<u8>, Option<u8>) {
376 self.inner.color()
377 }
378
379 fn set_color(&mut self, fg: Option<u8>, bg: Option<u8>) -> io::Result<()> {
380 self.inner.set_color(fg, bg)
381 }
382
383 fn enter_alt(&mut self) -> io::Result<()> {
384 self.inner.enter_alt()
385 }
386
387 fn hide_cursor(&mut self) -> io::Result<()> {
388 self.inner.hide_cursor()
389 }
390
391 fn is_interactive(&self) -> bool {
392 self.inner.is_interactive()
393 }
394
395 fn leave_alt(&mut self) -> io::Result<()> {
396 self.inner.leave_alt()
397 }
398
399 fn locate(&mut self, pos: CharsXY) -> io::Result<()> {
400 self.inner.locate(pos)
401 }
402
403 fn move_within_line(&mut self, off: i16) -> io::Result<()> {
404 self.inner.move_within_line(off)
405 }
406
407 fn print(&mut self, text: &str) -> io::Result<()> {
408 self.inner.print(text)
409 }
410
411 async fn poll_key(&mut self) -> io::Result<Option<Key>> {
412 self.inner.poll_key().await
413 }
414
415 async fn read_key(&mut self) -> io::Result<Key> {
416 self.inner.read_key().await
417 }
418
419 fn show_cursor(&mut self) -> io::Result<()> {
420 self.inner.show_cursor()
421 }
422
423 fn size_chars(&self) -> io::Result<CharsXY> {
424 self.inner.size_chars()
425 }
426
427 fn size_pixels(&self) -> io::Result<SizeInPixels> {
428 self.inner.size_pixels()
429 }
430
431 fn write(&mut self, text: &str) -> io::Result<()> {
432 self.inner.write(text)
433 }
434
435 fn draw_circle(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> {
436 self.inner.draw_circle(center, radius)
437 }
438
439 fn draw_circle_filled(&mut self, center: PixelsXY, radius: u16) -> io::Result<()> {
440 self.inner.draw_circle_filled(center, radius)
441 }
442
443 fn draw_line(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
444 self.inner.draw_line(x1y1, x2y2)
445 }
446
447 fn draw_pixel(&mut self, xy: PixelsXY) -> io::Result<()> {
448 self.inner.draw_pixel(xy)
449 }
450
451 fn draw_rect(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
452 self.inner.draw_rect(x1y1, x2y2)
453 }
454
455 fn draw_rect_filled(&mut self, x1y1: PixelsXY, x2y2: PixelsXY) -> io::Result<()> {
456 self.inner.draw_rect_filled(x1y1, x2y2)
457 }
458
459 fn sync_now(&mut self) -> io::Result<()> {
460 self.inner.sync_now()
461 }
462
463 fn set_sync(&mut self, enabled: bool) -> io::Result<bool> {
464 self.inner.set_sync(enabled)
465 }
466}
467
468pub fn new_st7735s_console(signals_tx: Sender<Signal>) -> io::Result<ST7735SConsole> {
470 let mut gpio = Gpio::new().map_err(gpio_error_to_io_error)?;
471
472 let lcd = ST7735SLcd::new(&mut gpio)?;
473 let input = ST7735SInput::new(&mut gpio, signals_tx)?;
474 let lcd = BufferedLcd::new(lcd);
475 let inner = GraphicsConsole::new(input, lcd)?;
476 Ok(ST7735SConsole { _gpio: gpio, inner })
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482
483 #[test]
484 fn test_query_spi_bufsiz_with_newline() {
485 let tempdir = tempfile::tempdir().unwrap();
486 let tempfile = tempdir.path().join("bufsiz");
487 fs::write(&tempfile, "1024\n").unwrap();
488 assert_eq!(1024, query_spi_bufsiz(Some(&tempfile)).unwrap());
489 }
490
491 #[test]
492 fn test_query_spi_bufsiz_without_newline() {
493 let tempdir = tempfile::tempdir().unwrap();
494 let tempfile = tempdir.path().join("bufsiz");
495 fs::write(&tempfile, "4096").unwrap();
496 assert_eq!(4096, query_spi_bufsiz(Some(&tempfile)).unwrap());
497 }
498}