mod buffer;
mod display;
mod one_spi;
mod text;
#[path = "cyd/touch.rs"]
mod touch_driver;
use core::{convert::Infallible, fmt};
use device_envoy_core::button::Button;
use device_envoy_core::cyd::{
SCREEN_PIXELS,
display::{CydFrame, RectanglePixels},
touch::{
RawTouchEvent, TouchEvent,
calibration::{CalibrationConfig, ensure_calibration},
},
};
use device_envoy_core::pixel_target::PixelTarget;
pub use device_envoy_core::cyd::{
Cyd, CydDisplay, CydTouch, CydTouchUncalibrated,
display::{Orientation, tiling},
touch,
};
use embassy_rp::Peri;
use embassy_rp::gpio::Pin;
use embassy_rp::peripherals::{SPI0, SPI1};
use embassy_rp::spi::{ClkPin, MisoPin, MosiPin};
use embedded_graphics::{
Pixel,
mono_font::MonoFont,
pixelcolor::{IntoStorage, Rgb565, Rgb888},
prelude::{Dimensions, DrawTarget, OriginDimensions, Point, Size},
primitives::Rectangle,
};
use embedded_hal::spi::SpiDevice;
use static_cell::StaticCell;
use buffer::DynPixelBuffer;
pub use buffer::{PixelBuffer, RegionBuffer, RegionView};
pub use display::{CydDisplayRpFlushError, CydDisplayRpInitError, DEFAULT_DISPLAY_SPI_HZ};
pub use one_spi::{CydRpOneSpi, CydRpOneSpiStatic};
pub use text::DEFAULT_FONT;
pub use touch_driver::{CydTouchRpInitError, TOUCH_SPI_HZ};
use crate::flash_block::FlashBlockRp;
use display::CydDisplayRp as CydDisplayRpDevice;
use touch_driver::CydTouchRp as CydTouchRpDevice;
pub struct CydDisplayRp<D: SpiDevice<u8> = display::CydDisplaySpiDevice> {
display: CydDisplayRpDevice<D>,
orientation: Orientation,
pixel_buffer: &'static mut dyn DynPixelBuffer,
background_color: Rgb888,
foreground_color: Rgb888,
background565: Rgb565,
foreground565: Rgb565,
font: &'static MonoFont<'static>,
}
pub struct CydTouchUncalibratedRp<D = touch_driver::CydTouchSpiDevice> {
touch: CydTouchRpDevice<D>,
}
pub struct CydTouchRp<D = touch_driver::CydTouchSpiDevice> {
raw: CydTouchUncalibratedRp<D>,
calibration_config: CalibrationConfig,
}
pub struct CydRp {
pub display: CydDisplayRp,
pub touch: CydTouchRp,
}
pub(crate) struct CydRpUncalibrated {
pub display: CydDisplayRp,
pub touch: CydTouchUncalibratedRp,
}
pub struct CydStaticRp<const PIXEL_COUNT: usize> {
pixel_buffer: StaticCell<PixelBuffer<PIXEL_COUNT>>,
}
impl<const PIXEL_COUNT: usize> CydStaticRp<PIXEL_COUNT> {
pub(crate) const fn new() -> Self {
Self {
pixel_buffer: StaticCell::new(),
}
}
}
pub struct CydFrameRp<'a, D: SpiDevice<u8> = display::CydDisplaySpiDevice> {
display: &'a mut CydDisplayRpDevice<D>,
view: RegionView<'a>,
rectangle: Rectangle,
tile_top_left: Point,
pub(crate) background565: Rgb565,
pub(crate) foreground565: Rgb565,
pub(crate) font: &'static MonoFont<'static>,
}
impl<'a, D: SpiDevice<u8>> CydFrameRp<'a, D> {
pub fn view_mut(&mut self) -> &mut RegionView<'a> {
&mut self.view
}
pub fn fill(&mut self, color: Rgb565) -> &mut Self {
self.view.fill(color);
self
}
#[must_use]
pub fn width(&self) -> usize {
self.view.width()
}
#[must_use]
pub fn height(&self) -> usize {
self.view.height()
}
pub fn raw_pixels_mut(&mut self) -> &mut [u16] {
self.view.raw_pixels_mut()
}
pub fn flush(&mut self) -> Result<(), Error> {
Ok(self
.display
.flush_buffer(&self.view, self.rectangle.top_left)?)
}
fn local_x(&self, x: i32) -> Option<usize> {
usize::try_from(x.checked_sub(self.tile_top_left.x)?).ok()
}
fn local_y(&self, y: i32) -> Option<usize> {
usize::try_from(y.checked_sub(self.tile_top_left.y)?).ok()
}
}
impl<D: SpiDevice<u8>> DrawTarget for CydFrameRp<'_, D> {
type Color = Rgb565;
type Error = Infallible;
fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
self.fill(color);
Ok(())
}
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(point, color) in pixels {
let Some(local_x) = self.local_x(point.x) else {
continue;
};
let Some(local_y) = self.local_y(point.y) else {
continue;
};
if local_x < self.view.width() && local_y < self.view.height() {
let index = local_y * self.view.width() + local_x;
self.raw_pixels_mut()[index] = color.into_storage();
}
}
Ok(())
}
}
impl<D: SpiDevice<u8>> Dimensions for CydFrameRp<'_, D> {
fn bounding_box(&self) -> Rectangle {
Rectangle::new(self.tile_top_left, self.view.size())
}
}
impl<D: SpiDevice<u8>> PixelTarget for CydFrameRp<'_, D> {
fn width(&self) -> usize {
usize::try_from(self.tile_top_left.x)
.expect("tile top-left x must be non-negative")
.checked_add(self.width())
.expect("frame width must fit in usize")
}
fn height(&self) -> usize {
usize::try_from(self.tile_top_left.y)
.expect("tile top-left y must be non-negative")
.checked_add(self.height())
.expect("frame height must fit in usize")
}
fn put_pixel(&mut self, x: usize, y: usize, color: Rgb888) {
let Some(local_x) = self.local_x(x as i32) else {
return;
};
let Some(local_y) = self.local_y(y as i32) else {
return;
};
if local_x >= self.view.width() || local_y >= self.view.height() {
return;
}
let stride = self.view.width();
self.raw_pixels_mut()[local_y * stride + local_x] = Rgb565::from(color).into_storage();
}
fn put_pixel_565(&mut self, x: usize, y: usize, rgb565: u16) {
let Some(local_x) = self.local_x(x as i32) else {
return;
};
let Some(local_y) = self.local_y(y as i32) else {
return;
};
if local_x >= self.view.width() || local_y >= self.view.height() {
return;
}
let stride = self.view.width();
self.raw_pixels_mut()[local_y * stride + local_x] = rgb565;
}
}
#[derive(Debug, derive_more::From)]
pub enum Error {
DisplayInit(CydDisplayRpInitError),
TouchInit(CydTouchRpInitError),
DisplayFlush(CydDisplayRpFlushError),
}
impl<D: SpiDevice<u8>> CydDisplayRp<D> {
fn from_display_device(
mut display: CydDisplayRpDevice<D>,
orientation: Orientation,
background_color: Rgb888,
foreground_color: Rgb888,
font: &'static MonoFont<'static>,
pixel_buffer: &'static mut dyn DynPixelBuffer,
) -> Result<Self, Error> {
let background565 = rgb565(background_color);
display.fill(background565)?;
Ok(Self {
display,
orientation,
pixel_buffer,
background_color,
foreground_color,
background565,
foreground565: rgb565(foreground_color),
font,
})
}
#[expect(clippy::too_many_arguments, reason = "mirrors CydDisplayRp::new")]
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,
background_color: Rgb888,
foreground_color: Rgb888,
font: &'static MonoFont<'static>,
pixel_buffer: &'static mut dyn DynPixelBuffer,
) -> Result<Self, Error>
where
Dc: Pin,
Rst: Pin,
Backlight: Pin,
{
let display = CydDisplayRpDevice::new_from_device(
spi_device,
dc_pin,
rst_pin,
backlight_pin,
orientation,
)?;
Self::from_display_device(
display,
orientation,
background_color,
foreground_color,
font,
pixel_buffer,
)
}
}
impl CydDisplayRp<display::CydDisplaySpiDevice> {
#[expect(
clippy::too_many_arguments,
reason = "mirrors CydEsp's constructor shape"
)]
pub fn new<const PIXEL_COUNT: usize, Sck, Mosi, Miso, Cs, Dc, Rst, Backlight>(
statics: &'static CydStaticRp<PIXEL_COUNT>,
display_spi: Peri<'static, SPI0>,
display_sck_pin: Peri<'static, Sck>,
display_mosi_pin: Peri<'static, Mosi>,
display_miso_pin: Peri<'static, Miso>,
display_cs_pin: Peri<'static, Cs>,
display_dc_pin: Peri<'static, Dc>,
display_rst_pin: Peri<'static, Rst>,
display_backlight_pin: Peri<'static, Backlight>,
display_spi_hz: u32,
orientation: Orientation,
background_color: Rgb888,
foreground_color: Rgb888,
font: &'static MonoFont<'static>,
) -> Result<Self, Error>
where
Sck: Pin + ClkPin<SPI0>,
Mosi: Pin + MosiPin<SPI0>,
Miso: Pin + MisoPin<SPI0>,
Cs: Pin,
Dc: Pin,
Rst: Pin,
Backlight: Pin,
{
let pixel_buffer = PixelBuffer::init_static(&statics.pixel_buffer);
let display = CydDisplayRpDevice::new(
display_spi,
display_sck_pin,
display_mosi_pin,
display_miso_pin,
display_cs_pin,
display_dc_pin,
display_rst_pin,
display_backlight_pin,
display_spi_hz,
orientation,
)?;
Self::from_display_device(
display,
orientation,
background_color,
foreground_color,
font,
pixel_buffer,
)
}
}
impl<D: SpiDevice<u8>> CydTouchUncalibratedRp<D> {
pub(crate) fn from_device<Irq: Pin>(
touch_spi_device: D,
touch_irq_pin: Peri<'static, Irq>,
) -> Self {
Self {
touch: CydTouchRpDevice::from_device(touch_spi_device, touch_irq_pin),
}
}
}
impl CydTouchUncalibratedRp<touch_driver::CydTouchSpiDevice> {
#[expect(clippy::too_many_arguments, reason = "mirrors CydDisplayRp::new")]
pub(crate) fn new<TouchSck, TouchMosi, TouchMiso, TouchCs, TouchIrq>(
touch_spi: Peri<'static, SPI1>,
touch_sck_pin: Peri<'static, TouchSck>,
touch_mosi_pin: Peri<'static, TouchMosi>,
touch_miso_pin: Peri<'static, TouchMiso>,
touch_cs_pin: Peri<'static, TouchCs>,
touch_irq_pin: Peri<'static, TouchIrq>,
) -> Result<Self, Error>
where
TouchSck: Pin + ClkPin<SPI1>,
TouchMosi: Pin + MosiPin<SPI1>,
TouchMiso: Pin + MisoPin<SPI1>,
TouchCs: Pin,
TouchIrq: Pin,
{
Ok(Self {
touch: CydTouchRpDevice::new(
touch_spi,
touch_sck_pin,
touch_mosi_pin,
touch_miso_pin,
touch_cs_pin,
touch_irq_pin,
)?,
})
}
}
impl CydRp {
pub const SCREEN_PIXELS: usize = SCREEN_PIXELS;
#[must_use]
pub const fn new_static<const PIXEL_COUNT: usize>() -> CydStaticRp<PIXEL_COUNT> {
CydStaticRp::new()
}
#[expect(clippy::too_many_arguments, reason = "mirrors CydDisplayRp::new")]
pub async fn new<
const PIXEL_COUNT: usize,
Sck,
Mosi,
Miso,
Cs,
Dc,
Rst,
Backlight,
TouchSck,
TouchMosi,
TouchMiso,
TouchCs,
TouchIrq,
>(
statics: &'static CydStaticRp<PIXEL_COUNT>,
display_spi: Peri<'static, SPI0>,
display_sck_pin: Peri<'static, Sck>,
display_mosi_pin: Peri<'static, Mosi>,
display_miso_pin: Peri<'static, Miso>,
display_cs_pin: Peri<'static, Cs>,
display_dc_pin: Peri<'static, Dc>,
display_rst_pin: Peri<'static, Rst>,
display_backlight_pin: Peri<'static, Backlight>,
display_spi_hz: u32,
orientation: Orientation,
background_color: Rgb888,
foreground_color: Rgb888,
font: &'static MonoFont<'static>,
touch_spi: Peri<'static, SPI1>,
touch_sck_pin: Peri<'static, TouchSck>,
touch_mosi_pin: Peri<'static, TouchMosi>,
touch_miso_pin: Peri<'static, TouchMiso>,
touch_cs_pin: Peri<'static, TouchCs>,
touch_irq_pin: Peri<'static, TouchIrq>,
calibration_flash_block: &mut FlashBlockRp,
recalibration_button: &mut impl Button,
) -> crate::Result<Self>
where
Sck: Pin + ClkPin<SPI0>,
Mosi: Pin + MosiPin<SPI0>,
Miso: Pin + MisoPin<SPI0>,
Cs: Pin,
Dc: Pin,
Rst: Pin,
Backlight: Pin,
TouchSck: Pin + ClkPin<SPI1>,
TouchMosi: Pin + MosiPin<SPI1>,
TouchMiso: Pin + MisoPin<SPI1>,
TouchCs: Pin,
TouchIrq: Pin,
{
let CydRpUncalibrated { mut display, touch } = CydRpUncalibrated::new(
statics,
display_spi,
display_sck_pin,
display_mosi_pin,
display_miso_pin,
display_cs_pin,
display_dc_pin,
display_rst_pin,
display_backlight_pin,
display_spi_hz,
orientation,
background_color,
foreground_color,
font,
touch_spi,
touch_sck_pin,
touch_mosi_pin,
touch_miso_pin,
touch_cs_pin,
touch_irq_pin,
)?;
let (touch, _) = ensure_calibration(
&mut display,
touch,
calibration_flash_block,
recalibration_button,
None,
)
.await
.map_err(|error| match error.kind {
device_envoy_core::cyd::touch::calibration::ErrorKind::Device(cyd_error) => {
crate::Error::from(cyd_error)
}
device_envoy_core::cyd::touch::calibration::ErrorKind::Flash(flash_error) => {
flash_error
}
})?;
Ok(Self { display, touch })
}
}
impl Cyd for CydRp {
type Error = Error;
type Display = CydDisplayRp;
type Touch = CydTouchRp;
fn parts(&mut self) -> (&mut Self::Display, &mut Self::Touch) {
(&mut self.display, &mut self.touch)
}
fn orientation(&self) -> Orientation {
self.display.orientation
}
}
impl CydRpUncalibrated {
#[expect(clippy::too_many_arguments, reason = "mirrors CydDisplayRp::new")]
pub(crate) fn new<
const PIXEL_COUNT: usize,
Sck,
Mosi,
Miso,
Cs,
Dc,
Rst,
Backlight,
TouchSck,
TouchMosi,
TouchMiso,
TouchCs,
TouchIrq,
>(
statics: &'static CydStaticRp<PIXEL_COUNT>,
display_spi: Peri<'static, SPI0>,
display_sck_pin: Peri<'static, Sck>,
display_mosi_pin: Peri<'static, Mosi>,
display_miso_pin: Peri<'static, Miso>,
display_cs_pin: Peri<'static, Cs>,
display_dc_pin: Peri<'static, Dc>,
display_rst_pin: Peri<'static, Rst>,
display_backlight_pin: Peri<'static, Backlight>,
display_spi_hz: u32,
orientation: Orientation,
background_color: Rgb888,
foreground_color: Rgb888,
font: &'static MonoFont<'static>,
touch_spi: Peri<'static, SPI1>,
touch_sck_pin: Peri<'static, TouchSck>,
touch_mosi_pin: Peri<'static, TouchMosi>,
touch_miso_pin: Peri<'static, TouchMiso>,
touch_cs_pin: Peri<'static, TouchCs>,
touch_irq_pin: Peri<'static, TouchIrq>,
) -> Result<Self, Error>
where
Sck: Pin + ClkPin<SPI0>,
Mosi: Pin + MosiPin<SPI0>,
Miso: Pin + MisoPin<SPI0>,
Cs: Pin,
Dc: Pin,
Rst: Pin,
Backlight: Pin,
TouchSck: Pin + ClkPin<SPI1>,
TouchMosi: Pin + MosiPin<SPI1>,
TouchMiso: Pin + MisoPin<SPI1>,
TouchCs: Pin,
TouchIrq: Pin,
{
Ok(Self {
display: CydDisplayRp::new(
statics,
display_spi,
display_sck_pin,
display_mosi_pin,
display_miso_pin,
display_cs_pin,
display_dc_pin,
display_rst_pin,
display_backlight_pin,
display_spi_hz,
orientation,
background_color,
foreground_color,
font,
)?,
touch: CydTouchUncalibratedRp::new(
touch_spi,
touch_sck_pin,
touch_mosi_pin,
touch_miso_pin,
touch_cs_pin,
touch_irq_pin,
)?,
})
}
}
fn rgb565(color: Rgb888) -> Rgb565 {
Rgb565::from(color)
}
impl<D: SpiDevice<u8>> fmt::Debug for CydDisplayRp<D> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CydDisplayRp")
.finish_non_exhaustive()
}
}
impl<D> fmt::Debug for CydTouchUncalibratedRp<D> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CydTouchUncalibratedRp")
.finish_non_exhaustive()
}
}
impl<D> fmt::Debug for CydTouchRp<D> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CydTouchRp")
.field("calibration_config", &self.calibration_config)
.finish_non_exhaustive()
}
}
impl fmt::Debug for CydRp {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_struct("CydRp").finish_non_exhaustive()
}
}
impl fmt::Debug for CydRpUncalibrated {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CydRpUncalibrated")
.finish_non_exhaustive()
}
}
impl<D: SpiDevice<u8>> CydDisplay for CydDisplayRp<D> {
type Error = Error;
type Frame<'a>
= CydFrameRp<'a, D>
where
Self: 'a;
#[inline]
fn screen_size(&self) -> Size {
self.display.size()
}
fn background_color(&self) -> Rgb888 {
self.background_color
}
fn foreground_color(&self) -> Rgb888 {
self.foreground_color
}
fn background_565(&self) -> Rgb565 {
self.background565
}
fn foreground_565(&self) -> Rgb565 {
self.foreground565
}
fn frame_mut_with_tile_top_left(
&mut self,
rectangle: Rectangle,
tile_top_left: Point,
) -> CydFrameRp<'_, D> {
self.display.make_frame_with_tile_top_left(
&mut *self.pixel_buffer,
rectangle,
tile_top_left,
self.background565,
self.foreground565,
self.font,
)
}
#[inline]
fn fill_rectangle(&mut self, rectangle: Rectangle, color: Rgb565) -> Result<(), Error> {
Ok(self.display.fill_rectangle(rectangle, color)?)
}
#[inline]
fn fill_contiguous<I>(&mut self, rectangle: Rectangle, pixels: I) -> Result<(), Error>
where
I: IntoIterator<Item = Rgb565>,
{
Ok(self.display.fill_contiguous(rectangle, pixels)?)
}
#[inline]
fn flush_at(&mut self, buffer: &impl RectanglePixels, top_left: Point) -> Result<(), Error> {
Ok(self.display.flush_buffer(buffer, top_left)?)
}
}
impl<D: SpiDevice<u8>> CydTouchUncalibrated for CydTouchUncalibratedRp<D> {
type Error = Error;
type Calibrated = CydTouchRp<D>;
fn read_raw_touch_event(&mut self) -> Result<Option<RawTouchEvent>, Self::Error> {
Ok(self.touch.read_raw_touch_event())
}
fn calibrate(self, calibration_config: CalibrationConfig) -> Self::Calibrated {
CydTouchRp {
raw: self,
calibration_config,
}
}
}
impl<D: SpiDevice<u8>> CydTouch for CydTouchRp<D> {
type Error = Error;
type Uncalibrated = CydTouchUncalibratedRp<D>;
fn read(&mut self) -> Result<Option<TouchEvent>, Error> {
Ok(self
.raw
.touch
.read_raw_touch_event()
.map(|raw_touch_event| match raw_touch_event {
RawTouchEvent::Down { raw_x, raw_y } => {
let (x, y) = self.calibration_config.map_raw_to_screen(raw_x, raw_y);
TouchEvent::Down {
point: Point::new(x as i32, y as i32),
}
}
RawTouchEvent::Move { raw_x, raw_y } => {
let (x, y) = self.calibration_config.map_raw_to_screen(raw_x, raw_y);
TouchEvent::Move {
point: Point::new(x as i32, y as i32),
}
}
RawTouchEvent::Up => TouchEvent::Up,
}))
}
fn calibration_config(&self) -> CalibrationConfig {
self.calibration_config
}
fn decalibrate(self) -> Self::Uncalibrated {
self.raw
}
}
impl<D: SpiDevice<u8>> CydFrame for CydFrameRp<'_, D> {
type Error = Error;
fn tile_top_left(&self) -> Point {
self.tile_top_left
}
fn rectangle(&self) -> Rectangle {
self.rectangle
}
fn fill(&mut self, color: Rgb565) -> &mut Self {
CydFrameRp::fill(self, color)
}
fn clear(&mut self) -> &mut Self {
self.fill(self.background565)
}
fn write_text(&mut self, text: &str) -> &mut Self {
CydFrameRp::write_text(self, text)
}
fn copy_from_565(&mut self, src: &[u16]) -> device_envoy_core::Result<()> {
let dst = self.raw_pixels_mut();
if dst.len() != src.len() {
return Err(device_envoy_core::Error::CopySize {
src_len: src.len(),
frame_len: dst.len(),
});
}
dst.copy_from_slice(src);
Ok(())
}
async fn flush(&mut self) -> Result<(), Error> {
CydFrameRp::flush(self)
}
}