diff --git a/advanced/button-interrupt/examples/solution_led.rs b/advanced/button-interrupt/examples/solution_led.rs index f6f5f351..0212f85f 100644 --- a/advanced/button-interrupt/examples/solution_led.rs +++ b/advanced/button-interrupt/examples/solution_led.rs @@ -14,7 +14,9 @@ fn main() -> Result<()> { esp_idf_svc::sys::link_patches(); let peripherals = Peripherals::take()?; + // ANCHOR: led let mut led = WS2812RMT::new(peripherals.pins.gpio2, peripherals.rmt.channel0)?; + // ANCHOR_END: led // Configures the button let mut button = PinDriver::input(peripherals.pins.gpio9)?; @@ -33,6 +35,7 @@ fn main() -> Result<()> { })?; } + // ANCHOR: loop loop { // Enable interrupt and wait for new notificaton button.enable_interrupt()?; @@ -41,9 +44,11 @@ fn main() -> Result<()> { // Generates random rgb values and sets them in the led. random_light(&mut led); } + // ANCHOR_END: loop } #[allow(unused)] +// ANCHOR: random_light fn random_light(led: &mut WS2812RMT) { let mut color = RGB8::new(0, 0, 0); unsafe { @@ -56,3 +61,4 @@ fn random_light(led: &mut WS2812RMT) { led.set_pixel(color).unwrap(); } +// ANCHOR_END: random_light diff --git a/advanced/i2c-driver/src/icm42670p_solution.rs b/advanced/i2c-driver/src/icm42670p_solution.rs index 0d80849e..83a08562 100644 --- a/advanced/i2c-driver/src/icm42670p_solution.rs +++ b/advanced/i2c-driver/src/icm42670p_solution.rs @@ -4,6 +4,7 @@ use embedded_hal::blocking::i2c; /// ICM42670P device driver. /// Datasheet: https://invensense.tdk.com/wp-content/uploads/2021/07/DS-000451-ICM-42670-P-v1.0.pdf +// ANCHOR: struct #[derive(Debug)] pub struct ICM42670P { // The concrete I²C device implementation. @@ -12,17 +13,21 @@ pub struct ICM42670P { // Device address address: DeviceAddr, } +// ANCHOR_END: struct // See Table 3.3.2 in Documentation /// Contains the possible variants of the devices addesses as binary numbers. #[derive(Debug, Clone, Copy, PartialEq)] +// ANCHOR: device_addr pub enum DeviceAddr { /// 0x68 AD0 = 0b110_1000, /// 0x69 AD1 = 0b110_1001, } +// ANCHOR_END: device_addr +// ANCHOR: impl impl ICM42670P where I2C: i2c::WriteRead + i2c::Write, @@ -31,6 +36,7 @@ where pub fn new(i2c: I2C, address: DeviceAddr) -> Result { Ok(Self { i2c, address }) } + // ANCHOR_END: impl /// Returns the device's ID `0x67 //(if it doesn't, something is amiss) @@ -39,6 +45,7 @@ where self.read_register(Register::WhoAmI) } + // ANCHOR: read_write /// Writes into a register // This method is not public as it is only needed inside this file. #[allow(unused)] @@ -56,10 +63,12 @@ where .write_read(self.address as u8, &[register.address()], &mut data)?; Ok(u8::from_le_bytes(data)) } + // ANCHOR_END: read_write } // See Table 14.1 in documentation /// This enum represents the device's registers +// ANCHOR: register #[derive(Clone, Copy)] pub enum Register { WhoAmI = 0x75, @@ -70,3 +79,4 @@ impl Register { *self as u8 } } +// ANCHOR_END: register diff --git a/advanced/i2c-sensor-reading/examples/part_2.rs b/advanced/i2c-sensor-reading/examples/part_2.rs index 36c38ee1..2f5313f2 100644 --- a/advanced/i2c-sensor-reading/examples/part_2.rs +++ b/advanced/i2c-sensor-reading/examples/part_2.rs @@ -6,8 +6,12 @@ use esp_idf_svc::hal::{ peripherals::Peripherals, prelude::*, }; +// ANCHOR: include use icm42670::{Address, Icm42670, PowerMode as imuPowerMode}; +// ANCHOR_END: include +// ANCHOR: shared_bus use shared_bus::BusManagerSimple; +// ANCHOR_END: shared_bus use shtcx::{self, PowerMode as shtPowerMode}; // Goals of this exercise: diff --git a/book/src/02_0_preparations.md b/book/src/02_0_preparations.md index b0d3b42c..c6e18400 100644 --- a/book/src/02_0_preparations.md +++ b/book/src/02_0_preparations.md @@ -6,12 +6,23 @@ This chapter contains information about the course material, the required hardwa We use Icons to mark different kinds of information in the book: * ✅ Call for action. -* ⚠️ Warnings, details that require special attention. +* ⚠️ Warnings, and details that require special attention. * 🔎 Knowledge that dives deeper into a subject but which you aren't required to understand, proceeding. * 💡 Hints that might help you during the exercises > Example note: Notes like this one contain helpful information +## Code Annotations + +In some Rust files, you can find some anchor comments: +```rust,ignore +// ANCHOR: test +let foo = 1; +... +// ANCHOR_END: test +``` +Anchor comments can be ignored, they are only used to introduce those parts of code in this book. See [`mdBook` documentation](https://rust-lang.github.io/mdBook/format/mdbook.html#including-portions-of-a-file) + ## Required Hardware - [Rust ESP Board](https://github.com/esp-rs/esp-rust-board): available on Mouser, Aliexpress. [Full list of vendors](https://github.com/esp-rs/esp-rust-board#where-to-buy). diff --git a/book/src/02_2_software.md b/book/src/02_2_software.md index f747ec0c..2398b5ed 100644 --- a/book/src/02_2_software.md +++ b/book/src/02_2_software.md @@ -83,7 +83,7 @@ to compile the binaries for the Espressif target. Flashing binaries from contain ✅ Install [`Docker`](https://docs.docker.com/get-docker/) for your operating system. -✅ Get the docker image: There are 2 ways of getting the Docker image: +✅ Get the Docker image: There are 2 ways of getting the Docker image: - Build the Docker image from the `Dockerfile`: ```console docker image build --tag rust-std-training --file .devcontainer/Dockerfile . diff --git a/book/src/03_2_cargo_generate.md b/book/src/03_2_cargo_generate.md index af007ad5..a357bbee 100644 --- a/book/src/03_2_cargo_generate.md +++ b/book/src/03_2_cargo_generate.md @@ -8,13 +8,13 @@ More information on generating projects can be found in the [Writing Your Own Ap > ✅ Install `cargo-generate`: -```shell +```console cargo install cargo-generate ``` ✅ Change to the `intro` directory and run `cargo generate` with the [`esp-idf` template](https://github.com/esp-rs/esp-idf-template): -```shell +```console cd intro cargo generate esp-rs/esp-idf-template cargo ``` @@ -59,14 +59,14 @@ channel = "nightly-2023-11-14" # change this line ✅ Run your project by using the following command out of the `hello-world` directory. -```shell +```console cd hello-world cargo run ``` ✅ The last lines of your output should look like this: -```shell +```console (...) I (268) cpu_start: Starting scheduler. Hello, world! diff --git a/book/src/03_3_2_http_client.md b/book/src/03_3_2_http_client.md index 631cf171..e9c84c49 100644 --- a/book/src/03_3_2_http_client.md +++ b/book/src/03_3_2_http_client.md @@ -37,8 +37,7 @@ The `get` function uses [as_ref()](https://doc.rust-lang.org/std/convert/trait.A ```rust -let request = client.get(url.as_ref())?; -let response = request.submit()?; +{{#include ../../intro/http-client/examples/http_client.rs:request}} ``` A successful response has [a status code in the 2xx range](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes). Followed by the raw html of the website. diff --git a/book/src/03_3_3_https_client.md b/book/src/03_3_3_https_client.md index c2f47a04..3a57a893 100644 --- a/book/src/03_3_3_https_client.md +++ b/book/src/03_3_3_https_client.md @@ -4,18 +4,14 @@ You will now make changes to your HTTP client files so that it also works for en `intro/http-client/examples/http_client.rs` contains the solution. You can run it with the following command: -```shell +```console cargo run --example https_client ``` Create a custom client configuration to use an `esp_idf_svc::http::client::EspHttpConnection` which enables the use of these certificates and uses default values for everything else: ```rust -let connection = EspHttpConnection::new(&Configuration { - use_global_ca_store: true, - crt_bundle_attach: Some(esp_idf_sys::esp_crt_bundle_attach), - ..Default::default() - } +{{#include ../../intro/http-client/examples/https_client.rs:connection}} ``` ✅ Initialize your HTTP client with this new configuration and verify HTTPS works by downloading from an `https` resource, e.g. `https://espressif.com/`. the download will show as raw HTML in the terminal output. diff --git a/book/src/04_3_1_i2c.md b/book/src/04_3_1_i2c.md index 4bfaad0e..e9d55834 100644 --- a/book/src/04_3_1_i2c.md +++ b/book/src/04_3_1_i2c.md @@ -71,7 +71,7 @@ HUM: [local humidity] % Using a bus manager, implement the second sensor. Read out its values and print the values from both sensors. -Continue with your own solution from part one. Alternatively you can start with the provided partial solution of Part 1: `i2c-sensor-reading/examples/part_1.rs`. +Continue with your own solution from part one. Alternatively, you can start with the provided partial solution of Part 1: `i2c-sensor-reading/examples/part_1.rs`. `i2c-sensor-reading/examples/part_2.rs` contains a working solution of Part 2. You can consult it if you need help, by running: @@ -84,7 +84,7 @@ cargo run --example part_2 ✅ Import the driver crate for the ICM42670p. ```rust -use icm42670::{Address, Icm42670, PowerMode as imuPowerMode}; +{{#include ../../advanced/i2c-sensor-reading/examples/part_2.rs:include}} ``` ✅ Create an instance of the sensor. @@ -101,7 +101,7 @@ This is an ownership issue. Every place in memory needs to be owned by something ✅ Import the bus manager crate. ```rust -use shared_bus::BusManagerSimple; +{{#include ../../advanced/i2c-sensor-reading/examples/part_2.rs:shared_bus}} ``` ✅ Create an instance of a simple bus manager. Make two proxies and use them instead of the original I²C instance to pass to the sensors. diff --git a/book/src/04_3_2_i2c.md b/book/src/04_3_2_i2c.md index 12062f3a..5ee3629b 100644 --- a/book/src/04_3_2_i2c.md +++ b/book/src/04_3_2_i2c.md @@ -13,29 +13,13 @@ We're not going to write an entire driver, merely the first step: the `hello wor To use a peripheral sensor first you must get an instance of it. The sensor is represented as a struct that contains both its device address, and an object representing the I²C bus itself. This is done using traits defined in the [`embedded-hal`](https://docs.rs/embedded-hal/latest/embedded_hal/) crate. The struct is public as it needs to be accessible from outside this crate, but its fields are private. ```rust -#[derive(Debug)] -pub struct ICM42670P { - /// The concrete I²C device implementation. - i2c: I2C, - - /// Device address - address: DeviceAddr, -} - -// ... +{{#include ../../advanced/i2c-driver/src/icm42670p_solution.rs:struct}} ``` We add an `impl` block that will contain all the methods that can be used on the sensor instance. It also defines the Error Handling. In this block, we also implement an instantiating method. Methods can also be public or private. This method needs to be accessible from outside, so it's labelled `pub`. Note that written this way, the sensor instance takes ownership of the I²C bus. ```rust -implICM42670P -where - I2C: i2c::WriteRead + i2c::Write, -{ - /// Create a new instance of the ICM42670P. - pub fn new(i2c: I2C, address: DeviceAddr) -> Result { - Ok(Self{ i2c, address }) - } +{{#include ../../advanced/i2c-driver/src/icm42670p_solution.rs:impl}} // ... ``` ### Device Address @@ -43,8 +27,7 @@ where - The device's addresses are available in the code: ```rust -AD0 = 0b110_1000, // or 0x68 -AD1 = 0b110_1001, // or 0x69 +{{#include ../../advanced/i2c-driver/src/icm42670p_solution.rs:device_addr}} ``` - This I²C device has two possible addresses - `0x68` and `0x69`. @@ -56,16 +39,7 @@ More information is available in the [datasheet, section 9.3](https://invensense The sensor's registers are represented as enums. Each variant has the register's address as value. The type `Register` implements a method that exposes the variant's address. ```rust -#[derive(Clone, Copy)] -pub enum Register { - WhoAmI = 0x75, -} - -impl Register { - fn address(&self) -> u8 { - *self as u8 - } -} +{{#include ../../advanced/i2c-driver/src/icm42670p_solution.rs:register}} ``` @@ -74,24 +48,9 @@ impl Register { We define a _read_ and a _write_ method, based on methods provided by the `embedded-hal` crate. They serve as helpers for more specific methods and as an abstraction that is adapted to a sensor with 8-bit registers. Note how the `read_register()` method is based on a `write_read()` method. The reason for this lies in the characteristics of the I²C protocol: We first need to write a command over the I²C bus to specify which register we want to read from. Helper methods can remain private as they don't need to be accessible from outside this crate. ```rust -implICM42670P -where - I2C: i2c::WriteRead + i2c::Write, -{ - //... - fn write_register(&mut self, register: Register, value: u8) -> Result<(), E> { - let byte = value as u8; - self.i2c - .write(self.address as u8, &[register.address(), byte]) - } - - fn read_register(&mut self, register: Register) -> Result { - let mut data = [0]; - self.i2c - .write_read(self.address as u8, &[register.address()], &mut data)?; - Ok(u8::from_le_bytes(data)) - } -} +{{#include ../../advanced/i2c-driver/src/icm42670p_solution.rs:impl}} + // ... +{{#include ../../advanced/i2c-driver/src/icm42670p_solution.rs:read_write}} ``` ✅ Implement a public method that reads the `WhoAmI` register with the address `0x75`. Make use of the above `read_register()` method. diff --git a/book/src/04_4_3_interrupts.md b/book/src/04_4_3_interrupts.md index e7c30f46..b48c48f4 100644 --- a/book/src/04_4_3_interrupts.md +++ b/book/src/04_4_3_interrupts.md @@ -2,7 +2,7 @@ 1. Initialize the LED peripheral and switch the LED on with an arbitrary value just to see that it works. ```rust - let mut led = WS2812RMT::new(peripherals.pins.gpio2, peripherals.rmt.channel0)?; +{{#include ../../advanced/button-interrupt/examples/solution_led.rs:led}} led.set_pixel(RGB8::new(20, 0, 20)).unwrap(); // Remove this line after you tried it once ``` @@ -31,28 +31,12 @@ 4. **Optional**: If you intend to reuse this code in another place, it makes sense to put it into its own function. This lets us explore, in detail, which parts of the code need to be in `unsafe` blocks. - ```rust - // ... - loop { - // enable_interrupt should also be called after each received notification from non-ISR context - button.enable_interrupt()?; - notification.wait(esp_idf_svc::hal::delay::BLOCK); - println!("Button pressed!"); - // Generates random rgb values and sets them in the led. - random_light(&mut led); - } - // ... - fn random_light(led: &mut WS2812RMT) { - let mut color = RGB8::new(0, 0, 0); - unsafe { - let r = esp_random() as u8; - let g = esp_random() as u8; - let b = esp_random() as u8; - - color = RGB8::new(r, g, b); - } - - led.set_pixel(color).unwrap(); - } - ``` +```rust +// ... +{{#include ../../advanced/button-interrupt/examples/solution_led.rs:loop}} + +// ... +{{#include ../../advanced/button-interrupt/examples/solution_led.rs:random_light}} + +``` diff --git a/intro/http-client/examples/http_client.rs b/intro/http-client/examples/http_client.rs index 369724f5..c6911568 100644 --- a/intro/http-client/examples/http_client.rs +++ b/intro/http-client/examples/http_client.rs @@ -50,7 +50,9 @@ fn get(url: impl AsRef) -> Result<()> { // 3. Open a GET request to `url` let headers = [("accept", "text/plain")]; + // ANCHOR: request let request = client.request(Method::Get, url.as_ref(), &headers)?; + // ANCHOR_END: request // 4. Submit the request and check the status code of the response. // Successful http status codes are in the 200..=299 range. diff --git a/intro/http-client/examples/https_client.rs b/intro/http-client/examples/https_client.rs index 03b423ea..264ae697 100644 --- a/intro/http-client/examples/https_client.rs +++ b/intro/http-client/examples/https_client.rs @@ -44,11 +44,13 @@ fn main() -> Result<()> { fn get(url: impl AsRef) -> Result<()> { // 1. Create a new EspHttpClient. (Check documentation) + // ANCHOR: connection let connection = EspHttpConnection::new(&Configuration { use_global_ca_store: true, crt_bundle_attach: Some(esp_idf_svc::sys::esp_crt_bundle_attach), ..Default::default() })?; + // ANCHOR_END: connection let mut client = Client::wrap(connection); // 2. Open a GET request to `url`