ili9488_rs/
lib.rs

1#![no_std]
2
3//! ILI9488 Display Driver
4//!
5//! ### Usage
6//!
7//! To control the display you need to set up:
8//!
9//! * Interface for communicating with display ([display-interface-spi crate] for SPI)
10//! * Configuration (reset pin, delay, orientation and size) for display
11//!
12//! ```ignore
13//! let iface = SPIInterface::new(spi_bus, cs);
14//!
15//! let mut display = Ili9488::new(
16//!     iface,
17//!     reset_gpio,
18//!     &mut delay,
19//!     Orientation::Landscape,
20//!     Rgb666Mode,
21//! )
22//! .unwrap();
23//!
24//! display.clear(Rgb666::RED).unwrap()
25//! ```
26//!
27//! [display-interface-spi crate]: https://crates.io/crates/display-interface-spi
28use 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
43/// Trait that defines display size information
44pub trait DisplaySize {
45    /// Width in pixels
46    const WIDTH: usize;
47    /// Height in pixels
48    const HEIGHT: usize;
49}
50
51/// Generic display size of 320x480 pixels
52pub struct DisplaySize320x480;
53
54impl DisplaySize for DisplaySize320x480 {
55    const WIDTH: usize = 320;
56    const HEIGHT: usize = 480;
57}
58
59/// Trait for Valid Pixel Formats for the ILI9488
60pub trait Ili9488PixelFormat: Copy + Clone {
61    /// The data used for the PixelFormatSet command
62    const DATA: u8;
63}
64
65/// 3 bpp
66#[derive(Copy, Clone)]
67pub struct Rgb111Mode;
68
69impl Ili9488PixelFormat for Rgb111Mode {
70    const DATA: u8 = 0x1;
71}
72/// 16 bpp
73#[derive(Copy, Clone)]
74pub struct Rgb565Mode;
75
76impl Ili9488PixelFormat for Rgb565Mode {
77    const DATA: u8 = 0x55;
78}
79/// 18 bpp
80#[derive(Copy, Clone)]
81pub struct Rgb666Mode;
82impl Ili9488PixelFormat for Rgb666Mode {
83    const DATA: u8 = 0x66;
84}
85
86/// Trait implementation for writing different pixel formats to the ili9488's memory
87pub 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
93/// For quite a few boards (ESP32-S2-Kaluga-1, M5Stack, M5Core2 and others),
94/// the ILI9488 initialization command arguments are slightly different
95///
96/// This trait provides the flexibility for users to define their own
97/// initialization command arguments suitable for the particular board they are using
98pub trait Mode {
99    fn mode(&self) -> u8;
100
101    fn is_landscape(&self) -> bool;
102}
103
104/// The default implementation of the Mode trait from above
105/// Should work for most (but not all) boards
106pub 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
131/// Specify state of specific mode of operation
132pub enum ModeState {
133    On,
134    Off,
135}
136
137/// The ILI9488 Driver
138///
139/// There are two method for drawing to the screen:
140/// [Ili9488::draw_raw_iter] and [Ili9488::draw_raw_slice]
141///
142/// The hardware makes it efficient to draw rectangles on the screen.
143///
144/// What happens is the following:
145///
146/// - A drawing window is prepared (with the 2 opposite corner coordinates)
147/// - The starting point for drawint is the top left corner of this window
148/// - Every pair of bytes received is intepreted as a pixel value in the current display format (Rgb111, Rgb565, etc.)
149/// - As soon as a pixel is received, an internal counter is incremented,
150///   and the next word will fill the next pixel (the adjacent on the right, or
151///   the first of the next row if the row ended)
152pub 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        // Put SPI bus in known state for TFT with CS tied low
188        ili9488.command(Command::NOP, &[])?;
189
190        ili9488
191            .reset
192            .set_high()
193            .map_err(|_| DisplayError::RSError)?;
194        delay.delay_ms(5);
195
196        // Do hardware reset by holding reset low for at least 10us
197        ili9488.reset.set_low().map_err(|_| DisplayError::RSError)?;
198        let _ = delay.delay_ms(20);
199
200        // Set high for normal operation
201        ili9488
202            .reset
203            .set_high()
204            .map_err(|_| DisplayError::RSError)?;
205
206        // Wait for reset to complete
207        let _ = delay.delay_ms(150);
208
209        // Do software reset
210        ili9488.command(Command::SoftwareReset, &[])?;
211
212        // Wait 5ms after reset before sending commands
213        // and 120ms before sending Sleep Out
214        let _ = delay.delay_ms(150);
215
216        // Initialization Sequence, taken from (https://github.com/Bodmer/TFT_eSPI/blob/master/TFT_Drivers/ILI9488_Init.h)
217
218        // Positive Gamma Control
219        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        // Negative Gamma Control
228        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])?; // MX, BGR
243
244        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    /// Configures the screen for hardware-accelerated vertical scrolling.
315    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    /// Change the orientation of the screen
359    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    /// Control the screen sleep mode:
373    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    /// Control the screen display mode
381    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    /// Invert the pixel color on screen
389    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    /// Idle mode reduces the number of colors to 8
397    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    /// Set display brightness to the value between 0 and 255
405    pub fn brightness(&mut self, brightness: u8) -> Result {
406        self.command(Command::SetBrightness, &[brightness])
407    }
408
409    /// Set adaptive brightness value equal to [AdaptiveBrightness]
410    pub fn content_adaptive_brightness(&mut self, value: AdaptiveBrightness) -> Result {
411        self.command(Command::ContentAdaptiveBrightness, &[value as _])
412    }
413
414    /// Configure [FrameRateClockDivision] and [FrameRate] in normal mode
415    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    /// Configure [FrameRateClockDivision] and [FrameRate] in idle mode
427    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    // TODO: Fix implementations for embedded graphics
490    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    /// Draw a raw RGB565 image buffer to the display in RGB666 mode.
516    ///
517    /// `data` - A slice of u16 values in RGB565 big endian format.
518    ///
519    /// Use [image2cpp](https://javl.github.io/image2cpp/)
520    /// to convert images to u16 arrays. `Draw mode` should be `Horizontal - 2 bytes per pixel (565)`
521    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    /// Draw an upscaled raw RGB565 image buffer to the display in RGB666 mode.
537    ///
538    /// `data` - A slice of u16 values in RGB565 big endian format.
539    ///
540    /// `original_width` - The original image width
541    ///
542    /// `screen_width` - The image width on the screen
543    ///
544    /// Use [image2cpp](https://javl.github.io/image2cpp/)
545    /// to convert images to u16 arrays. `Draw mode` should be `Horizontal - 2 bytes per pixel (565)`
546    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 each horizontal line
559        //  For each pixel, repeat it ratio times
560        //  Repeat expanded horizontal line ratio times
561        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                        // Convert rgb565 to rgb666
566                        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    /// Draw a rectangle on the screen, represented by top-left corner (x0, y0)
600    /// and bottom-right corner (x1, y1).
601    ///
602    /// The border is included.
603    ///
604    /// This method accepts a raw buffer of words that will be copied to the screen
605    /// video memory.
606    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    /// Fill entire screen with specfied color
618    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    /// Fast way to fill the entire screen. Only works with [Rgb111] colors
626    pub fn clear_screen_fast(&mut self, color: Rgb111) -> Result {
627        // Switch pixel format to 3 bpp
628        if PixelFormat::DATA != Rgb111Mode::DATA {
629            self.command(Command::PixelFormatSet, &[Rgb111Mode::DATA])?;
630        }
631
632        // Clear the screen with 3 bpp
633        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        // Switch back to original pixel format
641        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    /// Get the current screen width. It can change based on the current orientation
651    pub fn width(&self) -> usize {
652        self.width
653    }
654
655    /// Get the current screen heighth. It can change based on the current orientation
656    pub fn height(&self) -> usize {
657        self.height
658    }
659    /// Consumes the ILI9488, gives back the interface and reset peripherals
660    pub fn release(self) -> (IFACE, RESET) {
661        (self.interface, self.reset)
662    }
663}
664
665/// Scroller must be provided in order to scroll the screen. It can only be obtained
666/// by configuring the screen for scrolling.
667pub 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
685/// Available Adaptive Brightness values
686pub enum AdaptiveBrightness {
687    Off = 0x00,
688    UserInterfaceImage = 0x01,
689    StillPicture = 0x02,
690    MovingImage = 0x03,
691}
692
693/// Available frame rate in Hz
694pub 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
713/// Frame rate clock division
714pub 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    // MemoryWriteContinue = 0x3c,
741    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}