Skip to content

Commit f600181

Browse files
authored
Second round of changes to the MCU docs (#1624)
1 parent 911515b commit f600181

File tree

1 file changed

+56
-66
lines changed

1 file changed

+56
-66
lines changed

api/rs/slint/mcu.md

+56-66
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
# Slint on Microcontrollers (MCU)
22

3-
This document explains how to use Slint to develop a UI on a MCU in a bare metal environment.
3+
The following sections explain how to use Slint to develop a UI on a MCU in a bare metal environment.
44

5-
## Install Toolchain / Hardware Abstraction Layer (HAL)
5+
## Prerequisites
66

7-
Writing an application in Rust that runs on a MCU requires the following software components:
7+
Writing an application in Rust that runs on a MCU requires several prerequisites:
88

9-
* You need to install a Rust toolchain to cross-compile to the target architecture.
10-
* You need to locate and select the correct HAL crates and drivers, and depend on them in your `Cargo.toml`.
11-
* You need to install tools for flashing and debugging your code on the device.
9+
* Install a Rust toolchain to cross-compile to the target architecture.
10+
* Locate and select the correct Hardware Abstraction Layer (HAL) crates and drivers, and depend on them in your `Cargo.toml`.
11+
* Install tools for flashing and debugging your code on the device.
1212

13-
Covering these is out of scope for this document, but we recommend reading the [Rust Embedded Book](https://docs.rust-embedded.org/book/),
13+
We recommend reading the [Rust Embedded Book](https://docs.rust-embedded.org/book/),
1414
as well as the curated list of [Awesome Embedded Rust](https://github.com/rust-embedded/awesome-embedded-rust) for a wide range of different
1515
crates, tools and training materials. These resources should guide you through the initial setup and often come with "hello world" examples
1616
to get started with your device.
1717

18-
In order to set a global allocator, required by Slint, you will need a **nightly** version of Rust. This is due to the fact that the support for using a custom global allocator in a bare metal
19-
environment with `#![no_std]` has not been stabilized yet (see [#51540](https://github.com/rust-lang/rust/issues/51540) or
18+
Slint requires a global memory allocator. That is currently only possible in the nightly version of Rust, because the support for using a custom global
19+
allocator in a bare metal environment with `#![no_std]` has not been stabilized yet (see [#51540](https://github.com/rust-lang/rust/issues/51540) or
2020
[#66741](https://github.com/rust-lang/rust/issues/66741) for tracking issues).
2121

22-
In the following sections we assume that your setup is complete and you have a non-graphical skeleton Rust program running on your MCU.
22+
The following sections assume that your setup is complete and you have a non-graphical skeleton Rust program running on your MCU.
2323

2424
## Changes to `Cargo.toml`
2525

26-
Start by adding a dependency to the `slint` and the `slint-build` crate to your `Cargo.toml`:
26+
Start by adding a dependency to the `slint` and the `slint-build` crates to your `Cargo.toml`:
2727

2828
```toml
2929
[dependencies]
@@ -35,31 +35,24 @@ default-features = false
3535
features = ["compat-0.3.0", "unsafe-single-threaded", "libm"]
3636

3737
[build-dependencies]
38-
slint-build = "0.3"
38+
slint-build = "0.3.0"
3939
```
4040

41-
The default features of the `slint` create are tailored towards hosted environments and includes the "std" feature. In bare metal environments,
41+
The default features of the `slint` crate are tailored towards hosted environments and includes the "std" feature. In bare metal environments,
4242
you need to disable the default features.
4343

44-
Three features are selected:
44+
In the snippet above, three features are selected:
4545

46-
* `compat-0.3.0`: You need to select this feature when disabling the default features. See [this blog post](https://slint-ui.com/blog/rust-adding-default-cargo-feature.html)
47-
for a detailed explanation.
46+
* `compat-0.3.0`: We select this feature when disabling the default features. For a detailed explanation see our blog post ["Adding default cargo features without breaking Semantic Versioning"](https://slint-ui.com/blog/rust-adding-default-cargo-feature.html).
4847
* `unsafe-single-threaded`: Slint internally uses Rust's [`thread_local!`](https://doc.rust-lang.org/std/macro.thread_local.html) macro to store global data.
49-
This feature is only available in the Rust Standard Library (std), which is not available in bare-metal environments. As a fallback, the `unsafe-single-threaded`
50-
feature will change Slint to use unsafe static for storage. By setting this feature, you guarantee not to use Slint API from a thread other than the main thread,
51-
or from interrupt handlers.
52-
* `libm`: The Rust Standard Library (std) provides traits and functions for floating point arithmetic. Without `std`, this feature enables the use of the
53-
[libm](https://crates.io/crates/libm) crate to substitute this functionality.
48+
This macro is only available in the Rust Standard Library (std), but not in bare metal environments. As a fallback, the `unsafe-single-threaded`
49+
feature changes Slint to use unsafe static for storage. This way, you guarantee to use Slint API only from a single thread, and not from interrupt handlers.
50+
* `libm`: We select this feature to enable the use of the [libm](https://crates.io/crates/libm) crate to provide traits and functions for floating point arithmetic.
51+
They are typically provided by the Rust Standard Library (std), but that is not available in bare metal environments.
5452

5553
## Changes to `build.rs`
5654

57-
When targeting MCUs, you need a [build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) to compile the `.slint` files using the `slint-build` crate.
58-
Use the `slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer` configuration option to tell the slint compiler to embed the images and fonts in the binary
59-
in a format that's suitable for the software based renderer we're going to use.
60-
61-
The following example of a `build.rs` script compiles the `main.slint` design file in the `ui/` sub-directory to Rust code and embeds the code as well as all
62-
graphical assets needed into the program binary.
55+
Next, write a build script to compile the `.slint` files to Rust code for embedding into the program binary, using the `slint-build` crate:
6356

6457
```rust,no_run
6558
fn main() {
@@ -71,44 +64,47 @@ fn main() {
7164
}
7265
```
7366

67+
Use the `slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer` configuration option to tell the Slint compiler to embed the images and fonts in the binary
68+
in a format that's suitable for the software based renderer we're going to use.
69+
7470
## Application Structure
7571

76-
A graphical application in hosted environments is typically composed of at least three different tasks:
72+
Typically, a graphical application in hosted environments is comprised of at least three different tasks:
7773

78-
* Receive user input from operation system APIs.
79-
* React to the input by performing application specific computations.
80-
* Render an updated user interface and present it on the screen using device-independent operating system APIs.
74+
* Receives user input from operation system APIs.
75+
* Reacts to the input by performing application specific computations.
76+
* Renders an updated user interface and presents it on the screen using device-independent operating system APIs.
8177

82-
The operating system provides what is typically called an event loop to connect and schedule these tasks. Slint implements the
83-
task of receiving user input and forwarding it to the user interface layer, as well as rendering the interface to the screen.
78+
The operating system provides an event loop to connect and schedule these tasks. Slint implements the
79+
task of receiving user input and forwarding it to the user interface layer, as well as rendering the user interface to the screen.
8480

85-
In bare metal environments it becomes your responsibility to substitute and connect functionality that is otherwise provided by the operating system:
81+
In bare metal environments it is your responsibility to substitute and connect functionality that is otherwise provided by the operating system:
8682

87-
* You need to select crates that allow you to initialize the chips that drive peripherals, such as a touch input or display controller.
88-
Sometimes it may be necessary for you to develop your own drivers.
89-
* You need to drive the event loop yourself by querying peripherals for input, forwarding input into computational modules of your
83+
* Select crates that allow you to initialize the chips that operate peripherals, such as a touch input or display controller.
84+
If there are no crates, you may have to to develop your own drivers.
85+
* Drive the event loop yourself by querying peripherals for input, forwarding that input into computational modules of your
9086
application and instructing Slint to render the user interface.
9187

92-
In Slint the two primary APIs you need to use to accomplish these tasks the [`slint::platform::Platform`] trait as well as the [`slint::Window`] struct.
93-
In the following sections we are going to cover how to use them and how they integrate into your event loop.
88+
In Slint, the two primary APIs you need to use to accomplish these tasks are the [`slint::platform::Platform`] trait as well as the [`slint::Window`] struct.
89+
In the following sections we're going to cover how to use them and how they integrate into your event loop.
9490

9591
### The `Platform` Trait
9692

9793
The [`slint::platform::Platform`] trait defines the interface between Slint and platform APIs typically provided by operating and windowing systems.
9894

99-
You need to provide an minimal implementation of this trait and call [`slint::platform::set_platform`] before constructing your Slint application.
95+
You need to provide a minimal implementation of this trait and call [`slint::platform::set_platform`] before constructing your Slint application.
10096

101-
A minimal implementation needs to cover two functions:
97+
This minimal implementation needs to cover two functions:
10298

10399
* `fn create_window_adapter(&self) -> Rc<dyn WindowAdapter + 'static>;`: Implement this function to return an implementation of the `WindowAdapter`
104100
trait that will be associated with the Slint components you create. We provide a convenience struct [`slint::platform::software_renderer::MinimalSoftwareWindow`]
105101
that implements this trait.
106-
* `fn duration_since_start(&self) -> Duration`: In order for animations in `.slint` design files to change properties correctly, Slint needs to know
107-
how much time has elapsed since two rendered frames. In a bare metal environment you need to provide a source of time. Often the HAL crate of your
102+
* `fn duration_since_start(&self) -> Duration`: For animations in `.slint` design files to change properties correctly, Slint needs to know
103+
how much time has elapsed between two rendered frames. In a bare metal environment you need to provide a source of time. Often the HAL crate of your
108104
device provides a system timer API for this, which you can query in your impementation.
109105

110-
There are additional functions in the trait that you can implement, for example handling of debug output, a delegated event loop or an interface
111-
to safely deliver events in multi-threaded environments.
106+
There are additional functions in the trait that you can implement, for example to handle debug output, to delegate the event loop or to implement
107+
the interface to safely deliver events in multi-threaded environments.
112108

113109
A typical minimal implementation of the [`Platform`] trait that uses the [`MinimalSoftwareWindow`] looks like this:
114110

@@ -225,24 +221,21 @@ loop {
225221

226222
### The Renderer
227223

228-
In desktop and embedded environments, Slint typically uses operating system provided, often hardware-accelerated APIs to render the user interface.
229-
In contrast, most MCUs don't have dedicated chips to render advanced graphics. Instead, the CPU is responsible for computing the colors of each
230-
pixels on the screen. This is called software rendering, and Slint provides a software renderer for this task.
224+
In desktop and embedded environments, Slint typically uses operating system provided APIs to render the user interface using the GPU.
225+
In contrast, most MCUs don't have GPUs. Instead, the all the rendering is done by sotfware on the CPU. This is called software rendering, and Slint provides a SoftwareRenderer for this task.
231226

232227
In the previous example, we've instantiated a [`slint::platform::software_renderer::MinimalSoftwareWindow`]. This struct implements the
233-
`slint::platform::WindowAdapter` trait and also holds an instance of a [`slint::platform::software_renderer::SoftwareRenderer`]. You can access it
234-
through the [`draw_if_needed()`](MinimalSoftwareWindow::draw_if_needed) function.
228+
`slint::platform::WindowAdapter` trait and also holds an instance of a [`slint::platform::software_renderer::SoftwareRenderer`]. You obtain access to it
229+
through the callback parameter of the [`draw_if_needed()`](MinimalSoftwareWindow::draw_if_needed) function.
235230

236-
We provide two different ways of using the renderer, depending on the amount of RAM your MCU is equipped with and the kind of screen that is attached:
231+
Depending on the amount of RAM your MCU is equipped with, and the kind of screen that is attached, you can choose between two different ways of using the renderer:
237232

238233
* Use the [`SoftwareRenderer::render()`] function if you have enough RAM to allocate one, or even two, copies of the entire screen (also known as
239234
frame buffer).
240235
* Use the [`SoftwareRenderer::render_by_line()`] function to render the entire user interface line by line and send each line of pixels to the screen,
241236
typically via the SPI. This requires allocating at least enough RAM to store one single line of pixels.
242237

243-
With both methods you instruct Slint to render into a buffer, that either represents the entire screen or just a line. That buffer is a slice of
244-
a type that implements the [`slint::platform::software_renderer::TargetPixel`] trait.
245-
238+
With both methods Slint renders into a provided buffer, which is a slice of a type that implements the [`slint::platform::software_renderer::TargetPixel`] trait.
246239
For convenience, Slint provides an implementation for [`slint::Rgb8Pixel`] as well as [`slint::platform::software_renderer::Rgb565Pixel`].
247240

248241
#### Rendering into a Buffer
@@ -296,17 +289,17 @@ loop {
296289
297290
```
298291

299-
#### Render Line by Line
292+
#### Rendering Line by Line
300293

301294
When rendering the user interface line by line, you need to implement the [`LineBufferProvider`] trait. It
302-
defines a bi-directional interface between Slint and your code to send lines to your screen:
295+
defines a bi-directional interface between Slint and your code to send lines to the screen:
303296

304-
* Through the associated `TargetPixel` type Slint knows how to create and manipulate pixels without having to know
305-
the exact device-specific binary representation and operations for blending.
306-
* Through the `process_line` function Slint notifies you when a line can be rendered and provides a callback that
307-
you can invoke to fill a slice of pixels for the given line.
297+
* The trait's associated `TargetPixel` type let's Slint know how to create and manipulate pixels. How exactly the pixels are
298+
represented in your device and how they are blended remains your implementation detail.
299+
* The trait's `process_line` function notifies you when a line can be rendered and provides a callback that you can invoke
300+
to fill a slice of pixels for the given line.
308301

309-
The following example defines a `DisplayWrapper` struct that connects screen driver that implements the [`embedded_graphics`](https://lib.rs/embedded-graphics) traits
302+
The following example defines a `DisplayWrapper` struct: It connects screen driver that implements the [`embedded_graphics`](https://lib.rs/embedded-graphics) traits
310303
with Slint's `Rgb565Pixel` type to implement the `LineBufferProvider` trait. The pixels for one line are sent to the screen by calling
311304
the [DrawTarget::fill_contiguous](https://docs.rs/embedded-graphics/0.7.1/embedded_graphics/draw_target/trait.DrawTarget.html) function.
312305

@@ -379,17 +372,14 @@ loop {
379372
380373
```
381374

382-
Hint: In our experience, using the synchronous `DrawTarget::fill_contiguous` function is slow. If
375+
Note: In our experience, using the synchronous `DrawTarget::fill_contiguous` function is slow. If
383376
your device is capable of using DMA, you may be able to achieve better performance by using
384377
two line buffers: One buffer to render into with the CPU, while the other buffer is transferred to
385378
the screen using DMA asynchronously.
386379

387-
Hint: If you see wrong colors on your device, it may be necessary to invert the bits in the u16 pixel
388-
representation before sending them to the screen driver.
389-
390380
## Example Implementations
391381

392-
Slint's own examples use a helper crate called `mcu-board-support` that provides implementations of
382+
The examples that come with Slint use a helper crate called `mcu-board-support`. It provides implementations of
393383
the `Platform` trait for some MCUs, along with support for touch input and system timers.
394384

395385
You can find the crate in our Git repository at:

0 commit comments

Comments
 (0)