In my previous blog link, I wrote about getting started with Embedded Rust on the ESP-32.
In this blog we will now create our first Embedded Rust application.
We will be creating the famous "Hello World" application.
Installing the necessary tools - Part 2
Install esp-generate
. This generates the template for an Embedded Rust application for ESP32.
cargo install esp-generate
Setting Up
Assuming that you have installed all the required tools as told in the previous blog and above, we can start with using a template to create a project (This make things really easy). You can build from scratch too, but , only if you have enough technical knowledge and debugging expertise.
- Navigate to the directory where you want to save you project (Say
Desktop
) using the commandcd Desktop
. - Create the project using the
esp-generate
function as follows :
esp-generate --chip=esp32 <project_name>
The
--chip
parameter depends on the ESP32 module being used. I personally am using the ESP32-WROOM module so it'sesp32
for me. Refer the documentation for more chips.Make sure you do not forget to follow the steps here
After creating the project, you will now get a directory of the below structure :
.
├── .cargo
│ └── config.toml
├── .gitignore
├── build.rs
├── Cargo.lock
├── Cargo.toml
├── rust-toolchain.toml
├── src
│ ├── bin
│ │ └── main.rs
│ └── lib.rs
└── tests
└── hello_test.rs
Before going further, let us see what these files are for
-
build.rs
Sets the linker script arguments based on the template options.
.cargo/config.toml
The Cargo configuration. This defines a few options to correctly build the project. Contains the custom runner command for espflash
or probe-rs
. For example, runner = "espflash flash --monitor"
- this means you can just use cargo run
to flash and monitor your code
Cargo.toml
The usual Cargo manifest declares some meta-data and dependencies of the project
.gitignore
Tells git which folders and files to ignore
rust-toolchain.toml
Defines which Rust toolchain to use. The toolchain will be nightly or esp depending on your target
src/bin/main.rs
The main source file of the newly created project.
src/lib.rs
This tells the Rust compiler that this code doesn't use libstd.
The Hello World
Embedded Rust Program
Creating the project
When you run the command
esp-generate --chip=esp32 hello_world
You will get a TUI interface for more settings.
To select a feature, press →. As shown in the below GIF.
Make sure you choose the ones selected on the GIF only i.e only the Enable Unstable HAL features and Use esp-backtrace as panic handler which is inside Flashing, Logging and Debugging.
Writing the code
Now, in the src/bin/main.rs
file inside the project directory, write the below code :
#![no_std]
#![no_main]
use esp_backtrace as _;
use esp_hal::{
clock::CpuClock,
delay::Delay,
main
};
use esp_println::println;
esp_bootloader_esp_idf::esp_app_desc!();
#[main]
fn main() -> ! {
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let _peripherals = esp_hal::init(config);
let delay = Delay::new();
loop {
println!("Hello World");
delay.delay_millis(500);
}
}
This code prints Hello World
after every 500 milliseconds or 1/2 a second.
To run and flash this code , type
cargo run --release
This will compile and flash the code to the ESP32.
Here's the output
Into the Code : A line-by-line explanation
Line-by-Line Explanation: ESP32 "Hello World" in Embedded Rust
#![no_std]
- This disables the Rust standard library. In embedded systems, the standard library is often unavailable or unnecessary due to hardware constraints.
#![no_main]
- This tells the Rust compiler not to use the default entry point (
main
from the std lib). Instead, we define our own entry point suitable for embedded devices.
use esp_backtrace as _;
- This pulls in the
esp_backtrace
crate to provide panic handlers and backtraces (when supported), which are essential for debugging embedded systems. Theas _
suppresses the unused import warning.
use esp_hal::{
clock::CpuClock,
delay::Delay,
main
};
- Imports:
-
CpuClock
: lets you configure the CPU clock speed. -
Delay
: for inserting delays into your program. -
main
: an attribute macro that marks the main function as the program's entry point.
-
use esp_println::println;
- This brings in
println!
macro functionality, which outputs to UART so we can see debug output from the ESP32.
esp_bootloader_esp_idf::esp_app_desc!();
- This macro embeds metadata from the
esp-idf
build into the binary (like project name, version, etc.). It’s optional but helpful for diagnostics and logging.
#[main]
- The
#[main]
macro fromesp_hal
designates the following function as the program’s entry point.
fn main() -> ! {
- Defines the entry point of the application. The
-> !
return type indicates it never returns (infinite loop).
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
- Creates a default configuration and sets the CPU clock to the maximum supported frequency.
let _peripherals = esp_hal::init(config);
- Initializes the ESP32 HAL with the provided configuration and takes ownership of the device’s peripherals.
let delay = Delay::new();
- Instantiates a delay object used to insert blocking delays into the code.
loop {
- Infinite loop to run your logic continuously (typical in embedded firmware).
println!("Hello World");
- Sends the string "Hello World" to the serial output using UART.
delay.delay_millis(500);
- Inserts a 500 ms delay between messages.
}
}
- Closes the loop and main function.
Important Links
Thank You