/*
For a detailed explanation of this code check out the associated blog post:
https://apollolabsblog.hashnode.dev/esp32-embedded-rust-at-the-hal-pwm-buzzer
GitHub Repo containing source code and other examples:
https://github.com/apollolabsdev
For notifications on similar examples and more, subscribe to newsletter here:
https://www.theembeddedrustacean.com/subscribe
*/
#![no_std]
#![no_main]
use esp32c3_hal::{
clock::ClockControl,
delay::Delay,
ledc::{
channel,
timer::{self},
LSGlobalClkSource, LowSpeed, LEDC,
},
peripherals::Peripherals,
prelude::*,
timer::TimerGroup,
Rtc, IO,
};
use esp_backtrace as _;
#[entry]
fn main() -> ! {
// Take Peripherals, Initialize Clocks, and Create a Handle for Each
let peripherals = Peripherals::take();
let mut system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
// Instantiate and Create Handles for the RTC and TIMG watchdog timers
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
let mut wdt1 = timer_group1.wdt;
// Disable the RTC and TIMG watchdog timers
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
// Instantiate and Create Handle for IO
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
// Instantiate and Create Handle for LED output & Button Input
let mut buzzer_pin = io.pins.gpio1.into_push_pull_output();
// Define the notes and their frequencies
let tones = [
('c', 261_u32.Hz()),
('d', 294_u32.Hz()),
('e', 329_u32.Hz()),
('f', 349_u32.Hz()),
('g', 329_u32.Hz()),
('a', 440_u32.Hz()),
('b', 493_u32.Hz()),
];
// Define the notes to be played and the beats per note
let tune = [
('c', 1),
('c', 1),
('g', 1),
('g', 1),
('a', 1),
('a', 1),
('g', 2),
('f', 1),
('f', 1),
('e', 1),
('e', 1),
('d', 1),
('d', 1),
('c', 2),
(' ', 4),
];
// Define the tempo
let tempo = 300_u32;
// Initialize and create handle for LEDC peripheral
let mut buzzer = LEDC::new(
peripherals.LEDC,
&clocks,
&mut system.peripheral_clock_control,
);
// Set up global clock source for LEDC to APB Clk
buzzer.set_global_slow_clock(LSGlobalClkSource::APBClk);
// Instantiate Delay handle
let mut delay = Delay::new(&clocks);
// Application Loop
loop {
// Obtain a note in the tune
for note in tune {
// Retrieve the freqeuncy and beat associated with the note
for tone in tones {
// Find a note match in the tones array and update frequency and beat variables accordingly
if tone.0 == note.0 {
// Play the note for the desired duration (beats*tempo)
// Adjust period of the PWM output to match the new frequency
let mut lstimer0 = buzzer.get_timer::<LowSpeed>(timer::Number::Timer0);
lstimer0
.configure(timer::config::Config {
duty: timer::config::Duty::Duty13Bit,
clock_source: timer::LSClockSource::APBClk,
frequency: tone.1,
})
.unwrap();
let mut channel0 =
buzzer.get_channel(channel::Number::Channel0, &mut buzzer_pin);
channel0
.configure(channel::config::Config {
timer: &lstimer0,
duty_pct: 50,
})
.unwrap();
// Keep the output on for as long as required by note
delay.delay_ms(note.1 * tempo);
} else if note.0 == ' ' {
// If ' ' tone is found disable output for one beat
let mut lstimer0 = buzzer.get_timer::<LowSpeed>(timer::Number::Timer0);
lstimer0
.configure(timer::config::Config {
duty: timer::config::Duty::Duty13Bit,
clock_source: timer::LSClockSource::APBClk,
frequency: 1_u32.Hz(),
})
.unwrap();
let mut channel0 =
buzzer.get_channel(channel::Number::Channel0, &mut buzzer_pin);
channel0
.configure(channel::config::Config {
timer: &lstimer0,
duty_pct: 0,
})
.unwrap();
// Keep the output off for as long as required by note
delay.delay_ms(tempo);
}
}
// Silence for half a beat between notes
let mut lstimer0 = buzzer.get_timer::<LowSpeed>(timer::Number::Timer0);
lstimer0
.configure(timer::config::Config {
duty: timer::config::Duty::Duty13Bit,
clock_source: timer::LSClockSource::APBClk,
frequency: 1_u32.Hz(),
})
.unwrap();
let mut channel0 = buzzer.get_channel(channel::Number::Channel0, &mut buzzer_pin);
channel0
.configure(channel::config::Config {
timer: &lstimer0,
duty_pct: 0,
})
.unwrap();
// Keep the output off for half a beat between notes
delay.delay_ms(tempo / 2);
}
}
}