esp_hal_buzzer/
lib.rs

1//! # Buzzer
2//!
3//! ## Overview
4//!
5//! This driver provides an abstraction over LEDC to drive a piezo-electric
6//! buzzer through a user-friendly API.
7//!
8//! The [buzzer example](https://github.com/esp-rs/esp-hal-community/blob/main/esp-hal-buzzer/examples/buzzer.rs)
9//! contains pre-programmed songs to play through the buzzer.
10//!
11//! ## Example
12//!
13//! ```rust,ignore
14//! let peripherals = esp_hal::init(esp_hal::Config::default());
15//!
16//! let mut ledc = Ledc::new(peripherals.LEDC);
17//! ledc.set_global_slow_clock(LSGlobalClkSource::APBClk);
18//!
19//! let mut buzzer = Buzzer::new(
20//!     &ledc,
21//!     timer::Number::Timer0,
22//!     channel::Number::Channel1,
23//!     peripherals.GPIO4,
24//! );
25//!
26//! // Play a 1000Hz frequency
27//! buzzer.play(1000).unwrap()
28//! ```
29//!
30//! ## Feature Flags
31#![doc = document_features::document_features!()]
32#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
33#![deny(missing_docs)]
34#![no_std]
35
36use core::fmt::Debug;
37
38use esp_hal::{
39    clock::Clocks,
40    delay::Delay,
41    gpio::{AnyPin, DriveMode, Level, Output, OutputConfig, OutputPin},
42    ledc::{
43        Ledc, LowSpeed,
44        channel::{self, Channel, ChannelIFace},
45        timer::{self, Timer, TimerIFace},
46    },
47    time::Rate,
48};
49
50pub mod notes;
51
52/// Errors from Buzzer
53#[derive(Debug)]
54#[cfg_attr(feature = "defmt", derive(defmt::Format))]
55pub enum Error {
56    /// Errors from [channel::Error]
57    Channel(channel::Error),
58
59    /// Errors from [timer::Error]
60    Timer(timer::Error),
61
62    /// Error when the volume pin isn't set and we try to use it
63    VolumeNotSet,
64
65    /// When the volume level is out of range. Either too low or too high.
66    VolumeOutOfRange,
67
68    /// Sequence and timings slice aren't of the same length
69    LengthMismatch,
70}
71
72/// Converts [channel::Error] into [self::Error]
73impl From<channel::Error> for Error {
74    fn from(error: channel::Error) -> Self {
75        Error::Channel(error)
76    }
77}
78
79/// Converts [timer::Error] into [self::Error]
80impl From<timer::Error> for Error {
81    fn from(error: timer::Error) -> Self {
82        Error::Timer(error)
83    }
84}
85
86/// Represents a tone value to play through the buzzer
87pub struct ToneValue {
88    /// Frequency of the tone in Hz
89    /// *Use 0 for a silent tone*
90    pub frequency: u32,
91
92    /// Duration for the frequency in ms
93    pub duration: u32,
94}
95
96/// Represents different volume strategies for the buzzer.
97///
98/// - [VolumeType::OnOff] is a simple on or off volume. It's similar as using
99///   `.mute()` except that the volume control is on a second pin independent of
100///   the buzzer.
101///
102/// - [VolumeType::Duty] uses the duty as the volume control. It acts like a PWM
103///   by switching the power on and off. This may require extra logic gates in
104///   the circuit.
105#[derive(Debug)]
106#[cfg_attr(feature = "defmt", derive(defmt::Format))]
107pub enum VolumeType {
108    /// An On / Off based volume
109    OnOff,
110
111    /// A duty based volume where 0% is the lowest and 100% the highest.
112    Duty,
113}
114
115/// Volume configuration for the buzzer
116struct Volume<'d> {
117    /// Output pin for the volume
118    volume_pin: AnyPin<'d>,
119
120    /// Type of the volume
121    volume_type: VolumeType,
122
123    /// Volume level
124    ///
125    /// For [VolumeType::OnOff], should be 0 for Off, or 1 or more for On
126    /// For [VolumeType::Duty], should be between 0 and 100.
127    level: u8,
128}
129
130/// A buzzer instance driven by Ledc
131pub struct Buzzer<'a> {
132    timer: Timer<'a, LowSpeed>,
133    channel_number: channel::Number,
134    output_pin: AnyPin<'a>,
135    delay: Delay,
136    volume: Option<Volume<'a>>,
137}
138
139impl<'a> Buzzer<'a> {
140    /// Create a new buzzer for the given pin
141    pub fn new(
142        ledc: &'a Ledc,
143        timer_number: timer::Number,
144        channel_number: channel::Number,
145        output_pin: impl OutputPin + 'a,
146    ) -> Self {
147        let timer = ledc.timer(timer_number);
148        Self {
149            timer,
150            channel_number,
151            output_pin: output_pin.degrade(),
152            delay: Delay::new(),
153            volume: None::<Volume>,
154        }
155    }
156
157    /// Add a volume control for the buzzer.
158    pub fn with_volume(mut self, volume_pin: impl OutputPin + 'a, volume_type: VolumeType) -> Self {
159        self.volume = Some(Volume {
160            volume_pin: volume_pin.degrade(),
161            volume_type,
162            level: 50,
163        });
164
165        self
166    }
167
168    /// Set the volume of the buzzer
169    ///
170    /// For [VolumeType::Duty], the level should be between 0 and 100.
171    /// For [VolumeType::OnOff], it will only be mute on 0 and playing on 1 or
172    /// more
173    pub fn set_volume(&mut self, level: u8) -> Result<(), Error> {
174        if let Some(ref mut volume) = self.volume {
175            match volume.volume_type {
176                VolumeType::OnOff => {
177                    // Only turn off when level is set to 0, else set to high
178                    Output::new(
179                        unsafe { volume.volume_pin.clone_unchecked() },
180                        if level != 0 { Level::High } else { Level::Low },
181                        OutputConfig::default(),
182                    );
183                    Ok(())
184                }
185                VolumeType::Duty => {
186                    match level {
187                        0..=99 => {
188                            volume.level = level;
189
190                            // Put a dummy config in the timer if it's not already configured
191                            if !self.timer.is_configured() {
192                                self.timer.configure(timer::config::Config {
193                                    duty: timer::config::Duty::Duty11Bit,
194                                    clock_source: timer::LSClockSource::APBClk,
195                                    frequency: Rate::from_hz(20_000),
196                                })?;
197                            }
198
199                            let mut channel = Channel::new(self.channel_number, unsafe {
200                                volume.volume_pin.clone_unchecked()
201                            });
202                            channel
203                                .configure(channel::config::Config {
204                                    timer: &self.timer,
205                                    duty_pct: level,
206                                    drive_mode: DriveMode::PushPull,
207                                })
208                                .map_err(|e| e.into())
209                        }
210                        100 => {
211                            // If level is 100, we just keep the pin high
212                            Output::new(
213                                unsafe { volume.volume_pin.clone_unchecked() },
214                                Level::High,
215                                OutputConfig::default(),
216                            );
217                            Ok(())
218                        }
219                        _ => Err(Error::VolumeOutOfRange),
220                    }
221                }
222            }
223        } else {
224            Err(Error::VolumeNotSet)
225        }
226    }
227
228    /// Mute the buzzer
229    ///
230    /// The muting is done by simply setting the duty to 0
231    pub fn mute(&self) {
232        // Timer is not configured, we do an early return since no sound is playing anyway.
233        if !self.timer.is_configured() {
234            return;
235        }
236        let mut channel = Channel::new(self.channel_number, unsafe {
237            self.output_pin.clone_unchecked()
238        });
239        // Safety:
240        // - Error::Duty cannot happen, we hardcode 0 which is valid
241        // - Error::Channel cannot happen, channel is configured below
242        // - Error::Timer cannot happen, it's an early return no-op above
243        channel
244            .configure(channel::config::Config {
245                timer: &self.timer,
246                duty_pct: 0,
247                drive_mode: DriveMode::PushPull,
248            })
249            .unwrap()
250    }
251
252    /// Play a frequency through the buzzer
253    pub fn play(&mut self, frequency: u32) -> Result<(), Error> {
254        // Mute if frequency is 0Hz
255        if frequency == 0 {
256            self.mute();
257            return Ok(());
258        }
259
260        // Max duty resolution for a frequency:
261        // Integer(log2(LEDC_APB_CKL / frequency))
262        let mut result = 0;
263        let mut value = Clocks::get().apb_clock / Rate::from_hz(frequency);
264
265        // Limit duty resolution to 14 bits
266        while value > 1 && result < 14 {
267            value >>= 1;
268            result += 1;
269        }
270
271        self.timer.configure(timer::config::Config {
272            // Safety: This should never fail because resolution is limited to 14 bits
273            duty: timer::config::Duty::try_from(result).unwrap(),
274            clock_source: timer::LSClockSource::APBClk,
275            frequency: Rate::from_hz(frequency),
276        })?;
277
278        let mut channel = Channel::new(self.channel_number, unsafe {
279            self.output_pin.clone_unchecked()
280        });
281        channel.configure(channel::config::Config {
282            timer: &self.timer,
283            // Use volume as duty if set since we use the same channel.
284            duty_pct: self.volume.as_ref().map_or(50, |v| v.level),
285            drive_mode: DriveMode::PushPull,
286        })?;
287
288        Ok(())
289    }
290
291    /// Play a sound sequence through the buzzer
292    ///
293    /// Uses a pair of frequencies and timings to play a sound sequence.
294    ///
295    /// # Arguments
296    /// * `sequence` - A list of frequencies to play through the buzzer
297    /// * `timings` - A list of timings in ms for each frequencies
298    ///
299    /// # Examples
300    /// Play a single beep at 300Hz for 1 second
301    /// ```
302    /// buzzer.play_tones([300], [1000]);
303    /// ```
304    ///
305    /// Play a sequence of 3 beeps with a break inbetween
306    /// ```
307    /// buzzer.play_tones([200, 0, 200, 0, 200], [200, 50, 200, 50, 200]);
308    /// ```
309    ///
310    /// Play a sequence of 3 beeps with the same duration
311    /// ```
312    /// buzzer.play_tones([100, 200, 300], [100; 3]);
313    /// ```
314    ///
315    /// # Errors
316    /// This function returns an [Error] in case of an error.
317    /// An error can occur when an invalid value is used as a tone
318    pub fn play_tones<const T: usize>(
319        &mut self,
320        sequence: [u32; T],
321        timings: [u32; T],
322    ) -> Result<(), Error> {
323        // Iterate for each frequency / timing pair
324        for (frequency, timing) in sequence.iter().zip(timings.iter()) {
325            self.play(*frequency)?;
326            self.delay.delay_millis(*timing);
327            self.mute();
328        }
329        // Mute at the end of the sequence
330        self.mute();
331        Ok(())
332    }
333
334    /// Play a sound sequence through the buzzer
335    ///
336    /// Uses a pair of frequency and duration slices to play a sound sequence.
337    /// Both slices must be of the same length, where each pair of `(frequency, duration)` defines one tone.
338    ///
339    /// # Arguments
340    /// * `sequence` - A slice of frequencies to play through the buzzer
341    /// * `timings` - A slice of durations in milliseconds for each frequency
342    ///
343    /// # Examples
344    /// Play a single beep at 300Hz for 1 second
345    /// ```
346    /// buzzer.play_tones_from_slice(&[300], &[1000]);
347    /// ```
348    ///
349    /// Play a sequence of 3 beeps with a break in between
350    /// ```
351    /// buzzer.play_tones_from_slice(&[200, 0, 200, 0, 200], &[200, 50, 200, 50, 200]);
352    /// ```
353    ///
354    /// Play a sequence of 3 beeps with the same duration
355    /// ```
356    /// buzzer.play_tones_from_slice(&[100, 200, 300], &[100; 3]);
357    /// ```
358    ///
359    /// # Errors
360    /// This function returns an [Error] in the following cases:
361    /// - If the `sequence` and `timings` slices have different lengths ([Error::LengthMismatch])
362    /// - If playing a frequency results in an error
363    pub fn play_tones_from_slice(
364        &mut self,
365        sequence: &[u32],
366        timings: &[u32],
367    ) -> Result<(), Error> {
368        if sequence.len() != timings.len() {
369            return Err(Error::LengthMismatch);
370        }
371
372        // Iterate for each frequency / timing pair
373        for (frequency, timing) in sequence.iter().zip(timings.iter()) {
374            self.play(*frequency)?;
375            self.delay.delay_millis(*timing);
376            self.mute();
377        }
378        // Mute at the end of the sequence
379        self.mute();
380        Ok(())
381    }
382
383    /// Play a tone sequence through the buzzer
384    ///
385    /// Uses a pair of frequencies and timings to play a sound sequence.
386    ///
387    /// # Arguments
388    /// * `tones` - A list of type [ToneValue] to play through the buzzer
389    ///
390    /// # Examples
391    /// Play a tone sequence
392    /// ```
393    /// let song = [
394    ///     ToneValue {
395    ///         frequency: 100,
396    ///         duration: 100,
397    ///     },
398    ///     ToneValue {
399    ///         frequency: 200,
400    ///         duration: 100,
401    ///     },
402    ///     ToneValue {
403    ///         frequency: 300,
404    ///         duration: 100,
405    ///     },
406    /// ];
407    /// buzzer.play_song(&song);
408    /// ```
409    ///
410    /// # Errors
411    /// This function returns an [Error] in case of an error.
412    /// An error can occur when an invalid value is used as a tone
413    pub fn play_song(&mut self, tones: &[ToneValue]) -> Result<(), Error> {
414        for tone in tones {
415            self.play(tone.frequency)?;
416            self.delay.delay_millis(tone.duration);
417            self.mute();
418        }
419        // Mute at the end of the sequence
420        self.mute();
421        Ok(())
422    }
423}