In my previous blog, we coded our first Embedded Rust application which prints Hello World
, now we will start a new application that is LED Blinking.
LED blinking in hardware is what Hello World
is to software i.e it is the starting point of anyone who plans on starting microcontroller programming.
LED blinking gives you a basic idea of how to select which pin to toggle, make it an input or an output and how to set the pin as HIGH or LOW.
In this blog, we will be learning how to blink an LED in 2 ways :
- [Setting HIGH and LOW separately]
- [Toggling the LED]
Getting Started
Let us create a new project
esp-generate --chip=esp32 blinky
Refer the previous blog , for the steps.
NOTE
The above link takes you directly to the section in the previous blog where steps are outlined
These steps are common for all projects.
After the project has been created, let us get into the programming aspect.
Open the src/bin/main.rs
file in the project.
Code 1 - Mentioning the Level of the Pin separately.
In this code, the Logic Level of the LED pin is mentioned explicitly as HIGH or LOW.
Here's the full code
#![no_std]
#![no_main]
#![deny(
clippy::mem_forget,
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
holding buffers for the duration of a data transfer."
)]
use esp_backtrace as _;
use esp_hal::{
delay::Delay,
gpio::{Level, Output, OutputConfig},
main,
};
use esp_println::println;
esp_bootloader_esp_idf::esp_app_desc!();
#[main]
fn main() -> ! {
let peripherals = esp_hal::init(esp_hal::Config::default());
println!("Hello World");
let mut led = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default());
let delay = Delay::new();
loop {
led.set_high();
println!("LED HIGH");
delay.delay_millis(1000);
led.set_low();
println!("LED LOW");
delay.delay_millis(1000);
}
}
Into the code : A line-by-line explanation
#![no_std]
- This disables Rust's standard library, which is required in embedded environments where there is no OS.
#![no_main]
- Tells the Rust compiler that this binary has no standard entry point (
main
fromstd
) and instead uses a custom entry point, suitable for embedded.
#![deny(
clippy::mem_forget,
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
holding buffers for the duration of a data transfer."
)]
- This directive disallows the use of
mem::forget
, which can lead to memory/resource leaks when dealing with HAL (Hardware Abstraction Layer) resources like GPIOs or DMA.
use esp_backtrace as _;
- This macro provides backtraces on panic, helping with debugging crashes.
use esp_hal::{
delay::Delay,
gpio::{Level, Output, OutputConfig},
main,
};
-
Brings in essential traits and modules from the
esp_hal
crate:-
Delay
: For implementing delay operations. -
gpio::{Level, Output, OutputConfig}
: For configuring and using GPIO pins. -
main
: Used to mark the entry point.
-
use esp_println::println;
- Provides the
println!
macro, which sends debug messages to a serial console.
esp_bootloader_esp_idf::esp_app_desc!();
- Macro that embeds application metadata like version, build date, etc., into the firmware binary.
#[main]
fn main() -> ! {
- Defines the custom entry point of the application, marked with
#[main]
foresp_hal
. The-> !
return type means this function never returns (infinite loop or system reset).
let peripherals = esp_hal::init(esp_hal::Config::default());
- Initializes the HAL with default configuration and gets access to hardware peripherals like GPIO.
println!("Hello World");
- Prints "Hello World" over serial (for debugging).
let mut led = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default());
- Configures GPIO2 as a digital output with default settings and initial level
Low
.Output::new()
takes the pin, initial logic level, and an optional output config.
let delay = Delay::new();
- Initializes the delay timer used for timing operations like
delay_millis()
.
loop {
- Infinite loop to blink the LED repeatedly.
led.set_high();
- Sets the GPIO2 pin to logic high (turns LED on).
println!("LED HIGH");
- Logs the LED state to the serial console.
delay.delay_millis(1000);
- Waits for 1000 milliseconds (1 second).
led.set_low();
- Sets the GPIO2 pin to logic low (turns LED off).
println!("LED LOW");
- Logs the LED state to the serial console.
delay.delay_millis(1000);
- Waits for another second before repeating.
}
}
Code 2 - Toggling the pin without mentioning it's level.
In this code, we directly toggle the pin instead of mentioning it's logic level. This reduces the lines of code.
#![no_std]
#![no_main]
#![deny(
clippy::mem_forget,
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
holding buffers for the duration of a data transfer."
)]
use esp_backtrace as _;
use esp_hal::{
delay::Delay,
gpio::{Level, Output, OutputConfig},
main
};
use esp_println::println;
esp_bootloader_esp_idf::esp_app_desc!();
#[main]
fn main() -> ! {
let peripherals = esp_hal::init(esp_hal::Config::default());
println!("Hello World");
let mut led = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default());
let delay = Delay::new();
loop {
led.toggle();
println!("LED TOGGLED");
delay.delay_millis(2000);
}
}
Into the code : Line-by-line explanation
#![no_std]
- Disables the standard Rust library, which isn't available in embedded systems.
#![no_main]
- Replaces the standard
main
entry point with a custom one suitable for bare-metal applications.
#![deny(
clippy::mem_forget,
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
holding buffers for the duration of a data transfer."
)]
- Forbids use of
mem::forget
, which can cause resource leaks in hardware abstractions.
use esp_backtrace as _;
- Enables backtrace functionality for debugging panics in embedded context.
use esp_hal::{
delay::Delay,
gpio::{Level, Output, OutputConfig},
main,
};
- Imports delay functionality, GPIO control types, and the custom
main
macro.
use esp_println::println;
- Allows the use of
println!
for serial output, typically for debugging.
esp_bootloader_esp_idf::esp_app_desc!();
- Embeds application metadata into the binary (e.g., version, date, etc.).
#[main]
fn main() -> ! {
- Marks this as the custom entry point with an infinite loop (
-> !
).
let peripherals = esp_hal::init(esp_hal::Config::default());
- Initializes hardware abstraction and retrieves access to peripherals (GPIO, timers, etc).
println!("Hello World");
- Sends a "Hello World" message over serial for initial confirmation/debug.
let mut led = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default());
- Initializes GPIO2 as a push-pull output pin, starting in a Low state.
let delay = Delay::new();
- Creates a delay instance for blocking delay operations.
loop {
- Starts an infinite loop for repeated toggling of the LED.
led.toggle();
- Toggles the LED pin state (High <-> Low) each cycle.
println!("LED TOGGLED");
- Logs that the LED state was toggled.
delay.delay_millis(2000);
- Waits for 2000 milliseconds (2 seconds) before toggling again.
}
}
Now let's run
cargo run --release
to get the output.
Outputs
Code 1 outputs
Terminal Output
Hardware output
Code 2 outputs
Terminal output
Hardware Output
Important Links
Thank You
Top comments (0)