use core::{cell::RefCell, fmt};
use device_envoy_core::button::Button;
use device_envoy_core::cyd::{Cyd, backend};
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig;
use embassy_rp::Peri;
use embassy_rp::gpio::{Level, Output, Pin};
use embassy_rp::spi::{
self, Blocking, ClkPin, Config as SpiConfig, MisoPin, MosiPin, Phase, Polarity, Spi,
};
use embassy_sync::blocking_mutex::{Mutex, raw::NoopRawMutex};
use embedded_graphics::{mono_font::MonoFont, pixelcolor::Rgb888};
use static_cell::StaticCell;
use super::{
CydDisplayRp, CydTouchRp, CydTouchUncalibratedRp, Error, Orientation, PixelBuffer, TOUCH_SPI_HZ,
};
use crate::flash_block::FlashBlockRp;
type SharedSpiBus<T> = Spi<'static, T, Blocking>;
type SharedSpiMutex<T> = Mutex<NoopRawMutex, RefCell<SharedSpiBus<T>>>;
type SharedSpiDevice<T> =
SpiDeviceWithConfig<'static, NoopRawMutex, SharedSpiBus<T>, Output<'static>>;
pub struct CydRpOneSpi<T: spi::Instance + 'static> {
display: CydDisplayRp<SharedSpiDevice<T>>,
touch: CydTouchRp<SharedSpiDevice<T>>,
}
pub struct CydRpOneSpiStatic<T: spi::Instance + 'static, const PIXEL_COUNT: usize> {
pixel_buffer: StaticCell<PixelBuffer<PIXEL_COUNT>>,
shared_spi: StaticCell<SharedSpiMutex<T>>,
}
impl<T: spi::Instance + 'static, const PIXEL_COUNT: usize> CydRpOneSpiStatic<T, PIXEL_COUNT> {
pub(crate) const fn new() -> Self {
assert!(
PIXEL_COUNT <= CydRpOneSpi::<T>::SCREEN_PIXELS,
"PIXEL_COUNT must not exceed SCREEN_PIXELS"
);
Self {
pixel_buffer: StaticCell::new(),
shared_spi: StaticCell::new(),
}
}
}
impl<T: spi::Instance + 'static> CydRpOneSpi<T> {
pub const SCREEN_PIXELS: usize = device_envoy_core::cyd::SCREEN_PIXELS;
#[must_use]
pub const fn new_static<const PIXEL_COUNT: usize>() -> CydRpOneSpiStatic<T, PIXEL_COUNT> {
CydRpOneSpiStatic::new()
}
#[expect(clippy::too_many_arguments, reason = "mirrors CydEspOneSpi::new")]
pub async fn new<
const PIXEL_COUNT: usize,
Sck,
Mosi,
Miso,
LcdCs,
Dc,
Rst,
Backlight,
TouchCs,
TouchIrq,
R: Button,
>(
statics: &'static CydRpOneSpiStatic<T, PIXEL_COUNT>,
spi: Peri<'static, T>,
sck_pin: Peri<'static, Sck>,
mosi_pin: Peri<'static, Mosi>,
miso_pin: Peri<'static, Miso>,
lcd_cs_pin: Peri<'static, LcdCs>,
lcd_dc_pin: Peri<'static, Dc>,
lcd_rst_pin: Peri<'static, Rst>,
lcd_backlight_pin: Peri<'static, Backlight>,
display_spi_hz: u32,
touch_cs_pin: Peri<'static, TouchCs>,
touch_irq_pin: Peri<'static, TouchIrq>,
orientation: Orientation,
background_color: Rgb888,
foreground_color: Rgb888,
font: &'static MonoFont<'static>,
calibration_flash_block: &mut FlashBlockRp,
recalibration_button: &mut R,
) -> crate::Result<Self>
where
Sck: Pin + ClkPin<T>,
Mosi: Pin + MosiPin<T>,
Miso: Pin + MisoPin<T>,
LcdCs: Pin,
Dc: Pin,
Rst: Pin,
Backlight: Pin,
TouchCs: Pin,
TouchIrq: Pin,
{
let spi_config = {
let mut spi_config = SpiConfig::default();
spi_config.frequency = TOUCH_SPI_HZ;
spi_config.polarity = Polarity::IdleLow;
spi_config.phase = Phase::CaptureOnFirstTransition;
spi_config
};
let spi = Spi::new_blocking(spi, sck_pin, mosi_pin, miso_pin, spi_config);
let shared_spi: &'static SharedSpiMutex<T> =
statics.shared_spi.init(Mutex::new(RefCell::new(spi)));
let lcd_cs = Output::new(lcd_cs_pin, Level::High);
let touch_cs = Output::new(touch_cs_pin, Level::High);
let lcd_spi_config = {
let mut lcd_spi_config = SpiConfig::default();
lcd_spi_config.frequency = display_spi_hz;
lcd_spi_config.polarity = Polarity::IdleLow;
lcd_spi_config.phase = Phase::CaptureOnFirstTransition;
lcd_spi_config
};
let touch_spi_config = {
let mut touch_spi_config = SpiConfig::default();
touch_spi_config.frequency = TOUCH_SPI_HZ;
touch_spi_config.polarity = Polarity::IdleLow;
touch_spi_config.phase = Phase::CaptureOnFirstTransition;
touch_spi_config
};
let lcd_spi_device = SpiDeviceWithConfig::new(shared_spi, lcd_cs, lcd_spi_config);
let touch_spi_device = SpiDeviceWithConfig::new(shared_spi, touch_cs, touch_spi_config);
let pixel_buffer = PixelBuffer::init_static(&statics.pixel_buffer);
let mut display = CydDisplayRp::new_from_device(
lcd_spi_device,
lcd_dc_pin,
lcd_rst_pin,
lcd_backlight_pin,
Orientation::Landscape,
background_color,
foreground_color,
font,
pixel_buffer,
)?;
let touch = CydTouchUncalibratedRp::from_device(touch_spi_device, touch_irq_pin);
let touch = backend::ensure_calibration(
&mut display,
touch,
calibration_flash_block,
recalibration_button,
None,
orientation,
)
.await
.map_err(|error| match error {
backend::Error::Device(cyd_error) => crate::Error::from(cyd_error),
backend::Error::Flash(flash_error) => flash_error,
})?;
display.set_orientation(orientation)?;
Ok(Self { display, touch })
}
}
impl<T: spi::Instance + 'static> Cyd for CydRpOneSpi<T> {
type Error = Error;
type Display = CydDisplayRp<SharedSpiDevice<T>>;
type Touch = CydTouchRp<SharedSpiDevice<T>>;
fn parts(&mut self) -> (&mut Self::Display, &mut Self::Touch) {
(&mut self.display, &mut self.touch)
}
fn orientation(&self) -> Orientation {
self.display.orientation
}
}
impl<T: spi::Instance + 'static> fmt::Debug for CydRpOneSpi<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CydRpOneSpi")
.field("orientation", &self.display.orientation)
.finish_non_exhaustive()
}
}