diff --git a/Cargo.toml b/Cargo.toml index 93cc34c0..ed11a6f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ version = "0.1.0" libtock_alarm = { path = "apis/alarm" } libtock_ambient_light = { path = "apis/ambient_light" } libtock_buttons = { path = "apis/buttons" } +libtock_buzzer = {path = "apis/buzzer"} libtock_console = { path = "apis/console" } libtock_debug_panic = { path = "panic_handlers/debug_panic" } libtock_gpio = { path = "apis/gpio" } @@ -39,6 +40,7 @@ members = [ "apis/alarm", "apis/gpio", "apis/buttons", + "apis/buzzer", "apis/console", "apis/leds", "apis/low_level_debug", diff --git a/apis/buzzer/Cargo.toml b/apis/buzzer/Cargo.toml new file mode 100644 index 00000000..09f460da --- /dev/null +++ b/apis/buzzer/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "libtock_buzzer" +version = "0.1.0" +authors = ["Tock Project Developers "] +license = "MIT/Apache-2.0" +edition = "2021" +repository = "https://www.github.com/tock/libtock-rs" +description = "libtock buzzer driver" + +[dependencies] +libtock_platform = { path = "../../platform" } + +[dev-dependencies] +libtock_unittest = { path = "../../unittest" } diff --git a/apis/buzzer/src/lib.rs b/apis/buzzer/src/lib.rs new file mode 100644 index 00000000..a540b789 --- /dev/null +++ b/apis/buzzer/src/lib.rs @@ -0,0 +1,173 @@ +//! Implementation started by : https://github.com/teodorobert +//! Continued and modified by : https://github.com/SheepSeb +#![no_std] + +use core::cell::Cell; +use core::time::Duration; + +use libtock_platform::{ + share, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, +}; +pub struct Buzzer(S); + +impl Buzzer { + /// Returns Ok() if the driver was present.This does not necessarily mean + /// that the driver is working. + pub fn exists() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() + } + + /// Initiate a tone + pub fn tone(freq: u32, duration: Duration) -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, BUZZER_ON, freq, duration.as_millis() as u32).to_result() + } + + /// Register an events listener + pub fn register_listener<'share, F: Fn(u32)>( + listener: &'share BuzzerListener, + subscribe: share::Handle>, + ) -> Result<(), ErrorCode> { + S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) + } + + /// Unregister the events listener + pub fn unregister_listener() { + S::unsubscribe(DRIVER_NUM, 0) + } + + /// Initiate a synchronous tone + /// Returns Ok() if the operation was successful + pub fn tone_sync(freq: u32, duration: Duration) -> Result<(), ErrorCode> { + let buzzer_cell: Cell> = Cell::new(None); + let listener = BuzzerListener(|buzzer_val| { + buzzer_cell.set(Some(buzzer_val)); + }); + share::scope(|subscribe| { + Self::register_listener(&listener, subscribe)?; + Self::tone(freq, duration)?; + while buzzer_cell.get() == None { + S::yield_wait(); + } + match buzzer_cell.get() { + None => Err(ErrorCode::Fail), + Some(_) => Ok(()), + } + }) + } +} + +pub struct BuzzerListener(pub F); +impl Upcall> for BuzzerListener { + fn upcall(&self, _arg0: u32, _arg1: u32, _arg2: u32) { + (self.0)(_arg0); + } +} + +#[cfg(test)] +mod tests; + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x90000; + +// Command IDs +const EXISTS: u32 = 0; +const BUZZER_ON: u32 = 1; + +/// The notes that can be played by the buzzer +#[allow(unused)] +#[repr(u32)] +#[derive(Copy, Clone, Debug)] +pub enum Note { + B0 = 31, + C1 = 33, + CS1 = 35, + D1 = 37, + DS1 = 39, + E1 = 41, + F1 = 44, + FS1 = 46, + G1 = 49, + GS1 = 52, + A1 = 55, + AS1 = 58, + B1 = 62, + C2 = 65, + CS2 = 69, + D2 = 73, + DS2 = 78, + E2 = 82, + F2 = 87, + FS2 = 93, + G2 = 98, + GS2 = 104, + A2 = 110, + AS2 = 117, + B2 = 123, + C3 = 131, + CS3 = 139, + D3 = 147, + DS3 = 156, + E3 = 165, + F3 = 175, + FS3 = 185, + G3 = 196, + GS3 = 208, + A3 = 220, + AS3 = 233, + B3 = 247, + C4 = 262, + CS4 = 277, + D4 = 294, + DS4 = 311, + E4 = 330, + F4 = 349, + FS4 = 370, + G4 = 392, + GS4 = 415, + A4 = 440, + AS4 = 466, + B4 = 494, + C5 = 523, + CS5 = 554, + D5 = 587, + DS5 = 622, + E5 = 659, + F5 = 698, + FS5 = 740, + G5 = 784, + GS5 = 831, + A5 = 880, + AS5 = 932, + B5 = 988, + C6 = 1047, + CS6 = 1109, + D6 = 1175, + DS6 = 1245, + E6 = 1319, + F6 = 1397, + FS6 = 1480, + G6 = 1568, + GS6 = 1661, + A6 = 1760, + AS6 = 1865, + B6 = 1976, + C7 = 2093, + CS7 = 2217, + D7 = 2349, + DS7 = 2489, + E7 = 2637, + F7 = 2794, + FS7 = 2960, + G7 = 3136, + GS7 = 3322, + A7 = 3520, + AS7 = 3729, + B7 = 3951, + C8 = 4186, + CS8 = 4435, + D8 = 4699, + DS8 = 4978, +} diff --git a/apis/buzzer/src/tests.rs b/apis/buzzer/src/tests.rs new file mode 100644 index 00000000..176b9782 --- /dev/null +++ b/apis/buzzer/src/tests.rs @@ -0,0 +1,44 @@ +use core::time::Duration; +use libtock_platform::ErrorCode; +use libtock_unittest::fake; + +type Buzzer = super::Buzzer; + +#[test] +fn no_driver() { + let _kernel = fake::Kernel::new(); + assert_eq!(Buzzer::exists(), Err(ErrorCode::NoDevice)); +} + +#[test] +fn driver_check() { + let kernel = fake::Kernel::new(); + let driver = fake::Buzzer::new(); + kernel.add_driver(&driver); + + assert_eq!(Buzzer::exists(), Ok(())); +} + +#[test] +fn tone() { + let kernel = fake::Kernel::new(); + let driver = fake::Buzzer::new(); + kernel.add_driver(&driver); + let duration = Duration::from_millis(100); + assert_eq!(Buzzer::tone(1000, duration), Ok(())); + assert!(driver.is_busy()); + + assert_eq!(Buzzer::tone(1000, duration), Err(ErrorCode::Busy)); +} + +#[test] +fn tone_sync() { + let kernel = fake::Kernel::new(); + let driver = fake::Buzzer::new(); + kernel.add_driver(&driver); + + let duration = Duration::from_millis(100); + + driver.set_tone_sync(1000, 100); + assert_eq!(Buzzer::tone_sync(1000, duration), Ok(())); +} diff --git a/examples/music.rs b/examples/music.rs new file mode 100644 index 00000000..5a854911 --- /dev/null +++ b/examples/music.rs @@ -0,0 +1,105 @@ +//! Implementation done by : https://github.com/teodorobert +//! A simple libtock-rs example. Plays Ode of Joy using the buzzer. +#![no_main] +#![no_std] + +use core::fmt::Write; +use core::time::Duration; +use libtock::buzzer::{Buzzer, Note}; +use libtock::console::Console; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x800} + +// Adapted from https://github.com/robsoncouto/arduino-songs + +// Notes in the form of (note_frequency, note_delay in musical terms) +const MELODY: [(Note, i32); 62] = [ + (Note::E4, 4), + (Note::E4, 4), + (Note::F4, 4), + (Note::G4, 4), + (Note::G4, 4), + (Note::F4, 4), + (Note::E4, 4), + (Note::D4, 4), + (Note::C4, 4), + (Note::C4, 4), + (Note::D4, 4), + (Note::E4, 4), + (Note::E4, -4), + (Note::D4, 8), + (Note::D4, 2), + (Note::E4, 4), + (Note::E4, 4), + (Note::F4, 4), + (Note::G4, 4), + (Note::G4, 4), + (Note::F4, 4), + (Note::E4, 4), + (Note::D4, 4), + (Note::C4, 4), + (Note::C4, 4), + (Note::D4, 4), + (Note::E4, 4), + (Note::D4, -4), + (Note::C4, 8), + (Note::C4, 2), + (Note::D4, 4), + (Note::D4, 4), + (Note::E4, 4), + (Note::C4, 4), + (Note::D4, 4), + (Note::E4, 8), + (Note::F4, 8), + (Note::E4, 4), + (Note::C4, 4), + (Note::D4, 4), + (Note::E4, 8), + (Note::F4, 8), + (Note::E4, 4), + (Note::D4, 4), + (Note::C4, 4), + (Note::D4, 4), + (Note::G3, 2), + (Note::E4, 4), + (Note::E4, 4), + (Note::F4, 4), + (Note::G4, 4), + (Note::G4, 4), + (Note::F4, 4), + (Note::E4, 4), + (Note::D4, 4), + (Note::C4, 4), + (Note::C4, 4), + (Note::D4, 4), + (Note::E4, 4), + (Note::D4, -4), + (Note::C4, 8), + (Note::C4, 2), +]; + +const TEMPO: u32 = 114; +const WHOLE_NOTE: u32 = (60000 * 4) / TEMPO; + +fn main() { + if let Err(_) = Buzzer::exists() { + writeln!(Console::writer(), "There is no available buzzer").unwrap(); + return; + } + + writeln!(Console::writer(), "Ode to Joy").unwrap(); + + for (frequency, duration) in MELODY.iter() { + let mut note_duration: Duration = + Duration::from_millis((WHOLE_NOTE / duration.unsigned_abs()) as u64); + // let mut note_duration = WHOLE_NOTE / duration.unsigned_abs(); + if duration < &0 { + note_duration = note_duration * 15 / 10; + } + + let note_duration = note_duration * 9 / 10; + Buzzer::tone_sync(*frequency as u32 * 3, note_duration).unwrap(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 2a9c12c8..95d48b9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,11 @@ pub mod buttons { pub type Buttons = buttons::Buttons; pub use buttons::{ButtonListener, ButtonState}; } +pub mod buzzer { + use libtock_buzzer as buzzer; + pub type Buzzer = buzzer::Buzzer; + pub use buzzer::Note; +} pub mod console { use libtock_console as console; pub type Console = console::Console; diff --git a/unittest/src/fake/buzzer/mod.rs b/unittest/src/fake/buzzer/mod.rs new file mode 100644 index 00000000..a8551256 --- /dev/null +++ b/unittest/src/fake/buzzer/mod.rs @@ -0,0 +1,89 @@ +//! Fake implementation of the Buzzer API, documented here: +//! +//! Like the real API, `Buzzer` controls a fake buzzer. It provides +//! a function `set_tone` used to immediately call an upcall with a tone set by the buzzer +//! and a function 'set_tone_sync' used to call the upcall when the tone command is received. + +use crate::{DriverInfo, DriverShareRef}; +use core::time::Duration; +use libtock_platform::{CommandReturn, ErrorCode}; +use std::cell::Cell; + +// The `upcall_on_command` field is set to Some(value) if an upcall(with value as its argument) should be called when tone command is received, +// or None otherwise. It was needed for testing `tone_sync` library function which simulates a synchronous tone set, +// because it was impossible to schedule an upcall during the `synchronous` tone set in other ways. +pub struct Buzzer { + busy: Cell, + upcall_on_command: [Cell>; 2], + share_ref: DriverShareRef, +} + +impl Buzzer { + pub fn new() -> std::rc::Rc { + std::rc::Rc::new(Buzzer { + busy: Cell::new(false), + upcall_on_command: [Cell::new(None), Cell::new(None)], + share_ref: Default::default(), + }) + } + + pub fn is_busy(&self) -> bool { + self.busy.get() + } + + pub fn set_tone(&self, freq: i32, duration: Duration) { + if self.busy.get() { + self.share_ref + .schedule_upcall(0, (freq as u32, duration.as_millis() as u32, 0)) + .expect("Unable to schedule upcall"); + self.busy.set(false); + } + } + + pub fn set_tone_sync(&self, freq: i32, duration: i32) { + self.upcall_on_command[0].set(Some(freq)); + self.upcall_on_command[1].set(Some(duration)); + } +} + +impl crate::fake::SyscallDriver for Buzzer { + fn info(&self) -> DriverInfo { + DriverInfo::new(DRIVER_NUM).upcall_count(1) + } + + fn register(&self, share_ref: DriverShareRef) { + self.share_ref.replace(share_ref); + } + + fn command(&self, command_num: u32, _argument0: u32, _argument1: u32) -> CommandReturn { + match command_num { + EXISTS => crate::command_return::success(), + TONE => { + if self.busy.get() { + return crate::command_return::failure(ErrorCode::Busy); + } + self.busy.set(true); + if let Some(freq) = self.upcall_on_command[0].take() { + if let Some(duration) = self.upcall_on_command[1].take() { + self.set_tone(freq, Duration::from_millis(duration as u64)); + } + } + crate::command_return::success() + } + _ => crate::command_return::failure(ErrorCode::NoSupport), + } + } +} + +#[cfg(test)] +mod tests; + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x90000; + +// Command IDs +const EXISTS: u32 = 0; +const TONE: u32 = 1; diff --git a/unittest/src/fake/buzzer/tests.rs b/unittest/src/fake/buzzer/tests.rs new file mode 100644 index 00000000..b556ff1f --- /dev/null +++ b/unittest/src/fake/buzzer/tests.rs @@ -0,0 +1,60 @@ +use crate::fake::{self, SyscallDriver}; +use fake::buzzer::*; +use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn}; + +#[test] +fn command() { + let buzzer = Buzzer::new(); + assert!(buzzer.command(EXISTS, 1, 2).is_success()); + assert!(buzzer.command(TONE, 0, 0).is_success()); + + assert_eq!( + buzzer.command(TONE, 0, 0).get_failure(), + Some(ErrorCode::Busy) + ); + + buzzer.set_tone(100, Duration::from_millis(100)); + assert!(buzzer.command(TONE, 0, 1).is_success()); + buzzer.set_tone(100, Duration::from_millis(100)); + + buzzer.set_tone_sync(100, 100); + assert!(buzzer.command(TONE, 0, 1).is_success()); + assert!(buzzer.command(TONE, 0, 1).is_success()); +} + +#[test] +fn kernel_integration() { + use libtock_platform::Syscalls; + let kernel = fake::Kernel::new(); + let buzzer = Buzzer::new(); + kernel.add_driver(&buzzer); + + assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success()); + assert!(fake::Syscalls::command(DRIVER_NUM, TONE, 0, 0).is_success()); + assert_eq!( + fake::Syscalls::command(DRIVER_NUM, TONE, 0, 0).get_failure(), + Some(ErrorCode::Busy) + ); + buzzer.set_tone(100, Duration::from_millis(100)); + assert!(fake::Syscalls::command(DRIVER_NUM, TONE, 0, 1).is_success()); + + let listener = Cell::>::new(None); + share::scope(|subscribe| { + assert_eq!( + fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), + Ok(()) + ); + + buzzer.set_tone(100, Duration::from_millis(100)); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(listener.get(), Some((100,))); + + buzzer.set_tone(200, Duration::from_millis(100)); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + assert!(fake::Syscalls::command(DRIVER_NUM, TONE, 0, 1).is_success()); + buzzer.set_tone(200, Duration::from_millis(100)); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(listener.get(), Some((200,))); + }); +} diff --git a/unittest/src/fake/mod.rs b/unittest/src/fake/mod.rs index 6818d5da..c1a1682f 100644 --- a/unittest/src/fake/mod.rs +++ b/unittest/src/fake/mod.rs @@ -12,6 +12,7 @@ mod alarm; mod ambient_light; mod buttons; +mod buzzer; mod console; mod gpio; mod kernel; @@ -25,6 +26,7 @@ mod temperature; pub use alarm::Alarm; pub use ambient_light::AmbientLight; pub use buttons::Buttons; +pub use buzzer::Buzzer; pub use console::Console; pub use gpio::{Gpio, GpioMode, InterruptEdge, PullMode}; pub use kernel::Kernel;