1#![no_std]
2
3use embedded_hal::delay::DelayNs;
29use embedded_hal::digital::OutputPin;
30
31use display_interface::{DataFormat, WriteOnlyDataCommand};
32
33use embedded_graphics_core::pixelcolor::{IntoStorage, Rgb565, Rgb666};
34use embedded_graphics_core::prelude::RgbColor;
35
36mod graphics_core;
37mod rgb111;
38pub use crate::rgb111::*;
39pub use display_interface::DisplayError;
40
41type Result<T = (), E = DisplayError> = core::result::Result<T, E>;
42
43pub trait DisplaySize {
45 const WIDTH: usize;
47 const HEIGHT: usize;
49}
50
51pub struct DisplaySize320x480;
53
54impl DisplaySize for DisplaySize320x480 {
55 const WIDTH: usize = 320;
56 const HEIGHT: usize = 480;
57}
58
59pub trait Ili9488PixelFormat: Copy + Clone {
61 const DATA: u8;
63}
64
65#[derive(Copy, Clone)]
67pub struct Rgb111Mode;
68
69impl Ili9488PixelFormat for Rgb111Mode {
70 const DATA: u8 = 0x1;
71}
72#[derive(Copy, Clone)]
74pub struct Rgb565Mode;
75
76impl Ili9488PixelFormat for Rgb565Mode {
77 const DATA: u8 = 0x55;
78}
79#[derive(Copy, Clone)]
81pub struct Rgb666Mode;
82impl Ili9488PixelFormat for Rgb666Mode {
83 const DATA: u8 = 0x66;
84}
85
86pub trait Ili9488MemoryWrite {
88 type PixelFormat: RgbColor;
89 fn write_iter<I: IntoIterator<Item = Self::PixelFormat>>(&mut self, data: I) -> Result;
90 fn write_slice(&mut self, data: &[Self::PixelFormat]) -> Result;
91}
92
93pub trait Mode {
99 fn mode(&self) -> u8;
100
101 fn is_landscape(&self) -> bool;
102}
103
104pub enum Orientation {
107 Portrait,
108 PortraitFlipped,
109 Landscape,
110 LandscapeFlipped,
111}
112
113impl Mode for Orientation {
114 fn mode(&self) -> u8 {
115 match self {
116 Self::Portrait => 0x40 | 0x08,
117 Self::Landscape => 0x20 | 0x08,
118 Self::PortraitFlipped => 0x80 | 0x08,
119 Self::LandscapeFlipped => 0x40 | 0x80 | 0x20 | 0x08,
120 }
121 }
122
123 fn is_landscape(&self) -> bool {
124 match self {
125 Self::Landscape | Self::LandscapeFlipped => true,
126 Self::Portrait | Self::PortraitFlipped => false,
127 }
128 }
129}
130
131pub enum ModeState {
133 On,
134 Off,
135}
136
137pub struct Ili9488<IFACE, RESET, PixelFormat> {
153 interface: IFACE,
154 reset: RESET,
155 width: usize,
156 height: usize,
157 landscape: bool,
158 _pixel_format: PixelFormat,
159}
160
161impl<IFACE, RESET, PixelFormat> Ili9488<IFACE, RESET, PixelFormat>
162where
163 IFACE: WriteOnlyDataCommand,
164 RESET: OutputPin,
165 PixelFormat: Ili9488PixelFormat,
166{
167 pub fn new<DELAY, MODE>(
168 interface: IFACE,
169 reset: RESET,
170 delay: &mut DELAY,
171 orientation: MODE,
172 pixel_format: PixelFormat,
173 ) -> Result<Self>
174 where
175 DELAY: DelayNs,
176 MODE: Mode,
177 {
178 let mut ili9488 = Self {
179 interface,
180 reset,
181 width: DisplaySize320x480::WIDTH,
182 height: DisplaySize320x480::HEIGHT,
183 landscape: false,
184 _pixel_format: pixel_format,
185 };
186
187 ili9488.command(Command::NOP, &[])?;
189
190 ili9488
191 .reset
192 .set_high()
193 .map_err(|_| DisplayError::RSError)?;
194 delay.delay_ms(5);
195
196 ili9488.reset.set_low().map_err(|_| DisplayError::RSError)?;
198 let _ = delay.delay_ms(20);
199
200 ili9488
202 .reset
203 .set_high()
204 .map_err(|_| DisplayError::RSError)?;
205
206 let _ = delay.delay_ms(150);
208
209 ili9488.command(Command::SoftwareReset, &[])?;
211
212 let _ = delay.delay_ms(150);
215
216 ili9488.command(
220 Command::PositiveGammaControl,
221 &[
222 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A,
223 0x0F,
224 ],
225 )?;
226
227 ili9488.command(
229 Command::NegativeGammaControl,
230 &[
231 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37,
232 0x0F,
233 ],
234 )?;
235
236 ili9488.command(Command::PowerControl1, &[0x17, 0x15])?;
237
238 ili9488.command(Command::PowerControl2, &[0x41])?;
239
240 ili9488.command(Command::VCOMControl, &[0x00, 0x12, 0x80])?;
241
242 ili9488.command(Command::MemoryAccessControl, &[0x48])?; ili9488.command(Command::PixelFormatSet, &[PixelFormat::DATA])?;
245
246 ili9488.command(Command::InterfaceModeControl, &[0x00])?;
247
248 ili9488.command(Command::NormalModeFrameRate, &[0xA0])?;
249
250 ili9488.command(Command::DisplayInversionControl, &[0x02])?;
251
252 ili9488.command(Command::DisplayFunctionControl, &[0x02, 0x02, 0x3B])?;
253
254 ili9488.command(Command::EntryModeSet, &[0xC6])?;
255
256 ili9488.command(Command::AdjustControl3, &[0xA9, 0x51, 0x2C, 0x82])?;
257
258 ili9488.sleep_mode(ModeState::Off)?;
259
260 ili9488.set_orientation(orientation)?;
261
262 ili9488.display_mode(ModeState::On)?;
263
264 Ok(ili9488)
265 }
266}
267
268impl<IFACE, RESET, PixelFormat> Ili9488<IFACE, RESET, PixelFormat>
269where
270 IFACE: WriteOnlyDataCommand,
271 PixelFormat: Ili9488PixelFormat,
272{
273 pub fn change_pixel_format<P: Ili9488PixelFormat>(
274 mut self,
275 pixel_format: P,
276 ) -> Result<Ili9488<IFACE, RESET, P>> {
277 self.command(Command::PixelFormatSet, &[P::DATA])?;
278
279 Ok(Ili9488 {
280 interface: self.interface,
281 reset: self.reset,
282 width: self.width,
283 height: self.height,
284 landscape: self.landscape,
285 _pixel_format: pixel_format,
286 })
287 }
288 fn command(&mut self, cmd: Command, args: &[u8]) -> Result {
289 self.interface.send_commands(DataFormat::U8(&[cmd as u8]))?;
290 self.interface.send_data(DataFormat::U8(args))
291 }
292
293 fn set_window(&mut self, x0: u16, y0: u16, x1: u16, y1: u16) -> Result {
294 self.command(
295 Command::ColumnAddressSet,
296 &[
297 (x0 >> 8) as u8,
298 (x0 & 0xff) as u8,
299 (x1 >> 8) as u8,
300 (x1 & 0xff) as u8,
301 ],
302 )?;
303 self.command(
304 Command::PageAddressSet,
305 &[
306 (y0 >> 8) as u8,
307 (y0 & 0xff) as u8,
308 (y1 >> 8) as u8,
309 (y1 & 0xff) as u8,
310 ],
311 )
312 }
313
314 pub fn configure_vertical_scroll(
316 &mut self,
317 fixed_top_lines: u16,
318 fixed_bottom_lines: u16,
319 ) -> Result<Scroller> {
320 let height = if self.landscape {
321 self.width
322 } else {
323 self.height
324 } as u16;
325 let scroll_lines = height as u16 - fixed_top_lines - fixed_bottom_lines;
326
327 self.command(
328 Command::VerticalScrollDefine,
329 &[
330 (fixed_top_lines >> 8) as u8,
331 (fixed_top_lines & 0xff) as u8,
332 (scroll_lines >> 8) as u8,
333 (scroll_lines & 0xff) as u8,
334 (fixed_bottom_lines >> 8) as u8,
335 (fixed_bottom_lines & 0xff) as u8,
336 ],
337 )?;
338
339 Ok(Scroller::new(fixed_top_lines, fixed_bottom_lines, height))
340 }
341
342 pub fn scroll_vertically(&mut self, scroller: &mut Scroller, num_lines: u16) -> Result {
343 scroller.top_offset += num_lines;
344 if scroller.top_offset > (scroller.height - scroller.fixed_bottom_lines) {
345 scroller.top_offset = scroller.fixed_top_lines
346 + (scroller.top_offset + scroller.fixed_bottom_lines - scroller.height)
347 }
348
349 self.command(
350 Command::VerticalScrollAddr,
351 &[
352 (scroller.top_offset >> 8) as u8,
353 (scroller.top_offset & 0xff) as u8,
354 ],
355 )
356 }
357
358 pub fn set_orientation<MODE>(&mut self, orientation: MODE) -> Result
360 where
361 MODE: Mode,
362 {
363 self.command(Command::MemoryAccessControl, &[orientation.mode()])?;
364
365 if self.landscape ^ orientation.is_landscape() {
366 core::mem::swap(&mut self.height, &mut self.width);
367 }
368 self.landscape = orientation.is_landscape();
369 Ok(())
370 }
371
372 pub fn sleep_mode(&mut self, mode: ModeState) -> Result {
374 match mode {
375 ModeState::On => self.command(Command::SleepModeOn, &[]),
376 ModeState::Off => self.command(Command::SleepModeOff, &[]),
377 }
378 }
379
380 pub fn display_mode(&mut self, mode: ModeState) -> Result {
382 match mode {
383 ModeState::On => self.command(Command::DisplayOn, &[]),
384 ModeState::Off => self.command(Command::DisplayOff, &[]),
385 }
386 }
387
388 pub fn invert_mode(&mut self, mode: ModeState) -> Result {
390 match mode {
391 ModeState::On => self.command(Command::InvertOn, &[]),
392 ModeState::Off => self.command(Command::InvertOff, &[]),
393 }
394 }
395
396 pub fn idle_mode(&mut self, mode: ModeState) -> Result {
398 match mode {
399 ModeState::On => self.command(Command::IdleModeOn, &[]),
400 ModeState::Off => self.command(Command::IdleModeOff, &[]),
401 }
402 }
403
404 pub fn brightness(&mut self, brightness: u8) -> Result {
406 self.command(Command::SetBrightness, &[brightness])
407 }
408
409 pub fn content_adaptive_brightness(&mut self, value: AdaptiveBrightness) -> Result {
411 self.command(Command::ContentAdaptiveBrightness, &[value as _])
412 }
413
414 pub fn normal_mode_frame_rate(
416 &mut self,
417 clk_div: FrameRateClockDivision,
418 frame_rate: FrameRate,
419 ) -> Result {
420 self.command(
421 Command::NormalModeFrameRate,
422 &[clk_div as _, frame_rate as _],
423 )
424 }
425
426 pub fn idle_mode_frame_rate(
428 &mut self,
429 clk_div: FrameRateClockDivision,
430 frame_rate: FrameRate,
431 ) -> Result {
432 self.command(Command::IdleModeFrameRate, &[clk_div as _, frame_rate as _])
433 }
434}
435
436impl<IFACE, RESET> Ili9488MemoryWrite for Ili9488<IFACE, RESET, Rgb666Mode>
437where
438 IFACE: WriteOnlyDataCommand,
439{
440 type PixelFormat = Rgb666;
441
442 fn write_iter<I: IntoIterator<Item = Self::PixelFormat>>(&mut self, data: I) -> Result {
443 self.command(Command::MemoryWrite, &[])?;
444 for color in data {
445 self.interface.send_data(DataFormat::U8(&[
446 color.r() << 2,
447 color.g() << 2,
448 color.b() << 2,
449 ]))?;
450 }
451 Ok(())
452 }
453 fn write_slice(&mut self, data: &[Self::PixelFormat]) -> Result {
454 self.command(Command::MemoryWrite, &[])?;
455 for color in data {
456 self.interface.send_data(DataFormat::U8(&[
457 color.r() << 2,
458 color.g() << 2,
459 color.b() << 2,
460 ]))?;
461 }
462 Ok(())
463 }
464}
465impl<IFACE, RESET> Ili9488MemoryWrite for Ili9488<IFACE, RESET, Rgb565Mode>
466where
467 IFACE: WriteOnlyDataCommand,
468{
469 type PixelFormat = Rgb565;
470
471 fn write_iter<I: IntoIterator<Item = Self::PixelFormat>>(&mut self, data: I) -> Result {
472 self.command(Command::MemoryWrite, &[])?;
473 use DataFormat::U16BEIter;
474 self.interface
475 .send_data(U16BEIter(&mut data.into_iter().map(|c| c.into_storage())))
476 }
477 fn write_slice(&mut self, data: &[Self::PixelFormat]) -> Result {
478 self.command(Command::MemoryWrite, &[])?;
479 self.interface.send_data(DataFormat::U16BEIter(
480 &mut data.into_iter().map(|c| c.into_storage()),
481 ))
482 }
483}
484impl<IFACE, RESET> Ili9488MemoryWrite for Ili9488<IFACE, RESET, Rgb111Mode>
485where
486 IFACE: WriteOnlyDataCommand,
487{
488 type PixelFormat = Rgb111;
489 fn write_iter<I: IntoIterator<Item = Self::PixelFormat>>(&mut self, data: I) -> Result {
491 self.command(Command::MemoryWrite, &[])?;
492
493 let mut data = data.into_iter();
494 while let Some(p1) = data.next() {
495 self.interface
496 .send_data(DataFormat::U8(&[(p1.into_storage() << 3)
497 | (data.next().map(|p| p.into_storage()).unwrap_or_default())]))?;
498 }
499 Ok(())
500 }
501 fn write_slice(&mut self, data: &[Self::PixelFormat]) -> Result {
502 self.command(Command::MemoryWrite, &[])?;
503 self.interface
504 .send_data(DataFormat::U8Iter(&mut data.chunks(2).map(|pixels| {
505 (pixels[0].raw() << 3) | pixels.get(1).map(|p| p.into_storage()).unwrap_or_default()
506 })))?;
507 Ok(())
508 }
509}
510
511impl<IFACE, RESET> Ili9488<IFACE, RESET, Rgb666Mode>
512where
513 IFACE: WriteOnlyDataCommand,
514{
515 pub fn draw_rgb565_image(&mut self, x0: u16, y0: u16, width: u16, data: &[u16]) -> Result {
522 self.set_window(
523 x0,
524 y0,
525 x0 + width - 1,
526 y0 + (data.len() / width as usize) as u16 - 1,
527 )?;
528 self.write_iter(data.iter().map(|c| {
529 Rgb666::new(
530 ((c & 0xF800) >> 10) as u8,
531 ((c & 0x07E0) >> 5) as u8,
532 (c & 0x001F << 1) as u8,
533 )
534 }))
535 }
536 pub fn draw_upscaled_rgb565_image(
547 &mut self,
548 x0: u16,
549 y0: u16,
550 original_width: u16,
551 screen_width: u16,
552 data: &[u16],
553 ) -> Result {
554 let ratio = screen_width / original_width;
555 let screen_height = (data.len() / original_width as usize) as u16 * ratio;
556 self.set_window(x0, y0, x0 + screen_width - 1, y0 + screen_height - 1)?;
557 self.command(Command::MemoryWrite, &[])?;
558 for line in data.chunks_exact(original_width as usize) {
562 for _ in 0..ratio {
563 for pixel in line {
564 for _ in 0..ratio {
565 self.interface.send_data(DataFormat::U8(&[
567 ((pixel & 0xF800) >> 8) as u8,
568 ((pixel & 0x07E0) >> 3) as u8,
569 (pixel & 0x001F << 3) as u8,
570 ]))?;
571 }
572 }
573 }
574 }
575 Ok(())
576 }
577}
578impl<IFACE, RESET, PixelFormat> Ili9488<IFACE, RESET, PixelFormat>
579where
580 Self: Ili9488MemoryWrite,
581 IFACE: WriteOnlyDataCommand,
582 PixelFormat: Ili9488PixelFormat,
583{
584 pub fn draw_raw_iter<
585 I: IntoIterator<
586 Item = <Ili9488<IFACE, RESET, PixelFormat> as Ili9488MemoryWrite>::PixelFormat,
587 >,
588 >(
589 &mut self,
590 x0: u16,
591 y0: u16,
592 x1: u16,
593 y1: u16,
594 data: I,
595 ) -> Result {
596 self.set_window(x0, y0, x1, y1)?;
597 self.write_iter(data)
598 }
599 pub fn draw_raw_slice(
607 &mut self,
608 x0: u16,
609 y0: u16,
610 x1: u16,
611 y1: u16,
612 data: &[<Ili9488<IFACE, RESET, PixelFormat> as Ili9488MemoryWrite>::PixelFormat],
613 ) -> Result {
614 self.set_window(x0, y0, x1, y1)?;
615 self.write_slice(data)
616 }
617 pub fn clear_screen(
619 &mut self,
620 color: <Ili9488<IFACE, RESET, PixelFormat> as Ili9488MemoryWrite>::PixelFormat,
621 ) -> Result {
622 let color = core::iter::repeat(color).take(self.width * self.height);
623 self.draw_raw_iter(0, 0, self.width as u16, self.height as u16, color)
624 }
625 pub fn clear_screen_fast(&mut self, color: Rgb111) -> Result {
627 if PixelFormat::DATA != Rgb111Mode::DATA {
629 self.command(Command::PixelFormatSet, &[Rgb111Mode::DATA])?;
630 }
631
632 let color = (color.into_storage() << 3) | color.into_storage();
634 let mut data = core::iter::repeat(color).take(self.width * self.height / 2);
635
636 self.set_window(0, 0, self.width as u16, self.height as u16)?;
637 self.command(Command::MemoryWrite, &[])?;
638 self.interface.send_data(DataFormat::U8Iter(&mut data))?;
639
640 if PixelFormat::DATA != Rgb111Mode::DATA {
642 self.command(Command::PixelFormatSet, &[PixelFormat::DATA])
643 } else {
644 Ok(())
645 }
646 }
647}
648
649impl<IFACE, RESET, PixelFormat> Ili9488<IFACE, RESET, PixelFormat> {
650 pub fn width(&self) -> usize {
652 self.width
653 }
654
655 pub fn height(&self) -> usize {
657 self.height
658 }
659 pub fn release(self) -> (IFACE, RESET) {
661 (self.interface, self.reset)
662 }
663}
664
665pub struct Scroller {
668 top_offset: u16,
669 fixed_bottom_lines: u16,
670 fixed_top_lines: u16,
671 height: u16,
672}
673
674impl Scroller {
675 fn new(fixed_top_lines: u16, fixed_bottom_lines: u16, height: u16) -> Scroller {
676 Scroller {
677 top_offset: fixed_top_lines,
678 fixed_top_lines,
679 fixed_bottom_lines,
680 height,
681 }
682 }
683}
684
685pub enum AdaptiveBrightness {
687 Off = 0x00,
688 UserInterfaceImage = 0x01,
689 StillPicture = 0x02,
690 MovingImage = 0x03,
691}
692
693pub enum FrameRate {
695 FrameRate119 = 0x10,
696 FrameRate112 = 0x11,
697 FrameRate106 = 0x12,
698 FrameRate100 = 0x13,
699 FrameRate95 = 0x14,
700 FrameRate90 = 0x15,
701 FrameRate86 = 0x16,
702 FrameRate83 = 0x17,
703 FrameRate79 = 0x18,
704 FrameRate76 = 0x19,
705 FrameRate73 = 0x1a,
706 FrameRate70 = 0x1b,
707 FrameRate68 = 0x1c,
708 FrameRate65 = 0x1d,
709 FrameRate63 = 0x1e,
710 FrameRate61 = 0x1f,
711}
712
713pub enum FrameRateClockDivision {
715 Fosc = 0x00,
716 FoscDiv2 = 0x01,
717 FoscDiv4 = 0x02,
718 FoscDiv8 = 0x03,
719}
720
721#[derive(Clone, Copy)]
722enum Command {
723 NOP = 0x00,
724 SoftwareReset = 0x01,
725 SleepModeOn = 0x10,
726 SleepModeOff = 0x11,
727 InvertOff = 0x20,
728 InvertOn = 0x21,
729 DisplayOff = 0x28,
730 DisplayOn = 0x29,
731 ColumnAddressSet = 0x2a,
732 PageAddressSet = 0x2b,
733 MemoryWrite = 0x2c,
734 VerticalScrollDefine = 0x33,
735 MemoryAccessControl = 0x36,
736 VerticalScrollAddr = 0x37,
737 IdleModeOff = 0x38,
738 IdleModeOn = 0x39,
739 PixelFormatSet = 0x3a,
740 SetBrightness = 0x51,
742 ContentAdaptiveBrightness = 0x55,
743 InterfaceModeControl = 0xb0,
744 NormalModeFrameRate = 0xb1,
745 IdleModeFrameRate = 0xb2,
746 DisplayInversionControl = 0xb4,
747 DisplayFunctionControl = 0xb6,
748 EntryModeSet = 0xb7,
749 PowerControl1 = 0xc0,
750 PowerControl2 = 0xc1,
751 VCOMControl = 0xc5,
752 PositiveGammaControl = 0xe0,
753 NegativeGammaControl = 0xe1,
754 AdjustControl3 = 0xf7,
755}