use core::fmt;
use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_hal::digital::OutputPin;
use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
use esp_hal::{
Blocking,
delay::Delay,
gpio::{DriveMode, Level, Output, OutputConfig},
ledc::{
LSGlobalClkSource, Ledc, LowSpeed,
channel::{self, ChannelIFace},
timer::{self, TimerIFace},
},
peripherals::{GPIO33, GPIO34, GPIO35, GPIO36, GPIO37, GPIO38, LEDC, SPI2},
spi::{
Mode,
master::{Config as SpiConfig, Spi},
},
time::Rate,
};
use mipidsi::{
Builder,
interface::{Interface, InterfacePixelFormat, SpiInterface},
models::ST7789,
options::{ColorInversion, Orientation, Rotation},
};
use static_cell::StaticCell;
use crate::{
Error, Percent,
panel::{HEIGHT, PANEL_OFFSET, WIDTH},
};
type DisplaySpi = ExclusiveDevice<Spi<'static, Blocking>, Output<'static>, NoDelay>;
pub type Display =
mipidsi::Display<SpiInterface<'static, DisplaySpi, Output<'static>>, ST7789, Output<'static>>;
#[expect(missing_docs, reason = "the fields are the pins they're named after")]
#[derive(Debug)]
pub struct DisplayPins {
pub spi: SPI2<'static>,
pub sck: GPIO36<'static>,
pub mosi: GPIO35<'static>,
pub cs: GPIO37<'static>,
pub dc: GPIO34<'static>,
pub reset: GPIO33<'static>,
pub backlight: GPIO38<'static>,
pub ledc: LEDC<'static>,
}
pub struct Backlight {
channel: channel::Channel<'static, LowSpeed>,
}
impl Backlight {
pub fn set_brightness(&mut self, brightness: Percent) -> Result<(), Error> {
self.channel
.set_duty(brightness.get())
.map_err(|_| Error::Backlight)
}
}
pub fn backlight(ledc: LEDC<'static>, pin: GPIO38<'static>) -> Result<Backlight, Error> {
static TIMER: StaticCell<timer::Timer<'static, LowSpeed>> = StaticCell::new();
let mut ledc = Ledc::new(ledc);
ledc.set_global_slow_clock(LSGlobalClkSource::APBClk);
let timer = TIMER.init(ledc.timer::<LowSpeed>(timer::Number::Timer0));
timer
.configure(timer::config::Config {
duty: timer::config::Duty::Duty10Bit,
clock_source: timer::LSClockSource::APBClk,
frequency: Rate::from_hz(256),
})
.map_err(|_| Error::Backlight)?;
let mut channel = ledc.channel(channel::Number::Channel0, pin);
channel
.configure(channel::config::Config {
timer,
duty_pct: 100,
drive_mode: DriveMode::PushPull,
})
.map_err(|_| Error::Backlight)?;
Ok(Backlight { channel })
}
pub fn display(pins: DisplayPins) -> Result<(Display, Backlight), Error> {
static BUFFER: StaticCell<[u8; 512]> = StaticCell::new();
let backlight = backlight(pins.ledc, pins.backlight)?;
let dc = Output::new(pins.dc, Level::Low, OutputConfig::default());
let reset = Output::new(pins.reset, Level::High, OutputConfig::default());
let cs = Output::new(pins.cs, Level::High, OutputConfig::default());
let spi = Spi::new(pins.spi, spi_config())
.map_err(|_| Error::DisplaySpi)?
.with_sck(pins.sck)
.with_mosi(pins.mosi);
let device = ExclusiveDevice::new_no_delay(spi, cs).map_err(|_| Error::DisplaySpi)?;
let interface = SpiInterface::new(device, dc, BUFFER.init([0; 512]));
let display = init_panel(interface, reset)?;
Ok((display, backlight))
}
pub fn spi_config() -> SpiConfig {
SpiConfig::default()
.with_frequency(Rate::from_mhz(40))
.with_mode(Mode::_0)
}
pub fn init_panel<DI, RST>(
interface: DI,
reset: RST,
) -> Result<mipidsi::Display<DI, ST7789, RST>, Error>
where
DI: Interface,
Rgb565: InterfacePixelFormat<DI::Word>,
RST: OutputPin,
{
Builder::new(ST7789, interface)
.reset_pin(reset)
.display_size(HEIGHT, WIDTH)
.display_offset(PANEL_OFFSET.0, PANEL_OFFSET.1)
.orientation(Orientation::new().rotate(Rotation::Deg90))
.invert_colors(ColorInversion::Inverted)
.init(&mut Delay::new())
.map_err(|_| Error::DisplayInit)
}
impl fmt::Debug for Backlight {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Backlight").finish_non_exhaustive()
}
}