esp-idf-hal/examples/rmt_musical_buzzer.rs
David Sheets 5657073293
RMT symbols and musical buzzer example bugfix for ESP32 (#354)
* rmt: wrap rmt_item32_t in a newtype

This makes it much easier to use `TxRmtDriver::start_iter` and
`TxRmtDriver::start_iter_blocking` (#113).

* examples/rmt_musical_buzzer: fix for ESP32

Possibly broken by #97, possibly never worked. The ESP-IDF repo
specifically excludes the ESP32 from the RMT musical buzzer demo
because its RMT unit lacks hardware looping. Before this change, the
demo would simply hang when trying to play the second note possibly
because the ESP32 RMT unit does not support asynchronous stopping.

As an added bonus, this example is now much easier to modify to
experiment with 1-bit chiptune synthesis by making every symbol
(cycle) accessible in the ISR iterator.

* rmt: remove Pulse::into_rmt_item in favor of Symbol::new

Also, factor Pulse pairs into function arguments for ease of use.
2024-01-31 15:39:29 +02:00

205 lines
4.9 KiB
Rust

//! Play a song using a piezo buzzer.
//!
//! Should play "Ode to Joy" on pin 17.
//!
//! Based off the ESP-IDF rmt musical buzzer example:
//! https://github.com/espressif/esp-idf/blob/b092fa073047c957545a0ae9504f04972a8c6d74/examples/peripherals/rmt/musical_buzzer/main/musical_buzzer_example_main.c
use core::time::Duration;
use esp_idf_hal::delay::Ets;
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::rmt::{self, config::TransmitConfig, TxRmtDriver};
use esp_idf_hal::units::Hertz;
use notes::*;
fn main() -> anyhow::Result<()> {
esp_idf_hal::sys::link_patches();
let peripherals = Peripherals::take()?;
let led = peripherals.pins.gpio17;
let channel = peripherals.rmt.channel0;
let config = TransmitConfig::new();
let mut tx: TxRmtDriver<'static> = TxRmtDriver::new(channel, led, &config)?;
loop {
play_song(&mut tx, ODE_TO_JOY)?;
Ets::delay_ms(3000);
}
}
pub fn play_song(tx: &mut TxRmtDriver<'static>, song: &[NoteValue]) -> anyhow::Result<()> {
for note_value in song {
note_value.play(tx)?;
}
Ok(())
}
pub struct NoteValueIter {
ticks: rmt::PulseTicks,
tone_cycles: u32,
pause_cycles: u32,
}
impl NoteValueIter {
fn new(ticks_per_sec: Hertz, note: &NoteValue) -> Self {
// Calculate the frequency for a piezo buzzer.
let dur_ms = note.duration.as_millis();
let cycles_per_sec = note.note.0; // pitch
let ticks_per_cycle = ticks_per_sec.0 as u128 / cycles_per_sec as u128;
let ticks_per_half = (ticks_per_cycle / 2_u128) as u16;
let ticks = rmt::PulseTicks::new(ticks_per_half).unwrap();
let total_cycles = (cycles_per_sec as u128 * dur_ms / 1000_u128) as u32;
// Pause for the last 40ms of every note
let pause_cycles = (cycles_per_sec as u128 * 40_u128 / 1000_u128) as u32;
let tone_cycles = total_cycles - pause_cycles;
Self {
ticks,
tone_cycles,
pause_cycles,
}
}
}
impl std::iter::Iterator for NoteValueIter {
type Item = rmt::Symbol;
// runs in ISR
fn next(&mut self) -> Option<Self::Item> {
if self.tone_cycles + self.pause_cycles > 0 {
let high_state = if self.tone_cycles > 0 {
self.tone_cycles -= 1;
rmt::PinState::High
} else {
self.pause_cycles -= 1;
rmt::PinState::Low
};
let level0 = rmt::Pulse::new(high_state, self.ticks);
let level1 = rmt::Pulse::new(rmt::PinState::Low, self.ticks);
Some(rmt::Symbol::new(level0, level1))
} else {
None
}
}
}
#[derive(Debug)]
pub struct Note(u16);
#[allow(dead_code)]
pub mod notes {
use crate::Note;
pub const A4: Note = Note(440);
pub const AS4: Note = Note(466);
pub const B4: Note = Note(494);
pub const C5: Note = Note(523);
pub const CS5: Note = Note(554);
pub const D5: Note = Note(587);
pub const DS5: Note = Note(622);
pub const E5: Note = Note(659);
pub const F5: Note = Note(698);
pub const FS5: Note = Note(740);
pub const G5: Note = Note(784);
pub const GS5: Note = Note(831);
pub const A5: Note = Note(880);
}
#[derive(Debug)]
pub struct NoteValue {
note: Note,
duration: Duration,
}
impl NoteValue {
pub fn play(&self, tx: &mut TxRmtDriver<'static>) -> anyhow::Result<()> {
let ticks_hz = tx.counter_clock()?;
tx.start_iter_blocking(self.iter(ticks_hz))?;
Ok(())
}
pub fn iter(&self, ticks_hz: Hertz) -> NoteValueIter {
NoteValueIter::new(ticks_hz, self)
}
}
macro_rules! n {
($n: expr, $duration: expr) => {
NoteValue {
note: $n,
duration: Duration::from_millis($duration),
}
};
}
const ODE_TO_JOY: &[NoteValue] = &[
n!(FS5, 400),
n!(FS5, 600),
n!(G5, 400),
n!(A5, 400),
n!(A5, 400),
n!(G5, 400),
n!(FS5, 400),
n!(E5, 400),
n!(D5, 400),
n!(D5, 400),
n!(E5, 400),
n!(FS5, 400),
n!(FS5, 400),
n!(FS5, 200),
n!(E5, 200),
n!(E5, 800),
n!(FS5, 400),
n!(FS5, 600),
n!(G5, 400),
n!(A5, 400),
n!(A5, 400),
n!(G5, 400),
n!(FS5, 400),
n!(E5, 400),
n!(D5, 400),
n!(D5, 400),
n!(E5, 400),
n!(FS5, 400),
n!(E5, 400),
n!(E5, 200),
n!(D5, 200),
n!(D5, 800),
n!(E5, 400),
n!(E5, 400),
n!(FS5, 400),
n!(D5, 400),
n!(E5, 400),
n!(FS5, 200),
n!(G5, 200),
n!(FS5, 400),
n!(D5, 400),
n!(E5, 400),
n!(FS5, 200),
n!(G5, 200),
n!(FS5, 400),
n!(E5, 400),
n!(D5, 400),
n!(E5, 400),
n!(A4, 400),
n!(A4, 400),
n!(FS5, 400),
n!(FS5, 600),
n!(G5, 400),
n!(A5, 400),
n!(A5, 400),
n!(G5, 400),
n!(FS5, 400),
n!(E5, 400),
n!(D5, 400),
n!(D5, 400),
n!(E5, 400),
n!(FS5, 400),
n!(E5, 400),
n!(E5, 200),
n!(D5, 200),
n!(D5, 800),
];