From e4bcf795455e4d30df81e686433c9b6484ee81d4 Mon Sep 17 00:00:00 2001 From: Adin Ackerman Date: Wed, 31 Jul 2024 09:30:26 -0700 Subject: [PATCH] finish up rust/blinking --- .../assignments/ControlWithPython/tutorial.md | 2 +- .../SpinningAndBlinking/Arduino/_index.md | 5 +- .../SpinningAndBlinking/Arduino/blinking.md | 27 +++ .../SpinningAndBlinking/Rust/_index.md | 6 +- .../SpinningAndBlinking/Rust/blinking.md | 162 +++++++++++++++++- .../SpinningAndBlinking/Rust/spinning.md | 10 ++ content/assignments/_index.md | 1 + content/support.md | 10 ++ hugo.yaml | 4 +- 9 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 content/assignments/SpinningAndBlinking/Arduino/blinking.md create mode 100644 content/assignments/SpinningAndBlinking/Rust/spinning.md create mode 100644 content/support.md diff --git a/content/assignments/ControlWithPython/tutorial.md b/content/assignments/ControlWithPython/tutorial.md index 625732e..625414e 100644 --- a/content/assignments/ControlWithPython/tutorial.md +++ b/content/assignments/ControlWithPython/tutorial.md @@ -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() { diff --git a/content/assignments/SpinningAndBlinking/Arduino/_index.md b/content/assignments/SpinningAndBlinking/Arduino/_index.md index e3ac10a..b83ceb4 100644 --- a/content/assignments/SpinningAndBlinking/Arduino/_index.md +++ b/content/assignments/SpinningAndBlinking/Arduino/_index.md @@ -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. diff --git a/content/assignments/SpinningAndBlinking/Arduino/blinking.md b/content/assignments/SpinningAndBlinking/Arduino/blinking.md new file mode 100644 index 0000000..060ef85 --- /dev/null +++ b/content/assignments/SpinningAndBlinking/Arduino/blinking.md @@ -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. diff --git a/content/assignments/SpinningAndBlinking/Rust/_index.md b/content/assignments/SpinningAndBlinking/Rust/_index.md index 588b786..ed2d4ad 100644 --- a/content/assignments/SpinningAndBlinking/Rust/_index.md +++ b/content/assignments/SpinningAndBlinking/Rust/_index.md @@ -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" >}} @@ -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. diff --git a/content/assignments/SpinningAndBlinking/Rust/blinking.md b/content/assignments/SpinningAndBlinking/Rust/blinking.md index fb5cf0b..0ca9598 100644 --- a/content/assignments/SpinningAndBlinking/Rust/blinking.md +++ b/content/assignments/SpinningAndBlinking/Rust/blinking.md @@ -1,7 +1,7 @@ --- title: Blinking type: docs -prev: assignments/SpinningAndBlinking/Setup +prev: assignments/SpinningAndBlinking/Rust/Setup weight: 2 --- @@ -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; 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. diff --git a/content/assignments/SpinningAndBlinking/Rust/spinning.md b/content/assignments/SpinningAndBlinking/Rust/spinning.md new file mode 100644 index 0000000..42b1b3e --- /dev/null +++ b/content/assignments/SpinningAndBlinking/Rust/spinning.md @@ -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. diff --git a/content/assignments/_index.md b/content/assignments/_index.md index 67ed224..af0dd19 100644 --- a/content/assignments/_index.md +++ b/content/assignments/_index.md @@ -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 >}} diff --git a/content/support.md b/content/support.md new file mode 100644 index 0000000..3d8e7bf --- /dev/null +++ b/content/support.md @@ -0,0 +1,10 @@ +--- +title: Support +toc: false +--- + +Karcher... yadda yadda. + +## Tutors + +Tutors... diff --git a/hugo.yaml b/hugo.yaml index 173ec35..16aede7 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -42,8 +42,8 @@ menu: - name: "Resources" pageRef: "/additional-resources" weight: 2 - - name: "Tutors" - url: "/about/#tutors" + - name: "Support" + url: "/support" weight: 3 params: