Skip to content

Commit

Permalink
examples: st7789-slint: Initial commit
Browse files Browse the repository at this point in the history
Signed-off-by: Alistair Francis <alistair@alistair23.me>
  • Loading branch information
alistair23 committed Jun 15, 2024
1 parent 6ecfaf5 commit 4900e32
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 1 deletion.
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
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 <alistair.francis@wdc.com>"]
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 { }
}
}

0 comments on commit 4900e32

Please sign in to comment.