Skip to content

Commit

Permalink
finish up rust/blinking
Browse files Browse the repository at this point in the history
  • Loading branch information
AdinAck committed Jul 31, 2024
1 parent 7f9ce2a commit e4bcf79
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 6 deletions.
2 changes: 1 addition & 1 deletion content/assignments/ControlWithPython/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ On the DevBoard, the pin of the LED we will use is `17` (LED1).

Here is the code:

```c
```cpp
const int LED{17};

void setup() {
Expand Down
5 changes: 4 additions & 1 deletion content/assignments/SpinningAndBlinking/Arduino/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
title: Arduino
type: docs
prev: assignments/SpinningAndBlinking/
next: assignments/SpinningAndBlinking/Arduino/Blinking
weight: 1
---

Download the Arduino software [here](https://www.arduino.cc/en/software).

If you are unfamiliar with Arduino, the official [Arduino website](https://www.arduino.cc/reference/en/) contains great resources for learning about embedded development. You may also want to take a look at the [reference page](https://www.arduino.cc/reference/en/) for help with Arduino's syntax and language.
If you are unfamiliar with Arduino, the official [Arduino website](https://www.arduino.cc/reference/en/)
contains great resources for learning about embedded development. You may also want to take a look at
the [reference page](https://www.arduino.cc/reference/en/) for help with Arduino's syntax and language.
27 changes: 27 additions & 0 deletions content/assignments/SpinningAndBlinking/Arduino/blinking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: Blinking
type: docs
prev: assignments/SpinningAndBlinking/Arduino
weight: 1
---

Making an LED blink is rather straight forward:

```c
const unsigned int LED{17}; // define a constant for the LED pin

void setup() {
pinMode(LED, OUTPUT); // configure the LED pin to be an output
}

void loop() {
digitalWrite(LED, HIGH); // turn the LED on
delay(1000); // wait 1 second
digitalWrite(LED, LOW); // turn the LED off
delay(1000); // wait 1 second
}
```

The `setup` function runs once on boot.

The `loop` function runs over and over again forever.
6 changes: 5 additions & 1 deletion content/assignments/SpinningAndBlinking/Rust/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ execute one instruction at a time, but by interleaving the instructions, the ill

In addition to this, microcontrollers are comprised of more than just a CPU, there are many peripherals[^9] the CPU uses to interact with the world.
These peripherals can do work while the CPU is handling other things, and use **D**irect **M**emory **A**ccess (DMA) to read, store, or exchange
information as it is served. A very special Rust crate called **Embassy** utilizes these very principles to provide a high level async interface
information as it is served. A very special Rust crate[^10] called **Embassy** utilizes these very principles to provide a high level async interface
for us to use.

{{< callout type="info" >}}
Expand All @@ -52,3 +52,7 @@ A third class citizen is a feature *derived* from other primitives of the langua
[^8]: A modern language feature that fascilitates concurrency.
[^9]: The core of microcontrollers is the concept of peripherals. The other "entities" that coexist with the CPU.
UART, Timers, ADC, etc.
[^10]: *Crate* is the term used for Rust packages. But *crate* is not a synonym
for *library*. Libraries are distributed as precompiled binaries, crates are
distributed as source code. This means when you build a Rust program, you also
build all dependencies from source.
162 changes: 161 additions & 1 deletion content/assignments/SpinningAndBlinking/Rust/blinking.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Blinking
type: docs
prev: assignments/SpinningAndBlinking/Setup
prev: assignments/SpinningAndBlinking/Rust/Setup
weight: 2
---

Expand Down Expand Up @@ -32,8 +32,168 @@ function named `main`, however as we will soon see, the entry-point will not be
defined by us, and will be generated by Embassy to begin async operation and start
the executor[^3].

Below this you will see:

```rust
use embassy_executor::Spawner;
use embassy_time::Timer;
use esp_backtrace as _;
use esp_hal::{
clock::ClockControl,
gpio::{Io, Level, Output},
peripherals::Peripherals,
prelude::*,
system::SystemControl,
timer::{timg::TimerGroup, ErasedTimer, OneShotTimer},
};
use esp_println::println;
use static_cell::StaticCell;
```

This looks like a lot, but these are just crates we are importing to use in our
program. We will see *exactly* which crates we use and why as we explore the rest
of this file.

```rust
#[main]
async fn main(_spawner: Spawner) {
// ...
}
```

Next you will see this ^ function.

This function is hosted by Embassy, and is what we will use to kick off our firmware.

You may notice it is **async**! This means the executor has already started, and we
can utilize all of Rust's concurrency features right away!

`#[main]` is an attribute macro[^5] from Embassy that transforms our async function
definition into the proper structure for starting the executor and configuring the
CPU/static memory.
> For funzies, you can use a tool called `cargo-expand` to view what this macro does!
> Your editor of choice may also support macro expansion inline.
This function provides us with a single parameter of type `Spawner`. This type allows
us to spawn tasks, which we don't need for a simple blink program so we marked the
variable as unused with the leading underscore.

Now let's look at what exactly we *do* in this function to configure our microcontroller.

```rust
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);

let clocks = ClockControl::max(system.clock_control).freeze();
```

In the first line, we take ownership of all peripherals. This operation can only be done
once, per RAII, we may only have *one* binding for *one* resource.

In the second line we create a binding to specifically the system peripheral, which controls
clocks, interconnects, etc.

And on the third line, configure the system clocks to run at their maximum frequency.

```rust
esp_println::logger::init_logger_from_env();
```

Next up we enable logging via the ESP32s JTAG[^8] controller.

```rust
let timg0 = TimerGroup::new(peripherals.TIMG0, &clocks, None);
```

Then we initialize the `G0` timer (which we can use to provide Embassy with a means for
keeping time).

```rust
let timer = {
static TIMER: StaticCell<[OneShotTimer<ErasedTimer>; 1]> = StaticCell::new();

TIMER.init([OneShotTimer::new(timg0.timer0.into())])
};
```

This section is a little more complicated, but the next line...

```rust
esp_hal_embassy::init(&clocks, timer);
```

...makes it a little more clear.

Embassy needs a `OneShotTimer<'static, T>` to provide monotonics[^10]. The `'static` indicates
that this timer must exist for the entire duration of the program. `let` bindings always exist
until the scope they were bound in ends. To create a `static` lifetime, the variable must be
defined as `static`. But `static` values must be `const` (i.e. known at compile time and constant).

So how do we take a runtime value (the timer) and coerce it into a `static` lifetime?

Well we know that even though the timer doesn't exist for the *entire* duration of the program
(it didn't exist before we initialized it at runtime), it *will* exist for the *rest* of the program. With this knowledge, we effectively satisfy the `static`
requirement because the scope of use of Embassy is a subset of the lifetime of the timer.

We use the `StaticCell` structure to accomplish the lifetime coersion.

{{< callout type="info" >}}
We understand that this may be uncharted territory for many of you. We implore you to do your own
research, read about Rust, and mess with it on your own. The foundational principles we are leveraging
are not easy to grasp initially, so know that you are not alone if you are struggling. The [tutors]({{< ref "/support#tutors" >}})
are here to help, and don't forget about the [resources]({{< ref "/additional-resources/rust" >}}) page.
{{< /callout >}}

The next section is fairly straight forward:

```rust
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);

let mut led = Output::new(io.pins.gpio17, Level::Low);

loop {
println!("Hello, World!");
led.toggle();
Timer::after_millis(1_000).await;
}
```

We create a binding to the `GPIO` and `IO_MUX` (multiplexer *for* the GPIO) peripherals to gain control of IO.

Then we configure pin `17` to be an output pin with a default level of low.

And finally, in an infinite loop, we print `Hello, World!`, toggle the LED, and wait for 1 second!

You may notice as part of that "waiting", there is a trailing `.await`. In async contexts, this indicates
*yielding* control back to the executor. If we had other tasks waiting to do work, they would get to
while this task waits. This simple handoff of execution is what makes async such a powerful
language feature.

> If you have operating system design experience, you may be itching to see some concurrency primitives
> in action, rest assured we will get to that. You may also be wondering how **preemption** can be done.
> The Embassy executor actually **does not** support preemption, and this was done intentionally.
> You can, however, create multiple executors of different priorities, and they will preempt each other's
> execution. You can learn more at [Embassy's docs](https://embassy.dev/book/#_executor_2).
And that's it! If you want you could try to get the other LED (pin `18`) blinking as well!

[^1]: All binaries have an entry-point, which is the symbol to start execution on.
[^2]: A top-level item is defined above all hierarchy of a file. Think of global
variable definitions, free-standing functions or type definitions.
[^3]: The *executor* is the "entity" provided by Embassy that fascilitates the
routing of execution between tasks.
[^5]: Attribute macros are procedural macros[^6] that attatch to structures
and directly transform them.
[^6]: Procedural macros are an advanced type of macro[^7] that are mini Rust programs
written to be executed by the compiler at compile-time. As opposed to *declarative*
macros which do a direct symbol transformation.
[^7]: Macros are like functions that transform code at compile-time.
[^8]: JTAG is a proprietary debug interface from SEGGER that exists on top of SWD[^9].
[^9]: **S**serial **W**ire **D**ebug (SWD) is a debug protocol created by ARM for
debugging ARM CPUs. This is extremely useful as we can remotely debug microcontrollers
via this interface. You can use it to transfer log-style information, or via the
microcontroller's debug peripheral, you could even start a GDB (or LLDB) session like any
local process.
[^10]: Monotonic means strictly increasing. A monotonic timer is usually at the heart
of time-keeping as the assumption that the absolute value of the timer is strictly
increasing can be leveraged to conduct time-keeping logic.
10 changes: 10 additions & 0 deletions content/assignments/SpinningAndBlinking/Rust/spinning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: Spinning
type: docs
prev: assignments/SpinningAndBlinking/Blinking
weight: 3
---

It's time to get our hands dirty and really change this firmware. We're going to make a motor spin, but to do that, firmware won't be enough.

Pick up a **Motion Shield** and any complementary components/equipment.
1 change: 1 addition & 0 deletions content/assignments/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ All of the ECE 196 assignment instructions can be found here.
{{< card link="lightshield" title="LightShield" icon="light-bulb" >}}
{{< card link="devboard" title="DevBoard" icon="chip" >}}
{{< card link="vumeter" title="VUMeter" icon="chart-square-bar" >}}
{{< card link="spinningandblinking" title="SpinningAndBlinking" icon="cog" >}}
{{< card link="controlwithpython" title="ControlWithPython" icon="code" >}}
{{< /cards >}}
10 changes: 10 additions & 0 deletions content/support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: Support
toc: false
---

Karcher... yadda yadda.

## Tutors

Tutors...
4 changes: 2 additions & 2 deletions hugo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ menu:
- name: "Resources"
pageRef: "/additional-resources"
weight: 2
- name: "Tutors"
url: "/about/#tutors"
- name: "Support"
url: "/support"
weight: 3

params:
Expand Down

0 comments on commit e4bcf79

Please sign in to comment.