Adapt to new screen, add more GNSS and timing info
This commit is contained in:
parent
38434b0d86
commit
1a5012dec5
4 changed files with 299 additions and 68 deletions
|
|
@ -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 172*320px screen (connected on GPIO8 (SCL/ SCK), GPIO10 (SDA/ MOSI), GPIO7 (reset), GPIO23 (DC), GPIO24 (CS), TODO GPIO0 (BL))
|
||||
- Some ST7789 240*280px screen (connected on GPIO8 (SCL/ SCK), GPIO10 (SDA/ MOSI), GPIO7 (reset), GPIO23 (DC), GPIO24 (CS), GPIO0 (BL))
|
||||
only available with `screen` feature (which is included in `spat` feature)
|
||||
|
||||
## Features
|
||||
|
|
|
|||
|
|
@ -106,7 +106,51 @@ pub struct GlosaSignalInfo {
|
|||
pub struct SignalGroupData {
|
||||
pub phase: SignalPhase,
|
||||
pub maneuver: map::Maneuvers,
|
||||
pub end_sec: Option<i16>,
|
||||
pub timing: Option<TimingData>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "spat")]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
pub struct TimingData {
|
||||
pub min_end_sec: i16,
|
||||
pub likely_end_sec: Option<i16>,
|
||||
pub max_end_sec: Option<i16>,
|
||||
}
|
||||
|
||||
impl TimingData {
|
||||
pub fn new(value: &map::SignalGroup, now: chrono::DateTime<chrono::Utc>) -> Option<Self> {
|
||||
let min = value
|
||||
.min_end_time()
|
||||
.map(|val| Self::time_to_duration_sec(now, val));
|
||||
let likely_end_sec = value
|
||||
.likely_time()
|
||||
.map(|val| Self::time_to_duration_sec(now, val));
|
||||
let max_end_sec = value
|
||||
.max_end_time()
|
||||
.map(|val| Self::time_to_duration_sec(now, val));
|
||||
|
||||
min.map(|min_end_sec| Self {
|
||||
min_end_sec,
|
||||
likely_end_sec,
|
||||
max_end_sec,
|
||||
})
|
||||
}
|
||||
|
||||
fn time_to_duration_sec(
|
||||
now: chrono::DateTime<chrono::Utc>,
|
||||
time_point: chrono::DateTime<chrono::Utc>,
|
||||
) -> i16 {
|
||||
use core::ops::Sub as _;
|
||||
|
||||
use num_traits::Float as _;
|
||||
|
||||
let dur = time_point.sub(now).as_seconds_f32();
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let duration_sec = dur.round() as i16;
|
||||
|
||||
duration_sec
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "spat")]
|
||||
|
|
@ -357,33 +401,17 @@ impl State {
|
|||
let sig_grps = approach_sig_grps
|
||||
.iter()
|
||||
.filter_map(|sig| {
|
||||
if let Some(manv) = sig.maneuvers() {
|
||||
use core::ops::Sub;
|
||||
|
||||
use num_traits::Float;
|
||||
|
||||
if let Some(maneuver) = sig.maneuvers() {
|
||||
let phase = sig
|
||||
.phase()
|
||||
.map(core::convert::Into::into)
|
||||
.unwrap_or_default();
|
||||
|
||||
let end_sec = sig
|
||||
.likely_time()
|
||||
.or_else(|| sig.min_end_time())
|
||||
.map(|time| {
|
||||
let dur =
|
||||
time.sub(self.time.and_utc()).as_seconds_f32();
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let end_sec = dur.round() as i16;
|
||||
|
||||
end_sec
|
||||
});
|
||||
let timing = TimingData::new(sig, self.time.and_utc());
|
||||
|
||||
Some(SignalGroupData {
|
||||
phase,
|
||||
maneuver: manv,
|
||||
end_sec,
|
||||
maneuver,
|
||||
timing,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
20
src/main.rs
20
src/main.rs
|
|
@ -154,8 +154,8 @@ async fn main(spawner: Spawner) -> ! {
|
|||
let mut spi_buffer = [0_u8; 512];
|
||||
#[cfg(feature = "screen")]
|
||||
let mut screen = {
|
||||
// 172*320px in landscape orientation
|
||||
let (display, width, height) = screen::make_small_display(
|
||||
// 240*280px in portrait orientation
|
||||
let (display, size) = screen::make_large_display(
|
||||
peripherals.GPIO8,
|
||||
peripherals.GPIO10,
|
||||
peripherals.GPIO7,
|
||||
|
|
@ -167,7 +167,7 @@ async fn main(spawner: Spawner) -> ! {
|
|||
)
|
||||
.expect("Fatal error building screen");
|
||||
|
||||
let mut screen = screen::Handler::new(display, width, height);
|
||||
let mut screen = screen::Handler::new(display, size);
|
||||
|
||||
let _ = screen
|
||||
.update(screen::ScreenState::Initial)
|
||||
|
|
@ -381,6 +381,15 @@ async fn main(spawner: Spawner) -> ! {
|
|||
}
|
||||
Err(err) => warn!("{err}"),
|
||||
}
|
||||
|
||||
// update GNSS HB on screen
|
||||
let _ = screen.update_gnss(pvt);
|
||||
}
|
||||
ublox::proto27::PacketRef::AckNak(nak) => {
|
||||
error!("UBX: {nak:?}");
|
||||
}
|
||||
ublox::proto27::PacketRef::AckAck(ack) => {
|
||||
info!("UBX: {ack:?}");
|
||||
}
|
||||
_ => {
|
||||
println!("UBX: {packet_ref:?}");
|
||||
|
|
@ -460,8 +469,9 @@ async fn main(spawner: Spawner) -> ! {
|
|||
if let applogic::GlosaOutput::Locked(data) = &glosa_data {
|
||||
for sig in &data.signal_groups {
|
||||
let duration_str = sig
|
||||
.end_sec
|
||||
.map(|v| alloc::format!("for {v} s"))
|
||||
.timing
|
||||
.as_ref()
|
||||
.map(|v| alloc::format!("for {} s", v.min_end_sec))
|
||||
.unwrap_or_default();
|
||||
println!("GLOSA: {} {} {duration_str}", sig.maneuver, sig.phase);
|
||||
}
|
||||
|
|
|
|||
277
src/screen.rs
277
src/screen.rs
|
|
@ -5,17 +5,24 @@ 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<D>
|
||||
where
|
||||
D: DrawTarget<Color = pixelcolor::Rgb565>,
|
||||
{
|
||||
target: D,
|
||||
width: u16,
|
||||
height: u16,
|
||||
size: DisplaySize,
|
||||
|
||||
// internal state
|
||||
prev_state: ScreenState,
|
||||
hb_state: bool,
|
||||
gnss_hb_state: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
|
|
@ -79,16 +86,20 @@ where
|
|||
|
||||
const DEFAULT_TEXT_STYLE: mono_font::MonoTextStyle<'static, pixelcolor::Rgb565> =
|
||||
mono_font::MonoTextStyle::new(&profont::PROFONT_24_POINT, Self::COLOR_FG);
|
||||
const MID_TEXT_STYLE: mono_font::MonoTextStyle<'static, pixelcolor::Rgb565> =
|
||||
mono_font::MonoTextStyle::new(&profont::PROFONT_18_POINT, Self::COLOR_FG);
|
||||
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, width: u16, height: u16) -> Self {
|
||||
const BIG_HEIGHT_THLD: u16 = 172;
|
||||
|
||||
pub fn new(target: D, display_size: DisplaySize) -> Self {
|
||||
Self {
|
||||
target,
|
||||
width,
|
||||
height,
|
||||
size: display_size,
|
||||
prev_state: ScreenState::Uninitialized,
|
||||
hb_state: false,
|
||||
gnss_hb_state: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,18 +126,19 @@ where
|
|||
// Update indicator/ heartbeat
|
||||
{
|
||||
let update_ind_dia = 10;
|
||||
let circle_x_pos = i32::from(self.width) - update_ind_dia - 10;
|
||||
let circle_x_pos =
|
||||
i32::from(self.size.width) - update_ind_dia - self.size.corner_radius;
|
||||
|
||||
text::Text::with_alignment(
|
||||
"HB",
|
||||
geometry::Point::new(circle_x_pos - 5, 10),
|
||||
geometry::Point::new(circle_x_pos - 5, 10 + self.size.offset_top),
|
||||
Self::SMALL_TEXT_STYLE,
|
||||
text::Alignment::Right,
|
||||
)
|
||||
.draw(&mut self.target)?;
|
||||
|
||||
primitives::Circle::new(
|
||||
geometry::Point::new(circle_x_pos, 2),
|
||||
geometry::Point::new(circle_x_pos, 2 + self.size.offset_top),
|
||||
update_ind_dia.cast_unsigned(),
|
||||
)
|
||||
.into_styled(if self.hb_state {
|
||||
|
|
@ -143,13 +155,67 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// on big screns: uses full screen width and height from 250 to 280 pixels
|
||||
pub fn update_gnss(
|
||||
&mut self,
|
||||
pvt: &ublox::nav_pvt::proto27::NavPvtRef,
|
||||
) -> Result<(), D::Error> {
|
||||
use alloc::string::ToString as _;
|
||||
|
||||
// this only applies to big screens
|
||||
if !self.is_big_height() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let num_sat = pvt.num_satellites();
|
||||
let fix_str = match pvt.fix_type() {
|
||||
ublox::GnssFixType::NoFix
|
||||
| ublox::GnssFixType::DeadReckoningOnly
|
||||
| ublox::GnssFixType::TimeOnlyFix => {
|
||||
alloc::format!("{num_sat:2} sat")
|
||||
}
|
||||
ublox::GnssFixType::Fix2D => "2D fix".to_string(),
|
||||
ublox::GnssFixType::Fix3D | ublox::GnssFixType::GPSPlusDeadReckoning => {
|
||||
"3D fix".to_string()
|
||||
}
|
||||
_ => " ".to_string(),
|
||||
};
|
||||
|
||||
// clear area and write information
|
||||
// note: text position is x baseline, not top left corner
|
||||
primitives::Rectangle::new(
|
||||
geometry::Point::new(0, 250 + self.size.offset_top),
|
||||
geometry::Size::new(self.size.width.into(), 20),
|
||||
)
|
||||
.into_styled(Self::BK_STYLE)
|
||||
.draw(&mut self.target)?;
|
||||
|
||||
let colon = if self.gnss_hb_state { ":" } else { " " };
|
||||
let message = alloc::format!(
|
||||
"{fix_str}{colon} {:2.0}km/h, {:3.0}°",
|
||||
pvt.ground_speed_2d() * 3.6,
|
||||
pvt.heading_motion()
|
||||
);
|
||||
text::Text::with_alignment(
|
||||
&message,
|
||||
geometry::Point::new(self.size.corner_radius / 2, 265 + self.size.offset_top),
|
||||
Self::SMALL_TEXT_STYLE,
|
||||
text::Alignment::Left,
|
||||
)
|
||||
.draw(&mut self.target)?;
|
||||
|
||||
self.gnss_hb_state = !self.gnss_hb_state;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear(&mut self) -> Result<(), D::Error>
|
||||
where
|
||||
D: DrawTarget<Color = pixelcolor::Rgb565>,
|
||||
{
|
||||
primitives::Rectangle::new(
|
||||
geometry::Point::new(0, 0),
|
||||
geometry::Size::new(self.width.into(), self.height.into()),
|
||||
geometry::Size::new(self.size.width.into(), self.size.height.into()),
|
||||
)
|
||||
.into_styled(Self::BK_STYLE)
|
||||
.draw(&mut self.target)?;
|
||||
|
|
@ -157,6 +223,10 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn is_big_height(&self) -> bool {
|
||||
self.size.height > Self::BIG_HEIGHT_THLD
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn test_img(&mut self) -> Result<(), D::Error> {
|
||||
mipidsi::TestImage::new().draw(&mut self.target)?;
|
||||
|
|
@ -171,7 +241,7 @@ where
|
|||
|
||||
text::Text::with_alignment(
|
||||
msg,
|
||||
geometry::Point::new(i32::from(self.width) / 2, 40),
|
||||
geometry::Point::new(i32::from(self.size.width) / 2, 40 + self.size.offset_top),
|
||||
Self::DEFAULT_TEXT_STYLE,
|
||||
text::Alignment::Center,
|
||||
)
|
||||
|
|
@ -186,8 +256,8 @@ where
|
|||
self.clear()?;
|
||||
|
||||
text::Text::with_alignment(
|
||||
"No upcoming SPAT",
|
||||
geometry::Point::new(i32::from(self.width) / 2, 130),
|
||||
"Searching...",
|
||||
geometry::Point::new(i32::from(self.size.width) / 2, 130 + self.size.offset_top),
|
||||
Self::DEFAULT_TEXT_STYLE,
|
||||
text::Alignment::Center,
|
||||
)
|
||||
|
|
@ -209,7 +279,7 @@ where
|
|||
|
||||
text::Text::with_alignment(
|
||||
&text,
|
||||
geometry::Point::new(i32::from(self.width) / 2, 130),
|
||||
geometry::Point::new(i32::from(self.size.width) / 2, 130 + self.size.offset_top),
|
||||
Self::DEFAULT_TEXT_STYLE,
|
||||
text::Alignment::Center,
|
||||
)
|
||||
|
|
@ -218,6 +288,9 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// uses full screen width and height
|
||||
// - from 0 to 105+67 = 172 pixels for small screens
|
||||
// - from 0 to 105+67+20 = 192 pixels for big screens
|
||||
#[allow(unused)]
|
||||
fn draw_glosa(
|
||||
&mut self,
|
||||
|
|
@ -226,6 +299,8 @@ where
|
|||
) -> Result<(), D::Error> {
|
||||
let num_signals = data.signal_groups.len();
|
||||
|
||||
let is_big_height = self.is_big_height();
|
||||
|
||||
if let Some(prev) = prev_data
|
||||
&& prev.signal_groups.len() != num_signals
|
||||
{
|
||||
|
|
@ -233,25 +308,30 @@ where
|
|||
self.clear();
|
||||
} else {
|
||||
// only clear maneuver and timing area otherwise
|
||||
|
||||
let sig_total_height = 67 + if is_big_height { 20 } else { 0 };
|
||||
primitives::Rectangle::new(
|
||||
geometry::Point::new(0, 105),
|
||||
geometry::Size::new(self.width.into(), 67),
|
||||
geometry::Point::new(0, 105 + self.size.offset_top),
|
||||
geometry::Size::new(self.size.width.into(), sig_total_height),
|
||||
)
|
||||
.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(15, 10),
|
||||
geometry::Point::new(self.size.corner_radius + 5, 10 + self.size.offset_top),
|
||||
Self::SMALL_TEXT_STYLE,
|
||||
)
|
||||
.draw(&mut self.target)?;
|
||||
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||
let sig_width = i32::from(self.width) / (num_signals as i32);
|
||||
let sig_width = i32::from(self.size.width) / (num_signals as i32);
|
||||
|
||||
for (idx, sig) in data.signal_groups.iter().enumerate() {
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||
|
|
@ -269,7 +349,7 @@ where
|
|||
applogic::SignalPhase::RedYellow | applogic::SignalPhase::Yellow => Self::YE_STYLE,
|
||||
};
|
||||
primitives::Circle::new(
|
||||
geometry::Point::new(offset + circle_margin, 15),
|
||||
geometry::Point::new(offset + circle_margin, 15 + self.size.offset_top),
|
||||
circle_dia.cast_unsigned(),
|
||||
)
|
||||
.into_styled(style)
|
||||
|
|
@ -280,24 +360,50 @@ where
|
|||
// draw maneuver
|
||||
text::Text::with_alignment(
|
||||
&maneuver_to_str(sig.maneuver),
|
||||
geometry::Point::new(centerline, 130),
|
||||
geometry::Point::new(centerline, 130 + self.size.offset_top),
|
||||
Self::DEFAULT_TEXT_STYLE,
|
||||
text::Alignment::Center,
|
||||
)
|
||||
.draw(&mut self.target)?;
|
||||
|
||||
// draw timing indicator
|
||||
if let Some(end_sec) = sig.end_sec {
|
||||
text::Text::with_alignment(
|
||||
&alloc::format!("{end_sec}s"),
|
||||
geometry::Point::new(centerline, 160),
|
||||
Self::DEFAULT_TEXT_STYLE,
|
||||
text::Alignment::Center,
|
||||
)
|
||||
.draw(&mut self.target)?;
|
||||
// draw timing indicator(s)
|
||||
if let Some(timing) = &sig.timing {
|
||||
if let (Some(likely), Some(max)) = (timing.likely_end_sec, timing.max_end_sec) {
|
||||
text::Text::with_alignment(
|
||||
&alloc::format!("{likely}s"),
|
||||
geometry::Point::new(centerline, 160 + self.size.offset_top),
|
||||
Self::DEFAULT_TEXT_STYLE,
|
||||
text::Alignment::Center,
|
||||
)
|
||||
.draw(&mut self.target)?;
|
||||
|
||||
// only for big screen
|
||||
if is_big_height {
|
||||
// TODO: show as diff to likely time?
|
||||
text::Text::with_alignment(
|
||||
&alloc::format!("{}-{max}", timing.min_end_sec),
|
||||
geometry::Point::new(centerline, 185 + self.size.offset_top),
|
||||
Self::MID_TEXT_STYLE,
|
||||
text::Alignment::Center,
|
||||
)
|
||||
.draw(&mut self.target)?;
|
||||
}
|
||||
} else {
|
||||
text::Text::with_alignment(
|
||||
&alloc::format!("{}s", timing.min_end_sec),
|
||||
geometry::Point::new(centerline, 160 + self.size.offset_top),
|
||||
Self::DEFAULT_TEXT_STYLE,
|
||||
text::Alignment::Center,
|
||||
)
|
||||
.draw(&mut self.target)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if is_big_height {
|
||||
// // TODO: Add ETA
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -307,7 +413,7 @@ where
|
|||
|
||||
self.clear()?;
|
||||
|
||||
let sig_width = i32::from(self.width) / 3;
|
||||
let sig_width = i32::from(self.size.width) / 3;
|
||||
let circle_dia = 50;
|
||||
let circle_margin = (sig_width - circle_dia) / 2;
|
||||
|
||||
|
|
@ -319,7 +425,7 @@ where
|
|||
let offset = (idx as i32) * sig_width;
|
||||
|
||||
primitives::Circle::new(
|
||||
geometry::Point::new(offset + circle_margin, 15),
|
||||
geometry::Point::new(offset + circle_margin, 15 + self.size.offset_top),
|
||||
circle_dia.cast_unsigned(),
|
||||
)
|
||||
.into_styled(*style)
|
||||
|
|
@ -327,10 +433,10 @@ where
|
|||
}
|
||||
|
||||
// draw welcome text
|
||||
let centerline = i32::from(self.width) / 2;
|
||||
let centerline = i32::from(self.size.width) / 2;
|
||||
text::Text::with_alignment(
|
||||
"C-ITS Signal Phase Display\nWaiting for GNSS fix...",
|
||||
geometry::Point::new(centerline, 130),
|
||||
"C-ITS Signal Phase\nWaiting for GNSS...",
|
||||
geometry::Point::new(centerline, 130 + self.size.offset_top),
|
||||
text_style,
|
||||
text::Alignment::Center,
|
||||
)
|
||||
|
|
@ -353,7 +459,7 @@ fn maneuver_to_str(value: applogic::v2x::map::Maneuvers) -> alloc::string::Strin
|
|||
///
|
||||
/// # Errors
|
||||
/// Human-readable fatal errors
|
||||
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||
#[allow(unused, clippy::too_many_arguments, clippy::type_complexity)]
|
||||
pub fn make_small_display<IoScl, IoSda, IoRes, IoDc, IoCs, IoBl, Spi>(
|
||||
scl: IoScl,
|
||||
sda: IoSda,
|
||||
|
|
@ -378,8 +484,7 @@ pub fn make_small_display<IoScl, IoSda, IoRes, IoDc, IoCs, IoBl, Spi>(
|
|||
mipidsi::models::ST7789,
|
||||
esp_hal::gpio::Output<'_>,
|
||||
>,
|
||||
u16,
|
||||
u16,
|
||||
DisplaySize,
|
||||
),
|
||||
alloc::string::String,
|
||||
>
|
||||
|
|
@ -396,10 +501,98 @@ 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}"))?;
|
||||
|
||||
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}"))?;
|
||||
|
||||
// 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)
|
||||
.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(unused, clippy::too_many_arguments, clippy::type_complexity)]
|
||||
pub fn make_large_display<IoScl, IoSda, IoRes, IoDc, IoCs, IoBl, Spi>(
|
||||
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}"))?;
|
||||
|
||||
|
|
@ -417,14 +610,14 @@ where
|
|||
mipidsi::models::ST7789,
|
||||
mipidsi::interface::SpiInterface::new(spi_device, dc_output, spi_buffer),
|
||||
)
|
||||
.display_size(DISPLAY_HEIGHT + DISPLAY_HIDDEN_X, DISPLAY_WIDTH)
|
||||
.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::Deg90))
|
||||
.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, DISPLAY_WIDTH, DISPLAY_HEIGHT))
|
||||
Ok((display, size))
|
||||
}
|
||||
|
||||
/// Builds the SPI interface for the ST7789 display
|
||||
|
|
|
|||
Loading…
Reference in a new issue