Writing Rust Code to Create an LED Fading Effect on ESP32

Now comes the fun part; let's dive into the coding!

Generate project using esp-generate

You have done this step already in the quick start section.

To create the project, use the esp-generate command. Run the following:

esp-generate --chip esp32 led-fader

This will open a screen asking you to select options. For now, we dont need to select any options. Just save it by pressing "s" in the keyboard.

Let's start by initializing the peripherals with the default configuration. This function configures the CPU clock and watchdog, and then returns the instance of the peripherals.

#![allow(unused)]
fn main() {
 let peripherals = esp_hal::init({
    let mut config = esp_hal::Config::default();
    config.cpu_clock = CpuClock::max();
    config
});
}

Next, we take our desired GPIO from the peripherals instance. In this case, we're turning on the onboard LED of the Devkit, which is connected to GPIO 2.

#![allow(unused)]
fn main() {
let led = peripherals.GPIO2;
}

PWM configuration

In this exercise, we will be using the low-speed PWM channel. First, we need to set the clock source. The esp-hal library defines the LSGlobalClkSource enum for the low-speed clock source, which currently has only one value: APBClk.

#![allow(unused)]
fn main() {
ledc.set_global_slow_clock(LSGlobalClkSource::APBClk);
}

Next, we configure the timer. Since we are using the low-speed PWM channel, we obviously need to use the low-speed timer. We also have to specify which low-speed timer to use (from 0 to 3).

#![allow(unused)]
fn main() {
let mut lstimer0 = ledc.timer::<LowSpeed>(timer::Number::Timer0);
}

We need to do a few more configurations before using the timer. We'll set the frequency to 24 kHz. For this frequency with the APB clock, the formula gives a maximum resolution of 12 bits and a minimum resolution of 2 bits. In the esp-hal, a 5-bit PWM resolution is used for this frequency, and we will use the same.

#![allow(unused)]
fn main() {
lstimer0.configure(timer::config::Config {
    duty: timer::config::Duty::Duty5Bit,
    clock_source: timer::LSClockSource::APBClk,
    frequency: 24.kHz(),
})
.unwrap();
}

PWM Channels

Next, we configure the PWM channel. We'll use channel0 and set it up with the selected timer and initial duty percentage "10%". Additionally, we'll set the pin configuration as PushPull.

#![allow(unused)]
fn main() {
let mut channel0 = ledc.channel(channel::Number::Channel0, led);
channel0.configure(channel::config::Config {
    timer: &lstimer0,
    duty_pct: 10,
    pin_config: channel::config::PinConfig::PushPull,
})
.unwrap();
}

Fading

The esp-hal has a function called start_duty_fade, which makes our job easier. Otherwise, we would have to manually increment and decrement the duty cycle in a loop at regular intervals. This function gradually changes from one duty cycle percentage to another. It also accepts a third parameter, which specifies how much time it should take to transition from one duty cycle to another.

#![allow(unused)]
fn main() {
channel0.start_duty_fade(0, 100, 1000).unwrap();
}

We will run this in a loop and use another function provided by the HAL, is_duty_fade_running; It returns boolean value whether the duty fade is complete or not.

#![allow(unused)]
fn main() {
while channel0.is_duty_fade_running() {}
}

The full code

#![no_std]
#![no_main]

use esp_backtrace as _;
use esp_hal::{
    ledc::{
        channel::{self, ChannelIFace},
        timer::{self, TimerIFace},
        LSGlobalClkSource, Ledc, LowSpeed,
    },
    prelude::*,
};

#[entry]
fn main() -> ! {
    let peripherals = esp_hal::init({
        let mut config = esp_hal::Config::default();
        config.cpu_clock = CpuClock::max();
        config
    });

    let led = peripherals.GPIO2;
    // let led = peripherals.GPIO5;

    let mut ledc = Ledc::new(peripherals.LEDC);
    ledc.set_global_slow_clock(LSGlobalClkSource::APBClk);

    let mut lstimer0 = ledc.timer::<LowSpeed>(timer::Number::Timer0);
    lstimer0
        .configure(timer::config::Config {
            duty: timer::config::Duty::Duty5Bit,
            clock_source: timer::LSClockSource::APBClk,
            frequency: 24.kHz(),
        })
        .unwrap();

    let mut channel0 = ledc.channel(channel::Number::Channel0, led);
    channel0
        .configure(channel::config::Config {
            timer: &lstimer0,
            duty_pct: 10,
            pin_config: channel::config::PinConfig::PushPull,
        })
        .unwrap();

    loop {
        channel0.start_duty_fade(0, 100, 1000).unwrap();
        while channel0.is_duty_fade_running() {}
        channel0.start_duty_fade(100, 0, 1000).unwrap();
        while channel0.is_duty_fade_running() {}
    }
}

Clone the existing project

You can also clone (or refer) project I created and navigate to the led-fader folder.

git clone https://github.com/ImplFerris/esp32-projects
cd esp32-projects/led-fader

Flashing

Once you flash the code into the ESP32, you should see the fading effect on the onboard LED.

#![allow(unused)]
fn main() {
cargo run --release
}