1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Licensed under the Apache License, Version 2.0 or the MIT License.
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright Tock Contributors 2022.

//! Service capsule for a buzzer that uses a PWM pin.
//!
//! ## Instantiation
//!
//! Instantiate the capsule for use as a service capsule, using a virtual pwm buzzer
//! and a virtual alarm. For example:
//!
//! ```rust,ignore
//! # use kernel::static_init;
//!
//! let mux_pwm = static_init!(
//!     capsules::virtual_pwm::MuxPwm<'static, nrf52833::pwm::Pwm>,
//!     capsules::virtual_pwm::MuxPwm::new(&base_peripherals.pwm0)
//! );
//! let virtual_pwm_buzzer = static_init!(
//!     capsules::virtual_pwm::PwmPinUser<'static, nrf52833::pwm::Pwm>,
//!     capsules::virtual_pwm::PwmPinUser::new(
//!         mux_pwm,
//!         nrf52833::pinmux::Pinmux::new(SPEAKER_PIN as u32)
//!     )
//! );
//! virtual_pwm_buzzer.add_to_mux();
//!
//! let virtual_alarm_buzzer = static_init!(
//!     capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc>,
//!     capsules::virtual_alarm::VirtualMuxAlarm::new(mux_alarm)
//! );
//! virtual_alarm_buzzer.setup();
//!
//! let pwm_buzzer = static_init!(
//!     capsules::buzzer_pwm::PwmBuzzer<
//!         'static,
//!         capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc>,
//!         capsules::virtual_pwm::PwmPinUser<'static, nrf52833::pwm::Pwm>,
//!     >,
//!     capsules::buzzer_pwm::PwmBuzzer::new(
//!         virtual_pwm_buzzer,
//!         virtual_alarm_buzzer,
//!         capsules::buzzer_pwm::DEFAULT_MAX_BUZZ_TIME_MS,
//!     )
//! );
//!
//! pwm_buzzer.set_client(buzzer);
//!
//! virtual_alarm_buzzer.set_alarm_client(pwm_buzzer);
//!
//! ```

use core::cmp;

use kernel::hil;
use kernel::hil::buzzer::BuzzerClient;
use kernel::hil::time::Frequency;
use kernel::utilities::cells::OptionalCell;
use kernel::ErrorCode;

/// Standard max buzz time.
pub const DEFAULT_MAX_BUZZ_TIME_MS: usize = 5000;

pub struct PwmBuzzer<'a, A: hil::time::Alarm<'a>, P: hil::pwm::PwmPin> {
    /// The underlying PWM generator to make the buzzer buzz.
    pwm_pin: &'a P,
    /// Alarm to stop the PWM after some time.
    alarm: &'a A,
    /// Max buzz time.
    max_duration_ms: usize,
    /// The client currently using the service capsule.
    client: OptionalCell<&'a dyn BuzzerClient>,
}

impl<'a, A: hil::time::Alarm<'a>, P: hil::pwm::PwmPin> PwmBuzzer<'a, A, P> {
    pub fn new(pwm_pin: &'a P, alarm: &'a A, max_duration_ms: usize) -> PwmBuzzer<'a, A, P> {
        PwmBuzzer {
            pwm_pin,
            alarm,
            client: OptionalCell::empty(),
            max_duration_ms,
        }
    }
}

impl<'a, A: hil::time::Alarm<'a>, P: hil::pwm::PwmPin> hil::buzzer::Buzzer<'a>
    for PwmBuzzer<'a, A, P>
{
    fn set_client(&self, client: &'a dyn BuzzerClient) {
        self.client.replace(client);
    }

    fn buzz(&self, frequency_hz: usize, duration_ms: usize) -> Result<(), ErrorCode> {
        let duration_ms_cmp = cmp::min(duration_ms, self.max_duration_ms);
        self.pwm_pin
            .start(frequency_hz, self.pwm_pin.get_maximum_duty_cycle() / 2)?;

        // Set an alarm for the given duration.
        let interval = (duration_ms_cmp as u32) * <A::Frequency>::frequency() / 1000;
        self.alarm
            .set_alarm(self.alarm.now(), A::Ticks::from(interval));
        Ok(())
    }

    fn stop(&self) -> Result<(), ErrorCode> {
        // Disarm the current alarm and instantly fire another.
        self.alarm.disarm()?;
        // This method was used to reduce the size of the code.
        self.alarm.set_alarm(self.alarm.now(), A::Ticks::from(0));
        Ok(())
    }
}

impl<'a, A: hil::time::Alarm<'a>, P: hil::pwm::PwmPin> hil::time::AlarmClient
    for PwmBuzzer<'a, A, P>
{
    fn alarm(&self) {
        // Stop the pin output and signal that the buzzer has finished
        // playing.
        self.client
            .map(|buzz_client| buzz_client.buzzer_done(self.pwm_pin.stop()));
    }
}