Skip to main content

embedded_wg_display/display/
mod.rs

1//! Display driver abstraction for the ESP32-S3-BOX.
2
3use alloc::boxed::Box;
4use defmt::info;
5use embedded_graphics::Drawable;
6use embedded_graphics::draw_target::DrawTarget;
7use embedded_graphics::mono_font::MonoTextStyle;
8use embedded_graphics::mono_font::iso_8859_1::FONT_8X13;
9use embedded_graphics::pixelcolor::Rgb565;
10use embedded_graphics::prelude::{Point, RgbColor};
11use embedded_graphics::text::Text;
12use embedded_hal_bus::spi::ExclusiveDevice;
13use esp_hal::delay::Delay;
14use esp_hal::gpio::DriveMode;
15use esp_hal::peripherals;
16use esp_hal::{
17    Blocking,
18    dma::{DmaRxBuf, DmaTxBuf},
19    dma_buffers,
20    gpio::{Level, Output, OutputConfig},
21    spi::master::{Spi, SpiDmaBus},
22    time::Rate,
23};
24use mipidsi::Builder;
25use mipidsi::interface::SpiInterface;
26use mipidsi::models::ILI9342CRgb565;
27use mipidsi::options::ColorOrder;
28
29// Use SPI with direct memory access, based on https://github.com/georgik/esp32-conways-game-of-life-rs/blob/main/esp32-s3-box-3/src/main.rs
30type MyDisplay = mipidsi::Display<
31    SpiInterface<
32        'static,
33        ExclusiveDevice<SpiDmaBus<'static, Blocking>, Output<'static>, Delay>,
34        Output<'static>,
35    >,
36    ILI9342CRgb565,
37    Output<'static>,
38>;
39
40const CONSOLE_X: i32 = 10;
41const CONSOLE_LINE_HEIGHT: i32 = 15;
42const CONSOLE_INITIAL_Y: i32 = 20;
43
44pub struct Display {
45    display: MyDisplay,
46    console_y: i32,
47}
48
49pub struct DisplayPeripherals {
50    pub spi2: peripherals::SPI2<'static>,
51    pub dma_ch0: peripherals::DMA_CH0<'static>,
52    pub gpio4: peripherals::GPIO4<'static>,
53    pub gpio5: peripherals::GPIO5<'static>,
54    pub gpio6: peripherals::GPIO6<'static>,
55    pub gpio7: peripherals::GPIO7<'static>,
56    pub gpio47: peripherals::GPIO47<'static>,
57    pub gpio48: peripherals::GPIO48<'static>,
58}
59
60impl Display {
61    /// Initialize the display, needs all peripherals as seperate arguements since [`Storage`](crate::storage::Storage) and [`Wifi`](crate::wifi::Wifi) also rely on peripherals.
62    pub fn new(display_peripherals: DisplayPeripherals) -> Self {
63        // direct memory access (dma) for SPI transfers
64        let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(8912);
65        let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
66        let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
67
68        // create SPI interface with DMA
69        let spi = Spi::<Blocking>::new(
70            display_peripherals.spi2,
71            esp_hal::spi::master::Config::default()
72                .with_frequency(Rate::from_mhz(40))
73                .with_mode(esp_hal::spi::Mode::_0),
74        )
75        .unwrap()
76        .with_sck(display_peripherals.gpio7)
77        .with_mosi(display_peripherals.gpio6)
78        .with_dma(display_peripherals.dma_ch0)
79        .with_buffers(dma_rx_buf, dma_tx_buf);
80
81        let cs_output = Output::new(
82            display_peripherals.gpio5,
83            Level::High,
84            OutputConfig::default(),
85        );
86        let spi_delay = Delay::new();
87        let spi_device = ExclusiveDevice::new(spi, cs_output, spi_delay).unwrap();
88
89        let lcd_dc = Output::new(
90            display_peripherals.gpio4,
91            Level::Low,
92            OutputConfig::default(),
93        );
94        let buffer: &'static mut [u8; 512] = Box::leak(Box::new([0_u8; 512]));
95        let di = SpiInterface::new(spi_device, lcd_dc, buffer);
96
97        // from https://github.com/georgik/esp32-conways-game-of-life-rs/blob/main/esp32-s3-box-3/src/main.rs
98        // needs .with_drive_mode(DriveMode::OpenDrain) for some reason
99        let reset = Output::new(
100            display_peripherals.gpio48,
101            Level::High,
102            OutputConfig::default().with_drive_mode(DriveMode::OpenDrain),
103        );
104
105        let mut display_delay = Delay::new();
106
107        // create display driver
108        let mut display: MyDisplay = Builder::new(ILI9342CRgb565, di)
109            .reset_pin(reset)
110            .display_size(320, 240)
111            .color_order(ColorOrder::Bgr)
112            .orientation(
113                mipidsi::options::Orientation::new()
114                    .flip_vertical()
115                    .flip_horizontal(),
116            )
117            // .invert_colors(ColorInversion::Inverted)
118            .init(&mut display_delay)
119            .unwrap();
120
121        // clear to black
122        display.clear(Rgb565::BLACK).unwrap();
123
124        // enable backlight
125        let mut backlight = Output::new(
126            display_peripherals.gpio47,
127            Level::High,
128            OutputConfig::default(),
129        );
130        backlight.set_high();
131
132        info!("Display initialized successfully");
133
134        Self {
135            display,
136            console_y: CONSOLE_INITIAL_Y,
137        }
138    }
139
140    pub fn display_mut(&mut self) -> &mut MyDisplay {
141        &mut self.display
142    }
143
144    pub fn console_println(&mut self, text: &str) {
145        Text::new(
146            text,
147            Point::new(CONSOLE_X, self.console_y),
148            MonoTextStyle::new(&FONT_8X13, Rgb565::WHITE),
149        )
150        .draw(&mut self.display)
151        .unwrap();
152        self.console_y += CONSOLE_LINE_HEIGHT;
153    }
154}