Skip to content

Commit dffbdbb

Browse files
Add STM32 to Lab04 (#676)
* add stm32u5 debug image. * fix stm32u5 debug image. * update lab01 * change defmt and cortex-m version. * Update website/lab/01/index.mdx Co-authored-by: Alexandru Radovici <msg4alex@gmail.com> * add defmt error snippet. * update cargo.toml for stm32u5 and add waiting explanation for stm32. * add informations about exti and update cargo.toml for rp2. * Update index.mdx with RP2350 and RP2040 dependencies * Remove comments from dependencies in index.mdx * Update website/lab/02/index.mdx Co-authored-by: Alexandru Radovici <msg4alex@gmail.com> * add pin `D10` to lab03 * add analog pins to lab03 * delete `Config` warning for ADC and PWM * delete the note about PWM channels * add `set_polarity` function to lab * add lab improvements * add stm32 to lab04 * format code Co-authored-by: Alexandru Radovici <msg4alex@gmail.com> * Update website/lab/04/index.md Co-authored-by: Alexandru Radovici <msg4alex@gmail.com> * add note about `'static` lifetime --------- Co-authored-by: Alexandru Radovici <msg4alex@gmail.com>
1 parent 96357e0 commit dffbdbb

3 files changed

Lines changed: 63 additions & 9 deletions

File tree

224 KB
Loading
-191 KB
Binary file not shown.

website/lab/04/index.md

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ slug: /lab/04
77

88
This lab will teach you the principles of asynchronous programming, and its application in Embassy.
99

10+
import Tabs from '@theme/Tabs';
11+
import TabItem from '@theme/TabItem';
12+
1013

1114
## Resources
1215

@@ -57,10 +60,50 @@ sequenceDiagram
5760

5861
To address this issue, we would need to spawn a new task in which we would wait for the button press, while blinking the LED in the `main` function.
5962

60-
When thinking of how exactly this works, you would probably think that the task is running on a separate *thread* than the `main` function. Usually this would be the case when developing a normal computer application. Multithreading is possible, but requires a preemptive operating system. Without one, only one thread can independently run per processor core and that means that, since we are using only one core of the RP2350 (which actually has only 2), we would only be able to run **one thread at a time**. So how exactly does the task wait for the button press in parallel with the LED blinking?
63+
When thinking of how exactly this works, you would probably think that the task is running on a separate *thread* than the `main` function. Usually this would be the case when developing a normal computer application. Multithreading is possible, but requires a preemptive operating system. Without one, only one thread can independently run per processor core and that means that, since we are using only one core of the STM32U545RE (which has only 1) or the RP2350\RP2040 (which actually has only 2), we would only be able to run **one thread at a time**. So how exactly does the task wait for the button press in parallel with the LED blinking?
6164
Short answer is: it doesn't. In reality, both functions run asynchronously.
6265

6366
A task in Embassy is represented by an *asynchronous function*. Asynchronous functions are different from normal functions, in the sense that they allow asynchronous code execution. Let's take an example from the previous lab:
67+
68+
:::note
69+
In the examples below you’ll see `Output<'static>` and `Input<'static>`.
70+
The `'static` lifetime here means that once the peripherals are initialized, those pins remain valid for the entire lifetime of the firmware. Their role as input or output will not change while the program is running, and Embassy’s async tasks can safely hold on to them without risk of dangling references.
71+
:::
72+
73+
74+
<Tabs>
75+
<TabItem value="stm32u5" label="STM32 Nucleo-U545RE-Q" default>
76+
```rust
77+
#[embassy_executor::task]
78+
async fn button_pressed(mut led: Output<'static>, mut button: Input<'static>) {
79+
loop {
80+
info!("waiting for button press");
81+
button.wait_for_falling_edge().await;
82+
led.toggle();
83+
}
84+
}
85+
86+
#[embassy_executor::main]
87+
async fn main(spawner: Spawner) {
88+
let peripherals = embassy_stm32::init(Default::default());
89+
90+
let button = Input::new(peripherals.PXn, Pull::None);
91+
let led2 = Output::new(peripherals.PXn, Level::Low, Speed::Medium);
92+
93+
spawner.spawn(button_pressed(led2, button)).unwrap();
94+
95+
let mut led = Output::new(peripherals.PXn, Level::Low, Speed::Medium);
96+
97+
loop {
98+
led.toggle();
99+
Timer::after_millis(200).await;
100+
}
101+
}
102+
```
103+
104+
</TabItem>
105+
106+
<TabItem value="rp2350" label="Raspberry Pi Pico 1 / 2" default>
64107
```rust
65108
#[embassy_executor::task]
66109
async fn button_pressed(mut led: Output<'static>, mut button: Input<'static>) {
@@ -88,6 +131,13 @@ async fn main(spawner: Spawner) {
88131
}
89132
}
90133
```
134+
</TabItem>
135+
136+
</Tabs>
137+
138+
139+
140+
91141
In this example, we notice that both the `button_pressed` and `main` functions are declared as `async`, telling the compiler to treat them as asynchronous functions. Inside the `main` function (which is also a task, actually), we blink the LED:
92142

93143
```rust
@@ -101,7 +151,7 @@ loop {
101151
## `await` keyword
102152

103153
After setting the timer, our `main` function would need to wait until the alarm fires after 200 ms. Instead of just waiting and blocking the current and *only* thread of execution, it could allow the thread to do another action in the meantime. This is where the `.await` keyword comes into play.
104-
When using `.await` inside of an asynchronous function, we are telling a third party (called the **executor**, detailed later) that this action might take more time to finish, so *do something else* until it's ready. Basically, the execution flow of the asynchronous function function is halted exactly where `.await` is used, and the executor starts running another task. In our case, it would halt the main function while waiting for the alarm to go off and it could start running the code inside the `button_pressed` task.
154+
When using `.await` inside of an asynchronous function, we are telling a third party (called the **executor**, detailed later) that this action might take more time to finish, so *do something else* until it's ready. Basically, the execution flow of the asynchronous function is halted exactly where `.await` is used, and the executor starts running another task. In our case, it would halt the main function while waiting for the alarm to go off and it could start running the code inside the `button_pressed` task.
105155
```rust
106156
loop {
107157
info!("waiting for button press");
@@ -358,14 +408,18 @@ A buzzer is a hardware device that emits sound. There are two types of buzzers:
358408
![Buzzer](images/buzzer.png)
359409

360410
:::tip
361-
To control the buzzer, all you need to do is to set the `top` value of the PWM config to match the frequency you want!
411+
To control the buzzer, you just need to set the PWM so that its period matches the sound frequency you want.
412+
- On RP2s, this is done by adjusting the `top` value.
413+
- On STM32U545RE, you achieve the same thing by adjusting the timer’s `SimplePwm` frequency using the [`set_frequency`](https://docs.embassy.dev/embassy-stm32/git/stm32u545re/timer/simple_pwm/struct.SimplePwm.html#method.set_frequency) function.
414+
415+
In both cases, the duty cycle controls how strong or “loud” the buzzer sounds.
362416
:::
363417

364418
#### How to wire an RGB LED
365419

366420
The buzzer on the development board is connected to a pin in the J9 block.
367421

368-
![board_buzzer](./images/board_buzzer.png)
422+
![board_buzzer](./images/board_buzzer.jpeg)
369423

370424
## Exercises
371425

@@ -378,7 +432,7 @@ while start_time.elapsed().as_millis() < time_interval {}
378432
You should notice that one of the tasks is not running. Why? (**1p**)
379433
:::tip
380434
Use a different task instance for each LED. You can spawn multiple instances of the same task, however you need to specify the pool size with `#[embassy_executor::task(pool_size = 2)]`. Take a look at [task-arena](https://docs.embassy.dev/embassy-executor/git/std/index.html#task-arena) for more info.
381-
Use [`AnyPin`](https://docs.embassy.dev/embassy-rp/git/rp2040/gpio/struct.AnyPin.html) and blinking frequency parameters for the task.
435+
Use [`AnyPin`](https://docs.embassy.dev/embassy-stm32/git/stm32u545re/gpio/struct.AnyPin.html) and blinking frequency parameters for the task.
382436
:::
383437

384438
2. Fix the usage of busy waiting from exercise 1 and make the 4 LEDs (YELLOW, RED, GREEN, BLUE) blink at different frequencies. (**1p**)
@@ -396,9 +450,9 @@ Blink:
396450
1 Hz means once per second.
397451
:::
398452

399-
3. Write a firmware that changes the RED LED's intensity, using switch **SW_4** and switch **SW_5**. Switch **SW_4** will increase the intensity, and switch **SW_5** will decrease it. You will implement this in three ways: (**3p**)
453+
3. Write a firmware that changes the RED LED's intensity, using switch **S1** and switch **S2**. Switch **S1** will increase the intensity, and switch **S2** will decrease it. You will implement this in three ways: (**3p**)
400454

401-
1. Use three tasks : one task will be the `main` to control the LED and another two tasks for each button (one for switch **SW_4**, one for switch **SW_5**). Use a [`Channel`](#channel) to send commands from each button task to the main task.
455+
1. Use three tasks : one task will be the `main` to control the LED and another two tasks for each button (one for switch **S1**, one for switch **S2**). Use a [`Channel`](#channel) to send commands from each button task to the main task.
402456
:::tip
403457
Use an `enum` to define the LED Intensity change command for point i.
404458
:::
@@ -411,7 +465,7 @@ Blink:
411465

412466

413467
4. Simulates a traffic light using the GREEN, YELLOW and RED LEDs on the board. Normally the traffic light goes from one state based on the time elapsed (Green -> 5s , Yellow Blink (4 times) -> 1s , Red -> 2s ).
414-
However if the switch **SW4** is pressed the state of traffic light changes immediately as shown in the diagram bellow.(**2p**)
468+
However if the switch **S1** is pressed the state of traffic light changes immediately as shown in the diagram bellow.(**2p**)
415469

416470
```mermaid
417471
flowchart LR
@@ -439,7 +493,7 @@ However if the switch **SW4** is pressed the state of traffic light changes imme
439493
For this exercise you only need one task. Define an `enum` to save the traffic light state (`Green`, `Yellow`,`Red`). Use `match` to check the current state of the traffic light. Then you need to wait for two futures, since the traffic light changes its color either because some time has elapsed or because the button was pressed. Use `select` to check which future completes first (`Timer` or button press).
440494
:::
441495

442-
5. Continue exercise 4: this time, instead of using only the switch **SW4** to change the state of traffic light, we will use the switches **SW4** and **SW7** pressed consecutively to trigger a state change of the traffic light. Use `join` to check that both switches were pressed. (**1p**)
496+
5. Continue exercise 4: this time, instead of using only the switch **S1** to change the state of traffic light, we will use the switches **S1** and **S3** pressed consecutively to trigger a state change of the traffic light. Use `join` to check that both switches were pressed. (**1p**)
443497
:::note
444498
The switches don't need to be pressed at the same time, but one after the other. The order does not matter.
445499

0 commit comments

Comments
 (0)