Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add esp-hal-get-started book content #43

Merged
merged 10 commits into from
Nov 2, 2022
10 changes: 8 additions & 2 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@
- [Wokwi](./tooling/simulating/wokwi.md)
- [QEMU](tooling/simulating/qemu.md)
- [Writing Your Own Application](./writing-your-own-application/index.md)
- [Generate Project from Template](./writing-your-own-application/generate-project-from-template.md)
- [Generating Projects from Templates](./writing-your-own-application/generate-project-from-template.md)
- [Writing `no_std` Applications](./writing-your-own-application/no-std-applications/index.md)
- [Understanding esp-template](./writing-your-own-application/no-std-applications/understanding-esp-template.md)
- [Hello World](./writing-your-own-application/no-std-applications/hello-world.md)
- [Panic!](./writing-your-own-application/no-std-applications/panic.md)
- [Blinky](./writing-your-own-application/no-std-applications/blinky.md)
- [Detect a button press](./writing-your-own-application/no-std-applications/button.md)
- [Detect a button press with interrupt](./writing-your-own-application/no-std-applications/interrupt.md)
- [Writing `std` Applications](./writing-your-own-application/writing-std-applications.md)
- [Writing `no_std` Applications (TODO)]()
- [Resources](./resources.md)
---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generating Projects From Templates
# Generating Projects from Templates

We currently maintin two template repositories:
- [esp-template] - `no_std` template.
Expand Down Expand Up @@ -56,7 +56,7 @@ cargo generate --git https://github.com/esp-rs/esp-template
🔧 Destination: /home/alice/esp-rust-app ...
🔧 Generating template ...
✔ 🤷 Which MCU to target? · esp32c3
✔ 🤷 Configure project to use Dev Containers (VS Code, GitHub Codespaces and Gitpod)? · true
✔ 🤷 Configure project to use Dev Containers (VS Code, GitHub Codespaces and Gitpod)? · false
✔ 🤷 Enable allocations via the esp-alloc crate? · false
[ 1/11] Done: .cargo/config.toml
[ 2/11] Done: .cargo
Expand Down
65 changes: 65 additions & 0 deletions src/writing-your-own-application/no-std-applications/blinky.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Blinky

Let's see how to create the iconic _Blinky_.

Change the code in `main.rs` to this
```rust,ignore
#![no_std]
#![no_main]

use esp32c3_hal::{
clock::ClockControl, pac::Peripherals, prelude::*, timer::TimerGroup, Delay, Rtc, IO,
};
use esp_backtrace as _;

#[riscv_rt::entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

// Disable the RTC and TIMG watchdog timers
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
let mut wdt1 = timer_group1.wdt;

rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();

esp_println::println!("Hello World");

// Set GPIO7 as an output, and set its state high initially.
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let mut led = io.pins.gpio7.into_push_pull_output();

led.set_high().unwrap();

// Initialize the Delay peripheral, and use it to toggle the LED state in a
// loop.
let mut delay = Delay::new(&clocks);

loop {
led.toggle().unwrap();
delay.delay_ms(500u32);
}
}
```

We need two new types in scope: [`IO`] and [`Delay`]

On [ESP32-C3-DevKit-RUST-1] there is a regular [LED connected to GPIO 7]. If you use another board consult the data-sheet.

