Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Boilerplate

A few lines frequently repeat, like a mantra througout the examples. It helps a lot to analyse them.

no_std, no_main

no_std, no_main, no_std, no_main, ...

The entry files start with

#![no_std]
#![no_main]
// ...
#[esp_hal::main]
fn main() -> ! { }
  • no_std disables loading the std crate. It only loads core by default.
  • no_main tells Rust not to search for a main function.
    • This is handled by esp_hal::main macro.

Imports

A few imports also repeat througout:

use esp_hal::{main, Config};
use esp_backtrace as _;
use esp_println::println;
  • esp_hal: provides main entry plus useful modules for dealing with peripherals, time, and so forth.

  • esp_backtrace: Out of bounds indexing and our own panic! calls have to be handled by a panic-handler (otherwise provided by std).

    • This crate provides a replacement through the panic-handler feature.
    • In summary, it handles the panic! calls, and prints the backtrace (call stack up to that point.).
  • esp_println: The macro println! comes from std. We don't use std so esp-println provides the macro for us.

    • Logging backends (log, defmt) can also be used:
    use esp_println::{logger,println};
    +use log::{info, trace};
    
    • The log import isn't needed if we don't use these macros.

App Descriptor

The line is:

esp_bootloader_esp_idf::esp_app_desc!();
  • The board has 2 bootloaders.
    1. First stage bootloader: written in ROM and can't be changed. It's read first. It loads the second stage bootloader.
    2. The second stage bootloader needs an application descriptor, which esp_bootloader_esp_idf::esp_app_desc!(); creates for us. This bootloader loads our application.

      Note

      Each time we flash a binary, espflash includes a pre-compiled second stage bootloader with default settings, alongside our binary-code.

init

The other line is within fn main() { } namely:

#[main]
fn main() -> ! {
    let _peripherals = esp_hal::init(Config::default());
}

This line initialises our MCU with default configuration. In their words, for esp_hal::init

Initialize the system.

This function sets up the CPU clock and watchdog, then, returns the peripherals and clocks.

Notice the "returns the peripherals". That's important. The Peripherals struct provides access to all of the hardware peripherals on the chip!

And for esp_hal::Config:

System configuration.

This struct is marked with #[non_exhaustive] and can't be instantiated directly. This is done to prevent breaking changes when new fields are added to the struct. Instead, use the [Config::default()] method to create a new instance.

Recap

So we have described the macro attributes (like #[main]), the module imports, the app descriptor and board initialisation.

Tip

All the snippets in this page are so ubiquitous that it is useful to memorise them and their role.

Suggested Reading