aboutsummaryrefslogtreecommitdiff
path: root/docs/pages/layer_by_layer.adoc
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2024-05-18 10:17:03 +0200
committerUlf Lilleengen <[email protected]>2024-05-21 10:05:21 +0200
commit739e5861c2e47db251725163fcd91cd822cf97b7 (patch)
tree947dc7961ca7c42ec216056fc2adf616ab812b10 /docs/pages/layer_by_layer.adoc
parent51d553092550059afb22b2620cea14bbed21abff (diff)
convert from antora to asciidoctor
Diffstat (limited to 'docs/pages/layer_by_layer.adoc')
-rw-r--r--docs/pages/layer_by_layer.adoc85
1 files changed, 85 insertions, 0 deletions
diff --git a/docs/pages/layer_by_layer.adoc b/docs/pages/layer_by_layer.adoc
new file mode 100644
index 000000000..f87291c20
--- /dev/null
+++ b/docs/pages/layer_by_layer.adoc
@@ -0,0 +1,85 @@
1= From bare metal to async Rust
2
3If you're new to Embassy, it can be overwhelming to grasp all the terminology and concepts. This guide aims to clarify the different layers in Embassy, which problem each layer solves for the application writer.
4
5This guide uses the STM32 IOT01A board, but should be easy to translate to any STM32 chip. For nRF, the PAC itself is not maintained within the Embassy project, but the concepts and the layers are similar.
6
7The application we'll write is a simple 'push button, blink led' application, which is great for illustrating input and output handling for each of the examples we'll go through. We'll start at the Peripheral Access Crate (PAC) example and end at the async example.
8
9== PAC version
10
11The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provides distinct types to make accessing peripheral registers easier, but it does not prevent you from writing unsafe code.
12
13Writing an application using the PAC directly is therefore not recommended, but if the functionality you want to use is not exposed in the upper layers, that's what you need to use.
14
15The blinky app using PAC is shown below:
16
17[source,rust]
18----
19include::../examples/layer-by-layer/blinky-pac/src/main.rs[]
20----
21
22As you can see, a lot of code is needed to enable the peripheral clocks and to configure the input pins and the output pins of the application.
23
24Another downside of this application is that it is busy-looping while polling the button state. This prevents the microcontroller from utilizing any sleep mode to save power.
25
26== HAL version
27
28To simplify our application, we can use the HAL instead. The HAL exposes higher level APIs that handle details such as:
29
30* Automatically enabling the peripheral clock when you're using the peripheral
31* Deriving and applying register configuration from higher level types
32* Implementing the embedded-hal traits to make peripherals useful in third party drivers
33
34The HAL example is shown below:
35
36[source,rust]
37----
38include::../examples/layer-by-layer/blinky-hal/src/main.rs[]
39----
40
41As you can see, the application becomes a lot simpler, even without using any async code. The `Input` and `Output` types hide all the details of accessing the GPIO registers and allow you to use a much simpler API for querying the state of the button and toggling the LED output.
42
43The same downside from the PAC example still applies though: the application is busy looping and consuming more power than necessary.
44
45== Interrupt driven
46
47To save power, we need to configure the application so that it can be notified when the button is pressed using an interrupt.
48
49Once the interrupt is configured, the application can instruct the microcontroller to enter a sleep mode, consuming very little power.
50
51Given Embassy focus on async Rust (which we'll come back to after this example), the example application must use a combination of the HAL and PAC in order to use interrupts. For this reason, the application also contains some helper functions to access the PAC (not shown below).
52
53[source,rust]
54----
55include::../examples/layer-by-layer/blinky-irq/src/main.rs[lines="1..57"]
56----
57
58The simple application is now more complex again, primarily because of the need to keep the button and LED states in the global scope where it is accessible by the main application loop, as well as the interrupt handler.
59
60To do that, the types must be guarded by a mutex, and interrupts must be disabled whenever we are accessing this global state to gain access to the peripherals.
61
62Luckily, there is an elegant solution to this problem when using Embassy.
63
64== Async version
65
66It's time to use the Embassy capabilities to its fullest. At the core, Embassy has an async executor, or a runtime for async tasks if you will. The executor polls a set of tasks (defined at compile time), and whenever a task `blocks`, the executor will run another task, or put the microcontroller to sleep.
67
68[source,rust]
69----
70include::../examples/layer-by-layer/blinky-async/src/main.rs[]
71----
72
73The async version looks very similar to the HAL version, apart from a few minor details:
74
75* The main entry point is annotated with a different macro and has an async type signature. This macro creates and starts an Embassy runtime instance and launches the main application task. Using the `Spawner` instance, the application may spawn other tasks.
76* The peripheral initialization is done by the main macro, and is handed to the main task.
77* Before checking the button state, the application is awaiting a transition in the pin state (low -> high or high -> low).
78
79When `button.await_for_any_edge().await` is called, the executor will pause the main task and put the microcontroller in sleep mode, unless there are other tasks that can run. Internally, the Embassy HAL has configured the interrupt handler for the button (in `ExtiButton`), so that whenever an interrupt is raised, the task awaiting the button will be woken up.
80
81The minimal overhead of the executor and the ability to run multiple tasks "concurrently" combined with the enormous simplification of the application, makes `async` a great fit for embedded.
82
83== Summary
84
85We have seen how the same application can be written at the different abstraction levels in Embassy. First starting out at the PAC level, then using the HAL, then using interrupts, and then using interrupts indirectly using async Rust.