diff --git a/firmware2/Cargo.lock b/firmware2/Cargo.lock index 70980ea..577639d 100644 --- a/firmware2/Cargo.lock +++ b/firmware2/Cargo.lock @@ -15,6 +15,7 @@ dependencies = [ "critical-section", "embassy-embedded-hal", "embassy-executor", + "embassy-sync 0.7.2", "embassy-time", "embedded-cli", "embedded-io 0.6.1", @@ -1521,6 +1522,7 @@ dependencies = [ "document-features", "embassy-executor-macros", "embassy-executor-timer-queue", + "log", ] [[package]] @@ -1599,6 +1601,7 @@ dependencies = [ "futures-core", "futures-sink", "heapless 0.8.0", + "log", ] [[package]] diff --git a/firmware2/Cargo.toml b/firmware2/Cargo.toml index caa1c7d..d6cae86 100644 --- a/firmware2/Cargo.toml +++ b/firmware2/Cargo.toml @@ -24,9 +24,10 @@ rmk = { version = "0.8.2", default-features = false, features = [ "storage", "vial", ] } -embassy-executor = { version = "0.9" } +embassy-executor = { version = "0.9", features = ["log"] } embassy-time = { version = "0.5.0", features = ["log"] } embassy-embedded-hal = "0.5.0" +embassy-sync = { version = "0.7.2", features = ["log"] } esp-backtrace = { version = "0.18", default-features = false, features = [ "esp32s3", "println", diff --git a/firmware2/src/main.rs b/firmware2/src/main.rs index 5511d44..488ddb0 100644 --- a/firmware2/src/main.rs +++ b/firmware2/src/main.rs @@ -6,13 +6,18 @@ extern crate alloc; use core::alloc::Layout; use core::cell::RefCell; +use core::sync::atomic::AtomicBool; use core::time::Duration; use alloc::boxed::Box; use alloc::rc::Rc; +use alloc::sync::Arc; use alloc::vec; use bt_hci::controller::ExternalController; use embassy_executor::Spawner; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::signal::Signal; +use embassy_time::{Delay, Timer}; use esp_alloc::{HeapRegion, MemoryCapability}; use esp_hal::clock::CpuClock; use esp_hal::dma::{BurstConfig, DmaDescriptor, DmaTxBuf, ExternalBurstConfig}; @@ -45,7 +50,7 @@ use rmk::storage::async_flash_wrapper; use rmk::{initialize_keymap_and_storage, run_devices, run_rmk}; use slint::platform::WindowAdapter; use slint::platform::software_renderer::{MinimalSoftwareWindow, RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer}; -use slint::{ComponentHandle, PhysicalSize, WindowSize}; +use slint::{ComponentHandle, PhysicalSize, PlatformError, WindowSize}; use static_cell::StaticCell; use ui::AppWindow; use ui::window_adapter::SoftwareWindowAdapter; @@ -74,6 +79,12 @@ const LOG_LEVEL_FILTER: LevelFilter = LevelFilter::Info; static PSRAM_ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty(); +/// Used to signal that MCU is ready to submit the framebuffer to the LCD. +static SIGNAL_LCD_SUBMIT: Signal = Signal::new(); + +/// Used to signal that the MCU is ready to render the GUI. +static SIGNAL_UI_RENDER: Signal = Signal::new(); + #[esp_rtos::main] async fn main(_spawner: Spawner) { #[cfg(not(feature = "alt-log"))] @@ -199,8 +210,6 @@ async fn main(_spawner: Spawner) { .with_data3(peripherals.GPIO41) .with_data4(peripherals.GPIO42) // Green - // .with_data5(peripherals.GPIO5) TODO - // .with_data6(peripherals.GPIO12) .with_data7(peripherals.GPIO13) .with_data8(peripherals.GPIO14) .with_data9(peripherals.GPIO15) @@ -212,6 +221,12 @@ async fn main(_spawner: Spawner) { .with_data14(peripherals.GPIO3) .with_data15(peripherals.GPIO4); + #[cfg(not(feature = "alt-log"))] + let unconfigured_dpi = unconfigured_dpi + // Green + .with_data5(peripherals.GPIO5) + .with_data6(peripherals.GPIO12); + let st7701s = St7701s::new(sck, mosi, cs, unconfigured_dpi).await; // RMK config @@ -326,10 +341,23 @@ async fn run_renderer_task(backend: SlintBackend) { slint::platform::set_platform(Box::new(backend)) .expect("backend already initialized"); - loop { - let main = AppWindow::new().unwrap(); + let main = AppWindow::new().unwrap(); - main.run().unwrap(); + // Instead of having a `loop` in the non-async `SlintBackend::run_event_loop`, we achieve + // async by having only one iteration of the loop run, and `await`ing here. + // The following block is analogous to `main.run()`. + { + main.show().unwrap(); + + loop { + slint::run_event_loop().unwrap(); + SIGNAL_LCD_SUBMIT.signal(()); + // Timer::after_millis(50).await; + SIGNAL_UI_RENDER.wait().await; + } + + #[expect(unreachable_code)] + main.hide().unwrap(); } } @@ -460,7 +488,8 @@ async fn run_lcd(mut st7701s: St7701s<'static, Blocking>, framebuffer: &'static loop { // Timer::after(Duration::from_millis(100)).await; - yield_now().await; + // yield_now().await; + SIGNAL_LCD_SUBMIT.wait().await; // TODO: Use bounce buffers: // https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32s3/api-reference/peripherals/lcd.html#bounce-buffer-with-single-psram-frame-buffer @@ -477,16 +506,22 @@ async fn run_lcd(mut st7701s: St7701s<'static, Blocking>, framebuffer: &'static Ok(transfer) => transfer, }; - while !transfer.is_done() { - // Timer::after_millis(1).await; - yield_now().await; - } + // This could be used to allow other tasks to be executed on the first core, but that causes + // the flash to be accessed, which interferes with the framebuffer transfer. + // For that reason, it is disabled, and this task blocks the first core, until the transfer + // is complete. + // while !transfer.is_done() { + // // Timer::after_millis(1).await; + // yield_now().await; + // } let result; let dma_buf; (result, st7701s.dpi, dma_buf) = transfer.wait(); framebuffer.dma_buf = Some(dma_buf); + SIGNAL_UI_RENDER.signal(()); + if let Err(error) = result { error!( "An error occurred while transferring framebuffer to the LCD display: {error:?}" diff --git a/firmware2/src/ui/backend.rs b/firmware2/src/ui/backend.rs index dcfc764..0216dde 100644 --- a/firmware2/src/ui/backend.rs +++ b/firmware2/src/ui/backend.rs @@ -1,7 +1,7 @@ use core::{cell::RefCell, time::Duration}; use alloc::rc::Rc; -use esp_hal::time::Instant; +use esp_hal::{time::Instant}; use log::info; use slint::{PhysicalSize, WindowSize, platform::software_renderer::{RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer}}; @@ -36,21 +36,23 @@ impl slint::platform::Platform for SlintBackend { } fn run_event_loop(&self) -> Result<(), slint::PlatformError> { - loop { - slint::platform::update_timers_and_animations(); - + // Instead of `loop`ing here, we execute a single iteration and handle `loop`ing + // in `crate::run_renderer_task`, where we can make use of `await`. + + /* loop */ { if let Some(window) = self.window.borrow().clone() { + // TODO: Event dispatch. Here or in `run_renderer_task`? // window.try_dispatch_event(todo!())?; window.draw_if_needed(|renderer| { // TODO: Proper synchronization. let framebuffer = unsafe { &mut *self.framebuffer.0 }; - // TODO: Try using height to see if it rotates the screen correctly. Might need - // to swap dimensions elsewhere. renderer.render(framebuffer, self.window_size[1] as usize); info!("UI rendered."); }); } } + + Ok(()) } }