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