use device_envoy_core::cyd::display::RectanglePixels;
use embassy_rp::Peri;
use embassy_rp::gpio::{Level, Output, Pin};
use embassy_rp::peripherals::SPI0;
use embassy_rp::spi::{
Blocking, ClkPin, Config as SpiConfig, MisoPin, MosiPin, Phase, Polarity, Spi,
};
use embedded_graphics::{
draw_target::DrawTarget,
mono_font::MonoFont,
pixelcolor::{Rgb565, raw::RawU16},
prelude::{Point, Size},
primitives::Rectangle,
};
use embedded_hal::spi::SpiDevice;
use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
use mipidsi::{
Builder,
interface::SpiInterface,
models::ST7789,
options::{ColorOrder, Orientation as MipiOrientation, Rotation},
};
use static_cell::StaticCell;
use super::{CydFrameRp, Orientation, buffer::DynPixelBuffer};
pub const DEFAULT_DISPLAY_SPI_HZ: u32 = 40_000_000;
const DISPLAY_SPI_BUFFER_LEN: usize = 64;
type CydDisplaySpiBus = Spi<'static, SPI0, Blocking>;
pub(crate) type CydDisplaySpiDevice = ExclusiveDevice<CydDisplaySpiBus, Output<'static>, NoDelay>;
type CydDisplayInterface<D> = SpiInterface<'static, D, Output<'static>>;
type CydDisplayDevice<D> = mipidsi::Display<CydDisplayInterface<D>, ST7789, Output<'static>>;
#[derive(Clone, Copy, Debug)]
pub enum CydDisplayRpInitError {
InitDisplay,
}
#[derive(Clone, Copy, Debug)]
pub enum CydDisplayRpFlushError {
FlushFrameBuffer,
}
pub(crate) struct CydDisplayRp<D: SpiDevice<u8> = CydDisplaySpiDevice> {
display: CydDisplayDevice<D>,
screen_size: Size,
_backlight: Output<'static>,
}
impl<D: SpiDevice<u8>> CydDisplayRp<D> {
#[must_use]
pub const fn size(&self) -> Size {
self.screen_size
}
#[must_use]
fn screen_rectangle(&self) -> Rectangle {
Rectangle::new(Point::new(0, 0), self.screen_size)
}
pub(crate) fn new_from_device<Dc, Rst, Backlight>(
spi_device: D,
dc_pin: Peri<'static, Dc>,
rst_pin: Peri<'static, Rst>,
backlight_pin: Peri<'static, Backlight>,
orientation: Orientation,
) -> Result<CydDisplayRp<D>, CydDisplayRpInitError>
where
Dc: Pin,
Rst: Pin,
Backlight: Pin,
{
let dc = Output::new(dc_pin, Level::Low);
let rst = Output::new(rst_pin, Level::High);
let mut backlight = Output::new(backlight_pin, Level::High);
static SPI_BUFFER: StaticCell<[u8; DISPLAY_SPI_BUFFER_LEN]> = StaticCell::new();
let spi_buffer = SPI_BUFFER.init([0u8; DISPLAY_SPI_BUFFER_LEN]);
let interface = SpiInterface::new(spi_device, dc, spi_buffer);
let mut delay = embassy_time::Delay;
let screen_size = orientation.size();
let display_orientation = match orientation {
Orientation::Landscape => MipiOrientation::new()
.rotate(Rotation::Deg90)
.flip_horizontal()
.rotate(Rotation::Deg180),
Orientation::Portrait => MipiOrientation::new()
.rotate(Rotation::Deg180)
.flip_horizontal(),
Orientation::LandscapeInverted => MipiOrientation::new()
.rotate(Rotation::Deg90)
.flip_horizontal(),
Orientation::PortraitInverted => MipiOrientation::new().flip_horizontal(),
};
let display = Builder::new(ST7789, interface)
.reset_pin(rst)
.display_size(240, 320)
.color_order(ColorOrder::Bgr)
.orientation(display_orientation)
.init(&mut delay)
.map_err(|_| CydDisplayRpInitError::InitDisplay)?;
backlight.set_high();
Ok(CydDisplayRp {
display,
screen_size,
_backlight: backlight,
})
}
pub(crate) fn flush_buffer(
&mut self,
buffer: &impl RectanglePixels,
top_left: Point,
) -> Result<(), CydDisplayRpFlushError> {
let rectangle = Rectangle::new(
top_left,
Size::new(buffer.width() as u32, buffer.height() as u32),
);
self.display
.fill_contiguous(
&rectangle,
buffer
.raw_pixels()
.iter()
.copied()
.map(|pixel| Rgb565::from(RawU16::new(pixel))),
)
.map_err(|_| CydDisplayRpFlushError::FlushFrameBuffer)
}
pub(crate) fn make_frame_with_tile_top_left<'a>(
&'a mut self,
pixel_buffer: &'a mut dyn DynPixelBuffer,
rectangle: Rectangle,
tile_top_left: Point,
background565: Rgb565,
foreground565: Rgb565,
font: &'static MonoFont<'static>,
) -> CydFrameRp<'a, D> {
let size = rectangle.size;
let mut view = pixel_buffer.view_mut(size.width as usize, size.height as usize);
view.fill(background565);
CydFrameRp {
display: self,
view,
rectangle,
tile_top_left,
background565,
foreground565,
font,
}
}
pub(crate) fn fill(&mut self, color: Rgb565) -> Result<(), CydDisplayRpFlushError> {
self.fill_rectangle(self.screen_rectangle(), color)
}
pub(crate) fn fill_rectangle(
&mut self,
rectangle: Rectangle,
color: Rgb565,
) -> Result<(), CydDisplayRpFlushError> {
self.display
.fill_solid(&rectangle, color)
.map_err(|_| CydDisplayRpFlushError::FlushFrameBuffer)
}
pub(crate) fn fill_contiguous<I>(
&mut self,
rectangle: Rectangle,
pixels: I,
) -> Result<(), CydDisplayRpFlushError>
where
I: IntoIterator<Item = Rgb565>,
{
if rectangle.size.width == 0 || rectangle.size.height == 0 {
return Ok(());
}
self.display
.fill_contiguous(&rectangle, pixels)
.map_err(|_| CydDisplayRpFlushError::FlushFrameBuffer)
}
}
impl CydDisplayRp<CydDisplaySpiDevice> {
#[expect(clippy::too_many_arguments, reason = "mirrors CydDisplayRp::new")]
pub(crate) fn new<Sck, Mosi, Miso, Cs, Dc, Rst, Backlight>(
spi: Peri<'static, SPI0>,
sck_pin: Peri<'static, Sck>,
mosi_pin: Peri<'static, Mosi>,
_miso_pin: Peri<'static, Miso>,
cs_pin: Peri<'static, Cs>,
dc_pin: Peri<'static, Dc>,
rst_pin: Peri<'static, Rst>,
backlight_pin: Peri<'static, Backlight>,
display_spi_hz: u32,
orientation: Orientation,
) -> Result<CydDisplayRp<CydDisplaySpiDevice>, CydDisplayRpInitError>
where
Sck: Pin + ClkPin<SPI0>,
Mosi: Pin + MosiPin<SPI0>,
Miso: Pin + MisoPin<SPI0>,
Cs: Pin,
Dc: Pin,
Rst: Pin,
Backlight: Pin,
{
let spi_config = {
let mut spi_config = SpiConfig::default();
spi_config.frequency = display_spi_hz;
spi_config.polarity = Polarity::IdleLow;
spi_config.phase = Phase::CaptureOnFirstTransition;
spi_config
};
let spi = Spi::new_blocking_txonly(spi, sck_pin, mosi_pin, spi_config);
let cs = Output::new(cs_pin, Level::High);
let spi_device =
ExclusiveDevice::<_, _, NoDelay>::new_no_delay(spi, cs).expect("CS pin is infallible");
Self::new_from_device(spi_device, dc_pin, rst_pin, backlight_pin, orientation)
}
}