diff --git a/Cargo.lock b/Cargo.lock index 6b5b672..d934acf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,7 @@ dependencies = [ "base64 0.23.1", "ch1115", "critical-section", + "display-interface", "display-interface-spi", "embassy-executor", "embassy-futures", @@ -343,6 +344,7 @@ dependencies = [ "log", "num_enum", "pn532", + "ssd1306", ] [[package]] @@ -607,6 +609,17 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ba2aab1ef3793e6f7804162debb5ac5edb93b3d650fbcc5aeb72fcd0e6c03a0" +[[package]] +name = "display-interface-i2c" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d964fa85bbbb5a6ecd06e58699407ac5dc3e3ad72dac0ab7e6b0d00a1cd262d" +dependencies = [ + "display-interface", + "embedded-hal 1.0.0", + "embedded-hal-async", +] + [[package]] name = "display-interface-spi" version = "0.5.0" @@ -1942,6 +1955,19 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "maybe-async-cfg" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e083394889336bc66a4eaf1011ffbfa74893e910f902a9f271fa624c61e1b2" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "pulldown-cmark", + "quote", + "syn 1.0.109", +] + [[package]] name = "memchr" version = "2.8.3" @@ -2690,6 +2716,30 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -2719,6 +2769,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "pulldown-cmark" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" +dependencies = [ + "bitflags 2.13.1", + "memchr", + "unicase", +] + [[package]] name = "quote" version = "1.0.47" @@ -3133,6 +3194,20 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0f368519fc6c85fc1afdb769fb5a51123f6158013e143656e25a3485a0d401c" +[[package]] +name = "ssd1306" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ea6aac2d078bbc71d9b8ac3f657335311f3b6625e9a1a96ccc29f5abfa77c56" +dependencies = [ + "display-interface", + "display-interface-i2c", + "display-interface-spi", + "embedded-graphics-core", + "embedded-hal 1.0.0", + "maybe-async-cfg", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -3400,6 +3475,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-id-start" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 82d1bfc..84462ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ esp-hal-wifimanager = { git = "https://github.com/InvisibleUSA/esp-hal-wifimanag ] } esp-println = { version = "0.17.0", features = ["esp32s3", "log-04"] } display-interface-spi = "0.5.0" +display-interface = "*" ili9341 = "0.6.0" embedded-hal = "1.0.0" embedded-hal-bus = "0.3.0" @@ -47,6 +48,7 @@ embassy-sync = "0.8.0" embedded-hal-async = "1.0.0" i2c-character-display = "0.5.1" ch1115 = { version = "0.1.2", features = ["graphics"] } +ssd1306 = { version = "0.10.0", features = [] } [patch.crates-io] diff --git a/src/bin/main.rs b/src/bin/main.rs index bd1ca91..5fe3d58 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -141,7 +141,7 @@ async fn main(spawner: Spawner) { } fn display_shit(oled: &mut SecondaryDisplay<'static>, text: &str) { - oled.clear().unwrap(); + oled.clear(BinaryColor::Off).unwrap(); let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On); Text::new(text, Point::new(10, 20), text_style) diff --git a/src/drivers/secondary_oled.rs b/src/drivers/secondary_oled.rs index 86b6101..5ac21cb 100644 --- a/src/drivers/secondary_oled.rs +++ b/src/drivers/secondary_oled.rs @@ -1,6 +1,6 @@ -use alloc::format; -use ch1115::{Ch1115, Size128x64}; +use alloc::{format, string::String}; use display_interface_spi::SPIInterface; +use embedded_graphics::pixelcolor::BinaryColor; use embedded_hal_bus::spi::AtomicDevice; use embedded_hal_bus::util::AtomicCell; use esp_hal::delay::Delay; @@ -8,10 +8,72 @@ use esp_hal::gpio::Output; use crate::drivers::spi_bus::{DisplaySpiBus, SharedOutput, SharedReset}; -pub type GenericSecondaryDisplay<'a, BUS, RST, DC> = - Ch1115, Delay>, DC>, RST, Size128x64>; -pub type SecondaryDisplay<'a> = - GenericSecondaryDisplay<'a, DisplaySpiBus, SharedReset<'a>, SharedOutput<'a>>; +pub struct SecondaryDisplay<'a>( + #[cfg(feature = "wokwi")] ssd1306_aliases::SecondaryDisplay<'a>, + #[cfg(not(feature = "wokwi"))] ch1115_aliases::SecondaryDisplay<'a>, +); + +impl<'a> SecondaryDisplay<'a> { + pub fn flush(&mut self) -> Result<(), String> { + #[cfg(not(feature = "wokwi"))] + return self.0.flush().map_err(|e| format!("{e:?}")); + #[cfg(feature = "wokwi")] + return Ok(()); + } +} + +impl<'a> embedded_graphics::geometry::Dimensions for SecondaryDisplay<'a> { + fn bounding_box(&self) -> embedded_graphics::primitives::Rectangle { + self.0.bounding_box() + } +} + +impl<'a> embedded_graphics::draw_target::DrawTarget for SecondaryDisplay<'a> { + type Color = BinaryColor; + + type Error = String; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + self.0.draw_iter(pixels).map_err(|e| format!("{e:?}")) + } + + fn fill_contiguous( + &mut self, + area: &embedded_graphics::primitives::Rectangle, + colors: I, + ) -> Result<(), Self::Error> + where + I: IntoIterator, + { + self.0 + .fill_contiguous(area, colors) + .map_err(|e| format!("{e:?}")) + } + + fn fill_solid( + &mut self, + area: &embedded_graphics::primitives::Rectangle, + color: Self::Color, + ) -> Result<(), Self::Error> { + self.0.fill_solid(area, color).map_err(|e| format!("{e:?}")) + } + + fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> { + #[cfg(feature = "wokwi")] + return self.0.clear(color).map_err(|e| format!("{e:?}")); + + #[cfg(not(feature = "wokwi"))] + { + let _ = color; + self.0.clear().map_err(|e| format!("{e:?}")) + } + } +} + +pub const SECONDARY_DISPLAY_COUNT: usize = 3; pub struct SecondaryDisplayPinConfiguration<'a> { pub spi_bus: &'a AtomicCell, @@ -21,39 +83,112 @@ pub struct SecondaryDisplayPinConfiguration<'a> { } impl<'a> SecondaryDisplayPinConfiguration<'a> { - pub fn build(self) -> [SecondaryDisplay<'a>; 3] { - let mut displays = self.cs_pins.map(|cs| { - let spi_dev = AtomicDevice::new(self.spi_bus, cs, Delay::new()).unwrap(); - let interface = SPIInterface::new(spi_dev, self.dc.clone()); + pub fn build(self) -> [SecondaryDisplay<'a>; SECONDARY_DISPLAY_COUNT] { + #[cfg(feature = "wokwi")] + type Displays<'b> = [ssd1306_aliases::SecondaryDisplay<'b>; SECONDARY_DISPLAY_COUNT]; + #[cfg(not(feature = "wokwi"))] + type Displays<'b> = [ch1115_aliases::SecondaryDisplay<'b>; SECONDARY_DISPLAY_COUNT]; - Ch1115::new(interface, self.res.clone(), Size128x64) - }); + let reset_pin = self.res.clone(); + let mut displays = Displays::new(self); + displays.reset(reset_pin); - init_single_reset(&mut displays, self.res); - - displays + displays.map(SecondaryDisplay) } } -fn init_single_reset<'a: 'b, 'b>( - displays: impl IntoIterator>, - shared_reset: SharedReset<'a>, -) { - let mut displays = displays.into_iter().peekable(); +trait MultiDisplay<'a> { + fn new(config: SecondaryDisplayPinConfiguration<'a>) -> Self; + fn reset(&mut self, shared_reset: SharedReset<'a>); +} - // Reset all secondaries via shared reset - let Some(any_display) = displays.peek_mut() else { - // no display to initialize - return; +mod ch1115_aliases { + use super::*; + use ch1115::{Ch1115, Size128x64}; + + type GenericSecondaryDisplay<'a, BUS, RST, DC> = + Ch1115, Delay>, DC>, RST, Size128x64>; + pub type SecondaryDisplay<'a> = + GenericSecondaryDisplay<'a, DisplaySpiBus, SharedReset<'a>, SharedOutput<'a>>; + + impl<'a> MultiDisplay<'a> for [SecondaryDisplay<'a>; SECONDARY_DISPLAY_COUNT] { + fn new(config: SecondaryDisplayPinConfiguration<'a>) -> Self { + config.cs_pins.map(|cs| { + let spi_dev = AtomicDevice::new(config.spi_bus, cs, Delay::new()).unwrap(); + let interface = SPIInterface::new(spi_dev, config.dc.clone()); + + SecondaryDisplay::new(interface, config.res.clone(), Size128x64) + }) + } + + fn reset(&mut self, shared_reset: SharedReset<'a>) { + let mut displays = self.iter_mut().peekable(); + + // Reset all secondaries via shared reset + let Some(any_display) = displays.peek_mut() else { + // no display to initialize + return; + }; + any_display.hard_reset(&mut Delay::new()).unwrap(); + + shared_reset.disable(true); + + for (i, display) in displays.enumerate() { + display + .init(&mut Delay::new()) + .map_err(|e| format!("Failed to init secondary display {}: {e:?}", i + 1)) + .unwrap(); + } + } + } +} + +mod ssd1306_aliases { + use super::*; + use ssd1306::{ + Ssd1306, mode::BufferedGraphicsMode, rotation::DisplayRotation, size::DisplaySize128x64, }; - any_display.hard_reset(&mut Delay::new()).unwrap(); - shared_reset.disable(true); + type GenericSecondaryDisplay<'a, BUS, DC> = Ssd1306< + SPIInterface, Delay>, DC>, + DisplaySize128x64, + BufferedGraphicsMode, + >; + pub type SecondaryDisplay<'a> = GenericSecondaryDisplay<'a, DisplaySpiBus, SharedOutput<'a>>; - for (i, display) in displays.enumerate() { - display - .init(&mut Delay::new()) - .map_err(|e| format!("Failed to init secondary display {}: {e:?}", i + 1)) - .unwrap(); + impl<'a> MultiDisplay<'a> for [SecondaryDisplay<'a>; SECONDARY_DISPLAY_COUNT] { + fn new(config: SecondaryDisplayPinConfiguration<'a>) -> Self { + config.cs_pins.map(|cs| { + let spi_dev = AtomicDevice::new(config.spi_bus, cs, Delay::new()).unwrap(); + let interface = SPIInterface::new(spi_dev, config.dc.clone()); + + Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) + .into_buffered_graphics_mode() + }) + } + + fn reset(&mut self, mut shared_reset: SharedReset<'a>) { + use ssd1306::mode::DisplayConfig as _; + + let mut displays = self.iter_mut().peekable(); + + // Reset all secondaries via shared reset + let Some(any_display) = displays.peek_mut() else { + // no display to initialize + return; + }; + any_display + .reset(&mut shared_reset, &mut Delay::new()) + .unwrap(); + + shared_reset.disable(true); + + for (i, display) in displays.enumerate() { + display + .init() + .map_err(|e| format!("Failed to init secondary display {}: {e:?}", i + 1)) + .unwrap(); + } + } } } diff --git a/src/navigation/outputs.rs b/src/navigation/outputs.rs index 03aba5a..a743a3a 100644 --- a/src/navigation/outputs.rs +++ b/src/navigation/outputs.rs @@ -1,6 +1,5 @@ use embedded_graphics::{ - draw_target::DrawTarget as _, - pixelcolor::{Rgb565, RgbColor as _}, + draw_target::DrawTarget as _, pixelcolor::{BinaryColor, Rgb565, RgbColor as _}, }; use crate::drivers::{ @@ -19,9 +18,9 @@ pub struct Outputs { impl Outputs { pub fn clear_screens(&mut self) { self.primary_display.clear(Rgb565::BLACK).unwrap(); - self.secondary_display_1.clear().unwrap(); - // self.secondary_display_2.clear().unwrap(); - // self.secondary_display_3.clear().unwrap(); + self.secondary_display_1.clear(BinaryColor::Off).unwrap(); + self.secondary_display_2.clear(BinaryColor::Off).unwrap(); + self.secondary_display_3.clear(BinaryColor::Off).unwrap(); self.tertiary_display.clear().unwrap(); } }