Code

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 buzzer-song

This will open a screen asking you to select options. In the latest esp-hal, ledc requires us to explicitly enable the unstable features.

  • So you select the option "Enable unstable HAL features."

Then save it by pressing "s" in the keyboard.

Buzzer Pin

We will set GPIO 33 as our output pin. This is the pin where we connected the positive pin of the buzzer.

#![allow(unused)]
fn main() {
    let mut buzzer = peripherals.GPIO33;
    let ledc = Ledc::new(peripherals.LEDC);
}

Song instance

Create instance of the Song struct with the tempo of the song we are going to play.

#![allow(unused)]
fn main() {
    let song = Song::new(pink_panther::TEMPO);
}

Playing Music Notes with PWM

We will loop through the song's notes, using each note's frequency and duration. We also add a 10% pause to each note's duration. The frequency constants are defined as f64, which we convert to u64 and use with the Hz. The timer and PWM channel configurations are as usual, with one difference: in the timer, we set the frequency to match the current note. We set the duty cycle to 50%.

#![allow(unused)]
fn main() {
 for (note, duration_type) in pink_panther::MELODY {
    // get music notes duration
    let note_duration = song.calc_note_duration(duration_type) as u64;
    let pause_duration = note_duration / 10; // 10% of note_duration
    if note == music::REST {
        blocking_delay(Duration::from_millis(note_duration));
        continue;
    }

    // Initialize LEDC
    let freq = Rate::from_hz(note as u32);
    let mut hstimer0 = ledc.timer::<HighSpeed>(timer::Number::Timer0);
    hstimer0
        .configure(timer::config::Config {
            duty: timer::config::Duty::Duty10Bit,
            clock_source: timer::HSClockSource::APBClk,
            frequency: freq,
        })
        .unwrap();

    // Initialize LEDC Channel with duty cycle 50%
    let mut channel0 = ledc.channel(channel::Number::Channel0, &mut buzzer);
    channel0
        .configure(channel::config::Config {
            timer: &hstimer0,
            duty_pct: 50,
            pin_config: channel::config::PinConfig::PushPull,
        })
        .unwrap();

    blocking_delay(Duration::from_millis(note_duration - pause_duration)); // play 90%
    channel0.set_duty(0).unwrap();
    blocking_delay(Duration::from_millis(pause_duration)); // Pause for 10%
}
}

Clone the existing project

You can clone (or refer) project I created and navigate to the buzzer-song folder.

git clone https://github.com/ImplFerris/esp32-projects
cd esp32-projects/buzzer-song

The Full code

#![no_std]
#![no_main]

use buzzer_song::{
    music::{self, Song},
    pink_panther,
};

use esp_hal::{
    clock::CpuClock,
    ledc::{
        channel::{self, ChannelIFace},
        timer::TimerIFace,
        Ledc,
    },
    time::Rate,
};
use esp_hal::{ledc::timer, main};
use esp_hal::{
    ledc::HighSpeed,
    time::{Duration, Instant},
};

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}

#[main]
fn main() -> ! {
    // generator version: 0.3.1

    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);

    let mut buzzer = peripherals.GPIO33;

    let ledc = Ledc::new(peripherals.LEDC);

    let song = Song::new(pink_panther::TEMPO);

    for (note, duration_type) in pink_panther::MELODY {
        let note_duration = song.calc_note_duration(duration_type) as u64;
        let pause_duration = note_duration / 10; // 10% of note_duration
        if note == music::REST {
            blocking_delay(Duration::from_millis(note_duration));
            continue;
        }
        let freq = Rate::from_hz(note as u32);

        let mut hstimer0 = ledc.timer::<HighSpeed>(timer::Number::Timer0);
        hstimer0
            .configure(timer::config::Config {
                duty: timer::config::Duty::Duty10Bit,
                clock_source: timer::HSClockSource::APBClk,
                frequency: freq,
            })
            .unwrap();

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

        blocking_delay(Duration::from_millis(note_duration - pause_duration)); // play 90%

        channel0.set_duty(0).unwrap();
        blocking_delay(Duration::from_millis(pause_duration)); // Pause for 10%
    }

    loop {
        blocking_delay(Duration::from_millis(5));
    }
}

fn blocking_delay(duration: Duration) {
    let delay_start = Instant::now();
    while delay_start.elapsed() < duration {}
}