1mod buffer;
49mod display;
50mod one_spi;
51mod text;
52#[path = "cyd/touch.rs"]
53mod touch_driver;
54
55use core::{convert::Infallible, fmt};
56
57use embedded_graphics::{
58 Pixel,
59 mono_font::MonoFont,
60 pixelcolor::{IntoStorage, Rgb565, Rgb888},
61 prelude::{Dimensions, DrawTarget, OriginDimensions, Point, Size},
62 primitives::Rectangle,
63};
64use embedded_hal::spi::SpiDevice;
65use static_cell::StaticCell;
66
67use buffer::DynPixelBuffer;
68use buffer::{PixelBuffer, RegionView};
69use device_envoy_core::button::Button;
70use device_envoy_core::cyd::backend;
71use device_envoy_core::cyd::{
72 SCREEN_PIXELS,
73 backend::{CalibrationConfig, RawTouchEvent, TouchUncalibrated},
74 display::CydFrame,
75 touch::TouchEvent,
76};
77use device_envoy_core::pixel_target::PixelTarget;
78pub use display::DEFAULT_DISPLAY_SPI_HZ;
79pub use device_envoy_core::cyd::{
82 Cyd, CydDisplay, CydTouch,
83 display::{Orientation, tiling},
84 touch,
85};
86pub use one_spi::CydEspOneSpi;
87pub use text::DEFAULT_FONT;
88use touch_driver::TOUCH_SPI_HZ;
89
90use crate::flash_block::FlashBlockEsp;
91use display::CydDisplayEsp as CydDisplayEspDevice;
92use touch_driver::CydTouchEsp as CydTouchEspDevice;
93
94pub struct CydDisplayEsp<D: SpiDevice<u8> = display::CydDisplaySpiDevice> {
105 display: CydDisplayEspDevice<D>,
106 orientation: Orientation,
107 pixel_buffer: &'static mut dyn DynPixelBuffer,
110 background_color: Rgb888,
115 foreground_color: Rgb888,
116 background565: Rgb565,
117 foreground565: Rgb565,
118 font: &'static MonoFont<'static>,
119}
120
121pub(crate) struct CydTouchUncalibratedEsp<D = touch_driver::CydTouchSpiDevice> {
126 touch: CydTouchEspDevice<D>,
127}
128
129pub struct CydTouchEsp<D = touch_driver::CydTouchSpiDevice> {
135 raw: CydTouchUncalibratedEsp<D>,
136 calibration_config: CalibrationConfig,
137 orientation: Orientation,
138}
139
140pub struct CydEsp {
147 pub display: CydDisplayEsp,
149 pub touch: CydTouchEsp,
151}
152
153pub(crate) struct CydEspUncalibrated {
155 pub display: CydDisplayEsp,
157 pub touch: CydTouchUncalibratedEsp,
159}
160
161pub struct CydStaticEsp<const PIXEL_COUNT: usize> {
176 pixel_buffer: StaticCell<PixelBuffer<PIXEL_COUNT>>,
177}
178
179impl<const PIXEL_COUNT: usize> CydStaticEsp<PIXEL_COUNT> {
180 pub(crate) const fn new() -> Self {
183 assert!(
184 PIXEL_COUNT <= SCREEN_PIXELS,
185 "PIXEL_COUNT must not exceed SCREEN_PIXELS"
186 );
187 Self {
188 pixel_buffer: StaticCell::new(),
189 }
190 }
191}
192
193pub struct CydFrameEsp<'a, D: SpiDevice<u8> = display::CydDisplaySpiDevice> {
200 display: &'a mut CydDisplayEspDevice<D>,
201 view: RegionView<'a>,
202 rectangle: Rectangle,
205 pub(crate) background565: Rgb565,
208 pub(crate) foreground565: Rgb565,
209 pub(crate) font: &'static MonoFont<'static>,
210}
211
212impl<'a, D: SpiDevice<u8>> CydFrameEsp<'a, D> {
213 pub fn fill(&mut self, color: Rgb565) -> &mut Self {
219 self.view.fill(color);
220 self
221 }
222
223 #[must_use]
225 pub fn width(&self) -> usize {
226 self.view.width()
227 }
228
229 #[must_use]
231 pub fn height(&self) -> usize {
232 self.view.height()
233 }
234
235 pub fn raw_pixels_mut(&mut self) -> &mut [u16] {
237 self.view.raw_pixels_mut()
238 }
239
240 pub fn flush(&mut self) -> Result<(), Error> {
248 Ok(self.display.flush_buffer(
249 self.view.size().width as usize,
250 self.view.size().height as usize,
251 self.view.raw_pixels(),
252 self.rectangle.top_left,
253 )?)
254 }
255
256 fn local_x(&self, x: i32) -> Option<usize> {
257 usize::try_from(x.checked_sub(self.rectangle.top_left.x)?).ok()
258 }
259
260 fn local_y(&self, y: i32) -> Option<usize> {
261 usize::try_from(y.checked_sub(self.rectangle.top_left.y)?).ok()
262 }
263}
264
265impl<D: SpiDevice<u8>> DrawTarget for CydFrameEsp<'_, D> {
266 type Color = Rgb565;
267 type Error = Infallible;
268
269 fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
270 self.fill(color);
271 Ok(())
272 }
273
274 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
275 where
276 I: IntoIterator<Item = Pixel<Self::Color>>,
277 {
278 for Pixel(point, color) in pixels {
279 let Some(local_x) = self.local_x(point.x) else {
280 continue;
281 };
282 let Some(local_y) = self.local_y(point.y) else {
283 continue;
284 };
285 if local_x < self.view.width() && local_y < self.view.height() {
286 let index = local_y * self.view.width() + local_x;
287 self.raw_pixels_mut()[index] = color.into_storage();
288 }
289 }
290 Ok(())
291 }
292}
293
294impl<D: SpiDevice<u8>> Dimensions for CydFrameEsp<'_, D> {
295 fn bounding_box(&self) -> Rectangle {
296 self.rectangle
297 }
298}
299
300impl<D: SpiDevice<u8>> PixelTarget for CydFrameEsp<'_, D> {
301 fn width(&self) -> usize {
302 usize::try_from(self.rectangle.top_left.x)
303 .expect("frame top-left x must be non-negative")
304 .checked_add(self.width())
305 .expect("frame width must fit in usize")
306 }
307
308 fn height(&self) -> usize {
309 usize::try_from(self.rectangle.top_left.y)
310 .expect("frame top-left y must be non-negative")
311 .checked_add(self.height())
312 .expect("frame height must fit in usize")
313 }
314
315 fn put_pixel(&mut self, x: usize, y: usize, color: Rgb888) {
316 let Some(local_x) = self.local_x(x as i32) else {
317 return;
318 };
319 let Some(local_y) = self.local_y(y as i32) else {
320 return;
321 };
322 if local_x >= self.view.width() || local_y >= self.view.height() {
323 return;
324 }
325 let stride = self.view.width();
326 self.raw_pixels_mut()[local_y * stride + local_x] = Rgb565::from(color).into_storage();
327 }
328
329 fn put_pixel_565(&mut self, x: usize, y: usize, rgb565: u16) {
332 let Some(local_x) = self.local_x(x as i32) else {
333 return;
334 };
335 let Some(local_y) = self.local_y(y as i32) else {
336 return;
337 };
338 if local_x >= self.view.width() || local_y >= self.view.height() {
339 return;
340 }
341 let stride = self.view.width();
342 self.raw_pixels_mut()[local_y * stride + local_x] = rgb565;
343 }
344}
345
346#[derive(Debug)]
371pub enum Error {
372 ConfigureDisplaySpi(esp_hal::spi::master::ConfigError),
375 InitDisplay,
378 ConfigureTouchSpi(esp_hal::spi::master::ConfigError),
381 FlushFrameBuffer,
384 SetOrientation,
387}
388
389impl<D: SpiDevice<u8>> CydDisplayEsp<D> {
390 fn set_orientation(&mut self, orientation: Orientation) -> Result<(), Error> {
391 self.display.set_orientation(orientation)?;
392 self.orientation = orientation;
393 Ok(())
394 }
395
396 fn from_display_device(
397 mut display: CydDisplayEspDevice<D>,
398 orientation: Orientation,
399 background_color: Rgb888,
400 foreground_color: Rgb888,
401 font: &'static MonoFont<'static>,
402 pixel_buffer: &'static mut dyn DynPixelBuffer,
403 ) -> Result<Self, Error> {
404 let background565 = rgb565(background_color);
405 display.fill(background565)?;
406
407 Ok(Self {
408 display,
409 orientation,
410 pixel_buffer,
411 background_color,
412 foreground_color,
413 background565,
414 foreground565: rgb565(foreground_color),
415 font,
416 })
417 }
418
419 pub(crate) fn new_from_device(
424 spi_device: D,
425 dc_pin: impl esp_hal::gpio::OutputPin + 'static,
426 rst_pin: impl esp_hal::gpio::OutputPin + 'static,
427 backlight_pin: impl esp_hal::gpio::OutputPin + 'static,
428 orientation: Orientation,
429 background_color: Rgb888,
430 foreground_color: Rgb888,
431 font: &'static MonoFont<'static>,
432 pixel_buffer: &'static mut dyn DynPixelBuffer,
433 ) -> Result<Self, Error> {
434 let display = CydDisplayEspDevice::new_from_device(
435 spi_device,
436 dc_pin,
437 rst_pin,
438 backlight_pin,
439 orientation,
440 )?;
441 Self::from_display_device(
442 display,
443 orientation,
444 background_color,
445 foreground_color,
446 font,
447 pixel_buffer,
448 )
449 }
450}
451
452impl CydDisplayEsp<display::CydDisplaySpiDevice> {
453 pub fn new<const PIXEL_COUNT: usize>(
476 statics: &'static CydStaticEsp<PIXEL_COUNT>,
477 display_spi: impl esp_hal::spi::master::Instance + 'static,
478 display_sck_pin: impl esp_hal::gpio::interconnect::PeripheralOutput<'static>,
479 display_mosi_pin: impl esp_hal::gpio::interconnect::PeripheralOutput<'static>,
480 display_miso_pin: impl esp_hal::gpio::interconnect::PeripheralInput<'static>,
481 display_cs_pin: impl esp_hal::gpio::OutputPin + 'static,
482 display_dc_pin: impl esp_hal::gpio::OutputPin + 'static,
483 display_rst_pin: impl esp_hal::gpio::OutputPin + 'static,
484 display_backlight_pin: impl esp_hal::gpio::OutputPin + 'static,
485 display_spi_hz: u32,
486 orientation: Orientation,
487 background_color: Rgb888,
488 foreground_color: Rgb888,
489 font: &'static MonoFont<'static>,
490 ) -> Result<Self, Error> {
491 let pixel_buffer = PixelBuffer::init_static(&statics.pixel_buffer);
492 let display = CydDisplayEspDevice::new(
493 display_spi,
494 display_sck_pin,
495 display_mosi_pin,
496 display_miso_pin,
497 display_cs_pin,
498 display_dc_pin,
499 display_rst_pin,
500 display_backlight_pin,
501 display_spi_hz,
502 orientation,
503 )?;
504 Self::from_display_device(
505 display,
506 orientation,
507 background_color,
508 foreground_color,
509 font,
510 pixel_buffer,
511 )
512 }
513}
514
515impl<D: SpiDevice<u8>> CydTouchUncalibratedEsp<D> {
516 pub(crate) fn from_device(
521 touch_spi_device: D,
522 touch_irq_pin: impl esp_hal::gpio::InputPin + 'static,
523 ) -> Self {
524 Self {
525 touch: CydTouchEspDevice::from_device(touch_spi_device, touch_irq_pin),
526 }
527 }
528}
529
530impl CydTouchUncalibratedEsp<touch_driver::CydTouchSpiDevice> {
531 pub(crate) fn new(
533 touch_spi: impl esp_hal::spi::master::Instance + 'static,
534 touch_sck_pin: impl esp_hal::gpio::interconnect::PeripheralOutput<'static>,
535 touch_mosi_pin: impl esp_hal::gpio::interconnect::PeripheralOutput<'static>,
536 touch_miso_pin: impl esp_hal::gpio::interconnect::PeripheralInput<'static>,
537 touch_cs_pin: impl esp_hal::gpio::OutputPin + 'static,
538 touch_irq_pin: impl esp_hal::gpio::InputPin + 'static,
539 ) -> Result<Self, Error> {
540 Ok(Self {
541 touch: CydTouchEspDevice::new(
542 touch_spi,
543 touch_sck_pin,
544 touch_mosi_pin,
545 touch_miso_pin,
546 touch_cs_pin,
547 touch_irq_pin,
548 )?,
549 })
550 }
551}
552
553impl CydEsp {
554 pub const SCREEN_PIXELS: usize = SCREEN_PIXELS;
558
559 #[must_use]
577 pub const fn new_static<const PIXEL_COUNT: usize>() -> CydStaticEsp<PIXEL_COUNT> {
578 CydStaticEsp::new()
579 }
580
581 pub async fn new<const PIXEL_COUNT: usize, R: Button>(
649 statics: &'static CydStaticEsp<PIXEL_COUNT>,
650 display_spi: impl esp_hal::spi::master::Instance + 'static,
651 display_sck_pin: impl esp_hal::gpio::interconnect::PeripheralOutput<'static>,
652 display_mosi_pin: impl esp_hal::gpio::interconnect::PeripheralOutput<'static>,
653 display_miso_pin: impl esp_hal::gpio::interconnect::PeripheralInput<'static>,
654 display_cs_pin: impl esp_hal::gpio::OutputPin + 'static,
655 display_dc_pin: impl esp_hal::gpio::OutputPin + 'static,
656 display_rst_pin: impl esp_hal::gpio::OutputPin + 'static,
657 display_backlight_pin: impl esp_hal::gpio::OutputPin + 'static,
658 display_spi_hz: u32,
659 orientation: Orientation,
660 background_color: Rgb888,
661 foreground_color: Rgb888,
662 font: &'static MonoFont<'static>,
663 touch_spi: impl esp_hal::spi::master::Instance + 'static,
664 touch_sck_pin: impl esp_hal::gpio::interconnect::PeripheralOutput<'static>,
665 touch_mosi_pin: impl esp_hal::gpio::interconnect::PeripheralOutput<'static>,
666 touch_miso_pin: impl esp_hal::gpio::interconnect::PeripheralInput<'static>,
667 touch_cs_pin: impl esp_hal::gpio::OutputPin + 'static,
668 touch_irq_pin: impl esp_hal::gpio::InputPin + 'static,
669 calibration_flash_block: &mut FlashBlockEsp,
670 recalibration_button: &mut R,
671 ) -> crate::Result<Self> {
672 let CydEspUncalibrated { mut display, touch } = CydEspUncalibrated::new(
673 statics,
674 display_spi,
675 display_sck_pin,
676 display_mosi_pin,
677 display_miso_pin,
678 display_cs_pin,
679 display_dc_pin,
680 display_rst_pin,
681 display_backlight_pin,
682 display_spi_hz,
683 orientation,
684 background_color,
685 foreground_color,
686 font,
687 touch_spi,
688 touch_sck_pin,
689 touch_mosi_pin,
690 touch_miso_pin,
691 touch_cs_pin,
692 touch_irq_pin,
693 )?;
694 let touch = backend::ensure_calibration(
695 &mut display,
696 touch,
697 calibration_flash_block,
698 recalibration_button,
699 None,
700 orientation,
701 )
702 .await
703 .map_err(|error| match error {
704 backend::Error::Device(cyd_error) => crate::Error::from(cyd_error),
705 backend::Error::Flash(flash_error) => flash_error,
706 })?;
707 display.set_orientation(orientation)?;
708 Ok(Self { display, touch })
709 }
710}
711
712impl Cyd for CydEsp {
713 type Error = Error;
714 type Display = CydDisplayEsp;
715 type Touch = CydTouchEsp;
716
717 fn parts(&mut self) -> (&mut Self::Display, &mut Self::Touch) {
718 (&mut self.display, &mut self.touch)
719 }
720
721 fn orientation(&self) -> Orientation {
722 self.display.orientation
723 }
724}
725
726impl CydEspUncalibrated {
727 pub(crate) fn new<const PIXEL_COUNT: usize>(
728 statics: &'static CydStaticEsp<PIXEL_COUNT>,
729 display_spi: impl esp_hal::spi::master::Instance + 'static,
730 display_sck_pin: impl esp_hal::gpio::interconnect::PeripheralOutput<'static>,
731 display_mosi_pin: impl esp_hal::gpio::interconnect::PeripheralOutput<'static>,
732 display_miso_pin: impl esp_hal::gpio::interconnect::PeripheralInput<'static>,
733 display_cs_pin: impl esp_hal::gpio::OutputPin + 'static,
734 display_dc_pin: impl esp_hal::gpio::OutputPin + 'static,
735 display_rst_pin: impl esp_hal::gpio::OutputPin + 'static,
736 display_backlight_pin: impl esp_hal::gpio::OutputPin + 'static,
737 display_spi_hz: u32,
738 _orientation: Orientation,
739 background_color: Rgb888,
740 foreground_color: Rgb888,
741 font: &'static MonoFont<'static>,
742 touch_spi: impl esp_hal::spi::master::Instance + 'static,
743 touch_sck_pin: impl esp_hal::gpio::interconnect::PeripheralOutput<'static>,
744 touch_mosi_pin: impl esp_hal::gpio::interconnect::PeripheralOutput<'static>,
745 touch_miso_pin: impl esp_hal::gpio::interconnect::PeripheralInput<'static>,
746 touch_cs_pin: impl esp_hal::gpio::OutputPin + 'static,
747 touch_irq_pin: impl esp_hal::gpio::InputPin + 'static,
748 ) -> Result<Self, Error> {
749 Ok(Self {
750 display: CydDisplayEsp::new(
751 statics,
752 display_spi,
753 display_sck_pin,
754 display_mosi_pin,
755 display_miso_pin,
756 display_cs_pin,
757 display_dc_pin,
758 display_rst_pin,
759 display_backlight_pin,
760 display_spi_hz,
761 Orientation::Landscape,
762 background_color,
763 foreground_color,
764 font,
765 )?,
766 touch: CydTouchUncalibratedEsp::new(
767 touch_spi,
768 touch_sck_pin,
769 touch_mosi_pin,
770 touch_miso_pin,
771 touch_cs_pin,
772 touch_irq_pin,
773 )?,
774 })
775 }
776}
777
778fn rgb565(color: Rgb888) -> Rgb565 {
779 Rgb565::from(color)
780}
781
782impl<D: SpiDevice<u8>> fmt::Debug for CydDisplayEsp<D> {
783 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
784 formatter
785 .debug_struct("CydDisplayEsp")
786 .field("orientation", &self.orientation)
787 .finish_non_exhaustive()
788 }
789}
790
791impl<D> fmt::Debug for CydTouchUncalibratedEsp<D> {
792 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
793 formatter
794 .debug_struct("CydTouchUncalibratedEsp")
795 .finish_non_exhaustive()
796 }
797}
798
799impl<D> fmt::Debug for CydTouchEsp<D> {
800 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
801 formatter
802 .debug_struct("CydTouchEsp")
803 .field("calibration_config", &self.calibration_config)
804 .field("orientation", &self.orientation)
805 .finish_non_exhaustive()
806 }
807}
808
809impl fmt::Debug for CydEsp {
810 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
811 formatter
812 .debug_struct("CydEsp")
813 .field("orientation", &self.display.orientation)
814 .finish_non_exhaustive()
815 }
816}
817
818impl fmt::Debug for CydEspUncalibrated {
819 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
820 formatter
821 .debug_struct("CydEspUncalibrated")
822 .field("orientation", &self.display.orientation)
823 .finish_non_exhaustive()
824 }
825}
826
827impl<D: SpiDevice<u8>> backend::DisplayBackend for CydDisplayEsp<D> {
828 type Error = Error;
829 type Frame<'a>
830 = CydFrameEsp<'a, D>
831 where
832 Self: 'a;
833
834 fn create_frame_mut(&mut self, rectangle: Rectangle) -> Self::Frame<'_> {
835 self.display.make_frame(
836 self.pixel_buffer,
837 rectangle,
838 self.background565,
839 self.foreground565,
840 self.font,
841 )
842 }
843}
844
845impl<D: SpiDevice<u8>> CydDisplay for CydDisplayEsp<D> {
846 #[inline]
847 fn screen_size(&self) -> Size {
848 self.display.size()
849 }
850
851 fn background_color(&self) -> Rgb888 {
852 self.background_color
853 }
854
855 fn foreground_color(&self) -> Rgb888 {
856 self.foreground_color
857 }
858
859 fn background_565(&self) -> Rgb565 {
860 self.background565
861 }
862
863 fn foreground_565(&self) -> Rgb565 {
864 self.foreground565
865 }
866
867 #[inline]
868 fn fill_rectangle(&mut self, rectangle: Rectangle, color: Rgb565) -> Result<(), Error> {
869 Ok(self.display.fill_rectangle(rectangle, color)?)
870 }
871
872 #[inline]
873 fn fill_contiguous<I>(&mut self, rectangle: Rectangle, pixels: I) -> Result<(), Error>
874 where
875 I: IntoIterator<Item = Rgb565>,
876 {
877 Ok(self.display.fill_contiguous(rectangle, pixels)?)
878 }
879}
880
881impl<D: SpiDevice<u8>> TouchUncalibrated for CydTouchUncalibratedEsp<D> {
882 type Error = Error;
883 type Calibrated = CydTouchEsp<D>;
884
885 fn read_raw_touch_event(&mut self) -> Result<Option<RawTouchEvent>, Self::Error> {
886 Ok(self.touch.read_raw_touch_event())
887 }
888
889 fn calibrate(
890 self,
891 calibration_config: CalibrationConfig,
892 orientation: Orientation,
893 ) -> Self::Calibrated {
894 CydTouchEsp {
895 raw: self,
896 calibration_config,
897 orientation,
898 }
899 }
900}
901
902impl<D: SpiDevice<u8>> CydTouch for CydTouchEsp<D> {
903 type Error = Error;
904
905 fn try_read(&mut self) -> Result<Option<TouchEvent>, Error> {
906 Ok(self
907 .raw
908 .touch
909 .read_raw_touch_event()
910 .map(|raw_touch_event| match raw_touch_event {
911 RawTouchEvent::Down { raw_x, raw_y } => {
912 let (x, y) = self.calibration_config.map_raw_to_screen(raw_x, raw_y);
913 TouchEvent::Down {
914 point: self
915 .orientation
916 .map_landscape_point(Point::new(x as i32, y as i32)),
917 }
918 }
919 RawTouchEvent::Move { raw_x, raw_y } => {
920 let (x, y) = self.calibration_config.map_raw_to_screen(raw_x, raw_y);
921 TouchEvent::Move {
922 point: self
923 .orientation
924 .map_landscape_point(Point::new(x as i32, y as i32)),
925 }
926 }
927 RawTouchEvent::Up => TouchEvent::Up,
928 }))
929 }
930}
931
932impl<D: SpiDevice<u8>> CydFrame for CydFrameEsp<'_, D> {
933 type Error = Error;
934
935 fn rectangle(&self) -> Rectangle {
936 self.rectangle
937 }
938
939 fn fill(&mut self, color: Rgb565) -> &mut Self {
940 CydFrameEsp::fill(self, color)
941 }
942
943 fn clear(&mut self) -> &mut Self {
944 self.fill(self.background565)
945 }
946
947 fn write_text(&mut self, text: &str) -> &mut Self {
948 CydFrameEsp::write_text(self, text)
949 }
950
951 fn copy_from_565(&mut self, src: &[u16]) -> device_envoy_core::Result<()> {
952 let dst = self.raw_pixels_mut();
953 if dst.len() != src.len() {
954 return Err(device_envoy_core::Error::CopySize {
955 src_len: src.len(),
956 frame_len: dst.len(),
957 });
958 }
959 dst.copy_from_slice(src);
960 Ok(())
961 }
962
963 async fn flush(&mut self) -> Result<(), Error> {
967 CydFrameEsp::flush(self)
968 }
969}