lilkaoxide/
display.rs

1use crate::spi::{InnerDelay, SpiBusWrapper, SpiDev, SpiInitError, make_device};
2use esp_hal::gpio::{DriveMode, Level, Output, OutputConfig, Pull};
3#[cfg(feature = "blocking")]
4use mipidsi::{
5    interface::SpiInterface,
6    models::ST7789,
7    options::{ColorInversion, Orientation, RefreshOrder, Rotation, TearingEffect},
8    Builder,
9    Display,
10    NoResetPin
11};
12#[cfg(feature = "async")]
13use lcd_async::{
14    interface::SpiInterface,
15    models::ST7789,
16    options::{ColorInversion, Orientation, RefreshOrder, Rotation, TearingEffect},
17    Builder,
18    Display,
19    NoResetPin
20};
21use static_cell::StaticCell;
22
23#[cfg(feature = "display_buffer_512")]
24static DISPLAY_BUFFER_CELL: StaticCell<[u8; 512]> = StaticCell::new();
25#[cfg(feature = "display_buffer_1024")]
26static DISPLAY_BUFFER_CELL: StaticCell<[u8; 1024]> = StaticCell::new();
27#[cfg(feature = "display_buffer_8192")]
28static DISPLAY_BUFFER_CELL: StaticCell<[u8; 8192]> = StaticCell::new();
29
30/// Type alias for the Lilka display handle.
31///
32/// The concrete type varies by feature flag:
33///
34/// * **`blocking`** — `Display<SpiInterface<'static, SpiDev<'static>, Output<'static>>, ST7789, NoResetPin>`
35/// * **`async`** — `Display<SpiInterface<SpiDev<'static>, Output<'static>>, ST7789, NoResetPin>`
36///
37/// The display is a **240×280** ST7789 TFT panel with a 20-pixel vertical offset
38/// (the controller addresses rows 20–299 of a virtual 240×320 frame buffer).
39/// Color order is RGB; colors are hardware-inverted by the panel (handled automatically).
40///
41/// # Usage
42///
43/// Obtain the display from [`crate::Lilka::display`] and draw with the `mipidsi` or
44/// `lcd-async` [`DrawTarget`](embedded_graphics_core::draw_target::DrawTarget) impl:
45///
46/// ```rust,no_run
47/// use lilkaoxide::prelude::*;
48/// use embedded_graphics::{
49///     pixelcolor::Rgb565,
50///     primitives::{Circle, PrimitiveStyle},
51///     prelude::*,
52/// };
53///
54/// # fn example(mut lilka: Lilka) {
55/// Circle::new(Point::new(100, 100), 40)
56///     .into_styled(PrimitiveStyle::with_fill(Rgb565::RED))
57///     .draw(&mut lilka.display)
58///     .ok();
59/// # }
60/// ```
61#[cfg(feature = "blocking")]
62pub type LilkaDisplay =
63    Display<SpiInterface<'static, SpiDev<'static>, Output<'static>>, ST7789, NoResetPin>;
64#[cfg(feature = "async")]
65pub type LilkaDisplay =
66    Display<SpiInterface<SpiDev<'static>, Output<'static>>, ST7789, NoResetPin>;
67
68/// Error returned when the ST7789 display fails to initialise.
69///
70/// Propagated as [`crate::InitError::Display`] from [`crate::Lilka::init`].
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum DisplayInitError {
73    /// Creating the SPI device (bus sharing layer) for the display failed.
74    SpiDevice(SpiInitError),
75    /// The ST7789 `init` sequence did not complete successfully.
76    ControllerInitFailed,
77    /// Applying the tearing-effect configuration failed after `init`.
78    TearingConfigFailed,
79}
80
81impl DisplayInitError {
82    /// Returns a short, `'static` error description suitable for `defmt` logging.
83    pub const fn as_str(self) -> &'static str {
84        match self {
85            Self::SpiDevice(err) => err.as_str(),
86            Self::ControllerInitFailed => "Display init failed",
87            Self::TearingConfigFailed => "Display tearing config failed",
88        }
89    }
90}
91
92#[cfg(feature = "blocking")]
93pub fn init_display(
94    dp: esp_hal::peripherals::GPIO46<'static>,
95    dc: esp_hal::peripherals::GPIO15<'static>,
96    cs: esp_hal::peripherals::GPIO7<'static>,
97    spi_bus: &'static SpiBusWrapper,
98    mut delay: InnerDelay,
99    refresh_order: RefreshOrder,
100    rotation: Rotation,
101    tearing: TearingEffect,
102) -> Result<LilkaDisplay, DisplayInitError> {
103    let mut display_power = Output::new(
104        dp,
105        Level::High,
106        OutputConfig::default()
107            .with_drive_mode(DriveMode::PushPull)
108            .with_pull(Pull::Down),
109    );
110    display_power.set_high();
111    let dc: Output<'static> = Output::new(
112        dc,
113        Level::Low,
114        OutputConfig::default().with_drive_mode(DriveMode::PushPull),
115    );
116    let disp_cs: Output<'static> = Output::new(
117        cs,
118        Level::Low,
119        OutputConfig::default().with_drive_mode(DriveMode::PushPull),
120    );
121    let spi_dev =
122        make_device(spi_bus, disp_cs, delay.clone()).map_err(DisplayInitError::SpiDevice)?;
123    #[cfg(feature = "blocking")]
124    {
125        cfg_if::cfg_if! {
126            if #[cfg(feature = "display_buffer_512")] {
127                let di = SpiInterface::new(spi_dev, dc, DISPLAY_BUFFER_CELL.init([0_u8; 512]));
128            } else if #[cfg(feature = "display_buffer_1024")] {
129                let di = SpiInterface::new(spi_dev, dc, DISPLAY_BUFFER_CELL.init([0_u8; 1024]));
130            } else if #[cfg(feature = "display_buffer_8192")] {
131                let di = SpiInterface::new(spi_dev, dc, DISPLAY_BUFFER_CELL.init([0_u8; 8192]));
132            } else {
133                compile_error!("Один з display_buffer_512, display_buffer_1024, або display_buffer_8192 має бути активний");
134            }
135        }
136        let mut display = Builder::new(ST7789, di)
137            .refresh_order(refresh_order)
138            .invert_colors(ColorInversion::Inverted)
139            .display_size(240, 280)
140            .orientation(Orientation::new().rotate(rotation))
141            .display_offset(0, 20)
142            .color_order(mipidsi::options::ColorOrder::Rgb)
143            .init(&mut delay)
144            .map_err(|_| DisplayInitError::ControllerInitFailed)?;
145        display
146            .set_tearing_effect(tearing)
147            .map_err(|_| DisplayInitError::TearingConfigFailed)?;
148        return Ok(display);
149    }
150}
151
152#[cfg(feature = "async")]
153pub async fn init_display(
154    dp: esp_hal::peripherals::GPIO46<'static>,
155    dc: esp_hal::peripherals::GPIO15<'static>,
156    cs: esp_hal::peripherals::GPIO7<'static>,
157    spi_bus: &'static SpiBusWrapper,
158    mut delay: InnerDelay,
159    refresh_order: RefreshOrder,
160    rotation: Rotation,
161    tearing: TearingEffect,
162) -> Result<LilkaDisplay, DisplayInitError> {
163    let mut display_power = Output::new(
164        dp,
165        Level::High,
166        OutputConfig::default()
167            .with_drive_mode(DriveMode::PushPull)
168            .with_pull(Pull::Down),
169    );
170    display_power.set_high();
171    let dc: Output<'static> = Output::new(
172        dc,
173        Level::Low,
174        OutputConfig::default().with_drive_mode(DriveMode::PushPull),
175    );
176    let disp_cs: Output<'static> = Output::new(
177        cs,
178        Level::Low,
179        OutputConfig::default().with_drive_mode(DriveMode::PushPull),
180    );
181    let spi_dev =
182        make_device(spi_bus, disp_cs, delay.clone()).map_err(DisplayInitError::SpiDevice)?;
183    let di = SpiInterface::new(spi_dev, dc);
184    let mut display = Builder::new(ST7789, di)
185        .refresh_order(refresh_order)
186        .invert_colors(ColorInversion::Inverted)
187        .display_size(240, 280)
188        .orientation(Orientation::new().rotate(rotation))
189        .display_offset(0, 20)
190        .color_order(lcd_async::options::ColorOrder::Rgb)
191        .init(&mut delay)
192        .await
193        .map_err(|_| DisplayInitError::ControllerInitFailed)?;
194    display
195        .set_tearing_effect(tearing)
196        .await
197        .map_err(|_| DisplayInitError::TearingConfigFailed)?;
198
199    Ok(display)
200}