diff --git a/Readme.md b/Readme.md index 03f74f9..2ae3aab 100644 --- a/Readme.md +++ b/Readme.md @@ -8,7 +8,7 @@ The code is currently tailored to: - U-Blox M10Q GNSS (connected on GPIO12 (RX), GPIO11 (TX), and GPIO25 (reset)) or Seeed XIAO L67K (connected on GPIO12 (RX), GPIO11 (TX), GPIO1 (wakeup) and GPIO25 (reset)) only available with `gnss_ubx` or `gnss_l67k` feature (which is included in `spat` feature) -- Some ST7789 240*280px screen (connected on GPIO8 (SCL/ SCK), GPIO10 (SDA/ MOSI), GPIO7 (reset), GPIO23 (DC), GPIO24 (CS), GPIO0 (BL)) +- Some ST7789 172*320px screen (connected on GPIO8 (SCL/ SCK), GPIO10 (SDA/ MOSI), GPIO7 (reset), GPIO23 (DC), GPIO24 (CS), TODO GPIO0 (BL)) only available with `screen` feature (which is included in `spat` feature) ## Features diff --git a/src/main.rs b/src/main.rs index 1ebfb06..67e181c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -127,8 +127,8 @@ async fn main(spawner: Spawner) -> ! { let mut spi_buffer = [0_u8; 512]; #[cfg(feature = "screen")] let mut screen = { - // 240*280px in portrait orientation - let (display, size) = screen::make_large_display( + // 172*320px in landscape orientation + let (display, width, height) = screen::make_small_display( peripherals.GPIO8, peripherals.GPIO10, peripherals.GPIO7, @@ -140,7 +140,7 @@ async fn main(spawner: Spawner) -> ! { ) .expect("Fatal error building screen"); - let mut screen = screen::Handler::new(display, size); + let mut screen = screen::Handler::new(display, width, height); let _ = screen .update(screen::ScreenState::Initial) diff --git a/src/screen.rs b/src/screen.rs index a0968f7..b2ddf90 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -5,19 +5,13 @@ use embedded_graphics::{geometry, mono_font, pixelcolor, primitives, text}; use crate::applogic; -pub struct DisplaySize { - width: u16, - height: u16, - offset_top: i32, - corner_radius: i32, -} - pub struct Handler where D: DrawTarget, { target: D, - size: DisplaySize, + width: u16, + height: u16, // internal state prev_state: ScreenState, @@ -88,10 +82,11 @@ where const SMALL_TEXT_STYLE: mono_font::MonoTextStyle<'static, pixelcolor::Rgb565> = mono_font::MonoTextStyle::new(&profont::PROFONT_14_POINT, Self::COLOR_FG); - pub fn new(target: D, display_size: DisplaySize) -> Self { + pub fn new(target: D, width: u16, height: u16) -> Self { Self { target, - size: display_size, + width, + height, prev_state: ScreenState::Uninitialized, hb_state: false, } @@ -120,19 +115,18 @@ where // Update indicator/ heartbeat { let update_ind_dia = 10; - let circle_x_pos = - i32::from(self.size.width) - update_ind_dia - self.size.corner_radius; + let circle_x_pos = i32::from(self.width) - update_ind_dia - 10; text::Text::with_alignment( "HB", - geometry::Point::new(circle_x_pos - 5, 10 + self.size.offset_top), + geometry::Point::new(circle_x_pos - 5, 10), Self::SMALL_TEXT_STYLE, text::Alignment::Right, ) .draw(&mut self.target)?; primitives::Circle::new( - geometry::Point::new(circle_x_pos, 2 + self.size.offset_top), + geometry::Point::new(circle_x_pos, 2), update_ind_dia.cast_unsigned(), ) .into_styled(if self.hb_state { @@ -155,7 +149,7 @@ where { primitives::Rectangle::new( geometry::Point::new(0, 0), - geometry::Size::new(self.size.width.into(), self.size.height.into()), + geometry::Size::new(self.width.into(), self.height.into()), ) .into_styled(Self::BK_STYLE) .draw(&mut self.target)?; @@ -177,7 +171,7 @@ where text::Text::with_alignment( msg, - geometry::Point::new(i32::from(self.size.width) / 2, 40 + self.size.offset_top), + geometry::Point::new(i32::from(self.width) / 2, 40), Self::DEFAULT_TEXT_STYLE, text::Alignment::Center, ) @@ -192,8 +186,8 @@ where self.clear()?; text::Text::with_alignment( - "Searching...", - geometry::Point::new(i32::from(self.size.width) / 2, 130 + self.size.offset_top), + "No upcoming SPAT", + geometry::Point::new(i32::from(self.width) / 2, 130), Self::DEFAULT_TEXT_STYLE, text::Alignment::Center, ) @@ -215,7 +209,7 @@ where text::Text::with_alignment( &text, - geometry::Point::new(i32::from(self.size.width) / 2, 130 + self.size.offset_top), + geometry::Point::new(i32::from(self.width) / 2, 130), Self::DEFAULT_TEXT_STYLE, text::Alignment::Center, ) @@ -241,28 +235,23 @@ where // only clear maneuver and timing area otherwise primitives::Rectangle::new( - geometry::Point::new(0, 105 + self.size.offset_top), - geometry::Size::new(self.size.width.into(), 67), + geometry::Point::new(0, 105), + geometry::Size::new(self.width.into(), 67), ) .into_styled(Self::BK_STYLE) .draw(&mut self.target)?; } - // do nothing more when no signals - if num_signals == 0 { - return Ok(()); - } - // draw intersection ID text::Text::new( &alloc::format!("#{}", data.intersection_id), - geometry::Point::new(self.size.corner_radius + 5, 10 + self.size.offset_top), + geometry::Point::new(15, 10), Self::SMALL_TEXT_STYLE, ) .draw(&mut self.target)?; #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] - let sig_width = i32::from(self.size.width) / (num_signals as i32); + let sig_width = i32::from(self.width) / (num_signals as i32); for (idx, sig) in data.signal_groups.iter().enumerate() { #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] @@ -280,7 +269,7 @@ where applogic::SignalPhase::RedYellow | applogic::SignalPhase::Yellow => Self::YE_STYLE, }; primitives::Circle::new( - geometry::Point::new(offset + circle_margin, 15 + self.size.offset_top), + geometry::Point::new(offset + circle_margin, 15), circle_dia.cast_unsigned(), ) .into_styled(style) @@ -291,7 +280,7 @@ where // draw maneuver text::Text::with_alignment( &maneuver_to_str(sig.maneuver), - geometry::Point::new(centerline, 130 + self.size.offset_top), + geometry::Point::new(centerline, 130), Self::DEFAULT_TEXT_STYLE, text::Alignment::Center, ) @@ -301,7 +290,7 @@ where if let Some(end_sec) = sig.end_sec { text::Text::with_alignment( &alloc::format!("{end_sec}s"), - geometry::Point::new(centerline, 160 + self.size.offset_top), + geometry::Point::new(centerline, 160), Self::DEFAULT_TEXT_STYLE, text::Alignment::Center, ) @@ -318,7 +307,7 @@ where self.clear()?; - let sig_width = i32::from(self.size.width) / 3; + let sig_width = i32::from(self.width) / 3; let circle_dia = 50; let circle_margin = (sig_width - circle_dia) / 2; @@ -330,7 +319,7 @@ where let offset = (idx as i32) * sig_width; primitives::Circle::new( - geometry::Point::new(offset + circle_margin, 15 + self.size.offset_top), + geometry::Point::new(offset + circle_margin, 15), circle_dia.cast_unsigned(), ) .into_styled(*style) @@ -338,10 +327,10 @@ where } // draw welcome text - let centerline = i32::from(self.size.width) / 2; + let centerline = i32::from(self.width) / 2; text::Text::with_alignment( - "C-ITS Signal Phase\nWaiting for GNSS...", - geometry::Point::new(centerline, 130 + self.size.offset_top), + "C-ITS Signal Phase Display\nWaiting for GNSS fix...", + geometry::Point::new(centerline, 130), text_style, text::Alignment::Center, ) @@ -389,7 +378,8 @@ pub fn make_small_display( mipidsi::models::ST7789, esp_hal::gpio::Output<'_>, >, - DisplaySize, + u16, + u16, ), alloc::string::String, > @@ -406,15 +396,10 @@ where esp_hal::gpio::interconnect::PeripheralOutput<'static> + esp_hal::gpio::OutputPin + 'static, Spi: esp_hal::spi::master::Instance + 'static, { + const DISPLAY_WIDTH: u16 = 320; + const DISPLAY_HEIGHT: u16 = 172; const DISPLAY_HIDDEN_X: u16 = 34; // somehow this display has 34 px of invisible space on the left (in native orientation) - let size = DisplaySize { - width: 320, - height: 172, - offset_top: 0, - corner_radius: 10, - }; - let (spi, cs) = setup_st7789_spi(scl, sda, cs, spi) .map_err(|err| alloc::format!("Failed to build SPI config: {err}"))?; @@ -428,101 +413,18 @@ where let spi_device = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs) .map_err(|err| alloc::format!("Failed to initialize SPI device: {err}"))?; - // Note: height and width are swapped b/c of 90° display rotation let display = mipidsi::Builder::new( mipidsi::models::ST7789, mipidsi::interface::SpiInterface::new(spi_device, dc_output, spi_buffer), ) - .display_size(size.height + DISPLAY_HIDDEN_X, size.width) + .display_size(DISPLAY_HEIGHT + DISPLAY_HIDDEN_X, DISPLAY_WIDTH) .reset_pin(rst_output) .invert_colors(mipidsi::options::ColorInversion::Inverted) .orientation(mipidsi::options::Orientation::new().rotate(mipidsi::options::Rotation::Deg90)) .init(&mut display_delay) .map_err(|err| alloc::format!("Failed to initialize display: {err:?}"))?; - Ok((display, size)) -} - -/// Creates an [`mipidsi::Display`], width, height tuple for an ST7789 240*280px screen in portrait orientation -/// -/// # Errors -/// Human-readable fatal errors -#[allow(clippy::too_many_arguments, clippy::type_complexity)] -pub fn make_large_display( - scl: IoScl, - sda: IoSda, - res: IoRes, - dc: IoDc, - cs: IoCs, - bl: IoBl, - spi: Spi, - spi_buffer: &mut [u8], -) -> Result< - ( - mipidsi::Display< - mipidsi::interface::SpiInterface< - '_, - embedded_hal_bus::spi::ExclusiveDevice< - esp_hal::spi::master::Spi<'_, esp_hal::Blocking>, - esp_hal::gpio::Output<'_>, - embedded_hal_bus::spi::NoDelay, - >, - esp_hal::gpio::Output<'_>, - >, - mipidsi::models::ST7789, - esp_hal::gpio::Output<'_>, - >, - DisplaySize, - ), - alloc::string::String, -> -where - IoScl: esp_hal::gpio::interconnect::PeripheralOutput<'static>, - IoSda: esp_hal::gpio::interconnect::PeripheralOutput<'static>, - IoRes: - esp_hal::gpio::interconnect::PeripheralOutput<'static> + esp_hal::gpio::OutputPin + 'static, - IoDc: - esp_hal::gpio::interconnect::PeripheralOutput<'static> + esp_hal::gpio::OutputPin + 'static, - IoCs: - esp_hal::gpio::interconnect::PeripheralOutput<'static> + esp_hal::gpio::OutputPin + 'static, - IoBl: - esp_hal::gpio::interconnect::PeripheralOutput<'static> + esp_hal::gpio::OutputPin + 'static, - Spi: esp_hal::spi::master::Instance + 'static, -{ - const DISPLAY_HIDDEN_Y: u16 = 20; // somehow this display has 20 px of invisible space on the top (in native orientation) - - let size = DisplaySize { - width: 240, - height: 280 + DISPLAY_HIDDEN_Y, - offset_top: DISPLAY_HIDDEN_Y.into(), - corner_radius: 30, - }; - - let (spi, cs) = setup_st7789_spi(scl, sda, cs, spi) - .map_err(|err| alloc::format!("Failed to build SPI config: {err}"))?; - - let output_config = esp_hal::gpio::OutputConfig::default(); - let dc_output = esp_hal::gpio::Output::new(dc, esp_hal::gpio::Level::Low, output_config); - let rst_output = esp_hal::gpio::Output::new(res, esp_hal::gpio::Level::High, output_config); - let _ = esp_hal::gpio::Output::new(bl, esp_hal::gpio::Level::High, output_config); // enable backlight - - let mut display_delay = esp_hal::delay::Delay::new(); - - let spi_device = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs) - .map_err(|err| alloc::format!("Failed to initialize SPI device: {err}"))?; - - let display = mipidsi::Builder::new( - mipidsi::models::ST7789, - mipidsi::interface::SpiInterface::new(spi_device, dc_output, spi_buffer), - ) - .display_size(size.width, size.height + DISPLAY_HIDDEN_Y) - .reset_pin(rst_output) - .invert_colors(mipidsi::options::ColorInversion::Inverted) - .orientation(mipidsi::options::Orientation::new().rotate(mipidsi::options::Rotation::Deg0)) - .init(&mut display_delay) - .map_err(|err| alloc::format!("Failed to initialize display: {err:?}"))?; - - Ok((display, size)) + Ok((display, DISPLAY_WIDTH, DISPLAY_HEIGHT)) } /// Builds the SPI interface for the ST7789 display