Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

demos: Add support for a Slint demo #550

Merged
merged 2 commits into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ members = [
"apis/sensors/temperature",
"apis/storage/key_value",
"demos/st7789",
"demos/st7789-slint",
"panic_handlers/debug_panic",
"panic_handlers/small_panic",
"platform",
Expand Down
17 changes: 16 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ EXCLUDE_RUNTIME := --exclude libtock --exclude libtock_runtime \
--exclude libtock_debug_panic --exclude libtock_small_panic

# Arguments to pass to cargo to exclude demo crates.
EXCLUDE_RUNTIME := $(EXCLUDE_RUNTIME) --exclude st7789
EXCLUDE_RUNTIME := $(EXCLUDE_RUNTIME) --exclude st7789 --exclude st7789-slint

# Arguments to pass to cargo to exclude crates that cannot be tested by Miri. In
# addition to excluding libtock_runtime, Miri also cannot test proc macro crates
Expand All @@ -134,6 +134,7 @@ test: examples
LIBTOCK_PLATFORM=hifive1 cargo clippy $(EXCLUDE_STD) \
--target=riscv32imac-unknown-none-elf --workspace
$(MAKE) apollo3-st7789
$(MAKE) apollo3-st7789-slint
cd nightly && \
MIRIFLAGS="-Zmiri-strict-provenance -Zmiri-symbolic-alignment-check" \
cargo miri test $(EXCLUDE_MIRI) --manifest-path=../Cargo.toml \
Expand Down Expand Up @@ -227,6 +228,14 @@ $(1)-st7789: toolchain
mkdir -p target/tbf/$(1)
cp demos/st7789/target/$(1)/$(2)/release/st7789.{tab,tbf} \
target/tbf/$(1)

.PHONY: $(1)-st7789-slint
$(1)-st7789-slint: toolchain
cd demos/st7789-slint && LIBTOCK_PLATFORM=$(1) cargo run $(features) \
$(release) --target=$(2) --target-dir=target/$(1)
mkdir -p target/tbf/$(1)
cp demos/st7789-slint/target/$(1)/$(2)/release/st7789-slint.{tab,tbf} \
target/tbf/$(1)
endef

# Creates the `make flash-<BOARD> EXAMPLE=<EXAMPLE>` targets. Arguments:
Expand All @@ -243,6 +252,12 @@ flash-$(1)-st7789: toolchain
cd demos/st7789 && LIBTOCK_PLATFORM=$(1) cargo run $(features) \
$(release) --target=$(2) --target-dir=target/flash-$(1) -- \
--deploy=tockloader

.PHONY: flash-$(1)-st7789-slint
flash-$(1)-st7789-slint: toolchain
cd demos/st7789-slint && LIBTOCK_PLATFORM=$(1) cargo run $(features) \
$(release) --target=$(2) --target-dir=target/flash-$(1) -- \
--deploy=tockloader
endef

$(eval $(call platform_build,apollo3,thumbv7em-none-eabi))
Expand Down
2 changes: 1 addition & 1 deletion build_scripts/libtock_layout.ld
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ SECTIONS {
.data ALIGN(4) : {
data_ram_start = .;
/* .sdata is the RISC-V small data section */
*(.sdata .data)
*(.sdata .data*)
/* Pad to word alignment so the relocation loop can use word-sized
* copies.
*/
Expand Down
31 changes: 31 additions & 0 deletions demos/st7789-slint/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "st7789-slint"
version = "0.1.0"
edition = "2021"
rust-version.workspace = true
authors = ["Alistair Francis <[email protected]>"]
description = """A demo to use the Slint GUI library with a ST7789 display via SPI using libtock-rs."""
license = "Apache-2.0 OR MIT"

[dependencies]
libtock = { path = "../../", features = ["rust_embedded"] }

embedded-hal = "1.0"

mipidsi = "0.8.0"
display-interface-spi = "0.5"
embedded-graphics = "0.8"

# The heap allocator and portable atomics
embedded-alloc = "0.5.1"
critical-section = "1.0"

slint = { git = "https://github.com/slint-ui/slint", default-features = false, features = ["libm", "unsafe-single-threaded"] }
mcu-board-support = { git = "https://github.com/slint-ui/slint" }

display-interface = "0.5"

[build-dependencies]
libtock_build_scripts = { path = "../../build_scripts" }

slint-build = { git = "https://github.com/slint-ui/slint" }
8 changes: 8 additions & 0 deletions demos/st7789-slint/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fn main() {
libtock_build_scripts::auto_layout();

let config = slint_build::CompilerConfiguration::new()
.embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer);
slint_build::compile_with_config("ui/appwindow.slint", config).unwrap();
slint_build::print_rustc_flags().unwrap();
}
187 changes: 187 additions & 0 deletions demos/st7789-slint/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//! This sample demonstrates displaying a slint GUI on a ST7789 display
//! using a rust-embedded based crate

#![no_main]
#![no_std]

extern crate alloc;

use core::fmt::Write;
use libtock::alarm::{Alarm, Milliseconds};
use libtock::console::Console;
use libtock::gpio::Gpio;
use libtock::platform::ErrorCode;
use libtock::runtime::{set_main, stack_size};
use libtock::spi_controller::EmbeddedHalSpi;

use critical_section::RawRestoreState;
use embedded_alloc::Heap;

use display_interface_spi::SPIInterface;
use embedded_hal::digital::OutputPin;
use mipidsi::{models::ST7789, options::ColorInversion, Builder, Display};

slint::include_modules!();

set_main! {main}
stack_size! {0x1400}

#[global_allocator]
static HEAP: Heap = Heap::empty();

struct MyCriticalSection;
critical_section::set_impl!(MyCriticalSection);

unsafe impl critical_section::Impl for MyCriticalSection {
unsafe fn acquire() -> RawRestoreState {
// Tock is single threaded, so this can only be preempted by interrupts
// The kernel won't schedule anything from our app unless we yield
// so as long as we don't yield we won't concurrently run with
// other critical sections from our app.
// The kernel might schedule itself or other applications, but there
// is nothing we can do about that.
}

unsafe fn release(_token: RawRestoreState) {}
}

// Setup the heap and the global allocator.
unsafe fn setup_heap() {
use core::mem::MaybeUninit;

const HEAP_SIZE: usize = 1024 * 6;
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
}

// Display
const W: i32 = 240;
const H: i32 = 240;

struct Delay;

impl embedded_hal::delay::DelayNs for Delay {
fn delay_ns(&mut self, ns: u32) {
Alarm::sleep_for(Milliseconds(ns / (1000 * 1000))).unwrap();
}
}

#[mcu_board_support::entry]
fn main() {
writeln!(Console::writer(), "st7789-slint: example\r").unwrap();

unsafe {
setup_heap();
}

// Configure platform for Slint
let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(
slint::platform::software_renderer::RepaintBufferType::ReusedBuffer,
);

window.set_size(slint::PhysicalSize::new(240, 240));

slint::platform::set_platform(alloc::boxed::Box::new(SlintPlatform {
window: window.clone(),
}))
.unwrap();

MainWindow::new().unwrap().run().unwrap();
}

struct SlintPlatform {
window: alloc::rc::Rc<slint::platform::software_renderer::MinimalSoftwareWindow>,
}

impl slint::platform::Platform for SlintPlatform {
fn create_window_adapter(
&self,
) -> Result<alloc::rc::Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
Ok(self.window.clone())
}

fn duration_since_start(&self) -> core::time::Duration {
core::time::Duration::from_millis(Alarm::get_milliseconds().unwrap())
}

fn debug_log(&self, arguments: core::fmt::Arguments) {
writeln!(Console::writer(), "{}\r", arguments).unwrap();
}

fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
let mut gpio_dc = Gpio::get_pin(0).unwrap();
let dc = gpio_dc.make_output().unwrap();

let mut gpio_reset = Gpio::get_pin(1).unwrap();
let reset = gpio_reset.make_output().unwrap();

let di = SPIInterface::new(EmbeddedHalSpi, dc);

let mut delay = Delay;
let display = Builder::new(ST7789, di)
.display_size(W as u16, H as u16)
.invert_colors(ColorInversion::Inverted)
.reset_pin(reset)
.init(&mut delay)
.unwrap();

let mut buffer_provider = DrawBuffer {
display,
buffer: &mut [slint::platform::software_renderer::Rgb565Pixel(100); 240],
};

writeln!(Console::writer(), "Setup platform, running loop\r").unwrap();

loop {
slint::platform::update_timers_and_animations();

self.window.draw_if_needed(|renderer| {
renderer.render_by_line(&mut buffer_provider);
});

if self.window.has_active_animations() {
continue;
}

if let Some(duration) = slint::platform::duration_until_next_timer_update() {
Alarm::sleep_for(Milliseconds(duration.as_millis() as u32)).unwrap();
}
}
}
}