> Note that most dev-boards today use an addressable LED which works differently and is beyond the scope of this book. In that case, you can also connect a regular LED to some of the free pins (and don't forget to add a resistor).
SergioGasquez marked this conversation as resolved.
Show resolved Hide resolved

Here we see that we can drive the pin `high`, `low`, or `toggle` it.

We also see that the HAL offers a way to delay execution.

[ESP32-C3-DevKit-RUST-1]: https://github.com/esp-rs/esp-rust-board
[LED connected to GPIO 7]: https://github.com/esp-rs/esp-rust-board#pin-layout
[`IO`]: https://docs.rs/esp32c3-hal/0.2.0/esp32c3_hal/gpio/struct.IO.html
[`Delay`]: https://docs.rs/esp32c3-hal/0.2.0/esp32c3_hal/struct.Delay.html
51 changes: 51 additions & 0 deletions src/writing-your-own-application/no-std-applications/button.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Detect a button press

Most of the dev-boards have a button, in our case, we will use the one labeled [`BOOT` on `GPIO9`]. Let's see how to check the state of the button.

```rust,ignore
#![no_std]
#![no_main]

use esp32c3_hal::{
clock::ClockControl, pac::Peripherals, prelude::*, timer::TimerGroup, Rtc, IO,
};
use esp_backtrace as _;

#[riscv_rt::entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

// Disable the RTC and TIMG watchdog timers
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
let mut wdt1 = timer_group1.wdt;

rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();

// Set GPIO7 as an output, GPIO9 as input
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let mut led = io.pins.gpio7.into_push_pull_output();
let button = io.pins.gpio9.into_pull_up_input();

loop {
if button.is_high().unwrap() {
led.set_high().unwrap();
} else {
led.set_low().unwrap();
}
}
}
```

Now if the button is not pressed the LED is lit. If the button is pressed the LED is off.

Similarly to turning a `GPIO` into an `output` we can turn it into an `input`. Then we can get the current state of the `input` pin with `is_high` and similar functions.

[`BOOT` on `GPIO9`]: https://github.com/esp-rs/esp-rust-board#ios
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Hello World

In the last chapter you flashed and run your first piece of code on the SoC - while that is already really exciting we can do better.

Traditionally the first thing to run on a microcontroller is _blinky_.

However, we will start with _Hello World_ here.

## Add a Dependency
You can add a dependency by any of the following methods:
- Editing `Cargo.toml`
In `Cargo.toml` in the `[dependencies]` section add this line:
```toml
esp-println = { version = "0.3.1", features = ["esp32c3"] }
```
- Using [`cargo add`]
```sh
cargo add esp-println --features "esp32c3"
```

[`esp-println`] is an additional crate that calls ROM functions to print text that is shown by [`espflash`] (or any other serial monitor).

We need to pass the feature `esp32c3` since that crate targets multiple SoCs and needs to know which one it is supposed to run on.

> Note that there might be new versions by the time you are reading this, please check [crates.io].

## Print Something

In `main.rs` before the `loop {}` add this line
```rust,ignore
esp_println::println!("Hello World");
```

> Note: [`espflash`] only shows output when a new-line is detected. There is also a `print!` macro but nothing will be shown until a new-line is received.
SergioGasquez marked this conversation as resolved.
Show resolved Hide resolved

However other serial monitors work differently.

## See Results

Again run
```shell
cargo run
```

You should see the text _Hello World_ printed!

[`espflash`]: https://github.com/esp-rs/espflash
[`esp-println`]: https://github.com/esp-rs/esp-println
[crates.io]: https://crates.io/crates/esp-println
[`cargo add`]: https://doc.rust-lang.org/cargo/commands/cargo-add.html
13 changes: 13 additions & 0 deletions src/writing-your-own-application/no-std-applications/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Writing no_std applications

The goal of this chapter is to provide a getting-started guide on using the Rust programming language with Espressif SoCs and modules using [esp-hal].

Examples shown here usually apply to ESP32-C3 using the [ESP32-C3-DevKit-RUST-1] board.

You can use any other ESP32, ESP32-C3, ESP32-S2, or ESP32-S3 development board but smaller code changes and configuration changes might be needed.

Also, this section of the book will only cover working locally.
SergioGasquez marked this conversation as resolved.
Show resolved Hide resolved


[esp-hal]: https://github.com/esp-rs/esp-hal
[ESP32-C3-DevKit-RUST-1]: https://github.com/esp-rs/esp-rust-board
95 changes: 95 additions & 0 deletions src/writing-your-own-application/no-std-applications/interrupt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Detect a button press with interrupt
[Interrupts] offer a mechanism by which the processor handles asynchronous events and fatal errors.

Let's add the [`critical-section`] crate [(see instructions on how to add a dependency)], and change `main.rs` to look like this:
```rust,ignore
#![no_std]
#![no_main]

use core::cell::RefCell;
use critical_section::Mutex;
use esp32c3_hal::{
clock::ClockControl,
gpio::Gpio9,
gpio_types::{Event, Input, Pin, PullUp},
interrupt,
pac::{self, Peripherals},
prelude::*,
timer::TimerGroup,
Rtc, IO,
};
use esp_backtrace as _;

static BUTTON: Mutex<RefCell<Option<Gpio9<Input<PullUp>>>>> = Mutex::new(RefCell::new(None));

#[riscv_rt::entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

// Disable the RTC and TIMG watchdog timers
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
let mut wdt1 = timer_group1.wdt;

rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();

// Set GPIO9 as input
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let mut button = io.pins.gpio9.into_pull_up_input();
button.listen(Event::FallingEdge); // raise interrupt on falling edge

critical_section::with(|cs| BUTTON.borrow_ref_mut(cs).replace(button));

interrupt::enable(pac::Interrupt::GPIO, interrupt::Priority::Priority3).unwrap();

loop {}
}

#[interrupt]
fn GPIO() {
critical_section::with(|cs| {
esp_println::println!("GPIO interrupt");
BUTTON
.borrow_ref_mut(cs)
.as_mut()
.unwrap()
.clear_interrupt();
});
}
```

There are quite a lot of new things here.

First thing is the `static BUTTON`. We need it since in the interrupt handler we have to clear the pending interrupt on the button and we somehow need to pass the button from main to the interrupt handler.

Since an interrupt handler can't have arguments we need a static to get the button into the interrupt handler.

We need the `Mutex` to make access to the button safe.

> Please note that this is not the Mutex you might know from `libstd` but it's the Mutex from [`critical-section`] (and that's why we need to add it as a dependency).

Then we need to call `listen` on the `output` pin to configure the peripheral to raise interrupts. We can raise interrupts for different events - here we want to raise the interrupt on the falling edge.

In the next line we move our button into the `static BUTTON` for the interrupt handler to get hold of it.

Last thing we need to do is actually enable the interrupt.

First parameter here is the kind of interrupt we want. There are several [possible interrupts].

Second parameter is the priority of the interrupt.

The interrupt handler is defined via the `#[interrupt]` macro.
Here the name of the function must match the interrupt.


[Interrupts]: https://docs.rust-embedded.org/book/start/interrupts.html
[`critical-section`]: https://crates.io/crates/critical-section
[(see instructions on how to add a dependency)]: ./hello-world.md#add-a-dependency
[possible interrupts]: https://docs.rs/esp32c3/0.5.1/esp32c3/enum.Interrupt.html
Loading