From de5d78764601cec48488a7940ccbb51eb4086f7a Mon Sep 17 00:00:00 2001 From: David Haig Date: Wed, 11 Dec 2024 11:16:21 +0000 Subject: [PATCH 01/13] Add embassy microcontroller example demonstrating async --- Cargo.toml | 1 + examples/mcu-embassy/.cargo/config.toml | 17 + examples/mcu-embassy/.vscode/settings.json | 9 + examples/mcu-embassy/Cargo.toml | 111 ++++++ examples/mcu-embassy/README.md | 88 +++++ .../mcu-embassy/slint_generated/Cargo.toml | 20 ++ examples/mcu-embassy/slint_generated/build.rs | 6 + .../mcu-embassy/slint_generated/src/lib.rs | 3 + examples/mcu-embassy/src/bin/ui_mcu.rs | 324 ++++++++++++++++++ examples/mcu-embassy/src/bin/ui_simulator.rs | 250 ++++++++++++++ examples/mcu-embassy/src/controller.rs | 100 ++++++ examples/mcu-embassy/src/lib.rs | 18 + examples/mcu-embassy/src/mcu/double_buffer.rs | 60 ++++ examples/mcu-embassy/src/mcu/hardware.rs | 15 + examples/mcu-embassy/src/mcu/mod.rs | 8 + examples/mcu-embassy/src/mcu/rcc_setup.rs | 32 ++ .../mcu-embassy/src/simulator/hardware.rs | 14 + examples/mcu-embassy/src/simulator/mod.rs | 1 + examples/mcu-embassy/src/slint_backend.rs | 35 ++ examples/mcu-embassy/ui/common.slint | 109 ++++++ examples/mcu-embassy/ui/main.slint | 62 ++++ 21 files changed, 1283 insertions(+) create mode 100644 examples/mcu-embassy/.cargo/config.toml create mode 100644 examples/mcu-embassy/.vscode/settings.json create mode 100644 examples/mcu-embassy/Cargo.toml create mode 100644 examples/mcu-embassy/README.md create mode 100644 examples/mcu-embassy/slint_generated/Cargo.toml create mode 100644 examples/mcu-embassy/slint_generated/build.rs create mode 100644 examples/mcu-embassy/slint_generated/src/lib.rs create mode 100644 examples/mcu-embassy/src/bin/ui_mcu.rs create mode 100644 examples/mcu-embassy/src/bin/ui_simulator.rs create mode 100644 examples/mcu-embassy/src/controller.rs create mode 100644 examples/mcu-embassy/src/lib.rs create mode 100644 examples/mcu-embassy/src/mcu/double_buffer.rs create mode 100644 examples/mcu-embassy/src/mcu/hardware.rs create mode 100644 examples/mcu-embassy/src/mcu/mod.rs create mode 100644 examples/mcu-embassy/src/mcu/rcc_setup.rs create mode 100644 examples/mcu-embassy/src/simulator/hardware.rs create mode 100644 examples/mcu-embassy/src/simulator/mod.rs create mode 100644 examples/mcu-embassy/src/slint_backend.rs create mode 100644 examples/mcu-embassy/ui/common.slint create mode 100644 examples/mcu-embassy/ui/main.slint diff --git a/Cargo.toml b/Cargo.toml index 59395b13c79..2a10d08d108 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ 'examples/carousel/rust', 'demos/energy-monitor', 'demos/home-automation/rust', + 'examples/mcu-embassy', 'examples/mcu-board-support', 'examples/uefi-demo', 'demos/weather-demo', diff --git a/examples/mcu-embassy/.cargo/config.toml b/examples/mcu-embassy/.cargo/config.toml new file mode 100644 index 00000000000..3d00bce0897 --- /dev/null +++ b/examples/mcu-embassy/.cargo/config.toml @@ -0,0 +1,17 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = [ + "-C", + "link-arg=--nmagic", + "-C", + "link-arg=-Tlink.x", + "-C", + "link-arg=-Tdefmt.x", +] +runner = "probe-rs run --chip STM32U5G9ZJTxQ" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "info" +RUST_LOG = "info" diff --git a/examples/mcu-embassy/.vscode/settings.json b/examples/mcu-embassy/.vscode/settings.json new file mode 100644 index 00000000000..17f909cac35 --- /dev/null +++ b/examples/mcu-embassy/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + // uncomment this to have rust-analyzer work with mcu related code + "rust-analyzer.cargo.features": [ + "mcu", + //"simulator", + ], + "rust-analyzer.check.allTargets": false, + "rust-analyzer.showUnlinkedFileNotification": false, +} \ No newline at end of file diff --git a/examples/mcu-embassy/Cargo.toml b/examples/mcu-embassy/Cargo.toml new file mode 100644 index 00000000000..a3f6a380073 --- /dev/null +++ b/examples/mcu-embassy/Cargo.toml @@ -0,0 +1,111 @@ +[package] +name = "mcu-embassy" +version = "0.1.0" +edition = "2021" +authors = ["David Haig "] +readme = "README.md" +license = "Apache-2.0" + +[dependencies] +log = "0.4" +defmt = { version = "0.3", optional = true, features = ["alloc"] } +defmt-rtt = { version = "0.4", optional = true } +panic-probe = { version = "0.3", optional = true, features = ["print-defmt"] } +cortex-m = { version = "0.7.7", optional = true, features = [ + "inline-asm", + "critical-section-single-core", +] } +cortex-m-rt = { version = "0.7.3", optional = true } +slint-generated = { path = "./slint_generated" } +embedded-alloc = { version = "0.5", optional = true } +heapless = { version = "0.8", default-features = false, features = [ + "defmt-03", +] } +static_cell = { version = "2", optional = true } + +slint = { version = "=1.9.0", path = "../../api/rs/slint", default-features = false, features = [ + "compat-1-2", + "unsafe-single-threaded", + "libm", + "renderer-software", +] } +i-slint-core-macros = { version = "=1.9.0", path = "../../internal/core-macros" } + +embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", optional = true, features = [ + "stm32u5g9zj", + "time-driver-tim2", + "exti", + "memory-x", + "unstable-pac", + "chrono", + "time", +] } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", optional = true } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", optional = true, features = [ + "task-arena-size-32768", + "executor-thread", + "integrated-timers", +] } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", optional = true } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", optional = true } + +env_logger = { version = "0.9.0", optional = true } +sdl2 = { git = "https://github.com/Rust-SDL2/rust-sdl2", rev = "400e033", optional = true } +object-pool = { version = "0.6.0", optional = true } +embedded-hal = { version = "1.0.0", optional = true } +gt911 = { version = "0.1", features = ["defmt"], optional = true } + +[features] +default = ["mcu"] +mcu = [ + "defmt", + "defmt-rtt", + "panic-probe", + "cortex-m", + "cortex-m-rt", + "embassy-stm32", + "embassy-stm32/defmt", + "embassy-sync", + "embassy-sync/defmt", + "embassy-executor", + "embassy-executor/arch-cortex-m", + "embassy-executor/executor-interrupt", + "embassy-executor/defmt", + "embassy-time", + "embassy-time/tick-hz-32_768", + "embassy-time/defmt", + "embassy-time/defmt-timestamp-uptime", + "embassy-futures", + "embedded-alloc", + "embedded-hal", + "gt911", +] + +simulator = [ + "slint/renderer-software", + "slint/backend-winit", + "slint/std", + "embassy-executor/arch-std", + "embassy-time/std", + "embassy-futures", + "embassy-time", + "embassy-time/std", + "embassy-sync", + "env_logger", + "sdl2", + "object-pool", + "static_cell", +] + +[profile.release] +debug = true # required for decent panic messages and log line locations +opt-level = "s" +lto = "thin" + +[[bin]] +name = "ui_mcu" +required-features = ["mcu"] + +[[bin]] +name = "ui_simulator" +required-features = ["simulator"] diff --git a/examples/mcu-embassy/README.md b/examples/mcu-embassy/README.md new file mode 100644 index 00000000000..48a9fdc08c7 --- /dev/null +++ b/examples/mcu-embassy/README.md @@ -0,0 +1,88 @@ +# Embassy Slint stm32u5g9j-dk2 Demo + +An embedded async Slint GUI demo using Embassy and an stm32u5g9j-dk2 development kit. This demo was written to run on a resource constrained device, not a PC or laptop. +The simulator can run on a PC if you do not have the dev kit on hand but it is not meant to be a reference design for an async GUI implementation on a PC. + +The stm32u5g9j-dk2 was chosen because of its availability and price point and has enough onboard ram (3MB) and flash (4MB) to run Slint without external psram and flash, reducing setup complexity. +It comes with a 5" 800x480 IPS touchscreen display. Async is useful for building more complex UIs because you don't have to hand code your own state machines. + +Things that are demonstrated here: +- Sending rendered display buffer to LCD screen asynchronously freeing up the mcu to do other things +- Responding to hardware events (pressing the USER button on the DK2 changes the colour of the grey circle to blue) +- Touchscreen actions setting physical hardware (toggling the switch on the touchscreen to turn on the green led on the DK2) +- Cooperative multitasking (red led continues to flash on a separate task regardless of UI actions) +- UI animations work +- The application can be simulated on a PC without having to download to the DK2 every time you want to test something + +# Installation instructions + +Install the cross compilation target for the mcu: + +```bash + rustup target add thumbv8m.main-none-eabihf +``` + +You need software to be able to flash the firmware to the dev kit. At the time of writing (7 Dec 2024) you need the latest version of probe-rs to flash to STM32U5G9ZJTxQ, not the one in crates.io. + +```bash +cargo install --force --locked --git https://github.com/probe-rs/probe-rs probe-rs-tools +``` + +# Running the application + +Plug a usbc cable into the ST-LINK port on the dk2 and run the following: + +```bash +cargo run --bin ui_mcu --release --features=mcu +``` + +Troubleshooting: + +If you are getting some complication errors from cortex-m like "error: invalid register `r1`: unknown register" make sure that you are cross compiling for the correct cpu target: + +You can specify the target in the cargo run command in the following file: + +In `.cargo/Cargo.toml` +```toml +[build] +target = "thumbv8m.main-none-eabihf" +``` + +If using vscode then make sure `rust-analyzer.cargo.features` is set to `mcu` in `.vscode/settings.json` + +You may be wondering why you get the following message in the logs: `invalid location: defmt frame-index` +In the Slint workspace `Cargo.toml` file overrides the `Cargo.toml` file in this crate so make sure the release profile is as follows in that workspace file: +```toml +[profile.release] +debug = true # required for decent panic messages and log line locations +opt-level = "s" +lto = "thin" +``` + +# Running the simulator + +Of course you can use Slint's vscode plugin to preview slint files but you may want to actually run your application and simulate the hardware interactions. +The simulator runs Embassy on the host machine (instead of on an mcu) and renders to the screen using the sdl2 library. +Hardware like leds and buttons are emulated in the hardware module. + +To install SDL2 follow the instructions here: https://github.com/Rust-SDL2/rust-sdl2 + +To run the simulator: +```bash +cargo run --bin ui_simulator --release --features=simulator +``` + + +Troubleshooting: + +If you are getting some compilation errors from arrayvec like "error: requires `sized` lang_item" make sure you are NOT targeting the mcu when building for your pc. + +Set the target correctly in the command line or comment out the following: + +In `.cargo/Cargo.toml` +```toml +#[build] +#target = "thumbv8m.main-none-eabihf" +``` + +If using vscode then make sure `rust-analyzer.cargo.features` is set to `simulator` in `.vscode/settings.json` diff --git a/examples/mcu-embassy/slint_generated/Cargo.toml b/examples/mcu-embassy/slint_generated/Cargo.toml new file mode 100644 index 00000000000..30365cbe125 --- /dev/null +++ b/examples/mcu-embassy/slint_generated/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "slint-generated" +version = "0.1.0" +edition = "2021" +rust-version = "1.79" +build = "build.rs" +readme = "README.md" +resolver = "2" + +[dependencies] +slint = { version = "=1.9.0", path = "../../../api/rs/slint", default-features = false, features = [ + "compat-1-2", + "unsafe-single-threaded", + "libm", + "renderer-software", +] } +i-slint-core-macros = { version = "=1.9.0", path = "../../../internal/core-macros" } + +[build-dependencies] +slint-build = { version = "=1.9.0", path = "../../../api/rs/build" } diff --git a/examples/mcu-embassy/slint_generated/build.rs b/examples/mcu-embassy/slint_generated/build.rs new file mode 100644 index 00000000000..959c4d2f8d9 --- /dev/null +++ b/examples/mcu-embassy/slint_generated/build.rs @@ -0,0 +1,6 @@ +fn main() { + let config = slint_build::CompilerConfiguration::new() + .embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer); + slint_build::compile_with_config("../ui/main.slint", config).unwrap(); + slint_build::print_rustc_flags().unwrap(); +} diff --git a/examples/mcu-embassy/slint_generated/src/lib.rs b/examples/mcu-embassy/slint_generated/src/lib.rs new file mode 100644 index 00000000000..3107212d8f6 --- /dev/null +++ b/examples/mcu-embassy/slint_generated/src/lib.rs @@ -0,0 +1,3 @@ +#![no_std] + +slint::include_modules!(); diff --git a/examples/mcu-embassy/src/bin/ui_mcu.rs b/examples/mcu-embassy/src/bin/ui_mcu.rs new file mode 100644 index 00000000000..ac1b071b354 --- /dev/null +++ b/examples/mcu-embassy/src/bin/ui_mcu.rs @@ -0,0 +1,324 @@ +// A demo for stm32u5g9j-dk2 +// The application renders a simple Slint screen to the display and the user can interact with it +// by toggling the green led on and off as well as pushing the blue button on the dk which should +// turn the grey circle next to "Hardware User Button" blue. +// The hello world button demonstrates animations. More details in the readme. + +#![no_std] +#![no_main] +#![macro_use] +#![allow(static_mut_refs)] + +extern crate alloc; + +use alloc::{boxed::Box, rc::Rc}; +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_futures::select::{select, Either}; +use embassy_stm32::{ + bind_interrupts, + exti::ExtiInput, + gpio::{Level, Output, Pull, Speed}, + i2c::{self, I2c}, + ltdc::{ + self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge, + }, + mode::{self}, + peripherals, + time::Hertz, +}; +use embassy_time::{Duration, Timer}; +use gt911::Gt911; +use mcu_embassy::{ + controller::{self, Action, Controller}, + mcu::{double_buffer::DoubleBuffer, hardware::HardwareMcu, rcc_setup, ALLOCATOR}, + slint_backend::{StmBackend, TargetPixelType, DISPLAY_HEIGHT, DISPLAY_WIDTH}, +}; +use slint::{ + platform::{ + software_renderer::{MinimalSoftwareWindow, RepaintBufferType, Rgb565Pixel}, + PointerEventButton, WindowEvent, + }, + ComponentHandle, +}; +use slint_generated::MainWindow; +use {defmt_rtt as _, panic_probe as _}; + +const MY_TASK_POOL_SIZE: usize = 2; +const HEAP_SIZE: usize = 200 * 1024; +static mut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE]; + +// the following two display buffers consume 1536000 bytes that just about fits into the ram found on the mcu +static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = + [Rgb565Pixel(0); DISPLAY_WIDTH * DISPLAY_HEIGHT]; +static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = + [Rgb565Pixel(0); DISPLAY_WIDTH * DISPLAY_HEIGHT]; + +bind_interrupts!(struct Irqs { + LTDC => ltdc::InterruptHandler; + I2C2_EV => i2c::EventInterruptHandler; + I2C2_ER => i2c::ErrorInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = rcc_setup::stm32u5g9zj_init(); + + // setup an allocator + unsafe { ALLOCATOR.init(&mut HEAP as *const u8 as usize, core::mem::size_of_val(&HEAP)) } + + // enable instruction cache + embassy_stm32::pac::ICACHE.cr().write(|w| { + w.set_en(true); + }); + + // enable data cache 1 + embassy_stm32::pac::DCACHE1.cr().write(|w| { + w.set_en(true); + }); + + // enable data cache 2 + embassy_stm32::pac::DCACHE2.cr().write(|w| { + w.set_en(true); + }); + + // used for the touch events + let i2c = I2c::new( + p.I2C2, + p.PF1, + p.PF0, + Irqs, + p.GPDMA1_CH0, + p.GPDMA1_CH1, + Hertz(100_000), + Default::default(), + ); + + // TASK: blink the red led on another task + let red_led = Output::new(p.PD2, Level::High, Speed::Low); + unwrap!(spawner.spawn(led_task(red_led))); + + // TASK: wait for hardware user button press + let user_btn = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); + unwrap!(spawner.spawn(user_btn_task(user_btn))); + + // set up the LTDC peripheral to send data to the LCD screen + // numbers from STM32U5G9J-DK2.ioc + const RK050HR18H_HSYNC: u16 = 5; // Horizontal synchronization + const RK050HR18H_HBP: u16 = 8; // Horizontal back porch + const RK050HR18H_HFP: u16 = 8; // Horizontal front porch + const RK050HR18H_VSYNC: u16 = 5; // Vertical synchronization + const RK050HR18H_VBP: u16 = 8; // Vertical back porch + const RK050HR18H_VFP: u16 = 8; // Vertical front porch + + // NOTE: all polarities have to be reversed with respect to the STM32U5G9J-DK2 CubeMX parametrization + let ltdc_config = LtdcConfiguration { + active_width: DISPLAY_WIDTH as _, + active_height: DISPLAY_HEIGHT as _, + h_back_porch: RK050HR18H_HBP, + h_front_porch: RK050HR18H_HFP, + v_back_porch: RK050HR18H_VBP, + v_front_porch: RK050HR18H_VFP, + h_sync: RK050HR18H_HSYNC, + v_sync: RK050HR18H_VSYNC, + h_sync_polarity: PolarityActive::ActiveHigh, + v_sync_polarity: PolarityActive::ActiveHigh, + data_enable_polarity: PolarityActive::ActiveHigh, + pixel_clock_polarity: PolarityEdge::RisingEdge, + }; + + info!("init ltdc"); + let mut ltdc_de = Output::new(p.PD6, Level::Low, Speed::High); + let mut ltdc_disp_ctrl = Output::new(p.PE4, Level::Low, Speed::High); + let mut ltdc_bl_ctrl = Output::new(p.PE6, Level::Low, Speed::High); + let mut ltdc = Ltdc::new_with_pins( + p.LTDC, // PERIPHERAL + Irqs, // IRQS + p.PD3, // CLK + p.PE0, // HSYNC + p.PD13, // VSYNC + p.PB9, // B0 + p.PB2, // B1 + p.PD14, // B2 + p.PD15, // B3 + p.PD0, // B4 + p.PD1, // B5 + p.PE7, // B6 + p.PE8, // B7 + p.PC8, // G0 + p.PC9, // G1 + p.PE9, // G2 + p.PE10, // G3 + p.PE11, // G4 + p.PE12, // G5 + p.PE13, // G6 + p.PE14, // G7 + p.PC6, // R0 + p.PC7, // R1 + p.PE15, // R2 + p.PD8, // R3 + p.PD9, // R4 + p.PD10, // R5 + p.PD11, // R6 + p.PD12, // R7 + ); + ltdc.init(<dc_config); + ltdc_de.set_low(); + ltdc_bl_ctrl.set_high(); + ltdc_disp_ctrl.set_high(); + + // we only need to draw on one layer for this example (not to be confused with the double buffer) + info!("enable bottom layer"); + let layer_config = LtdcLayerConfig { + pixel_format: ltdc::PixelFormat::RGB565, // 2 bytes per pixel + layer: LtdcLayer::Layer1, + window_x0: 0, + window_x1: DISPLAY_WIDTH as _, + window_y0: 0, + window_y1: DISPLAY_HEIGHT as _, + }; + + // enable the bottom layer + ltdc.init_layer(&layer_config, None); + + // Safety: the DoubleBuffer controls access to the statically allocated frame buffers + // and it is the only thing that mutates their content + let double_buffer = + DoubleBuffer::new(unsafe { FB1.as_mut() }, unsafe { FB2.as_mut() }, layer_config); + + // create a slint window and register it with slint + let window = MinimalSoftwareWindow::new(RepaintBufferType::SwappedBuffers); + window.set_size(slint::PhysicalSize::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)); + let backend = Box::new(StmBackend::new(window.clone())); + slint::platform::set_platform(backend).expect("backend already initialized"); + info!("slint gui setup complete"); + + // TASK: run the gui render loop + unwrap!(spawner.spawn(render_loop(window, double_buffer, ltdc, i2c))); + + let main_window = MainWindow::new().unwrap(); + main_window.show().expect("unable to show main window"); + + let green_led = Output::new(p.PD4, Level::High, Speed::Low); + let hardware = HardwareMcu { green_led }; + + // run the controller event loop + let mut controller = Controller::new(&main_window, hardware); + controller.run().await; +} + +#[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)] +async fn led_task(mut led: Output<'static>) { + loop { + // on + led.set_low(); + Timer::after(Duration::from_millis(50)).await; + + // off + led.set_high(); + Timer::after(Duration::from_millis(450)).await; + } +} + +// low latency button press with debounce and toggle state recovery (for data races) +#[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)] +async fn user_btn_task(mut user_btn: ExtiInput<'static>) { + let mut is_high = false; + info!("Press the USER button..."); + + loop { + let any_edge = user_btn.wait_for_any_edge(); + let timeout = Timer::after(Duration::from_millis(1000)); + + // the timeout is here in case of a data race between the last button check + // and beginning the wait for an edge change + match select(any_edge, timeout).await { + Either::First(_) => {} + Either::Second(_) => {} + }; + + if user_btn.is_high() != is_high { + is_high = !is_high; + info!("Button is pressed: {}", is_high); + controller::send_action(Action::HardwareUserBtnPressed(is_high)); + + // debounce + Timer::after(Duration::from_millis(50)).await; + } + + // check button state again as the button may have been + // released (and remained released) within the debounce period + if user_btn.is_high() != is_high { + is_high = !is_high; + info!("Button is pressed: {}", is_high); + controller::send_action(Action::HardwareUserBtnPressed(is_high)); + } + } +} + +#[embassy_executor::task()] +pub async fn render_loop( + window: Rc, + mut double_buffer: DoubleBuffer, + mut ltdc: Ltdc<'static, peripherals::LTDC>, + mut i2c: I2c<'static, mode::Async>, +) { + let mut last_touch: Option = None; + let touch = Gt911::default(); + touch.init(&mut i2c).await.unwrap(); + + loop { + slint::platform::update_timers_and_animations(); + + // process touchscreen events + process_touch(&touch, &mut i2c, &mut last_touch, window.clone()).await; + + // blocking render + let is_dirty = window.draw_if_needed(|renderer| { + let buffer = double_buffer.current(); + renderer.render(buffer, DISPLAY_WIDTH); + }); + + if is_dirty { + // async transfer of frame buffer to lcd + double_buffer.swap(&mut ltdc).await.unwrap(); + } else { + Timer::after(Duration::from_millis(10)).await; + } + } +} + +async fn process_touch( + touch: &Gt911>, + i2c: &mut I2c<'static, mode::Async>, + last_touch: &mut Option, + window: Rc, +) { + // process touchscreen touch events + if let Ok(point) = touch.get_touch(i2c).await { + let button = PointerEventButton::Left; + let event = match point { + Some(point) => { + let position = slint::PhysicalPosition::new(point.x as i32, point.y as i32) + .to_logical(window.scale_factor()); + Some(match last_touch.replace(position) { + Some(_) => WindowEvent::PointerMoved { position }, + None => WindowEvent::PointerPressed { position, button }, + }) + } + None => { + last_touch.take().map(|position| WindowEvent::PointerReleased { position, button }) + } + }; + + if let Some(event) = event { + let is_pointer_release_event = matches!(event, WindowEvent::PointerReleased { .. }); + window.dispatch_event(event); + + // removes hover state on widgets + if is_pointer_release_event { + window.dispatch_event(WindowEvent::PointerExited); + } + } + } +} diff --git a/examples/mcu-embassy/src/bin/ui_simulator.rs b/examples/mcu-embassy/src/bin/ui_simulator.rs new file mode 100644 index 00000000000..dd482d90bf3 --- /dev/null +++ b/examples/mcu-embassy/src/bin/ui_simulator.rs @@ -0,0 +1,250 @@ +// A simulator for the stm32u5g9j-dk2 +// This uses the cross platform sdl2 library to render the application on a PC (see readme for installation instructions) +// The LEFTSHIFT key can be used in place of the blue push button on the dk2 (Hardware User Button) +// Then the Hardware Green Led TOGGLE button is pressed in the UI, the state of the led is logged to the console +// +// How it works: This demo still uses Embassy as the async runtime as well as Slint's software renderer. +// However, sdl2 is used to render the bitmap generated by Slint to a window at 60fps. Sdl2 is also used to +// emulate the touchscreen with the mouse and to capture keyboard events in place of the hardware button on the dk2. +// +// To run: `cargo run --bin ui_simulator --release --features=simulator` + +use std::{ + rc::Rc, + slice, + sync::mpsc::{self, Receiver, Sender}, + thread::{self}, + vec::Vec, +}; + +use embassy_executor::{Executor, Spawner}; +use embassy_time::{Duration, Timer}; +use log::*; +use mcu_embassy::{ + controller::{self, Action, Controller}, + simulator::hardware::HardwareSim, + slint_backend::{StmBackend, TargetPixelType, DISPLAY_HEIGHT, DISPLAY_WIDTH}, +}; +use object_pool::{Pool, Reusable}; +use sdl2::{ + event::Event, keyboard::Keycode, mouse::MouseButton, pixels::PixelFormatEnum, rect::Rect, +}; +use slint::{ + platform::{ + software_renderer::{MinimalSoftwareWindow, RepaintBufferType}, + PointerEventButton, WindowAdapter, WindowEvent, + }, + ComponentHandle, +}; +use slint_generated::MainWindow; +use static_cell::StaticCell; + +static EXECUTOR: StaticCell = StaticCell::new(); +static POOL: StaticCell>> = StaticCell::new(); + +fn main() { + env_logger::builder().filter_level(log::LevelFilter::Debug).format_timestamp_nanos().init(); + + thread::scope(|scope| { + let (tx_render, rx_render) = mpsc::channel(); + let (tx_event, rx_event) = mpsc::channel(); + + let pool = POOL.init(Pool::new(4, || { + vec![TargetPixelType::default(); DISPLAY_WIDTH * DISPLAY_HEIGHT] + })); + + scope.spawn(move || sdl2_render_loop(rx_render, tx_event).unwrap()); + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| { + spawner.spawn(main_task(spawner, tx_render, rx_event, pool)).unwrap(); + }); + }); +} + +fn sdl2_render_loop( + rx_render: Receiver>>, + tx_event: Sender, +) -> Result<(), String> { + let sdl_context = sdl2::init()?; + let video_subsystem = sdl_context.video()?; + + let window = video_subsystem + .window("Demo", DISPLAY_WIDTH as _, DISPLAY_HEIGHT as _) + .position_centered() + .opengl() + .build() + .map_err(|e| e.to_string())?; + + let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?; + let texture_creator = canvas.texture_creator(); + + let mut texture = texture_creator + .create_texture_streaming(PixelFormatEnum::RGB565, DISPLAY_WIDTH as _, DISPLAY_HEIGHT as _) + .map_err(|e| e.to_string())?; + + canvas.clear(); + canvas.copy(&texture, None, Some(Rect::new(0, 0, DISPLAY_WIDTH as _, DISPLAY_HEIGHT as _)))?; + canvas.present(); + + let mut event_pump = sdl_context.event_pump()?; + + loop { + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { + std::process::exit(0) + } + Event::KeyDown { keycode: Some(Keycode::LSHIFT), .. } => { + controller::send_action(Action::HardwareUserBtnPressed(true)) + } + Event::KeyUp { keycode: Some(Keycode::LSHIFT), .. } => { + controller::send_action(Action::HardwareUserBtnPressed(false)) + } + Event::MouseButtonDown { + timestamp: _timestamp, + window_id: _window_id, + which: _which, + mouse_btn, + clicks: _clicks, + x, + y, + } => { + if mouse_btn == MouseButton::Left { + let button = PointerEventButton::Left; + let position = slint::PhysicalPosition::new(x, y).to_logical(1.0); + let event = WindowEvent::PointerPressed { position, button }; + tx_event.send(event).unwrap(); + } + } + Event::MouseButtonUp { + timestamp: _timestamp, + window_id: _window_id, + which: _which, + mouse_btn, + clicks: _clicks, + x, + y, + } => { + if mouse_btn == MouseButton::Left { + let button = PointerEventButton::Left; + let position = slint::PhysicalPosition::new(x, y).to_logical(1.0); + let event = WindowEvent::PointerReleased { position, button }; + tx_event.send(event).unwrap(); + } + } + Event::MouseMotion { + timestamp: _timestamp, + window_id: _window_id, + which: _which, + mousestate, + x, + y, + xrel: _xrel, + yrel: _yrel, + } => { + if mousestate.is_mouse_button_pressed(MouseButton::Left) { + let position = slint::PhysicalPosition::new(x, y).to_logical(1.0); + let event = WindowEvent::PointerMoved { position }; + tx_event.send(event).unwrap(); + } + } + + _ => {} + } + } + + 'render_buffers: loop { + match rx_render.try_recv() { + Ok(buf) => { + texture.with_lock(None, |buffer: &mut [u8], _pitch: usize| { + let buf_ptr = buf.as_ptr() as *const u8; + let buf_slice = unsafe { slice::from_raw_parts(buf_ptr, buf.len() * 2) }; + buffer.copy_from_slice(buf_slice); + drop(buf); // returns buffer to pool + })?; + canvas.clear(); + canvas.copy_ex( + &texture, + None, + Some(Rect::new(0, 0, DISPLAY_WIDTH as _, DISPLAY_HEIGHT as _)), + 0.0, + None, + false, + false, + )?; + canvas.present(); + } + _ => { + // ignore + break 'render_buffers; + } + } + } + } +} + +#[embassy_executor::task] +async fn main_task( + spawner: Spawner, + tx_render: Sender>>, + rx_event: Receiver, + pool: &'static Pool>, +) { + let window = MinimalSoftwareWindow::new(RepaintBufferType::SwappedBuffers); + window.set_size(slint::PhysicalSize::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)); + let backend = Box::new(StmBackend::new(window.clone())); + slint::platform::set_platform(backend).expect("backend already initialized"); + info!("slint gui setup complete"); + + spawner.spawn(embassy_render_loop(window, tx_render, rx_event, pool)).unwrap(); + + // give the render loop time to come up (otherwise it will draw a blank screen) + Timer::after(Duration::from_millis(200)).await; + let main_window = MainWindow::new().unwrap(); + main_window.show().expect("unable to show main window"); + + let hardware = HardwareSim {}; + + // run the gui controller loop + let mut controller = Controller::new(&main_window, hardware); + controller.run().await; +} + +#[embassy_executor::task] +async fn embassy_render_loop( + window: Rc, + tx_render: Sender>>, + rx_event: Receiver, + pool: &'static Pool>, +) { + info!("embassy_render_loop"); + + loop { + slint::platform::update_timers_and_animations(); + + 'event: loop { + match rx_event.try_recv() { + Ok(e) => { + window.dispatch_event(e); + } + Err(_) => break 'event, + } + } + + // redraw the entire window (otherwise we get partial redraws which are more complicated to deal with) + window.request_redraw(); + + let _is_dirty = window.draw_if_needed(|renderer| match pool.try_pull() { + Some(mut buffer) => { + renderer.render(&mut buffer, DISPLAY_WIDTH as _); + tx_render.send(buffer).ok(); + } + None => { + // this happens when the MainWindow hasn't yet been created or if it has been closed by the user + } + }); + + // for approx 60fps + Timer::after(Duration::from_millis(16)).await; + } +} diff --git a/examples/mcu-embassy/src/controller.rs b/examples/mcu-embassy/src/controller.rs new file mode 100644 index 00000000000..9d1c33a2838 --- /dev/null +++ b/examples/mcu-embassy/src/controller.rs @@ -0,0 +1,100 @@ +use embassy_sync::channel::Channel; +use slint::ComponentHandle; +use slint_generated::{Globals, MainWindow}; + +use crate::{error, warn}; + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone)] +pub enum Action { + HardwareUserBtnPressed(bool), + TouchscreenToggleBtn(bool), +} + +#[cfg(feature = "mcu")] +type ActionChannelType = Channel; + +#[cfg(feature = "simulator")] +type ActionChannelType = + Channel; + +pub static ACTION: ActionChannelType = Channel::new(); + +// see mcu::hardware or simulator::hardware modules for impl +// depending on features used +pub trait Hardware { + fn green_led_set_high(&mut self) {} + + fn green_led_set_low(&mut self) {} +} + +pub struct Controller<'a, Hardware> { + main_window: &'a MainWindow, + hardware: Hardware, +} + +impl<'a, H> Controller<'a, H> +where + H: Hardware, +{ + pub fn new(main_window: &'a MainWindow, hardware: H) -> Self { + Self { + main_window, + hardware, + } + } + + pub async fn run(&mut self) { + self.set_action_event_handlers(); + + loop { + let action = ACTION.receive().await; + + match self.process_action(action).await { + Ok(()) => { + // all good + } + Err(e) => { + error!("process action: {:?}", e); + } + } + } + } + + pub async fn process_action(&mut self, action: Action) -> Result<(), ()> { + let globals = self.main_window.global::(); + + match action { + Action::HardwareUserBtnPressed(is_pressed) => { + globals.set_hardware_user_btn_pressed(is_pressed); + } + Action::TouchscreenToggleBtn(on) => { + if on { + self.hardware.green_led_set_low(); + } else { + self.hardware.green_led_set_high() + } + } + } + Ok(()) + } + + // user initiated action event handlers + fn set_action_event_handlers(&self) { + let globals = self.main_window.global::(); + globals.on_toggle_btn(|on| send_action(Action::TouchscreenToggleBtn(on))); + } +} + +pub fn send_action(a: Action) { + // use non-blocking try_send here because this function needs is called from sync code (the gui callbacks) + match ACTION.try_send(a) { + Ok(_) => { + // see loop in `fn run()` for dequeue + } + Err(a) => { + // this could happen because the controller is slow to respond or we are making too many requests + warn!("user action queue full, could not add: {:?}", a) + } + } +} diff --git a/examples/mcu-embassy/src/lib.rs b/examples/mcu-embassy/src/lib.rs new file mode 100644 index 00000000000..05e09f4748d --- /dev/null +++ b/examples/mcu-embassy/src/lib.rs @@ -0,0 +1,18 @@ +#![cfg_attr(feature = "mcu", no_std)] + +extern crate alloc; + +pub mod controller; +pub mod slint_backend; + +#[cfg(feature = "mcu")] +pub mod mcu; + +#[cfg(feature = "mcu")] +pub use defmt::{debug, error, info, trace, warn}; + +#[cfg(feature = "simulator")] +pub mod simulator; + +#[cfg(feature = "simulator")] +pub use log::{debug, error, info, trace, warn}; diff --git a/examples/mcu-embassy/src/mcu/double_buffer.rs b/examples/mcu-embassy/src/mcu/double_buffer.rs new file mode 100644 index 00000000000..9e5f1e6b5f4 --- /dev/null +++ b/examples/mcu-embassy/src/mcu/double_buffer.rs @@ -0,0 +1,60 @@ +use embassy_stm32::ltdc::{self, Ltdc, LtdcLayerConfig}; +use slint::platform::software_renderer::Rgb565Pixel; + +use crate::slint_backend::TargetPixelType; + +// A simple double buffer +pub struct DoubleBuffer { + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + is_buf0: bool, + layer_config: LtdcLayerConfig, +} + +impl DoubleBuffer { + pub fn new( + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + layer_config: LtdcLayerConfig, + ) -> Self { + Self { + buf0, + buf1, + is_buf0: true, + layer_config, + } + } + + pub fn current(&mut self) -> &mut [TargetPixelType] { + if self.is_buf0 { + self.buf0 + } else { + self.buf1 + } + } + + pub fn swap_temp(&mut self) { + self.is_buf0 = !self.is_buf0; + } + + pub async fn swap( + &mut self, + ltdc: &mut Ltdc<'_, T>, + ) -> Result<(), ltdc::Error> { + let buf = self.current(); + let frame_buffer = buf.as_ptr(); + self.is_buf0 = !self.is_buf0; + ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _) + .await + } + + // Clears the buffer + pub fn clear(&mut self) { + let buf = self.current(); + let solid_black = Rgb565Pixel::default(); + + for a in buf.iter_mut() { + *a = solid_black; + } + } +} diff --git a/examples/mcu-embassy/src/mcu/hardware.rs b/examples/mcu-embassy/src/mcu/hardware.rs new file mode 100644 index 00000000000..fa0172c550f --- /dev/null +++ b/examples/mcu-embassy/src/mcu/hardware.rs @@ -0,0 +1,15 @@ +use crate::controller::Hardware; + +pub struct HardwareMcu { + pub green_led: embassy_stm32::gpio::Output<'static>, +} + +impl Hardware for HardwareMcu { + fn green_led_set_high(&mut self) { + self.green_led.set_high(); + } + + fn green_led_set_low(&mut self) { + self.green_led.set_low(); + } +} diff --git a/examples/mcu-embassy/src/mcu/mod.rs b/examples/mcu-embassy/src/mcu/mod.rs new file mode 100644 index 00000000000..ffce7e7605e --- /dev/null +++ b/examples/mcu-embassy/src/mcu/mod.rs @@ -0,0 +1,8 @@ +pub mod double_buffer; +pub mod hardware; +pub mod rcc_setup; + +use embedded_alloc::Heap; + +#[global_allocator] +pub static ALLOCATOR: Heap = Heap::empty(); diff --git a/examples/mcu-embassy/src/mcu/rcc_setup.rs b/examples/mcu-embassy/src/mcu/rcc_setup.rs new file mode 100644 index 00000000000..a338bd8c2f2 --- /dev/null +++ b/examples/mcu-embassy/src/mcu/rcc_setup.rs @@ -0,0 +1,32 @@ +use embassy_stm32::time::Hertz; +use embassy_stm32::{rcc, Config, Peripherals}; + +/// Sets up clocks for the stm32u5g9zj mcu +/// change this if you plan to use a different microcontroller +pub fn stm32u5g9zj_init() -> Peripherals { + // setup power and clocks for an STM32U5G9J-DK2 run from an external 16 Mhz external oscillator + let mut config = Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: Hertz(16_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.pll1 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV1, + mul: rcc::PllMul::MUL10, + divp: None, + divq: None, + divr: Some(rcc::PllDiv::DIV1), + }); + config.rcc.sys = rcc::Sysclk::PLL1_R; // 160 Mhz + config.rcc.pll3 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV4, // PLL_M + mul: rcc::PllMul::MUL125, // PLL_N + divp: None, + divq: None, + divr: Some(rcc::PllDiv::DIV20), + }); + config.rcc.mux.ltdcsel = rcc::mux::Ltdcsel::PLL3_R; // 25 MHz + embassy_stm32::init(config) +} diff --git a/examples/mcu-embassy/src/simulator/hardware.rs b/examples/mcu-embassy/src/simulator/hardware.rs new file mode 100644 index 00000000000..def7d272e52 --- /dev/null +++ b/examples/mcu-embassy/src/simulator/hardware.rs @@ -0,0 +1,14 @@ +use crate::controller::Hardware; +use crate::info; + +pub struct HardwareSim {} + +impl Hardware for HardwareSim { + fn green_led_set_high(&mut self) { + info!("green led OFF"); + } + + fn green_led_set_low(&mut self) { + info!("green led ON"); + } +} diff --git a/examples/mcu-embassy/src/simulator/mod.rs b/examples/mcu-embassy/src/simulator/mod.rs new file mode 100644 index 00000000000..9c06cff9153 --- /dev/null +++ b/examples/mcu-embassy/src/simulator/mod.rs @@ -0,0 +1 @@ +pub mod hardware; diff --git a/examples/mcu-embassy/src/slint_backend.rs b/examples/mcu-embassy/src/slint_backend.rs new file mode 100644 index 00000000000..31425825ffa --- /dev/null +++ b/examples/mcu-embassy/src/slint_backend.rs @@ -0,0 +1,35 @@ +use alloc::rc::Rc; +use embassy_time::Instant; +use slint::{ + platform::{ + software_renderer::{self, MinimalSoftwareWindow}, + Platform, WindowAdapter, + }, + PlatformError, +}; + +pub const DISPLAY_WIDTH: usize = 800; +pub const DISPLAY_HEIGHT: usize = 480; +pub type TargetPixelType = software_renderer::Rgb565Pixel; + +pub struct StmBackend { + window: Rc, +} + +impl StmBackend { + pub fn new(window: Rc) -> Self { + Self { window } + } +} + +impl Platform for StmBackend { + fn create_window_adapter(&self) -> Result, PlatformError> { + let window = self.window.clone(); + crate::info!("create_window_adapter called"); + Ok(window) + } + + fn duration_since_start(&self) -> core::time::Duration { + Instant::now().duration_since(Instant::from_secs(0)).into() + } +} diff --git a/examples/mcu-embassy/ui/common.slint b/examples/mcu-embassy/ui/common.slint new file mode 100644 index 00000000000..3ba157fcbbf --- /dev/null +++ b/examples/mcu-embassy/ui/common.slint @@ -0,0 +1,109 @@ + +export global Globals { + in property hardware-user-btn-pressed; + callback toggle-btn(bool); +} + +global Palette { + out property neutralSecondaryAlt: #8a8886; + out property neutralLight: #edebe9; + out property white: #ffffff; + out property black: #000000; + out property neutralDark: #201f1e; +} + +export global Theme { + out property page-background-color: Palette.white; + out property text-foreground-color: Palette.black; + out property font-size-standard: 24px; + out property page-width: 800px; + out property page-height: 480px; +} + +export component Button { + callback clicked; + in property text <=> text.text; + out property pressed: touch.pressed; + in property checkable; + in-out property checked; + in property font-size <=> text.font-size; + in property background: Palette.white; + Rectangle { + border-width: 1px; + border-radius: 2px; + border-color: Palette.neutralSecondaryAlt; + background: (touch.pressed || root.checked) ? Palette.neutralLight : root.background; + } + + horizontal-stretch: 0; + vertical-stretch: 0; + min-height: max(32px, l.min-height); + l := HorizontalLayout { + padding-left: 10px; + padding-right: 10px; + padding-top: 3px; + padding-bottom: 3px; + text := Text { + color: Palette.neutralDark; + horizontal-alignment: center; + vertical-alignment: center; + font-size: Theme.font-size-standard; + } + } + + touch := TouchArea { + clicked => { + if (root.checkable) { + root.checked = !root.checked; + } + root.clicked(); + } + } + + @children +} + +export component Toggle inherits Rectangle { + callback clicked(); + in-out property on; + width: 100px; + height: 40px; + + Rectangle { + width: 100px; + height: 40px; + background: on ? blue : gray; + animate background { + duration: 100ms; + easing: ease; + } + border-radius: 20px; + + Text { + text: on ? "On" : "Off"; + x: on ? 8px : parent.width - 50px; + color: white; + font-size: Theme.font-size-standard; + } + + Rectangle { + width: parent.height - 4px; + height: parent.height - 4px; + x: on ? parent.width - (parent.height - 2px) : 2px; + animate x { + duration: 100ms; + easing: ease; + } + y: 2px; + background: white; + border-radius: (parent.height - 4px) / 2; + } + } + + TouchArea { + clicked => { + on = !on; + root.clicked(); + } + } +} diff --git a/examples/mcu-embassy/ui/main.slint b/examples/mcu-embassy/ui/main.slint new file mode 100644 index 00000000000..45c62b5cfaf --- /dev/null +++ b/examples/mcu-embassy/ui/main.slint @@ -0,0 +1,62 @@ +import { Globals, Button, Theme, Toggle } from "common.slint"; + +export { Globals } + +export component MainWindow inherits Window { + width: 800px; + height: 480px; + + HorizontalLayout { + alignment: center; + VerticalLayout { + alignment: center; + spacing: 50px; + + Button { + text: "Hello, World"; + font-size: Theme.font-size-standard; + height: 50px; + animate height { + duration: 100ms; + easing: ease-in; + } + states [ + left-aligned when self.pressed: { + height: 80px; + } + ] + } + + HorizontalLayout { + Text { + width: 300px; + text: "Hardware Green Led"; + font-size: Theme.font-size-standard; + } + + Toggle { + width: 100px; + clicked => { + Globals.toggle-btn(self.on); + } + } + } + + HorizontalLayout { + Text { + width: 300px; + vertical-alignment: center; + text: "Hardware User Button"; + font-size: Theme.font-size-standard; + } + + Rectangle { + width: 100px; + height: 100px; + background: Globals.hardware-user-btn-pressed ? blue : lightgray; + border-radius: 100px; + } + } + } + } +} From 0f241d8c435e59619adefa8c19e6a6d25d5dce3a Mon Sep 17 00:00:00 2001 From: David Haig Date: Wed, 11 Dec 2024 11:35:16 +0000 Subject: [PATCH 02/13] Fixed some CI issues --- examples/mcu-embassy/slint_generated/Cargo.toml | 1 - examples/mcu-embassy/src/controller.rs | 5 +---- examples/mcu-embassy/src/mcu/double_buffer.rs | 10 ++-------- examples/mcu-embassy/src/mcu/rcc_setup.rs | 5 +---- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/examples/mcu-embassy/slint_generated/Cargo.toml b/examples/mcu-embassy/slint_generated/Cargo.toml index 30365cbe125..9e2741c652c 100644 --- a/examples/mcu-embassy/slint_generated/Cargo.toml +++ b/examples/mcu-embassy/slint_generated/Cargo.toml @@ -2,7 +2,6 @@ name = "slint-generated" version = "0.1.0" edition = "2021" -rust-version = "1.79" build = "build.rs" readme = "README.md" resolver = "2" diff --git a/examples/mcu-embassy/src/controller.rs b/examples/mcu-embassy/src/controller.rs index 9d1c33a2838..af596d74715 100644 --- a/examples/mcu-embassy/src/controller.rs +++ b/examples/mcu-embassy/src/controller.rs @@ -38,10 +38,7 @@ where H: Hardware, { pub fn new(main_window: &'a MainWindow, hardware: H) -> Self { - Self { - main_window, - hardware, - } + Self { main_window, hardware } } pub async fn run(&mut self) { diff --git a/examples/mcu-embassy/src/mcu/double_buffer.rs b/examples/mcu-embassy/src/mcu/double_buffer.rs index 9e5f1e6b5f4..75ee9669b6b 100644 --- a/examples/mcu-embassy/src/mcu/double_buffer.rs +++ b/examples/mcu-embassy/src/mcu/double_buffer.rs @@ -17,12 +17,7 @@ impl DoubleBuffer { buf1: &'static mut [TargetPixelType], layer_config: LtdcLayerConfig, ) -> Self { - Self { - buf0, - buf1, - is_buf0: true, - layer_config, - } + Self { buf0, buf1, is_buf0: true, layer_config } } pub fn current(&mut self) -> &mut [TargetPixelType] { @@ -44,8 +39,7 @@ impl DoubleBuffer { let buf = self.current(); let frame_buffer = buf.as_ptr(); self.is_buf0 = !self.is_buf0; - ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _) - .await + ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await } // Clears the buffer diff --git a/examples/mcu-embassy/src/mcu/rcc_setup.rs b/examples/mcu-embassy/src/mcu/rcc_setup.rs index a338bd8c2f2..670f53e844b 100644 --- a/examples/mcu-embassy/src/mcu/rcc_setup.rs +++ b/examples/mcu-embassy/src/mcu/rcc_setup.rs @@ -6,10 +6,7 @@ use embassy_stm32::{rcc, Config, Peripherals}; pub fn stm32u5g9zj_init() -> Peripherals { // setup power and clocks for an STM32U5G9J-DK2 run from an external 16 Mhz external oscillator let mut config = Config::default(); - config.rcc.hse = Some(rcc::Hse { - freq: Hertz(16_000_000), - mode: rcc::HseMode::Oscillator, - }); + config.rcc.hse = Some(rcc::Hse { freq: Hertz(16_000_000), mode: rcc::HseMode::Oscillator }); config.rcc.pll1 = Some(rcc::Pll { source: rcc::PllSource::HSE, prediv: rcc::PllPreDiv::DIV1, From 0373abd89ccbd80c8677d2594a12944041a3f8be Mon Sep 17 00:00:00 2001 From: David Haig Date: Wed, 11 Dec 2024 11:59:40 +0000 Subject: [PATCH 03/13] Added mcu-embassy tests to CI --- .github/workflows/build_docs.yaml | 2 +- .github/workflows/ci.yaml | 18 ++++++++++++++++-- examples/mcu-embassy/Cargo.toml | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_docs.yaml b/.github/workflows/build_docs.yaml index 9a4a3cd3c65..c1f19495272 100644 --- a/.github/workflows/build_docs.yaml +++ b/.github/workflows/build_docs.yaml @@ -180,7 +180,7 @@ jobs: api/node/docs docs/site - name: "Check for docs warnings in internal crates" - run: cargo doc --workspace --no-deps --all-features --exclude slint-node --exclude pyslint --exclude mcu-board-support --exclude printerdemo_mcu --exclude carousel --exclude test-* --exclude plotter --exclude uefi-demo --exclude ffmpeg --exclude gstreamer-player --exclude slint-cpp --exclude slint-python + run: cargo doc --workspace --no-deps --all-features --exclude slint-node --exclude pyslint --exclude mcu-board-support --exclude mcu-embassy --exclude printerdemo_mcu --exclude carousel --exclude test-* --exclude plotter --exclude uefi-demo --exclude ffmpeg --exclude gstreamer-player --exclude slint-cpp --exclude slint-python - name: Clean cache # Don't cache docs to avoid them including removed classes being published run: | rm -rf target/doc target/cppdocs api/node/docs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e2b3294a2c4..09fff89ae42 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -86,12 +86,12 @@ jobs: cargo update -p home --precise 0.5.9 fi - name: Run tests (not qt) - run: cargo test --verbose --all-features --workspace ${{ matrix.extra_args }} --exclude slint-node --exclude pyslint --exclude test-driver-node --exclude slint-node --exclude test-driver-nodejs --exclude test-driver-cpp --exclude mcu-board-support --exclude printerdemo_mcu --exclude uefi-demo --exclude slint-cpp --exclude slint-python -- --skip=_qt::t + run: cargo test --verbose --all-features --workspace ${{ matrix.extra_args }} --exclude slint-node --exclude pyslint --exclude test-driver-node --exclude slint-node --exclude test-driver-nodejs --exclude test-driver-cpp --exclude mcu-board-support --exclude mcu-embassy --exclude printerdemo_mcu --exclude uefi-demo --exclude slint-cpp --exclude slint-python -- --skip=_qt::t env: SLINT_CREATE_SCREENSHOTS: 1 shell: bash - name: Run tests (qt) - run: cargo test --verbose --all-features --workspace ${{ matrix.extra_args }} --exclude slint-node --exclude pyslint --exclude test-driver-node --exclude slint-node --exclude test-driver-nodejs --exclude test-driver-cpp --exclude mcu-board-support --exclude printerdemo_mcu --exclude uefi-demo --exclude slint-cpp --exclude slint-python --bin test-driver-rust -- _qt --test-threads=1 + run: cargo test --verbose --all-features --workspace ${{ matrix.extra_args }} --exclude slint-node --exclude pyslint --exclude test-driver-node --exclude slint-node --exclude test-driver-nodejs --exclude test-driver-cpp --exclude mcu-board-support --exclude mcu-embassy --exclude printerdemo_mcu --exclude uefi-demo --exclude slint-cpp --exclude slint-python --bin test-driver-rust -- _qt --test-threads=1 shell: bash - name: Archive screenshots after failed tests if: ${{ failure() }} @@ -375,6 +375,20 @@ jobs: - name: Check run: cargo check --target=${{matrix.target}} -p printerdemo_mcu --no-default-features --features=mcu-board-support/${{matrix.feature}} --release + # test to compile the mcu backend for the arm target (no_std) using embassy + mcu-embassy: + env: + SLINT_FONT_SIZES: 8,11,10,12,13,14,15,16,18,20,22,24,32 + RUSTFLAGS: --cfg slint_int_coord -D warnings + CARGO_PROFILE_DEV_DEBUG: 0 + CARGO_PROFILE_RELEASE_OPT_LEVEL: s + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-rust + - name: Check + run: cargo check --bin ui_mcu --target=thumbv8m.main-none-eabihf --no-default-features --features=mcu --release + mcu_esp: env: RUSTFLAGS: -D warnings diff --git a/examples/mcu-embassy/Cargo.toml b/examples/mcu-embassy/Cargo.toml index a3f6a380073..a982917d859 100644 --- a/examples/mcu-embassy/Cargo.toml +++ b/examples/mcu-embassy/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" authors = ["David Haig "] readme = "README.md" -license = "Apache-2.0" +license = "MIT" [dependencies] log = "0.4" From bdbe01209257a26fd173efa2335af7ca07fc37ab Mon Sep 17 00:00:00 2001 From: David Haig Date: Wed, 11 Dec 2024 13:37:02 +0000 Subject: [PATCH 04/13] Attempt to fix CI issue with mcu-embassy check --- .github/workflows/ci.yaml | 2 +- examples/mcu-embassy/Cargo.toml | 2 ++ examples/mcu-embassy/slint_generated/Cargo.toml | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 09fff89ae42..4193b6b7a4b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -387,7 +387,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-rust - name: Check - run: cargo check --bin ui_mcu --target=thumbv8m.main-none-eabihf --no-default-features --features=mcu --release + run: cargo check -p mcu_embassy --bin ui_mcu --target=thumbv8m.main-none-eabihf --no-default-features --features="mcu-embassy/mcu" --release mcu_esp: env: diff --git a/examples/mcu-embassy/Cargo.toml b/examples/mcu-embassy/Cargo.toml index a982917d859..d820301cc1c 100644 --- a/examples/mcu-embassy/Cargo.toml +++ b/examples/mcu-embassy/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" authors = ["David Haig "] readme = "README.md" license = "MIT" +repository = "https://github.com/slint-ui/slint" +homepage = "https://slint.dev" [dependencies] log = "0.4" diff --git a/examples/mcu-embassy/slint_generated/Cargo.toml b/examples/mcu-embassy/slint_generated/Cargo.toml index 9e2741c652c..e938527653d 100644 --- a/examples/mcu-embassy/slint_generated/Cargo.toml +++ b/examples/mcu-embassy/slint_generated/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" build = "build.rs" readme = "README.md" resolver = "2" +license = "MIT" +repository = "https://github.com/slint-ui/slint" +homepage = "https://slint.dev" [dependencies] slint = { version = "=1.9.0", path = "../../../api/rs/slint", default-features = false, features = [ From e644ae19828435249f8c45779ba2607a9b65e3a6 Mon Sep 17 00:00:00 2001 From: David Haig Date: Tue, 7 Jan 2025 12:47:40 +0000 Subject: [PATCH 05/13] Updated copyright notice and embassy dependency --- examples/mcu-embassy/Cargo.toml | 17 +++++++++++------ examples/mcu-embassy/slint_generated/Cargo.toml | 5 +++++ examples/mcu-embassy/slint_generated/README.md | 3 +++ examples/mcu-embassy/slint_generated/build.rs | 3 +++ examples/mcu-embassy/slint_generated/src/lib.rs | 3 +++ examples/mcu-embassy/src/bin/ui_mcu.rs | 3 +++ examples/mcu-embassy/src/bin/ui_simulator.rs | 3 +++ examples/mcu-embassy/src/controller.rs | 3 +++ examples/mcu-embassy/src/lib.rs | 3 +++ examples/mcu-embassy/src/mcu/double_buffer.rs | 3 +++ examples/mcu-embassy/src/mcu/hardware.rs | 3 +++ examples/mcu-embassy/src/mcu/mod.rs | 3 +++ examples/mcu-embassy/src/mcu/rcc_setup.rs | 3 +++ examples/mcu-embassy/src/simulator/hardware.rs | 3 +++ examples/mcu-embassy/src/simulator/mod.rs | 3 +++ examples/mcu-embassy/src/slint_backend.rs | 3 +++ examples/mcu-embassy/ui/common.slint | 2 ++ examples/mcu-embassy/ui/main.slint | 3 +++ 18 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 examples/mcu-embassy/slint_generated/README.md diff --git a/examples/mcu-embassy/Cargo.toml b/examples/mcu-embassy/Cargo.toml index d820301cc1c..f8ff867b9b5 100644 --- a/examples/mcu-embassy/Cargo.toml +++ b/examples/mcu-embassy/Cargo.toml @@ -1,10 +1,15 @@ +# Copyright © 2025 David Haig +# SPDX-License-Identifier: MIT + [package] name = "mcu-embassy" version = "0.1.0" edition = "2021" authors = ["David Haig "] readme = "README.md" +resolver = "2" license = "MIT" +publish = false repository = "https://github.com/slint-ui/slint" homepage = "https://slint.dev" @@ -33,7 +38,7 @@ slint = { version = "=1.9.0", path = "../../api/rs/slint", default-features = fa ] } i-slint-core-macros = { version = "=1.9.0", path = "../../internal/core-macros" } -embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", optional = true, features = [ +embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "4790f8f", optional = true, features = [ "stm32u5g9zj", "time-driver-tim2", "exti", @@ -42,14 +47,14 @@ embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe" "chrono", "time", ] } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", optional = true } -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", optional = true, features = [ +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "4790f8f", optional = true } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "4790f8f", optional = true, features = [ "task-arena-size-32768", + "arch-cortex-m", "executor-thread", - "integrated-timers", ] } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", optional = true } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", optional = true } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "4790f8f", optional = true } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "4790f8f", optional = true } env_logger = { version = "0.9.0", optional = true } sdl2 = { git = "https://github.com/Rust-SDL2/rust-sdl2", rev = "400e033", optional = true } diff --git a/examples/mcu-embassy/slint_generated/Cargo.toml b/examples/mcu-embassy/slint_generated/Cargo.toml index e938527653d..4c85fe7b5d9 100644 --- a/examples/mcu-embassy/slint_generated/Cargo.toml +++ b/examples/mcu-embassy/slint_generated/Cargo.toml @@ -1,11 +1,16 @@ +# Copyright © 2025 David Haig +# SPDX-License-Identifier: MIT + [package] name = "slint-generated" version = "0.1.0" edition = "2021" build = "build.rs" +authors = ["David Haig "] readme = "README.md" resolver = "2" license = "MIT" +publish = false repository = "https://github.com/slint-ui/slint" homepage = "https://slint.dev" diff --git a/examples/mcu-embassy/slint_generated/README.md b/examples/mcu-embassy/slint_generated/README.md new file mode 100644 index 00000000000..1925b715b52 --- /dev/null +++ b/examples/mcu-embassy/slint_generated/README.md @@ -0,0 +1,3 @@ +# Generated + +This crate is here to separate the `.slint` file compilation from the main application. \ No newline at end of file diff --git a/examples/mcu-embassy/slint_generated/build.rs b/examples/mcu-embassy/slint_generated/build.rs index 959c4d2f8d9..3d3994dd66b 100644 --- a/examples/mcu-embassy/slint_generated/build.rs +++ b/examples/mcu-embassy/slint_generated/build.rs @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + fn main() { let config = slint_build::CompilerConfiguration::new() .embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer); diff --git a/examples/mcu-embassy/slint_generated/src/lib.rs b/examples/mcu-embassy/slint_generated/src/lib.rs index 3107212d8f6..86b23c5a7ba 100644 --- a/examples/mcu-embassy/slint_generated/src/lib.rs +++ b/examples/mcu-embassy/slint_generated/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + #![no_std] slint::include_modules!(); diff --git a/examples/mcu-embassy/src/bin/ui_mcu.rs b/examples/mcu-embassy/src/bin/ui_mcu.rs index ac1b071b354..063082b20f8 100644 --- a/examples/mcu-embassy/src/bin/ui_mcu.rs +++ b/examples/mcu-embassy/src/bin/ui_mcu.rs @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + // A demo for stm32u5g9j-dk2 // The application renders a simple Slint screen to the display and the user can interact with it // by toggling the green led on and off as well as pushing the blue button on the dk which should diff --git a/examples/mcu-embassy/src/bin/ui_simulator.rs b/examples/mcu-embassy/src/bin/ui_simulator.rs index dd482d90bf3..179bce054e9 100644 --- a/examples/mcu-embassy/src/bin/ui_simulator.rs +++ b/examples/mcu-embassy/src/bin/ui_simulator.rs @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + // A simulator for the stm32u5g9j-dk2 // This uses the cross platform sdl2 library to render the application on a PC (see readme for installation instructions) // The LEFTSHIFT key can be used in place of the blue push button on the dk2 (Hardware User Button) diff --git a/examples/mcu-embassy/src/controller.rs b/examples/mcu-embassy/src/controller.rs index af596d74715..e0a5b240d4b 100644 --- a/examples/mcu-embassy/src/controller.rs +++ b/examples/mcu-embassy/src/controller.rs @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + use embassy_sync::channel::Channel; use slint::ComponentHandle; use slint_generated::{Globals, MainWindow}; diff --git a/examples/mcu-embassy/src/lib.rs b/examples/mcu-embassy/src/lib.rs index 05e09f4748d..e77432bda91 100644 --- a/examples/mcu-embassy/src/lib.rs +++ b/examples/mcu-embassy/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + #![cfg_attr(feature = "mcu", no_std)] extern crate alloc; diff --git a/examples/mcu-embassy/src/mcu/double_buffer.rs b/examples/mcu-embassy/src/mcu/double_buffer.rs index 75ee9669b6b..dd354f8959a 100644 --- a/examples/mcu-embassy/src/mcu/double_buffer.rs +++ b/examples/mcu-embassy/src/mcu/double_buffer.rs @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + use embassy_stm32::ltdc::{self, Ltdc, LtdcLayerConfig}; use slint::platform::software_renderer::Rgb565Pixel; diff --git a/examples/mcu-embassy/src/mcu/hardware.rs b/examples/mcu-embassy/src/mcu/hardware.rs index fa0172c550f..a232e4cc2a9 100644 --- a/examples/mcu-embassy/src/mcu/hardware.rs +++ b/examples/mcu-embassy/src/mcu/hardware.rs @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + use crate::controller::Hardware; pub struct HardwareMcu { diff --git a/examples/mcu-embassy/src/mcu/mod.rs b/examples/mcu-embassy/src/mcu/mod.rs index ffce7e7605e..ef8c204616f 100644 --- a/examples/mcu-embassy/src/mcu/mod.rs +++ b/examples/mcu-embassy/src/mcu/mod.rs @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + pub mod double_buffer; pub mod hardware; pub mod rcc_setup; diff --git a/examples/mcu-embassy/src/mcu/rcc_setup.rs b/examples/mcu-embassy/src/mcu/rcc_setup.rs index 670f53e844b..a4ae61f6d20 100644 --- a/examples/mcu-embassy/src/mcu/rcc_setup.rs +++ b/examples/mcu-embassy/src/mcu/rcc_setup.rs @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + use embassy_stm32::time::Hertz; use embassy_stm32::{rcc, Config, Peripherals}; diff --git a/examples/mcu-embassy/src/simulator/hardware.rs b/examples/mcu-embassy/src/simulator/hardware.rs index def7d272e52..3ac17665189 100644 --- a/examples/mcu-embassy/src/simulator/hardware.rs +++ b/examples/mcu-embassy/src/simulator/hardware.rs @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + use crate::controller::Hardware; use crate::info; diff --git a/examples/mcu-embassy/src/simulator/mod.rs b/examples/mcu-embassy/src/simulator/mod.rs index 9c06cff9153..66c47e35c68 100644 --- a/examples/mcu-embassy/src/simulator/mod.rs +++ b/examples/mcu-embassy/src/simulator/mod.rs @@ -1 +1,4 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + pub mod hardware; diff --git a/examples/mcu-embassy/src/slint_backend.rs b/examples/mcu-embassy/src/slint_backend.rs index 31425825ffa..329e4bbafa8 100644 --- a/examples/mcu-embassy/src/slint_backend.rs +++ b/examples/mcu-embassy/src/slint_backend.rs @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + use alloc::rc::Rc; use embassy_time::Instant; use slint::{ diff --git a/examples/mcu-embassy/ui/common.slint b/examples/mcu-embassy/ui/common.slint index 3ba157fcbbf..92a5c1b02b6 100644 --- a/examples/mcu-embassy/ui/common.slint +++ b/examples/mcu-embassy/ui/common.slint @@ -1,3 +1,5 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT export global Globals { in property hardware-user-btn-pressed; diff --git a/examples/mcu-embassy/ui/main.slint b/examples/mcu-embassy/ui/main.slint index 45c62b5cfaf..494ee1d8075 100644 --- a/examples/mcu-embassy/ui/main.slint +++ b/examples/mcu-embassy/ui/main.slint @@ -1,3 +1,6 @@ +// Copyright © 2025 David Haig +// SPDX-License-Identifier: MIT + import { Globals, Button, Theme, Toggle } from "common.slint"; export { Globals } From ab66c907fe5810fbe733d317bfd71466d399d125 Mon Sep 17 00:00:00 2001 From: David Haig Date: Wed, 8 Jan 2025 00:55:41 +0000 Subject: [PATCH 06/13] Fixed up dependencies --- examples/mcu-embassy/Cargo.toml | 32 +++++++------------ .../mcu-embassy/slint_generated/Cargo.toml | 6 ++-- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/examples/mcu-embassy/Cargo.toml b/examples/mcu-embassy/Cargo.toml index f8ff867b9b5..57bc9fe012d 100644 --- a/examples/mcu-embassy/Cargo.toml +++ b/examples/mcu-embassy/Cargo.toml @@ -7,8 +7,8 @@ version = "0.1.0" edition = "2021" authors = ["David Haig "] readme = "README.md" -resolver = "2" license = "MIT" +resolver = "2" publish = false repository = "https://github.com/slint-ui/slint" homepage = "https://slint.dev" @@ -28,17 +28,17 @@ embedded-alloc = { version = "0.5", optional = true } heapless = { version = "0.8", default-features = false, features = [ "defmt-03", ] } +tinybmp = { version = "0.5" } static_cell = { version = "2", optional = true } -slint = { version = "=1.9.0", path = "../../api/rs/slint", default-features = false, features = [ +slint = { path = "../../api/rs/slint", default-features = false, features = [ "compat-1-2", "unsafe-single-threaded", "libm", "renderer-software", ] } -i-slint-core-macros = { version = "=1.9.0", path = "../../internal/core-macros" } -embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "4790f8f", optional = true, features = [ +embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", optional = true, features = [ "stm32u5g9zj", "time-driver-tim2", "exti", @@ -46,24 +46,25 @@ embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "4790f8f" "unstable-pac", "chrono", "time", + "defmt", ] } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "4790f8f", optional = true } -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "4790f8f", optional = true, features = [ +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", features = [ "task-arena-size-32768", - "arch-cortex-m", "executor-thread", + "integrated-timers", ] } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "4790f8f", optional = true } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "4790f8f", optional = true } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe" } env_logger = { version = "0.9.0", optional = true } -sdl2 = { git = "https://github.com/Rust-SDL2/rust-sdl2", rev = "400e033", optional = true } +sdl2 = { git = "https://github.com/Rust-SDL2/rust-sdl2.git", rev = "400e033", optional = true } object-pool = { version = "0.6.0", optional = true } embedded-hal = { version = "1.0.0", optional = true } gt911 = { version = "0.1", features = ["defmt"], optional = true } [features] -default = ["mcu"] +default = [] mcu = [ "defmt", "defmt-rtt", @@ -71,18 +72,13 @@ mcu = [ "cortex-m", "cortex-m-rt", "embassy-stm32", - "embassy-stm32/defmt", - "embassy-sync", "embassy-sync/defmt", - "embassy-executor", "embassy-executor/arch-cortex-m", "embassy-executor/executor-interrupt", "embassy-executor/defmt", - "embassy-time", "embassy-time/tick-hz-32_768", "embassy-time/defmt", "embassy-time/defmt-timestamp-uptime", - "embassy-futures", "embedded-alloc", "embedded-hal", "gt911", @@ -94,10 +90,6 @@ simulator = [ "slint/std", "embassy-executor/arch-std", "embassy-time/std", - "embassy-futures", - "embassy-time", - "embassy-time/std", - "embassy-sync", "env_logger", "sdl2", "object-pool", diff --git a/examples/mcu-embassy/slint_generated/Cargo.toml b/examples/mcu-embassy/slint_generated/Cargo.toml index 4c85fe7b5d9..ff078778bf4 100644 --- a/examples/mcu-embassy/slint_generated/Cargo.toml +++ b/examples/mcu-embassy/slint_generated/Cargo.toml @@ -15,13 +15,13 @@ repository = "https://github.com/slint-ui/slint" homepage = "https://slint.dev" [dependencies] -slint = { version = "=1.9.0", path = "../../../api/rs/slint", default-features = false, features = [ +slint = { path = "../../../api/rs/slint", default-features = false, features = [ "compat-1-2", "unsafe-single-threaded", "libm", "renderer-software", ] } -i-slint-core-macros = { version = "=1.9.0", path = "../../../internal/core-macros" } +i-slint-core-macros = { path = "../../../internal/core-macros" } [build-dependencies] -slint-build = { version = "=1.9.0", path = "../../../api/rs/build" } +slint-build = { path = "../../../api/rs/build" } From df4b23c6f06a704c0a88e9889e8bdd1690543bec Mon Sep 17 00:00:00 2001 From: David Haig Date: Wed, 8 Jan 2025 01:30:58 +0000 Subject: [PATCH 07/13] Further attempts with dependencies --- examples/mcu-embassy/Cargo.toml | 7 ++++++- examples/mcu-embassy/slint_generated/Cargo.toml | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/examples/mcu-embassy/Cargo.toml b/examples/mcu-embassy/Cargo.toml index 57bc9fe012d..dd5aab1ac58 100644 --- a/examples/mcu-embassy/Cargo.toml +++ b/examples/mcu-embassy/Cargo.toml @@ -31,11 +31,16 @@ heapless = { version = "0.8", default-features = false, features = [ tinybmp = { version = "0.5" } static_cell = { version = "2", optional = true } +#slint = { version = "1.9.1", default-features = false, features = [ +# "compat-1-2", +# "unsafe-single-threaded", +# "libm", +#] } + slint = { path = "../../api/rs/slint", default-features = false, features = [ "compat-1-2", "unsafe-single-threaded", "libm", - "renderer-software", ] } embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "72976fe", optional = true, features = [ diff --git a/examples/mcu-embassy/slint_generated/Cargo.toml b/examples/mcu-embassy/slint_generated/Cargo.toml index ff078778bf4..87023b33dd5 100644 --- a/examples/mcu-embassy/slint_generated/Cargo.toml +++ b/examples/mcu-embassy/slint_generated/Cargo.toml @@ -25,3 +25,15 @@ i-slint-core-macros = { path = "../../../internal/core-macros" } [build-dependencies] slint-build = { path = "../../../api/rs/build" } + +#[dependencies] +#slint = { version = "1.9.1", default-features = false, features = [ +# "compat-1-2", +# "unsafe-single-threaded", +# "libm", +# "renderer-software", +#] } +#i-slint-core-macros = { version = "1.9.1" } +# +#[build-dependencies] +#slint-build = { version = "1.9.1" } From 0670e64a96118cdccdbf3155eb652857de7f115a Mon Sep 17 00:00:00 2001 From: David Haig Date: Wed, 8 Jan 2025 02:08:08 +0000 Subject: [PATCH 08/13] Update readme with latest probe-rs install instructions --- examples/mcu-embassy/Cargo.toml | 2 +- examples/mcu-embassy/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/mcu-embassy/Cargo.toml b/examples/mcu-embassy/Cargo.toml index dd5aab1ac58..d36db015f1c 100644 --- a/examples/mcu-embassy/Cargo.toml +++ b/examples/mcu-embassy/Cargo.toml @@ -69,7 +69,7 @@ embedded-hal = { version = "1.0.0", optional = true } gt911 = { version = "0.1", features = ["defmt"], optional = true } [features] -default = [] +default = ["mcu"] mcu = [ "defmt", "defmt-rtt", diff --git a/examples/mcu-embassy/README.md b/examples/mcu-embassy/README.md index 48a9fdc08c7..e561b873c31 100644 --- a/examples/mcu-embassy/README.md +++ b/examples/mcu-embassy/README.md @@ -22,10 +22,10 @@ Install the cross compilation target for the mcu: rustup target add thumbv8m.main-none-eabihf ``` -You need software to be able to flash the firmware to the dev kit. At the time of writing (7 Dec 2024) you need the latest version of probe-rs to flash to STM32U5G9ZJTxQ, not the one in crates.io. +You need software to be able to flash the firmware to the dev kit. ```bash -cargo install --force --locked --git https://github.com/probe-rs/probe-rs probe-rs-tools +cargo install --force --locked probe-rs-tools ``` # Running the application From 0335e71235746384417656e234b6ddb50204d0df Mon Sep 17 00:00:00 2001 From: David Haig Date: Thu, 9 Jan 2025 01:35:03 +0000 Subject: [PATCH 09/13] Fixed issue with conflicting embassy-time-driver --- Cargo.toml | 1 - examples/mcu-embassy/.gitignore | 11 +++++++++++ examples/mcu-embassy/Cargo.toml | 4 ++++ examples/mcu-embassy/README.md | 10 ++++++++-- examples/mcu-embassy/src/bin/ui_simulator.rs | 2 ++ 5 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 examples/mcu-embassy/.gitignore diff --git a/Cargo.toml b/Cargo.toml index 2a10d08d108..59395b13c79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ members = [ 'examples/carousel/rust', 'demos/energy-monitor', 'demos/home-automation/rust', - 'examples/mcu-embassy', 'examples/mcu-board-support', 'examples/uefi-demo', 'demos/weather-demo', diff --git a/examples/mcu-embassy/.gitignore b/examples/mcu-embassy/.gitignore new file mode 100644 index 00000000000..cd781f59248 --- /dev/null +++ b/examples/mcu-embassy/.gitignore @@ -0,0 +1,11 @@ +Cargo.lock +/target + +# Ignore all package-lock.json files +**/package-lock.json +# But keep these specific ones +!editors/vscode/package-lock.json + +.env +.envrc +__pycache__ diff --git a/examples/mcu-embassy/Cargo.toml b/examples/mcu-embassy/Cargo.toml index d36db015f1c..393691dfb45 100644 --- a/examples/mcu-embassy/Cargo.toml +++ b/examples/mcu-embassy/Cargo.toml @@ -1,6 +1,10 @@ # Copyright © 2025 David Haig # SPDX-License-Identifier: MIT +# A work around for an embassy-time-driver conflict with esp32-hal's embassy dependency. +# Remove this workspace and add 'examples/mcu-embassy' as a member to the slint root Cargo.toml once this issue has been resolved +[workspace] + [package] name = "mcu-embassy" version = "0.1.0" diff --git a/examples/mcu-embassy/README.md b/examples/mcu-embassy/README.md index e561b873c31..1994856c8d2 100644 --- a/examples/mcu-embassy/README.md +++ b/examples/mcu-embassy/README.md @@ -67,11 +67,17 @@ Hardware like leds and buttons are emulated in the hardware module. To install SDL2 follow the instructions here: https://github.com/Rust-SDL2/rust-sdl2 -To run the simulator: +To run the simulator on a pc: ```bash -cargo run --bin ui_simulator --release --features=simulator +# for linux +cargo run --bin ui_simulator --release --no-default-features --features=simulator --target x86_64-unknown-linux-gnu +# for windows +cargo run --bin ui_simulator --release --no-default-features --features=simulator --target x86_64-pc-windows-msvc +# for mac +cargo run --bin ui_simulator --release --no-default-features --features=simulator --target x86_64-apple-darwin ``` +Note: Instead of specifying a target you can simply remove the arm target in .cargo/config.toml and cargo will use the host by default Troubleshooting: diff --git a/examples/mcu-embassy/src/bin/ui_simulator.rs b/examples/mcu-embassy/src/bin/ui_simulator.rs index 179bce054e9..708fe6eb22a 100644 --- a/examples/mcu-embassy/src/bin/ui_simulator.rs +++ b/examples/mcu-embassy/src/bin/ui_simulator.rs @@ -206,6 +206,8 @@ async fn main_task( let main_window = MainWindow::new().unwrap(); main_window.show().expect("unable to show main window"); + info!("press LEFT SHIFT to simulate a hardware button press"); + let hardware = HardwareSim {}; // run the gui controller loop From 9bd4c09217257e95e34b754aca3fd329e7298653 Mon Sep 17 00:00:00 2001 From: David Haig Date: Sun, 12 Jan 2025 22:43:13 +0000 Subject: [PATCH 10/13] Fix continuous integration failure --- .github/workflows/ci.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4193b6b7a4b..e876fb97818 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -387,7 +387,8 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-rust - name: Check - run: cargo check -p mcu_embassy --bin ui_mcu --target=thumbv8m.main-none-eabihf --no-default-features --features="mcu-embassy/mcu" --release + working-directory: examples/mcu-embassy + run: cargo check --bin ui_mcu --target=thumbv8m.main-none-eabihf --no-default-features --features="mcu-embassy/mcu" --release mcu_esp: env: From 769e599554fed35912d32462e7d36f8eb30d333a Mon Sep 17 00:00:00 2001 From: David Haig Date: Mon, 13 Jan 2025 02:12:26 +0000 Subject: [PATCH 11/13] Fix copyright ci failures --- examples/mcu-embassy/.cargo/config.toml | 3 +++ examples/mcu-embassy/README.md | 2 ++ examples/mcu-embassy/slint_generated/README.md | 2 ++ 3 files changed, 7 insertions(+) diff --git a/examples/mcu-embassy/.cargo/config.toml b/examples/mcu-embassy/.cargo/config.toml index 3d00bce0897..1784a38657a 100644 --- a/examples/mcu-embassy/.cargo/config.toml +++ b/examples/mcu-embassy/.cargo/config.toml @@ -1,3 +1,6 @@ +# Copyright © 2025 David Haig +# SPDX-License-Identifier: MIT + [target.'cfg(all(target_arch = "arm", target_os = "none"))'] rustflags = [ "-C", diff --git a/examples/mcu-embassy/README.md b/examples/mcu-embassy/README.md index 1994856c8d2..0043d26aedb 100644 --- a/examples/mcu-embassy/README.md +++ b/examples/mcu-embassy/README.md @@ -1,3 +1,5 @@ + + # Embassy Slint stm32u5g9j-dk2 Demo An embedded async Slint GUI demo using Embassy and an stm32u5g9j-dk2 development kit. This demo was written to run on a resource constrained device, not a PC or laptop. diff --git a/examples/mcu-embassy/slint_generated/README.md b/examples/mcu-embassy/slint_generated/README.md index 1925b715b52..d52d53d676d 100644 --- a/examples/mcu-embassy/slint_generated/README.md +++ b/examples/mcu-embassy/slint_generated/README.md @@ -1,3 +1,5 @@ + + # Generated This crate is here to separate the `.slint` file compilation from the main application. \ No newline at end of file From b21e4ff802cd17ae46c2e25132a2fda096b6bd2d Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 02:18:06 +0000 Subject: [PATCH 12/13] [autofix.ci] apply automated fixes --- examples/mcu-embassy/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/mcu-embassy/README.md b/examples/mcu-embassy/README.md index 0043d26aedb..09668e45831 100644 --- a/examples/mcu-embassy/README.md +++ b/examples/mcu-embassy/README.md @@ -2,7 +2,7 @@ # Embassy Slint stm32u5g9j-dk2 Demo -An embedded async Slint GUI demo using Embassy and an stm32u5g9j-dk2 development kit. This demo was written to run on a resource constrained device, not a PC or laptop. +An embedded async Slint GUI demo using Embassy and an stm32u5g9j-dk2 development kit. This demo was written to run on a resource constrained device, not a PC or laptop. The simulator can run on a PC if you do not have the dev kit on hand but it is not meant to be a reference design for an async GUI implementation on a PC. The stm32u5g9j-dk2 was chosen because of its availability and price point and has enough onboard ram (3MB) and flash (4MB) to run Slint without external psram and flash, reducing setup complexity. @@ -24,7 +24,7 @@ Install the cross compilation target for the mcu: rustup target add thumbv8m.main-none-eabihf ``` -You need software to be able to flash the firmware to the dev kit. +You need software to be able to flash the firmware to the dev kit. ```bash cargo install --force --locked probe-rs-tools @@ -53,7 +53,7 @@ target = "thumbv8m.main-none-eabihf" If using vscode then make sure `rust-analyzer.cargo.features` is set to `mcu` in `.vscode/settings.json` You may be wondering why you get the following message in the logs: `invalid location: defmt frame-index` -In the Slint workspace `Cargo.toml` file overrides the `Cargo.toml` file in this crate so make sure the release profile is as follows in that workspace file: +In the Slint workspace `Cargo.toml` file overrides the `Cargo.toml` file in this crate so make sure the release profile is as follows in that workspace file: ```toml [profile.release] debug = true # required for decent panic messages and log line locations @@ -75,7 +75,7 @@ To run the simulator on a pc: cargo run --bin ui_simulator --release --no-default-features --features=simulator --target x86_64-unknown-linux-gnu # for windows cargo run --bin ui_simulator --release --no-default-features --features=simulator --target x86_64-pc-windows-msvc -# for mac +# for mac cargo run --bin ui_simulator --release --no-default-features --features=simulator --target x86_64-apple-darwin ``` From 0c6c18a2ba2ca1062e52742d191f3407116704e2 Mon Sep 17 00:00:00 2001 From: David Haig Date: Mon, 13 Jan 2025 03:17:32 +0000 Subject: [PATCH 13/13] Ensure correct target installed for cargo check mcu-embassy --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e876fb97818..8fe5f798400 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -386,6 +386,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-rust + with: + target: thumbv8m.main-none-eabihf - name: Check working-directory: examples/mcu-embassy run: cargo check --bin ui_mcu --target=thumbv8m.main-none-eabihf --no-default-features --features="mcu-embassy/mcu" --release