You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
8
8
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.
12
12
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/),
14
14
as well as the curated list of [Awesome Embedded Rust](https://github.com/rust-embedded/awesome-embedded-rust) for a wide range of different
15
15
crates, tools and training materials. These resources should guide you through the initial setup and often come with "hello world" examples
16
16
to get started with your device.
17
17
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
20
20
[#66741](https://github.com/rust-lang/rust/issues/66741) for tracking issues).
21
21
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.
23
23
24
24
## Changes to `Cargo.toml`
25
25
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`:
27
27
28
28
```toml
29
29
[dependencies]
@@ -35,31 +35,24 @@ default-features = false
35
35
features = ["compat-0.3.0", "unsafe-single-threaded", "libm"]
36
36
37
37
[build-dependencies]
38
-
slint-build = "0.3"
38
+
slint-build = "0.3.0"
39
39
```
40
40
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,
42
42
you need to disable the default features.
43
43
44
-
Three features are selected:
44
+
In the snippet above, three features are selected:
45
45
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).
48
47
*`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.
54
52
55
53
## Changes to `build.rs`
56
54
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:
63
56
64
57
```rust,no_run
65
58
fn main() {
@@ -71,44 +64,47 @@ fn main() {
71
64
}
72
65
```
73
66
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
+
74
70
## Application Structure
75
71
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:
77
73
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.
81
77
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.
84
80
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:
86
82
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
90
86
application and instructing Slint to render the user interface.
91
87
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.
94
90
95
91
### The `Platform` Trait
96
92
97
93
The [`slint::platform::Platform`] trait defines the interface between Slint and platform APIs typically provided by operating and windowing systems.
98
94
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.
100
96
101
-
A minimal implementation needs to cover two functions:
97
+
This minimal implementation needs to cover two functions:
102
98
103
99
*`fn create_window_adapter(&self) -> Rc<dyn WindowAdapter + 'static>;`: Implement this function to return an implementation of the `WindowAdapter`
104
100
trait that will be associated with the Slint components you create. We provide a convenience struct [`slint::platform::software_renderer::MinimalSoftwareWindow`]
105
101
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
108
104
device provides a system timer API for this, which you can query in your impementation.
109
105
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.
112
108
113
109
A typical minimal implementation of the [`Platform`] trait that uses the [`MinimalSoftwareWindow`] looks like this:
114
110
@@ -225,24 +221,21 @@ loop {
225
221
226
222
### The Renderer
227
223
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.
231
226
232
227
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.
235
230
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:
237
232
238
233
* Use the [`SoftwareRenderer::render()`] function if you have enough RAM to allocate one, or even two, copies of the entire screen (also known as
239
234
frame buffer).
240
235
* 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,
241
236
typically via the SPI. This requires allocating at least enough RAM to store one single line of pixels.
242
237
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.
246
239
For convenience, Slint provides an implementation for [`slint::Rgb8Pixel`] as well as [`slint::platform::software_renderer::Rgb565Pixel`].
247
240
248
241
#### Rendering into a Buffer
@@ -296,17 +289,17 @@ loop {
296
289
297
290
```
298
291
299
-
#### Render Line by Line
292
+
#### Rendering Line by Line
300
293
301
294
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:
303
296
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.
308
301
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
310
303
with Slint's `Rgb565Pixel` type to implement the `LineBufferProvider` trait. The pixels for one line are sent to the screen by calling
311
304
the [DrawTarget::fill_contiguous](https://docs.rs/embedded-graphics/0.7.1/embedded_graphics/draw_target/trait.DrawTarget.html) function.
312
305
@@ -379,17 +372,14 @@ loop {
379
372
380
373
```
381
374
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
383
376
your device is capable of using DMA, you may be able to achieve better performance by using
384
377
two line buffers: One buffer to render into with the CPU, while the other buffer is transferred to
385
378
the screen using DMA asynchronously.
386
379
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
-
390
380
## Example Implementations
391
381
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
393
383
the `Platform` trait for some MCUs, along with support for touch input and system timers.
0 commit comments