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}