diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index 3cdbc2f..09a962a 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -20,6 +20,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "base64" version = "0.13.1" @@ -52,6 +58,12 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "bytemuck" version = "1.25.0" @@ -221,6 +233,24 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "display-interface" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba2aab1ef3793e6f7804162debb5ac5edb93b3d650fbcc5aeb72fcd0e6c03a0" + +[[package]] +name = "display-interface-spi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9ec30048b1955da2038fcc3c017f419ab21bb0001879d16c0a3749dc6b7a" +dependencies = [ + "byte-slice-cast", + "display-interface", + "embedded-hal 1.0.0", + "embedded-hal-async", +] + [[package]] name = "document-features" version = "0.2.12" @@ -373,6 +403,29 @@ dependencies = [ "nb 1.1.0", ] +[[package]] +name = "embedded-graphics" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8da660bb0c829b34a56a965490597f82a55e767b91f9543be80ce8ccb416fe" +dependencies = [ + "az", + "byteorder", + "embedded-graphics-core", + "float-cmp", + "micromath", +] + +[[package]] +name = "embedded-graphics-core" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95743bef3ff70fcba3930246c4e6872882bbea0dcc6da2ca860112e0cd4bd09f" +dependencies = [ + "az", + "byteorder", +] + [[package]] name = "embedded-hal" version = "0.2.7" @@ -753,20 +806,34 @@ name = "firmware" version = "0.1.0" dependencies = [ "critical-section", + "display-interface-spi", + "embassy-embedded-hal", "embassy-executor", "embassy-sync 0.7.2", "embassy-time", + "embedded-graphics", "esp-alloc", "esp-backtrace", "esp-bootloader-esp-idf", "esp-hal", "esp-println", "esp-rtos", + "heapless 0.9.2", "log", + "mipidsi", "prost", "static_cell", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -976,6 +1043,25 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "micromath" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815" + +[[package]] +name = "mipidsi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44e2bbd372d8ae9ccd0fc6eb4d91742b971ed8149968bbc623f025506989bd30" +dependencies = [ + "display-interface", + "embedded-graphics-core", + "embedded-hal 1.0.0", + "heapless 0.8.0", + "nb 1.1.0", +] + [[package]] name = "nb" version = "0.1.3" diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index af76357..bca423f 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -22,10 +22,16 @@ log = "0.4.27" embassy-executor = { version = "0.9.1", features = ["log"] } embassy-time = { version = "0.5.0", features = ["log"] } +embassy-embedded-hal = { version = "0.5.0" } embassy-sync = { version = "0.7.2", features = ["log"] } esp-backtrace = { version = "0.18.1", features = ["esp32c6", "panic-handler", "println"] } esp-println = { version = "0.16.1", features = ["esp32c6", "log-04"] } esp-alloc = { version = "0.9.0", features = ["esp32c6"] } +heapless = { version = "0.9.2" } + +embedded-graphics = { version = "0.8.2" } +mipidsi = { version = "0.8", features = ["batch", "heapless"] } +display-interface-spi = { version = "0.5.0" } critical-section = "1.2.0" static_cell = "2.1.1" diff --git a/firmware/src/bin/main.rs b/firmware/src/bin/main.rs index d78c435..81ba023 100644 --- a/firmware/src/bin/main.rs +++ b/firmware/src/bin/main.rs @@ -18,17 +18,39 @@ use esp_alloc as _; )] use esp_backtrace as _; +use core::cell::RefCell; +use core::ops::Add; +use core::str::FromStr; +use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice; use embassy_executor::{Spawner, task}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::channel::Channel; +use embassy_sync::{ + blocking_mutex::{Mutex, raw::NoopRawMutex}, + channel::Channel, +}; +use embassy_time::Delay; use embassy_time::Timer; +use embedded_graphics::draw_target::DrawTarget; +use embedded_graphics::mono_font::{MonoTextStyleBuilder, ascii::FONT_10X20}; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::PrimitiveStyleBuilder; +use embedded_graphics::text::{Alignment, Baseline, TextStyleBuilder}; +use embedded_graphics::{pixelcolor::Rgb565, text::Text}; use esp_hal::Async; use esp_hal::clock::CpuClock; +use esp_hal::gpio::{Level, Output, OutputConfig}; +use esp_hal::spi::master::Spi; +use esp_hal::time::Rate; use esp_hal::timer::timg::TimerGroup; use esp_hal::uart::{Config as UartConfig, DataBits, Parity, StopBits, Uart, UartRx, UartTx}; use esp_println::println; +use firmware::layout::DisplayLayout; use firmware::midi::{MidiPacket, MidiParser}; +use heapless::String; use log::info; +use mipidsi::models::ST7789; +use mipidsi::options::Rotation::Deg270; +use mipidsi::options::{ColorInversion, Orientation}; // This creates a default app-descriptor required by the esp-idf bootloader. // For more information see: @@ -70,8 +92,7 @@ async fn midi_out_task(mut uart: UartTx<'static, Async>) { async fn main(spawner: Spawner) -> ! { esp_println::logger::init_logger_from_env(); - let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); - let peripherals = esp_hal::init(config); + let peripherals = esp_hal::init(esp_hal::Config::default().with_cpu_clock(CpuClock::max())); let timg0 = TimerGroup::new(peripherals.TIMG0); let sw_interrupt = @@ -80,6 +101,83 @@ async fn main(spawner: Spawner) -> ! { info!("Embassy initialized!"); + let spi = Spi::new( + peripherals.SPI2, + esp_hal::spi::master::Config::default().with_frequency(Rate::from_mhz(40)), + ) + .expect("Failed to initialise SPI2 peripheral") + .with_sck(peripherals.GPIO18) + .with_mosi(peripherals.GPIO19) + .with_cs(peripherals.GPIO20); + + let cs = Output::new(peripherals.GPIO5, Level::High, OutputConfig::default()); + let spi_bus: Mutex = Mutex::new(RefCell::new(spi)); + let spi_device = SpiDevice::new(&spi_bus, cs); + + let dc = Output::new(peripherals.GPIO21, Level::Low, OutputConfig::default()); + let di = display_interface_spi::SPIInterface::new(spi_device, dc); + let mut display = mipidsi::Builder::new(ST7789, di) + .display_size(240, 280) + .orientation(Orientation::default().rotate(Deg270)) + .display_offset(0, 20) + .invert_colors(ColorInversion::Inverted) + // TODO: Add reset pin + .init(&mut Delay) + .expect("Failed to initialise ST7789 display"); + + display.clear(Rgb565::BLACK).unwrap(); + + embedded_graphics::primitives::Rectangle::new( + Point::zero(), + Size::new(display.size().width, display.size().height / 3), + ) + .into_styled( + PrimitiveStyleBuilder::new() + .fill_color(Rgb565::CSS_ORANGE) + .build(), + ) + .draw(&mut display) + .unwrap(); + embedded_graphics::primitives::Rectangle::new( + Point::new( + 0, + display.size().height as i32 - display.size().height as i32 / 3, + ), + Size::new(display.size().width, display.size().height / 3), + ) + .into_styled( + PrimitiveStyleBuilder::new() + .fill_color(Rgb565::BLUE) + .build(), + ) + .draw(&mut display) + .unwrap(); + + let text_style = TextStyleBuilder::new() + .alignment(Alignment::Center) + .baseline(Baseline::Middle) + .build(); + let character_style = MonoTextStyleBuilder::new() + .font(&FONT_10X20) + .text_color(Rgb565::GREEN) + .build(); + Text::with_text_style( + "Hello, world!", + display + .bounding_box() + .top_left + .add(Point::new(display.size().width as i32 / 2, 60)), + character_style, + text_style, + ) + .draw(&mut display) + .unwrap(); + + let mut layout = DisplayLayout::new(&mut display); + layout.set_top_text(String::from_str("Foo").unwrap()); + layout.set_bottom_text(String::from_str("Bar").unwrap()); + layout.draw().unwrap(); + let uart = Uart::new( peripherals.UART1, UartConfig::default() @@ -88,7 +186,7 @@ async fn main(spawner: Spawner) -> ! { .with_parity(Parity::None) .with_stop_bits(StopBits::_1), ) - .expect("Failed to initialize UART0") + .expect("Failed to initialise UART1") .with_rx(peripherals.GPIO7) .with_tx(peripherals.GPIO8) .into_async(); @@ -106,7 +204,7 @@ async fn main(spawner: Spawner) -> ! { info!("Startup complete."); loop { - Timer::after_secs(1).await; + Timer::after_secs(5).await; println!("Heartbeat"); } } diff --git a/firmware/src/layout/mod.rs b/firmware/src/layout/mod.rs new file mode 100644 index 0000000..ea5e11f --- /dev/null +++ b/firmware/src/layout/mod.rs @@ -0,0 +1,126 @@ +use core::fmt::Write; +use core::ops::Add; +use core::ops::Div; +use core::result::{Result, Result::Ok}; +use embedded_graphics::{ + mono_font::{MonoTextStyleBuilder, ascii::FONT_10X20}, + pixelcolor::Rgb565, + prelude::*, + primitives::{PrimitiveStyle, Rectangle}, + text::Text, +}; +use heapless::String; + +type DisplayText = String<16>; + +pub struct DisplayLayout<'a, D> { + display: &'a mut D, + top_text: DisplayText, + top_box_color: Rgb565, + bottom_text: DisplayText, + bottom_box_color: Rgb565, + text_style: embedded_graphics::mono_font::MonoTextStyle<'a, Rgb565>, +} + +impl<'a, D> DisplayLayout<'a, D> +where + D: DrawTarget, +{ + pub fn new(display: &'a mut D) -> Self { + let text_style = MonoTextStyleBuilder::new() + .font(&FONT_10X20) + .text_color(Rgb565::WHITE) + .build(); + + let mut top_text = String::new(); + top_text.write_str("Hello").unwrap(); + let mut bottom_text = String::new(); + bottom_text.write_str("World").unwrap(); + + Self { + display, + top_text, + top_box_color: Rgb565::GREEN, + bottom_text, + bottom_box_color: Rgb565::BLUE, + text_style, + } + } + + pub fn draw_boxes(&mut self) -> Result<(), D::Error> { + let display_size = self.display.bounding_box().size; + + // Top box + Rectangle::new( + Point::zero(), + Size::new(display_size.width, display_size.height.div(3)), + ) + .into_styled(PrimitiveStyle::with_fill(self.top_box_color)) + .draw(self.display)?; + + // Bottom box + Rectangle::new( + Point::new( + 0, + display_size + .height + .div(3) + .add(display_size.height.div_ceil(3)) as i32, + ), + Size::new(display_size.width, display_size.height.div(3)), + ) + .into_styled(PrimitiveStyle::with_fill(self.bottom_box_color)) + .draw(self.display)?; + + Ok(()) + } + + pub fn draw_top_text(&mut self) -> Result<(), D::Error> { + Text::new(self.top_text.as_str(), Point::new(5, 10), self.text_style).draw(self.display)?; + Ok(()) + } + + pub fn draw_bottom_text(&mut self) -> Result<(), D::Error> { + Text::new( + self.bottom_text.as_str(), + Point::new(5, 210), + self.text_style, + ) + .draw(self.display)?; + Ok(()) + } + + pub fn clear_middle(&mut self) -> Result<(), D::Error> { + let display_size = self.display.bounding_box().size; + Rectangle::new( + Point::new(0, display_size.height.div(3) as i32), + Size::new(display_size.width, display_size.height.div_ceil(3)), + ) + .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) + .draw(self.display) + } + + pub fn set_top_box_colour(&mut self, colour: Rgb565) { + self.top_box_color = colour; + } + + pub fn set_bottom_box_colour(&mut self, colour: Rgb565) { + self.bottom_box_color = colour; + } + + pub fn set_top_text(&mut self, text: DisplayText) { + self.top_text = text; + } + + pub fn set_bottom_text(&mut self, text: DisplayText) { + self.bottom_text = text; + } + + pub fn draw(&mut self) -> Result<(), D::Error> { + self.draw_boxes()?; + self.draw_top_text()?; + self.draw_bottom_text()?; + self.clear_middle()?; + Ok(()) + } +} diff --git a/firmware/src/lib.rs b/firmware/src/lib.rs index bce0d4a..3bfb9e2 100644 --- a/firmware/src/lib.rs +++ b/firmware/src/lib.rs @@ -7,6 +7,7 @@ pub mod generated { pub mod device_v1; } +pub mod layout; pub mod midi; pub mod protocol;