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_stddisables loading thestdcrate. It only loadscoreby default.no_maintells Rust not to search for amainfunction.- This is handled by
esp_hal::mainmacro.
- This is handled by
Imports
A few imports also repeat througout:
use esp_hal::{main, Config};
use esp_backtrace as _;
use esp_println::println;
-
esp_hal: providesmainentry plus useful modules for dealing with peripherals, time, and so forth. -
esp_backtrace: Out of bounds indexing and our ownpanic!calls have to be handled by a panic-handler (otherwise provided bystd).- This crate provides a replacement through the
panic-handlerfeature. - In summary, it handles the
panic!calls, and prints the backtrace (call stack up to that point.).
- This crate provides a replacement through the
-
esp_println: The macroprintln!comes fromstd. We don't usestdsoesp-printlnprovides the macro for us.- Logging backends (
log,defmt) can also be used:
use esp_println::{logger,println}; +use log::{info, trace};- The
logimport isn't needed if we don't use these macros.
- Logging backends (
App Descriptor
The line is:
esp_bootloader_esp_idf::esp_app_desc!();
- The board has 2 bootloaders.
- First stage bootloader: written in ROM and can't be changed. It's read first. It loads the second stage bootloader.
- 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,
espflashincludes 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
structis marked with#[non_exhaustive]and can't be instantiated directly. This is done to prevent breaking changes when new fields are added to thestruct. 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.