struct DrawBuffer<'a, Display> {
display: Display,
buffer: &'a mut [slint::platform::software_renderer::Rgb565Pixel],
}

impl<DI: display_interface::WriteOnlyDataCommand, RST: OutputPin<Error = ErrorCode>>
slint::platform::software_renderer::LineBufferProvider
for &mut DrawBuffer<'_, Display<DI, mipidsi::models::ST7789, RST>>
{
type TargetPixel = slint::platform::software_renderer::Rgb565Pixel;

fn process_line(
&mut self,
line: usize,
range: core::ops::Range<usize>,
render_fn: impl FnOnce(&mut [slint::platform::software_renderer::Rgb565Pixel]),
) {
let buffer = &mut self.buffer[range.clone()];

render_fn(buffer);

// We send empty data just to get the device in the right window
self.display
.set_pixels(
range.start as u16,
line as _,
range.end as u16,
line as u16,
buffer
.iter()
.map(|x| embedded_graphics::pixelcolor::raw::RawU16::new(x.0).into()),
)
.unwrap();
}
}
9 changes: 9 additions & 0 deletions demos/st7789-slint/ui/appwindow.slint
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {Button, AboutSlint} from "std-widgets.slint";

export component MainWindow inherits Window {
width: 240px;
height: 240px;
VerticalLayout {
AboutSlint { }
}
}
Loading