1use embedded_hal::digital::OutputPin;
14use embedded_hal_async::delay::DelayNs;
15use embedded_hal_async::spi::SpiDevice;
16
17pub mod cmd {
19 #![allow(missing_docs)]
20 pub const SWRESET: u8 = 0x01;
21 pub const SLPIN: u8 = 0x10;
22 pub const SLPOUT: u8 = 0x11;
23 pub const NORON: u8 = 0x13;
24 pub const INVOFF: u8 = 0x20;
25 pub const INVON: u8 = 0x21;
26 pub const DISPOFF: u8 = 0x28;
27 pub const DISPON: u8 = 0x29;
28 pub const CASET: u8 = 0x2A;
29 pub const RASET: u8 = 0x2B;
30 pub const RAMWR: u8 = 0x2C;
31 pub const MADCTL: u8 = 0x36;
32 pub const IDMOFF: u8 = 0x38;
33 pub const COLMOD: u8 = 0x3A;
34}
35
36pub mod madctl {
38 #![allow(missing_docs)]
39 pub const MY: u8 = 0x80;
40 pub const MX: u8 = 0x40;
41 pub const MV: u8 = 0x20;
42 pub const ML: u8 = 0x10;
43 pub const BGR: u8 = 0x08;
44 pub const MH: u8 = 0x04;
45}
46
47const COLMOD_RGB565: u8 = 0x55;
49
50#[derive(Clone, Copy, Debug)]
52pub struct InitStep {
53 pub cmd: u8,
55 pub params: &'static [u8],
57 pub delay_ms: u16,
59}
60
61impl InitStep {
62 const fn new(cmd: u8, params: &'static [u8]) -> Self {
63 Self {
64 cmd,
65 params,
66 delay_ms: 0,
67 }
68 }
69}
70
71pub const M5_ST7789V2_INIT: &[InitStep] = &[
76 InitStep::new(0xB7, &[0x35]), InitStep::new(0xBB, &[0x28]), InitStep::new(0xC0, &[0x0C]), InitStep::new(0xC2, &[0x01, 0xFF]), InitStep::new(0xC3, &[0x10]), InitStep::new(0xC4, &[0x20]), InitStep::new(0xD0, &[0xA4, 0xA1]), InitStep::new(0xB0, &[0x00, 0xC0]), InitStep::new(
85 0xE0, &[
87 0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32, 0x44, 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17,
88 ],
89 ),
90 InitStep::new(
91 0xE1, &[
93 0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31, 0x54, 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E,
94 ],
95 ),
96 InitStep {
97 cmd: cmd::SLPOUT,
98 params: &[],
99 delay_ms: 130,
100 },
101 InitStep::new(cmd::IDMOFF, &[]),
102];
103
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106#[cfg_attr(feature = "defmt", derive(defmt::Format))]
107pub enum Rotation {
108 Deg0,
110 Deg90,
112 Deg180,
114 Deg270,
116}
117
118#[derive(Clone, Copy, Debug, PartialEq, Eq)]
120pub struct Panel {
121 pub width: u16,
123 pub height: u16,
125 pub offset_x: u16,
127 pub offset_y: u16,
129 pub memory_width: u16,
131 pub memory_height: u16,
133 pub bgr: bool,
135 pub invert: bool,
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141#[cfg_attr(feature = "defmt", derive(defmt::Format))]
142pub struct Geometry {
143 pub width: u16,
145 pub height: u16,
147 pub col_start: u16,
149 pub row_start: u16,
151 pub madctl: u8,
153}
154
155impl Panel {
156 pub const fn geometry(&self, rotation: Rotation) -> Geometry {
162 use madctl::*;
163 let (swap, mirror_col, mirror_row, mut bits) = match rotation {
164 Rotation::Deg0 => (false, false, false, 0),
165 Rotation::Deg90 => (true, false, true, MV | MX | MH),
166 Rotation::Deg180 => (false, true, true, MX | MH | MY | ML),
167 Rotation::Deg270 => (true, true, false, MV | MY | ML),
168 };
169 if self.bgr {
170 bits |= BGR;
171 }
172 let (w, h, ox, oy, mw, mh) = if swap {
173 (
174 self.height,
175 self.width,
176 self.offset_y,
177 self.offset_x,
178 self.memory_height,
179 self.memory_width,
180 )
181 } else {
182 (
183 self.width,
184 self.height,
185 self.offset_x,
186 self.offset_y,
187 self.memory_width,
188 self.memory_height,
189 )
190 };
191 Geometry {
192 width: w,
193 height: h,
194 col_start: if mirror_col { mw - (w + ox) } else { ox },
195 row_start: if mirror_row { mh - (h + oy) } else { oy },
196 madctl: bits,
197 }
198 }
199}
200
201#[derive(Clone, Copy, Debug, PartialEq, Eq)]
203#[cfg_attr(feature = "defmt", derive(defmt::Format))]
204pub enum Error<S, P> {
205 Spi(S),
207 Pin(P),
209}
210
211pub async fn hard_reset<P: OutputPin, D: DelayNs>(
213 rst: &mut P,
214 delay: &mut D,
215) -> Result<(), P::Error> {
216 rst.set_low()?;
217 delay.delay_us(20).await;
218 rst.set_high()?;
219 delay.delay_ms(120).await;
221 Ok(())
222}
223
224pub struct St7789<SPI, DC> {
226 spi: SPI,
227 dc: DC,
228 panel: Panel,
229 geometry: Geometry,
230}
231
232type LcdResult<T, SPI, DC> = core::result::Result<
233 T,
234 Error<
235 <SPI as embedded_hal_async::spi::ErrorType>::Error,
236 <DC as embedded_hal::digital::ErrorType>::Error,
237 >,
238>;
239
240impl<SPI: SpiDevice, DC: OutputPin> St7789<SPI, DC> {
241 pub const fn new(spi: SPI, dc: DC, panel: Panel, rotation: Rotation) -> Self {
243 Self {
244 spi,
245 dc,
246 panel,
247 geometry: panel.geometry(rotation),
248 }
249 }
250
251 pub async fn init(
254 &mut self,
255 script: &[InitStep],
256 delay: &mut impl DelayNs,
257 ) -> LcdResult<(), SPI, DC> {
258 for step in script {
259 self.command(step.cmd, step.params).await?;
260 if step.delay_ms > 0 {
261 delay.delay_ms(u32::from(step.delay_ms)).await;
262 }
263 }
264 self.command(cmd::COLMOD, &[COLMOD_RGB565]).await?;
265 self.command(cmd::MADCTL, &[self.geometry.madctl]).await?;
266 let inv = if self.panel.invert {
267 cmd::INVON
268 } else {
269 cmd::INVOFF
270 };
271 self.command(inv, &[]).await?;
272 self.command(cmd::NORON, &[]).await?;
273 self.command(cmd::DISPON, &[]).await
274 }
275
276 pub const fn geometry(&self) -> Geometry {
278 self.geometry
279 }
280
281 pub async fn set_rotation(&mut self, rotation: Rotation) -> LcdResult<(), SPI, DC> {
283 self.geometry = self.panel.geometry(rotation);
284 self.command(cmd::MADCTL, &[self.geometry.madctl]).await
285 }
286
287 pub async fn command(&mut self, cmd: u8, params: &[u8]) -> LcdResult<(), SPI, DC> {
289 self.dc.set_low().map_err(Error::Pin)?;
290 self.spi.write(&[cmd]).await.map_err(Error::Spi)?;
291 if !params.is_empty() {
292 self.dc.set_high().map_err(Error::Pin)?;
293 self.spi.write(params).await.map_err(Error::Spi)?;
294 }
295 Ok(())
296 }
297
298 pub async fn set_window(
302 &mut self,
303 x0: u16,
304 y0: u16,
305 x1: u16,
306 y1: u16,
307 ) -> LcdResult<(), SPI, DC> {
308 let g = self.geometry;
309 let [a, b] = (x0 + g.col_start).to_be_bytes();
310 let [c, d] = (x1 + g.col_start).to_be_bytes();
311 self.command(cmd::CASET, &[a, b, c, d]).await?;
312 let [a, b] = (y0 + g.row_start).to_be_bytes();
313 let [c, d] = (y1 + g.row_start).to_be_bytes();
314 self.command(cmd::RASET, &[a, b, c, d]).await
315 }
316
317 pub async fn write_pixels(&mut self, bytes: &[u8]) -> LcdResult<(), SPI, DC> {
319 self.command(cmd::RAMWR, &[]).await?;
320 self.dc.set_high().map_err(Error::Pin)?;
321 self.spi.write(bytes).await.map_err(Error::Spi)
322 }
323
324 pub async fn write_rows(&mut self, y: u16, bytes: &[u8]) -> LcdResult<(), SPI, DC> {
327 let row_bytes = usize::from(self.geometry.width) * 2;
328 debug_assert!(row_bytes > 0 && bytes.len().is_multiple_of(row_bytes));
329 let rows = (bytes.len() / row_bytes) as u16;
330 if rows == 0 {
331 return Ok(());
332 }
333 self.set_window(0, y, self.geometry.width - 1, y + rows - 1)
334 .await?;
335 self.write_pixels(bytes).await
336 }
337
338 pub async fn set_display_on(&mut self, on: bool) -> LcdResult<(), SPI, DC> {
340 self.command(if on { cmd::DISPON } else { cmd::DISPOFF }, &[])
341 .await
342 }
343
344 pub async fn set_sleep(
347 &mut self,
348 sleep: bool,
349 delay: &mut impl DelayNs,
350 ) -> LcdResult<(), SPI, DC> {
351 self.command(if sleep { cmd::SLPIN } else { cmd::SLPOUT }, &[])
352 .await?;
353 delay.delay_ms(120).await;
354 Ok(())
355 }
356
357 pub async fn soft_reset(&mut self, delay: &mut impl DelayNs) -> LcdResult<(), SPI, DC> {
359 self.command(cmd::SWRESET, &[]).await?;
360 delay.delay_ms(150).await;
361 Ok(())
362 }
363
364 pub fn release(self) -> (SPI, DC) {
366 (self.spi, self.dc)
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 extern crate std;
373 use super::*;
374 use crate::test_util::block_on;
375 use core::cell::RefCell;
376 use core::convert::Infallible;
377 use embedded_hal_async::spi::Operation;
378 use std::rc::Rc;
379 use std::vec::Vec;
380
381 const CARDPUTER: Panel = Panel {
383 width: 135,
384 height: 240,
385 offset_x: 52,
386 offset_y: 40,
387 memory_width: 240,
388 memory_height: 320,
389 bgr: true,
390 invert: true,
391 };
392
393 #[test]
394 fn cardputer_geometry_per_rotation() {
395 let table = [
396 (Rotation::Deg0, (135, 240, 52, 40, 0x08)),
397 (Rotation::Deg90, (240, 135, 40, 53, 0x6C)),
398 (Rotation::Deg180, (135, 240, 53, 40, 0xDC)),
399 (Rotation::Deg270, (240, 135, 40, 52, 0xB8)),
400 ];
401 for (r, expected) in table {
402 let g = CARDPUTER.geometry(r);
403 assert_eq!(
404 (g.width, g.height, g.col_start, g.row_start, g.madctl),
405 expected,
406 "{r:?}"
407 );
408 let (mw, mh) = if g.madctl & madctl::MV != 0 {
409 (320, 240)
410 } else {
411 (240, 320)
412 };
413 assert!(g.col_start + g.width <= mw, "{r:?} overflows columns");
414 assert!(g.row_start + g.height <= mh, "{r:?} overflows rows");
415 }
416 }
417
418 type WriteLog = Rc<RefCell<Vec<(bool, Vec<u8>)>>>;
420
421 #[derive(Clone, Default)]
423 struct Bus {
424 dc: Rc<RefCell<bool>>,
425 log: WriteLog,
426 }
427 struct Dc(Rc<RefCell<bool>>);
428
429 impl embedded_hal::digital::ErrorType for Dc {
430 type Error = Infallible;
431 }
432 impl OutputPin for Dc {
433 fn set_low(&mut self) -> Result<(), Infallible> {
434 *self.0.borrow_mut() = false;
435 Ok(())
436 }
437 fn set_high(&mut self) -> Result<(), Infallible> {
438 *self.0.borrow_mut() = true;
439 Ok(())
440 }
441 }
442 impl embedded_hal_async::spi::ErrorType for Bus {
443 type Error = Infallible;
444 }
445 impl SpiDevice for Bus {
446 async fn transaction(&mut self, ops: &mut [Operation<'_, u8>]) -> Result<(), Infallible> {
447 for op in ops {
448 if let Operation::Write(bytes) = op {
449 self.log
450 .borrow_mut()
451 .push((*self.dc.borrow(), bytes.to_vec()));
452 }
453 }
454 Ok(())
455 }
456 }
457
458 #[test]
459 fn write_rows_sets_offset_window_then_streams_data() {
460 let bus = Bus::default();
461 let dc = Dc(bus.dc.clone());
462 let mut lcd = St7789::new(bus.clone(), dc, CARDPUTER, Rotation::Deg90);
463 let rows = [0xAB; 240 * 2 * 2];
464 block_on(lcd.write_rows(10, &rows)).unwrap();
465
466 let log = bus.log.borrow();
467 let expected: [(bool, &[u8]); 6] = [
468 (false, &[cmd::CASET]),
469 (true, &[0, 40, 1, 23]), (false, &[cmd::RASET]),
471 (true, &[0, 63, 0, 64]), (false, &[cmd::RAMWR]),
473 (true, &rows),
474 ];
475 assert_eq!(log.len(), expected.len());
476 for (got, want) in log.iter().zip(expected) {
477 assert_eq!((got.0, got.1.as_slice()), want);
478 }
479 }
480}